feat: more readable time delta #193
5 changed files with 74 additions and 20 deletions
|
@ -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", () => {
|
describe("Date with correct timezone", () => {
|
||||||
const map = new Map([["u_time_at", "@"]]);
|
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/);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -131,3 +131,8 @@ export const emojiPng = (emoji: string) =>
|
||||||
`https://cdn.jsdelivr.net/gh/twitter/twemoji/assets/72x72/${emoji
|
`https://cdn.jsdelivr.net/gh/twitter/twemoji/assets/72x72/${emoji
|
||||||
.codePointAt(0)
|
.codePointAt(0)
|
||||||
?.toString(16)}.png`;
|
?.toString(16)}.png`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blank character
|
||||||
|
*/
|
||||||
|
export const blank = "\u200b";
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { EmbedBuilder } from "@discordjs/builders";
|
||||||
import { GuildQueue, QueueRepeatMode } from "discord-player";
|
import { GuildQueue, QueueRepeatMode } from "discord-player";
|
||||||
import { Client } from "discord.js";
|
import { Client } from "discord.js";
|
||||||
import { getLocale } from "./locales";
|
import { getLocale } from "./locales";
|
||||||
|
import { blank } from "./misc";
|
||||||
|
|
||||||
export const embedListQueue = (
|
export const embedListQueue = (
|
||||||
client: Client,
|
client: Client,
|
||||||
|
@ -30,7 +31,7 @@ export const embedListQueue = (
|
||||||
? loc.get("c_queue10")
|
? loc.get("c_queue10")
|
||||||
: (idx === 1 && page === 1) || (idx === 0 && page > 1)
|
: (idx === 1 && page === 1) || (idx === 0 && page > 1)
|
||||||
? loc.get("c_queue11")
|
? loc.get("c_queue11")
|
||||||
: "\u200b";
|
: blank;
|
||||||
const idx_track = now_playing ? "" : `${idx + limit_fields * (page - 1)}. `;
|
const idx_track = now_playing ? "" : `${idx + limit_fields * (page - 1)}. `;
|
||||||
embed.addFields({
|
embed.addFields({
|
||||||
name,
|
name,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Client, Colors, EmbedBuilder, User } from "discord.js";
|
import { Client, Colors, EmbedBuilder, User } from "discord.js";
|
||||||
import { getLocale } from "./locales";
|
import { getLocale } from "./locales";
|
||||||
import { cleanCodeBlock } from "./misc";
|
import { blank, cleanCodeBlock } from "./misc";
|
||||||
import { showDate, strToSeconds, timeDeltaToString } from "./time";
|
import { showDate, strToSeconds, timeDeltaToString } from "./time";
|
||||||
import { RegexC, RegExpFlags } from "./regex";
|
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 timeoutId = setTimeoutReminder(client, info, data.option, timeout);
|
||||||
|
|
||||||
|
const expiration_date = info.createdAt + timeout * 1000;
|
||||||
|
|
||||||
// Add the remind to the db
|
// Add the remind to the db
|
||||||
client.db.run(
|
client.db.run(
|
||||||
"INSERT INTO reminder ( \
|
"INSERT INTO reminder ( \
|
||||||
|
@ -95,7 +97,7 @@ export const newReminder = async (client: Client, time: string, info: infoRemind
|
||||||
) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ? );",
|
) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ? );",
|
||||||
[
|
[
|
||||||
info.message,
|
info.message,
|
||||||
`${info.createdAt + timeout * 1000}`,
|
`${expiration_date}`,
|
||||||
data.option.valueOf(),
|
data.option.valueOf(),
|
||||||
info.channelId,
|
info.channelId,
|
||||||
`${info.createdAt}`,
|
`${info.createdAt}`,
|
||||||
|
@ -110,7 +112,7 @@ export const newReminder = async (client: Client, time: string, info: infoRemind
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send confirmation to user
|
// 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 {
|
} else {
|
||||||
embed.addFields({
|
embed.addFields({
|
||||||
name: "\u200b",
|
name: blank,
|
||||||
value: `${loc.get("c_reminder10")}${page} ${loc.get("c_reminder11")}.`,
|
value: `${loc.get("c_reminder10")}${page} ${loc.get("c_reminder11")}.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,15 @@ import { RegexC, RegExpFlags } from "./regex";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parsed string adapted with TZ (locales) and format for the specified lang
|
* Parsed string adapted with TZ (locales) and format for the specified lang
|
||||||
* @param tz Lang
|
* @param lang Locale
|
||||||
* @param locale Locales
|
* @param translation Translation for "at"
|
||||||
* @param date Date
|
* @param date Date
|
||||||
* @returns String
|
* @returns String
|
||||||
*/
|
*/
|
||||||
export const showDate = (tz: string, locale: Map<string, unknown>, date: Date) => {
|
export const showDate = (lang: string, translation: Map<string, unknown>, date: Date) => {
|
||||||
const localeInfo = new Intl.Locale(tz);
|
const localeInfo = new Intl.Locale(lang);
|
||||||
const intlTimezone = moment.tz.zonesForCountry(localeInfo.region ?? localeInfo.baseName);
|
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",
|
timeZone: intlTimezone ? intlTimezone[0] : "Factory",
|
||||||
dateStyle: "short",
|
dateStyle: "short",
|
||||||
timeStyle: "medium",
|
timeStyle: "medium",
|
||||||
|
@ -19,14 +19,14 @@ export const showDate = (tz: string, locale: Map<string, unknown>, date: Date) =
|
||||||
.format(date)
|
.format(date)
|
||||||
.split(" ");
|
.split(" ");
|
||||||
|
|
||||||
return `${formattedDate[0]} ${locale.get("u_time_at")} ${formattedDate[1]}`;
|
return `${formattedDate[0]} ${translation.get("u_time_at")} ${formattedDate[1]}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum TimeSecond {
|
export enum TimeSecond {
|
||||||
Year = 31536000,
|
Year = 60 * 60 * 24 * 365,
|
||||||
Week = 604800,
|
Week = 60 * 60 * 24 * 7,
|
||||||
Day = 86400,
|
Day = 60 * 60 * 24,
|
||||||
Hour = 3600,
|
Hour = 60 * 60,
|
||||||
Minute = 60,
|
Minute = 60,
|
||||||
Second = 1,
|
Second = 1,
|
||||||
}
|
}
|
||||||
|
@ -95,13 +95,31 @@ export const strToSeconds = (time: string) => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculating the difference between a date and now
|
* Calculating the difference between a date and now
|
||||||
|
* @param lang Locale
|
||||||
* @param time Time
|
* @param time Time
|
||||||
* @returns Delta between the time and now
|
* @returns Delta between the time and now
|
||||||
*/
|
*/
|
||||||
export const timeDeltaToString = (time: number) => {
|
export const timeDeltaToString = (time: number) => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
// TODO: adapt the output and not always parse the time as seconds
|
let secondsDifference = Math.abs(Math.ceil((time - now) / 1000) - 2);
|
||||||
// https://git.mylloon.fr/ConfrerieDuKassoulait/Botanique/issues/189
|
|
||||||
// Use Intl.RelativeTimeFormat ?
|
if (secondsDifference === 0) {
|
||||||
return `${strToSeconds(`${(now - time) / 1000}`)} secs`;
|
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(" ");
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue