feat: more readable time delta (#193)
All checks were successful
PR Check / lint-and-format (pull_request) Successful in 18s

Close #189

Reviewed-on: #193
Co-authored-by: Mylloon <kennel.anri@tutanota.com>
Co-committed-by: Mylloon <kennel.anri@tutanota.com>
This commit is contained in:
Mylloon 2024-10-08 20:53:28 +02:00 committed by Mylloon
parent 32f537cd05
commit 85933e3ca2
Signed by: Forgejo
GPG key ID: E72245C752A07631
5 changed files with 74 additions and 20 deletions

View file

@ -1,4 +1,10 @@
import { nextTimeUnit, showDate, strToSeconds, TimeSecond } from "../../utils/time";
import {
nextTimeUnit,
showDate,
strToSeconds,
timeDeltaToString,
TimeSecond,
} from "../../utils/time";
describe("Date with correct timezone", () => {
const map = new Map([["u_time_at", "@"]]);
@ -84,3 +90,25 @@ describe("Next time unit", () => {
});
}
});
describe("Relative time", () => {
// Thoses tests are based on time, we have 10s of acceptance.
{
const name = Date.now() + (10 * TimeSecond.Minute + 30) * 1000;
test(name.toString(), () => {
expect(timeDeltaToString(name)).toMatch(/10m 30s|10m 2\ds/);
});
}
{
const name = Date.now() + (12 * TimeSecond.Hour + 30 * TimeSecond.Minute) * 1000;
test(name.toString(), () => {
expect(timeDeltaToString(name)).toMatch(/12h 30m|12h 29m 5\ds/);
});
}
{
const name = Date.now() + (TimeSecond.Week + TimeSecond.Day + 6 * TimeSecond.Hour) * 1000;
test(name.toString(), () => {
expect(timeDeltaToString(name)).toMatch(/1w 1d 6h|1w 1d 5h 59m 5\ds/);
});
}
});

View file

@ -131,3 +131,8 @@ export const emojiPng = (emoji: string) =>
`https://cdn.jsdelivr.net/gh/twitter/twemoji/assets/72x72/${emoji
.codePointAt(0)
?.toString(16)}.png`;
/**
* Blank character
*/
export const blank = "\u200b";

View file

@ -2,6 +2,7 @@ import { EmbedBuilder } from "@discordjs/builders";
import { GuildQueue, QueueRepeatMode } from "discord-player";
import { Client } from "discord.js";
import { getLocale } from "./locales";
import { blank } from "./misc";
export const embedListQueue = (
client: Client,
@ -30,7 +31,7 @@ export const embedListQueue = (
? loc.get("c_queue10")
: (idx === 1 && page === 1) || (idx === 0 && page > 1)
? loc.get("c_queue11")
: "\u200b";
: blank;
const idx_track = now_playing ? "" : `${idx + limit_fields * (page - 1)}. `;
embed.addFields({
name,

View file

@ -1,6 +1,6 @@
import { Client, Colors, EmbedBuilder, User } from "discord.js";
import { getLocale } from "./locales";
import { cleanCodeBlock } from "./misc";
import { blank, cleanCodeBlock } from "./misc";
import { showDate, strToSeconds, timeDeltaToString } from "./time";
import { RegexC, RegExpFlags } from "./regex";
@ -88,6 +88,8 @@ export const newReminder = async (client: Client, time: string, info: infoRemind
const timeoutId = setTimeoutReminder(client, info, data.option, timeout);
const expiration_date = info.createdAt + timeout * 1000;
// Add the remind to the db
client.db.run(
"INSERT INTO reminder ( \
@ -95,7 +97,7 @@ export const newReminder = async (client: Client, time: string, info: infoRemind
) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ? );",
[
info.message,
`${info.createdAt + timeout * 1000}`,
`${expiration_date}`,
data.option.valueOf(),
info.channelId,
`${info.createdAt}`,
@ -110,7 +112,7 @@ export const newReminder = async (client: Client, time: string, info: infoRemind
}
// Send confirmation to user
ok(`${loc.get("c_reminder1")} ${data.time}.`);
ok(`${loc.get("c_reminder1")} ${timeDeltaToString(expiration_date)}.`);
},
);
});
@ -440,7 +442,7 @@ export const embedListReminders = async (
});
} else {
embed.addFields({
name: "\u200b",
name: blank,
value: `${loc.get("c_reminder10")}${page} ${loc.get("c_reminder11")}.`,
});
}

View file

@ -3,15 +3,15 @@ import { RegexC, RegExpFlags } from "./regex";
/**
* Parsed string adapted with TZ (locales) and format for the specified lang
* @param tz Lang
* @param locale Locales
* @param lang Locale
* @param translation Translation for "at"
* @param date Date
* @returns String
*/
export const showDate = (tz: string, locale: Map<string, unknown>, date: Date) => {
const localeInfo = new Intl.Locale(tz);
export const showDate = (lang: string, translation: Map<string, unknown>, date: Date) => {
const localeInfo = new Intl.Locale(lang);
const intlTimezone = moment.tz.zonesForCountry(localeInfo.region ?? localeInfo.baseName);
const formattedDate = new Intl.DateTimeFormat(tz, {
const formattedDate = new Intl.DateTimeFormat(lang, {
timeZone: intlTimezone ? intlTimezone[0] : "Factory",
dateStyle: "short",
timeStyle: "medium",
@ -19,14 +19,14 @@ export const showDate = (tz: string, locale: Map<string, unknown>, date: Date) =
.format(date)
.split(" ");
return `${formattedDate[0]} ${locale.get("u_time_at")} ${formattedDate[1]}`;
return `${formattedDate[0]} ${translation.get("u_time_at")} ${formattedDate[1]}`;
};
export enum TimeSecond {
Year = 31536000,
Week = 604800,
Day = 86400,
Hour = 3600,
Year = 60 * 60 * 24 * 365,
Week = 60 * 60 * 24 * 7,
Day = 60 * 60 * 24,
Hour = 60 * 60,
Minute = 60,
Second = 1,
}
@ -95,13 +95,31 @@ export const strToSeconds = (time: string) => {
/**
* Calculating the difference between a date and now
* @param lang Locale
* @param time Time
* @returns Delta between the time and now
*/
export const timeDeltaToString = (time: number) => {
const now = Date.now();
// TODO: adapt the output and not always parse the time as seconds
// https://git.mylloon.fr/ConfrerieDuKassoulait/Botanique/issues/189
// Use Intl.RelativeTimeFormat ?
return `${strToSeconds(`${(now - time) / 1000}`)} secs`;
let secondsDifference = Math.abs(Math.ceil((time - now) / 1000) - 2);
if (secondsDifference === 0) {
return "0s";
}
return Object.entries(TimeSecond)
.map(([key, value]) => ({
label: key.charAt(0).toLowerCase(),
value: value as TimeSecond,
}))
.map(({ label, value }) => {
if (secondsDifference >= value) {
const amount = Math.floor(secondsDifference / value);
secondsDifference -= amount * value;
return `${amount}${label}`;
}
return null;
})
.filter(Boolean)
.join(" ");
};