Skip to content

Commit

Permalink
Update bot to use dynamic timestamps
Browse files Browse the repository at this point in the history
Discord recently introduced dynamic in-line timestamps
https://discord.com/developers/docs/reference#message-formatting-timestamp-styles
which appear different to users based on their local timezone

Updated the study session messages to use dynamic timezones to make
scheduled event times clearer

This also made the previously-introduced `!timezone` command obsolete
- Replaced `!timezone` command with `!time` command which can be used
by members to generate dynamic timestamps
  - Replaced `!help timezone` command with `!help time` command
  for additional information
- Removed 'luxon' package, which was added only for the `!timezone` command
  • Loading branch information
Fox-Islam committed Jul 13, 2021
1 parent bc686d3 commit 247e59d
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 89 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"discord.js": "^12.5.1",
"dotenv": "^8.2.0",
"googleapis": "^39.2.0",
"luxon": "^1.27.0",
"mongoose": "^5.11.8",
"node-cron": "^2.0.3"
},
Expand Down
12 changes: 6 additions & 6 deletions src/constants/studySession.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
const { getUTCFullDate, getUTCFullTime } = require("../utils/date");
const { getDynamicDateTime } = require("../utils/date");

// Study session's related messages
const STUDY_SESSION = {
CREATE: {
SUCCESS: (session) => ({
title: "STUDY SESSION",
content: "Study session has been registered successfully!",
description: `📆 ${getUTCFullDate(session.startDate, "date")} at ${getUTCFullDate(session.startDate, "time")} *(UTC)*\n🕑 Estimated length: ${session.estimatedLength} minutes.\n\n${getStudySessionText(session.message)}\n\n*If anybody wants to join the session, subscribe using the ⭐ button\nIf you want to cancel the session, delete the message used to create it*`,
description: `📆 ${getDynamicDateTime(session.startDate, 'long date time')}\n🕑 Estimated length: ${session.estimatedLength} minutes.\n\n${getStudySessionText(session.message)}\n\n*If anybody wants to join the session, subscribe using the ⭐ button\nIf you want to cancel the session, delete the message used to create it*`,
withAuthor: true,
}),
ERROR: (error) => ({
Expand Down Expand Up @@ -45,7 +45,7 @@ const STUDY_SESSION = {
content: "Here are the upcoming study sessions:\n*Make sure to check the time zones!*",
fields: sessions.map((session) => ({
name: `${session.author.username}'s study session`,
value: `*${getUTCFullDate(session.startDate)} UTC (${session.estimatedLength} min)*\n${getUpcomingStudySessionSummary(session.message)} - Subscribe [here](${session.message?.link})`,
value: `*${getDynamicDateTime(session.startDate, 'short date time')} (${session.estimatedLength} min)*\n${getUpcomingStudySessionSummary(session.message)} - Subscribe [here](${session.message?.link})`,
})),
}),
ERROR: (error) => ({
Expand All @@ -62,7 +62,7 @@ const STUDY_SESSION = {
content: `👋 Hey ${subscriber.username}, you successfully registered to <@${author.id}> study session! See you soon!`,
description: messageContent.substr(6).trim(),
}),
REMINDER: (studySession, subscriber) => ({ content: `👋 How is your day going, ${subscriber.username}? Thank you for waiting, <@${studySession.author.id}>'s study session is starting soon! See you on the Korean Study Group server at **${getUTCFullTime(studySession.startDate)} UTC**!\n*Make sure to check the time zone!*` }),
REMINDER: (studySession, subscriber) => ({ content: `👋 How is your day going, ${subscriber.username}? Thank you for waiting, <@${studySession.author.id}>'s study session is starting in an hour! See you on the Korean Study Group server at **${getDynamicDateTime(studySession.startDate, 'short time')}**!\n` }),
ERROR: (author, error) => ({
content: `${author.username}, you just tried to subscribe to a study session. Thanks for your participation! However, an error as occurred during the process. Please try again! (and don't hesitate to notify <@202787014502776832> about this error)`,
title: "❌ Subscription error",
Expand Down Expand Up @@ -138,7 +138,7 @@ function getStudySessionCancellationInformation(studySession) {
cancellationMessage = title;
}

return `${cancellationMessage} on ${getUTCFullDate(startDate, "date")} at ${getUTCFullDate(startDate, "time")} *(UTC)* has been cancelled`;
return `${cancellationMessage} on ${getDynamicDateTime(startDate, 'long date time')} has been cancelled`;
}

function getTitle(message) {
Expand All @@ -161,7 +161,7 @@ function getStudySessionCancellationSubscriberNotification(studySession) {
cancellationMessage = title;
}

return `${cancellationMessage} on ${getUTCFullDate(startDate, "date")} at ${getUTCFullDate(startDate, "time")} *(UTC)*`;
return `${cancellationMessage} on ${getDynamicDateTime(startDate, 'long date time')}`;
}

module.exports = { STUDY_SESSION };
6 changes: 3 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const { addBookmark, removeBookmark } = require("./scripts/users/dm/bookmarks");
const { unPin50thMsg, getAllChannels, ping, handleHelpCommand } = require("./scripts/utilities");
const { typingGame, typingGameListener, endTypingGame, gameExplanation } = require("./scripts/activities/games");
const { createStudySession, getUpcomingStudySessions, cancelStudySessionFromCommand, cancelStudySessionFromDeletion, subscribeStudySession, unsubscribeStudySession, updateStudySessionDetails } = require("./scripts/activities/study-session");
const { convertBetweenTimezones } = require("./scripts/utility-commands/time-and-date");
const { createDynamicTime } = require("./scripts/utility-commands/time-and-date");
const { loadMessageReaction } = require("./utils/cache");
const runScheduler = require("./scheduler").default;
/* ------------------------------------------------------ */
Expand Down Expand Up @@ -151,8 +151,8 @@ client.on("message", (message) => {
return;
}

if (text.startsWith("!timezone")) {
convertBetweenTimezones(message);
if (text.startsWith("!time")) {
createDynamicTime(message);
return;
}
});
Expand Down
52 changes: 28 additions & 24 deletions src/scripts/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ function logMessageDate() {
}

function handleHelpCommand(message) {
if (message.content.startsWith('!help timezone')) {
handleHelpTimezoneCommand(message);
if (message.content.startsWith('!help time')) {
handleHelpTimeCommand(message);
return;
}
if (message.content.startsWith('!help cancel study')) {
Expand Down Expand Up @@ -119,11 +119,11 @@ You can cancel a study session either by deleting the message used to create the
`
},
{
name: 'Timezones',
name: 'Time',
value: `
Use \`!timezone\` to convert a date-time to equivalent date-times in different regions
Use \`!time\` to generate a dynamic date-time which shows the correct time to each user based on their local timezone
Use \`!help timezone\` for more information
Use \`!help time\` for more information
`
}, {
name: 'Bookmarks',
Expand All @@ -143,36 +143,40 @@ Any message the bot sends via DM can be deleted by applying an 'x' (❌) reactio
});
}

function handleHelpTimezoneCommand(message) {
function handleHelpTimeCommand(message) {
const currentTime = Math.round(new Date().getTime() / 1000);
message.channel.send(null, {
embed: {
title: "The !timezone command",
title: "The !time command",
fields: [
{
name: 'Description',
value: 'The `!timezone` command can be used to convert a date-time into date-times around the world'
value: 'The `!time` command can be used to generate a dynamic date-time'
}, {
name: 'Format',
value: `
This requires a date in the format YYYY/MM/DD and a UTC time in HH:mm followed by any number of [TZ database names](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List)
This command optionally takes a UTC date-time in the format YYYY/MM/DD HH:mm and an output type
If a date is not supplied it will use the current date and time
If an output type is not supplied it will use the default output type ("short date time")
`
}, {
name: 'Example',
name: 'Examples',
value: `
If you've created a study session with these details
\`\`\`
!study 2022/03/05 at 13:30 ...
\`\`\`
You can get the equivalent date-times in Toronto, London and Seoul using
\`\`\`
!timezone 2022/03/05 13:30 America/Toronto Europe/London Asia/Seoul
\`\`\`
Which creates this response:
\`\`\`
Sat, 5 Mar at 08:30 (Eastern Standard Time)
Sat, 5 Mar at 13:30 (Greenwich Mean Time)
Sat, 5 Mar at 22:30 (Korean Standard Time)
\`\`\`
\`!time\` on its own will generate <t:${currentTime}:f>
\`!time short time\` will generate <t:${currentTime}:t>
\`!time 2021/07/29 22:00 relative\` will generate <t:1627592400:R>
`
}, {
name: 'Output types',
value: `
short date: <t:${currentTime}:d>
long date: <t:${currentTime}:D>
short time: <t:${currentTime}:t>
long time: <t:${currentTime}:T>
short date time: <t:${currentTime}:f>
long date time: <t:${currentTime}:F>
relative: <t:${currentTime}:R>
`
}
]
Expand Down
48 changes: 19 additions & 29 deletions src/scripts/utility-commands/time-and-date.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,19 @@
const { DateTime } = require("luxon");
const { getDynamicDateTime, dynamicDateFormats } = require("../../utils/date");

function convertBetweenTimezones(message) {
function createDynamicTime(message) {
let text = message.content ?? "";
if (text.length < 10) {
if (text.length < 5) {
return;
}
text = text.substring(9).trim();

const dateTime = getDateTimeFromMessage(text);
text = text.substring(5).trim();
let dateTime = getDateTimeFromMessage(text);
if (dateTime === null) {
message.reply("please provide a valid datetime in the format YYYY/MM/DD HH:mm");
return;
dateTime = new Date();
}

const timezones = getTimeZonesFromMessage(text);
if (timezones === null || timezones.length === 0) {
message.reply("please provide at least one valid timezone to convert to. \n\nFor more information use `!help timezone`");
return;
}

let convertedTimes = [];
timezones.forEach(timezone => {
const convertedTime = dateTime.setZone(timezone);
if (convertedTime.invalid === null) {
const timeZoneAbbreviation = convertedTime.offsetNameLong;
const dateTimeString = `${convertedTime.weekdayShort}, ${convertedTime.day} ${convertedTime.monthShort} at ${convertedTime.toFormat('HH:mm')} *(${timeZoneAbbreviation})*`;
convertedTimes.push(dateTimeString);
}
});

message.channel.send(convertedTimes.join('\n'));
const dynamicDateTimeString = getDynamicDateTime(dateTime, getFormat(text));
message.channel.send(`${dynamicDateTimeString}\n\nAdd this dynamic time to your messages using \`${dynamicDateTimeString}\`\nUse \`!help time\` for more information.`);
}

function getDateTimeFromMessage(text) {
Expand All @@ -41,12 +25,18 @@ function getDateTimeFromMessage(text) {
if (isNaN(jsDate.getTime())) {
return null;
}
return DateTime.fromISO(jsDate.toISOString());
return jsDate;
}

function getTimeZonesFromMessage(text) {
const timezoneRegex = /([A-Za-z]+[\/][A-Za-z0-9_\-+]*)/g;
return text.match(timezoneRegex);
function getFormat(text) {
const keys = Object.keys(dynamicDateFormats);
let format = null;
keys.forEach((possibleFormat) => {
if (text.toLowerCase().includes(possibleFormat)) {
format = possibleFormat;
}
});
return format;
}

module.exports = { convertBetweenTimezones };
module.exports = { createDynamicTime };
4 changes: 2 additions & 2 deletions src/tasks/studySession/channelReminder.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { getUpcomingStudySessionsForScheduler } = require('../../scripts/activities/study-session');
const { getUTCFullDate } = require("../../utils/date");
const { getDynamicDateTime } = require("../../utils/date");

const upcomingStudySessionMessageContent = "Here are the upcoming study sessions:\n*Make sure to check the time zones!*";
const oneHour = 60 * 60 * 1000;
Expand Down Expand Up @@ -69,7 +69,7 @@ function makeStudySessionMessage() {
title: "UPCOMING STUDY SESSIONS",
fields: upcomingStudySessions.map((session) => ({
name: `${session.author.username}'s study session`,
value: `*${getUTCFullDate(session.startDate)} UTC (${session.estimatedLength} min)*\n${getUpcomingStudySessionSummary(session.message)} - Subscribe [here](${session.message?.link})`,
value: `*${getDynamicDateTime(session.startDate, 'short date time')} (${session.estimatedLength} min)*\n${getUpcomingStudySessionSummary(session.message)} - Subscribe [here](${session.message?.link})`,
})),
color: "GREEN"
}
Expand Down
44 changes: 20 additions & 24 deletions src/utils/date.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
function getUTCFullDate(date, separateIntoGroups = "") {
if (!date) return "";
const dateUTC = date.toUTCString();
const dynamicDateFormats = {
"short date time": 'f',
"long date time": 'F',
"short date": 'd',
"long date": 'D',
"short time": 't',
"long time": 'T',
"relative": 'R'
};

// Optionally separate date, year, time, and timezone into groups
const separateRegEx = /(?<onlyDate>\w{3},\s\d+\s\w{3})\s(?<year>\d{4})\s(?<time>\d+:\d+).+(?<zone>\w{3})/;
const { onlyDate, year, time, zone } = dateUTC.match(separateRegEx).groups;
switch (separateIntoGroups) {
case "date":
return onlyDate;
case "year":
return year;
case "time":
return time;
case "zone":
return zone;
function getDynamicDateTime(date, outputFormat) {
let format = 'f';
if (dynamicDateFormats[outputFormat] !== undefined) {
format = dynamicDateFormats[outputFormat];
}

return dateUTC.substr(0, dateUTC.length - 7); // Remove seconds and GMT string
return `<t:${removeMilliseconds(date)}:${format}>`
}

function getUTCFullTime(date) {
if (!date) return "";
const hour = date.getUTCHours();
const min = date.getUTCMinutes();
const fullHour = hour >= 10 ? hour : `0${hour}`;
const fullMin = min > 10 ? min : `0${min}`;
return `${fullHour}:${fullMin}`;
/**
* @description JavaScript includes milliseconds but Discord's dynamic datetime feature only supports up to second precision
*/
function removeMilliseconds(date) {
return Math.round(date.getTime() / 1000);
}

module.exports = { getUTCFullDate, getUTCFullTime };
module.exports = { getDynamicDateTime, dynamicDateFormats };

0 comments on commit 247e59d

Please sign in to comment.