diff --git a/deno.json b/deno.json index 310ecbd..99f68b4 100644 --- a/deno.json +++ b/deno.json @@ -26,8 +26,6 @@ "drizzle-orm": "npm:drizzle-orm@^0.38.3", "drizzle-kit": "npm:drizzle-kit@^0.30.1", "html-entities": "npm:html-entities@^2.3.2", - "moment": "npm:moment@^2.29.1", - "moment-timezone": "npm:moment-timezone@^0.5.34", "mysql2": "npm:mysql2@^3.10.1", "node-cron": "npm:node-cron@^3.0.0", "turndown": "npm:turndown@^7.1.1" diff --git a/deno.lock b/deno.lock index 620f003..412936e 100644 --- a/deno.lock +++ b/deno.lock @@ -11,8 +11,6 @@ "npm:drizzle-kit@~0.30.1": "0.30.6_esbuild@0.19.12", "npm:drizzle-orm@~0.38.3": "0.38.4_mysql2@3.16.0", "npm:html-entities@^2.3.2": "2.6.0", - "npm:moment-timezone@~0.5.34": "0.5.48", - "npm:moment@^2.29.1": "2.30.1", "npm:mysql2@^3.10.1": "3.16.0", "npm:node-cron@3": "3.0.3", "npm:turndown@^7.1.1": "7.2.2" @@ -633,15 +631,6 @@ "mkdirp-classic@0.5.3": { "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, - "moment-timezone@0.5.48": { - "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", - "dependencies": [ - "moment" - ] - }, - "moment@2.30.1": { - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==" - }, "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, @@ -886,8 +875,6 @@ "npm:drizzle-kit@~0.30.1", "npm:drizzle-orm@~0.38.3", "npm:html-entities@^2.3.2", - "npm:moment-timezone@~0.5.34", - "npm:moment@^2.29.1", "npm:mysql2@^3.10.1", "npm:node-cron@3", "npm:turndown@^7.1.1" diff --git a/src/command/ContextFindCommand.ts b/src/command/ContextFindCommand.ts index 926359e..549651e 100644 --- a/src/command/ContextFindCommand.ts +++ b/src/command/ContextFindCommand.ts @@ -5,6 +5,7 @@ import { FetchCharacterService } from "../service/FetchCharacterService.ts"; import { Command } from "./type/Command.ts"; import { DiscordMessageService } from "../service/DiscordMessageService.ts"; import { LodestoneServiceUnavailableError } from "../naagostone/error/LodestoneServiceUnavailableError.ts"; +import { DateHelper } from "../helper/DateHelper.ts"; class ContextFindCommand extends Command { public readonly data = new ContextMenuCommandBuilder() @@ -47,7 +48,7 @@ class ContextFindCommand extends Command { const file = new AttachmentBuilder(profileImage); const components = ProfileGeneratorService.getComponents("profile", "profile", targetCharacter?.id); - const unix = targetCharacterDataDto.latestUpdate.unix(); + const unix = DateHelper.toEpochSeconds(targetCharacterDataDto.latestUpdate); const cachedHint = targetCharacterDataDto.isCachedDueToUnavailability ? "\n⚠️ *Lodestone is currently unavailable. Showing cached data.*" diff --git a/src/command/ProfileCommand.ts b/src/command/ProfileCommand.ts index 5014550..fa52751 100644 --- a/src/command/ProfileCommand.ts +++ b/src/command/ProfileCommand.ts @@ -25,6 +25,7 @@ import { InvalidSubCommandError } from "./error/InvalidSubCommandError.ts"; import { DiscordMessageService } from "../service/DiscordMessageService.ts"; import { LodestoneServiceUnavailableError } from "../naagostone/error/LodestoneServiceUnavailableError.ts"; import { CommandMentionService } from "../service/CommandMentionService.ts"; +import { DateHelper } from "../helper/DateHelper.ts"; class ProfileCommand extends Command { public readonly data = new SlashCommandBuilder() @@ -152,9 +153,10 @@ class ProfileCommand extends Command { const file = new AttachmentBuilder(profileImage); const components = ProfileGeneratorService.getComponents(profilePage, "profile", characterId); + const unix = DateHelper.toEpochSeconds(characterDataDto.latestUpdate); await interaction.editReply({ - content: `Latest Update: ${cachedHint}`, + content: `Latest Update: ${cachedHint}`, files: [file], embeds: [], attachments: [], @@ -246,13 +248,14 @@ class ProfileCommand extends Command { const file = new AttachmentBuilder(profileImage); const components = ProfileGeneratorService.getComponents("profile", "profile", characterId); + const unix = DateHelper.toEpochSeconds(characterDataDto.latestUpdate); const cachedHint = characterDataDto.isCachedDueToUnavailability ? "\n⚠️ *Lodestone is currently unavailable. Showing cached data.*" : ""; await interaction.editReply({ - content: `Latest Update: ${cachedHint}`, + content: `Latest Update: ${cachedHint}`, files: [file], embeds: [], attachments: [], diff --git a/src/database/repository/CharacterDataRepository.ts b/src/database/repository/CharacterDataRepository.ts index f59586c..8d9313a 100644 --- a/src/database/repository/CharacterDataRepository.ts +++ b/src/database/repository/CharacterDataRepository.ts @@ -1,7 +1,6 @@ import { database } from "../connection.ts"; import { CharacterData, characterData } from "../schema/character-data.ts"; import { eq } from "drizzle-orm"; -import moment from "moment-timezone"; import { Character } from "../../naagostone/type/CharacterTypes.ts"; export class CharacterDataRepository { @@ -18,8 +17,7 @@ export class CharacterDataRepository { } public static async set(character: Character): Promise { - const now = Date.now(); - const nowSQL = moment(now).tz("UTC").toDate(); + const nowSQL = new Date(); await database .insert(characterData) .values({ diff --git a/src/database/repository/MaintenancesRepository.ts b/src/database/repository/MaintenancesRepository.ts index 207e8d4..d39a300 100644 --- a/src/database/repository/MaintenancesRepository.ts +++ b/src/database/repository/MaintenancesRepository.ts @@ -1,6 +1,5 @@ import { and, eq, gt, gte, lte } from "drizzle-orm"; import { database } from "../connection.ts"; -import moment from "moment"; import { AlreadyInDatabaseError } from "../error/AlreadyInDatabaseError.ts"; import { MaintenanceData, maintenanceData } from "../schema/lodestone-news.ts"; import { Maintenance } from "../../naagostone/type/Maintenance.ts"; @@ -10,15 +9,13 @@ export class MaintenancesRepository { title: string, date: Date, ): Promise { - const dateSQL = moment(date).tz("Europe/London").toDate(); - const result = await database .select() .from(maintenanceData) .where( and( eq(maintenanceData.title, title), - eq(maintenanceData.date, dateSQL), + eq(maintenanceData.date, date), ), ) .limit(1); @@ -27,7 +24,7 @@ export class MaintenancesRepository { } public static async add(maintenance: Maintenance): Promise { - const dateSQL = moment(maintenance.date).tz("Europe/London").toDate(); + const dateSQL = new Date(maintenance.date); const currentMaintenance = await this.find(maintenance.title, dateSQL); if (currentMaintenance) { throw new AlreadyInDatabaseError( @@ -44,10 +41,8 @@ export class MaintenancesRepository { date: dateSQL, description: maintenance.description.markdown, descriptionV2: maintenance.description.discord_components_v2 ?? null, - startDate: maintenance.start_timestamp - ? moment(maintenance.start_timestamp).tz("Europe/London").toDate() - : null, - endDate: maintenance.end_timestamp ? moment(maintenance.end_timestamp).tz("Europe/London").toDate() : null, + startDate: maintenance.start_timestamp ? new Date(maintenance.start_timestamp) : null, + endDate: maintenance.end_timestamp ? new Date(maintenance.end_timestamp) : null, }) .$returningId(); diff --git a/src/database/repository/NoticesRepository.ts b/src/database/repository/NoticesRepository.ts index 84db837..a0aa26f 100644 --- a/src/database/repository/NoticesRepository.ts +++ b/src/database/repository/NoticesRepository.ts @@ -1,6 +1,5 @@ import { and, eq } from "drizzle-orm"; import { database } from "../connection.ts"; -import moment from "moment-timezone"; import { AlreadyInDatabaseError } from "../error/AlreadyInDatabaseError.ts"; import { NoticeData, noticeData } from "../schema/lodestone-news.ts"; import { Notice } from "../../naagostone/type/Notice.ts"; @@ -10,15 +9,13 @@ export class NoticesRepository { title: string, date: Date, ): Promise { - const dateSQL = moment(date).tz("Europe/London").toDate(); - const result = await database .select() .from(noticeData) .where( and( eq(noticeData.title, title), - eq(noticeData.date, dateSQL), + eq(noticeData.date, date), ), ) .limit(1); @@ -27,7 +24,7 @@ export class NoticesRepository { } public static async add(notice: Notice): Promise { - const dateSQL = moment(notice.date).tz("Europe/London").toDate(); + const dateSQL = new Date(notice.date); const currentNotice = await this.find(notice.title, dateSQL); if (currentNotice) { throw new AlreadyInDatabaseError( diff --git a/src/database/repository/StatusesRepository.ts b/src/database/repository/StatusesRepository.ts index 7013146..4364aac 100644 --- a/src/database/repository/StatusesRepository.ts +++ b/src/database/repository/StatusesRepository.ts @@ -1,6 +1,5 @@ import { and, eq } from "drizzle-orm"; import { database } from "../connection.ts"; -import moment from "moment-timezone"; import { AlreadyInDatabaseError } from "../error/AlreadyInDatabaseError.ts"; import { StatusData, statusData } from "../schema/lodestone-news.ts"; import { Status } from "../../naagostone/type/Status.ts"; @@ -10,15 +9,13 @@ export class StatusesRepository { title: string, date: Date, ): Promise { - const dateSQL = moment(date).tz("Europe/London").toDate(); - const result = await database .select() .from(statusData) .where( and( eq(statusData.title, title), - eq(statusData.date, dateSQL), + eq(statusData.date, date), ), ) .limit(1); @@ -27,7 +24,7 @@ export class StatusesRepository { } public static async add(status: Status): Promise { - const dateSQL = moment(status.date).tz("Europe/London").toDate(); + const dateSQL = new Date(status.date); const currentStatus = await this.find(status.title, dateSQL); if (currentStatus) { throw new AlreadyInDatabaseError( diff --git a/src/database/repository/TopicsRepository.ts b/src/database/repository/TopicsRepository.ts index 780c928..11bfb4a 100644 --- a/src/database/repository/TopicsRepository.ts +++ b/src/database/repository/TopicsRepository.ts @@ -1,7 +1,6 @@ import { and, desc, eq, gt, gte, isNotNull, lte, ne, or, sql } from "drizzle-orm"; import { database } from "../connection.ts"; import { TopicData, topicData } from "../schema/lodestone-news.ts"; -import moment from "moment-timezone"; import { AlreadyInDatabaseError } from "../error/AlreadyInDatabaseError.ts"; import { Topic } from "../../naagostone/type/Topic.ts"; @@ -15,15 +14,13 @@ export class TopicsRepository { title: string, date: Date, ): Promise { - const dateSQL = moment(date).tz("Europe/London").toDate(); - const result = await database .select() .from(topicData) .where( and( eq(topicData.title, title), - eq(topicData.date, dateSQL), + eq(topicData.date, date), ), ) .limit(1); @@ -42,7 +39,7 @@ export class TopicsRepository { } public static async add(topic: Topic): Promise { - const dateSQL = moment(topic.date).tz("Europe/London").toDate(); + const dateSQL = new Date(topic.date); const currentTopic = await this.find(topic.title, dateSQL); if (currentTopic) { throw new AlreadyInDatabaseError("This topic is already in the database"); diff --git a/src/database/repository/UpdatesRepository.ts b/src/database/repository/UpdatesRepository.ts index c64509a..4cd7651 100644 --- a/src/database/repository/UpdatesRepository.ts +++ b/src/database/repository/UpdatesRepository.ts @@ -1,6 +1,5 @@ import { and, eq } from "drizzle-orm"; import { database } from "../connection.ts"; -import moment from "moment-timezone"; import { AlreadyInDatabaseError } from "../error/AlreadyInDatabaseError.ts"; import { UpdateData, updateData } from "../schema/lodestone-news.ts"; import { Update } from "../../naagostone/type/Updates.ts"; @@ -10,15 +9,13 @@ export class UpdatesRepository { title: string, date: Date, ): Promise { - const dateSQL = moment(date).tz("Europe/London").toDate(); - const result = await database .select() .from(updateData) .where( and( eq(updateData.title, title), - eq(updateData.date, dateSQL), + eq(updateData.date, date), ), ) .limit(1); @@ -27,7 +24,7 @@ export class UpdatesRepository { } public static async add(update: Update): Promise { - const dateSQL = moment(update.date).tz("Europe/London").toDate(); + const dateSQL = new Date(update.date); const currentUpdate = await this.find(update.title, dateSQL); if (currentUpdate) { throw new AlreadyInDatabaseError( diff --git a/src/helper/DateHelper.ts b/src/helper/DateHelper.ts new file mode 100644 index 0000000..8d87d4b --- /dev/null +++ b/src/helper/DateHelper.ts @@ -0,0 +1,75 @@ +const MONTHS_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + +export class DateHelper { + static setUtcTime(date: Date, hours: number, minutes = 0, seconds = 0, milliseconds = 0): Date { + const clonedDate = new Date(date.getTime()); + clonedDate.setUTCHours(hours, minutes, seconds, milliseconds); + return clonedDate; + } + + static setUtcDayOfWeek(date: Date, targetDay: number, hours: number, minutes = 0, seconds = 0, ms = 0): Date { + const clonedDate = new Date(date.getTime()); + const diff = targetDay - clonedDate.getUTCDay(); + clonedDate.setUTCDate(clonedDate.getUTCDate() + diff); + clonedDate.setUTCHours(hours, minutes, seconds, ms); + return clonedDate; + } + + static addDays(date: Date, days: number): Date { + const clonedDate = new Date(date.getTime()); + clonedDate.setUTCDate(clonedDate.getUTCDate() + days); + return clonedDate; + } + + static addWeeks(date: Date, weeks: number): Date { + return DateHelper.addDays(date, weeks * 7); + } + + static addMilliseconds(date: Date, milliseconds: number): Date { + return new Date(date.getTime() + milliseconds); + } + + static subtractMinutes(date: Date, minutes: number): Date { + return new Date(date.getTime() - minutes * 60 * 1000); + } + + static formatLog(date: Date): string { + const pad = (value: number, length = 2) => value.toString().padStart(length, "0"); + + const year = date.getFullYear(); + const month = pad(date.getMonth() + 1); + const day = pad(date.getDate()); + const hours = pad(date.getHours()); + const minutes = pad(date.getMinutes()); + const seconds = pad(date.getSeconds()); + const milliseconds = pad(date.getMilliseconds(), 3); + + const offset = -date.getTimezoneOffset(); + const sign = offset >= 0 ? "+" : "-"; + const absOffset = Math.abs(offset); + const offsetHours = pad(Math.floor(absOffset / 60)); + const offsetMinutes = pad(absOffset % 60); + + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds} ${sign}${offsetHours}${offsetMinutes}`; + } + + static formatOrdinalDate(date: Date): string { + const day = date.getUTCDate(); + const suffix = DateHelper.getOrdinalSuffix(day); + return `${day}${suffix} ${MONTHS_SHORT[date.getUTCMonth()]} ${date.getUTCFullYear()}`; + } + + static formatMediumDate(date: Date): string { + return `${MONTHS_SHORT[date.getUTCMonth()]} ${date.getUTCDate()}, ${date.getUTCFullYear()}`; + } + + private static getOrdinalSuffix(day: number): string { + const suffixes = ["th", "st", "nd", "rd"]; + const remainder = day % 100; + return suffixes[(remainder - 20) % 10] || suffixes[remainder] || suffixes[0]; + } + + static toEpochSeconds(date: Date): number { + return Math.floor(date.getTime() / 1000); + } +} diff --git a/src/helper/HelpCommandHelper.ts b/src/helper/HelpCommandHelper.ts index 67615b1..b9ce631 100644 --- a/src/helper/HelpCommandHelper.ts +++ b/src/helper/HelpCommandHelper.ts @@ -9,7 +9,6 @@ import { } from "discord.js"; import { DiscordColorService } from "../service/DiscordColorService.ts"; import { InvalidHelpPageError } from "./error/InvalidHelpPageError.ts"; -import moment from "moment"; import { DiscordEmojiService } from "../service/DiscordEmojiService.ts"; import { CommandMentionService } from "../service/CommandMentionService.ts"; @@ -263,7 +262,7 @@ export class HelpCommandHelper { ): Promise<{ embeds: EmbedBuilder[]; components: ActionRowBuilder[] }> { const client = interaction.client; const components = HelpCommandHelper.getButtons("about"); - const uptimeFormatted = time(moment().subtract(client.uptime!, "ms").toDate(), "R"); + const uptimeFormatted = time(new Date(Date.now() - client.uptime!), "R"); const deploymentHash = Deno.env.get("DEPLOYMENT_HASH"); const embed = new EmbedBuilder() .setColor(await DiscordColorService.getBotColorByInteraction(interaction)) diff --git a/src/helper/ProfileCommandHelper.ts b/src/helper/ProfileCommandHelper.ts index a647ebf..74bc075 100644 --- a/src/helper/ProfileCommandHelper.ts +++ b/src/helper/ProfileCommandHelper.ts @@ -8,6 +8,7 @@ import { ProfileGeneratorService } from "../service/ProfileGeneratorService.ts"; import { ProfilePageType } from "../service/type/ProfilePageTypes.ts"; import { LodestoneServiceUnavailableError } from "../naagostone/error/LodestoneServiceUnavailableError.ts"; import { DiscordMessageService } from "../service/DiscordMessageService.ts"; +import { DateHelper } from "./DateHelper.ts"; export class ProfileCommandHandler { public static async handlePageSwapButton(interaction: ButtonInteraction, buttonIdSplit: string[]): Promise { @@ -77,9 +78,10 @@ export class ProfileCommandHandler { const file = new AttachmentBuilder(profileImage); const components = ProfileGeneratorService.getComponents(profilePage, "profile", characterId); + const unix = DateHelper.toEpochSeconds(characterDataDto.latestUpdate); await interaction.editReply({ - content: `Latest Update: ${cachedHint}`, + content: `Latest Update: ${cachedHint}`, files: [file], embeds: [], attachments: [], @@ -134,13 +136,14 @@ export class ProfileCommandHandler { const file = new AttachmentBuilder(profileImage); const components = ProfileGeneratorService.getComponents("profile", "profile", characterId); + const unix = DateHelper.toEpochSeconds(characterDataDto.latestUpdate); const cachedHint = characterDataDto.isCachedDueToUnavailability ? "\n⚠️ *Lodestone is currently unavailable. Showing cached data.*" : ""; await interaction.editReply({ - content: `Latest Update: ${cachedHint}`, + content: `Latest Update: ${cachedHint}`, files: [file], embeds: [], attachments: [], diff --git a/src/helper/WhenIsResetCommandHelper.ts b/src/helper/WhenIsResetCommandHelper.ts index abffc49..9990a13 100644 --- a/src/helper/WhenIsResetCommandHelper.ts +++ b/src/helper/WhenIsResetCommandHelper.ts @@ -11,7 +11,7 @@ import { time, TimestampStyles, } from "discord.js"; -import moment from "moment"; +import { DateHelper } from "./DateHelper.ts"; import { DiscordColorService } from "../service/DiscordColorService.ts"; import { DiscordEmojiService } from "../service/DiscordEmojiService.ts"; import { PaissaApiService } from "../paissa/service/PaissaApiService.ts"; @@ -35,7 +35,7 @@ export class WhenIsResetCommandHelper { ): Promise<{ content: string; components: (ContainerBuilder | ActionRowBuilder)[]; flags: number }> { const botColorResolvable = await DiscordColorService.getBotColorByInteraction(interaction); const color = resolveColor(botColorResolvable); - const now = moment.utc(); + const now = new Date(); const components: (ContainerBuilder | ActionRowBuilder)[] = []; @@ -81,14 +81,14 @@ export class WhenIsResetCommandHelper { } private static buildDailyContainer( - dailyReset: moment.Moment, - gcReset: moment.Moment, - levequestAllowance: moment.Moment, - cosmicExploration: moment.Moment, + dailyReset: Date, + gcReset: Date, + levequestAllowance: Date, + cosmicExploration: Date, color: number, ): ContainerBuilder { - const dailyTimestamp = time(Math.floor(dailyReset.valueOf() / 1000), TimestampStyles.ShortTime); - const dailyTimestampRelative = time(Math.floor(dailyReset.valueOf() / 1000), TimestampStyles.RelativeTime); + const dailyTimestamp = time(DateHelper.toEpochSeconds(dailyReset), TimestampStyles.ShortTime); + const dailyTimestampRelative = time(DateHelper.toEpochSeconds(dailyReset), TimestampStyles.RelativeTime); const dailyAffects = [ "Allied Society Quest Allowances", @@ -97,8 +97,8 @@ export class WhenIsResetCommandHelper { "Mini Cactpot", ]; - const gcTimestamp = time(Math.floor(gcReset.valueOf() / 1000), TimestampStyles.ShortTime); - const gcTimestampRelative = time(Math.floor(gcReset.valueOf() / 1000), TimestampStyles.RelativeTime); + const gcTimestamp = time(DateHelper.toEpochSeconds(gcReset), TimestampStyles.ShortTime); + const gcTimestampRelative = time(DateHelper.toEpochSeconds(gcReset), TimestampStyles.RelativeTime); const emojiDailyReset = DiscordEmojiService.getAsMarkdown("EMOJI_DAILY_RESET"); const emojiGrandCompany = DiscordEmojiService.getAsMarkdown("EMOJI_GRAND_COMPANY"); const emojiLeveQuest = DiscordEmojiService.getAsMarkdown("EMOJI_LEVE_QUEST"); @@ -109,28 +109,28 @@ export class WhenIsResetCommandHelper { "GC Supply and Provision Missions", ]; - const levequestTimestamp = time(Math.floor(levequestAllowance.valueOf() / 1000), TimestampStyles.FullDateShortTime); + const levequestTimestamp = time(DateHelper.toEpochSeconds(levequestAllowance), TimestampStyles.FullDateShortTime); const levequestTimestampRelative = time( - Math.floor(levequestAllowance.valueOf() / 1000), + DateHelper.toEpochSeconds(levequestAllowance), TimestampStyles.RelativeTime, ); - const now = moment.utc(); - const nextMidnight = now.clone().hour(0).minute(0).second(0).millisecond(0); - if (now.isSameOrAfter(nextMidnight)) { - nextMidnight.add(1, "day"); + const now = new Date(); + let nextMidnight = DateHelper.setUtcTime(now, 0); + if (now >= nextMidnight) { + nextMidnight = DateHelper.addDays(nextMidnight, 1); } - const nextNoon = now.clone().hour(12).minute(0).second(0).millisecond(0); - if (now.isSameOrAfter(nextNoon)) { - nextNoon.add(1, "day"); + let nextNoon = DateHelper.setUtcTime(now, 12); + if (now >= nextNoon) { + nextNoon = DateHelper.addDays(nextNoon, 1); } - const midnightTimestamp = time(Math.floor(nextMidnight.valueOf() / 1000), TimestampStyles.ShortTime); - const noonTimestamp = time(Math.floor(nextNoon.valueOf() / 1000), TimestampStyles.ShortTime); + const midnightTimestamp = time(DateHelper.toEpochSeconds(nextMidnight), TimestampStyles.ShortTime); + const noonTimestamp = time(DateHelper.toEpochSeconds(nextNoon), TimestampStyles.ShortTime); - const cosmicExplorationTimestamp = time(Math.floor(cosmicExploration.valueOf() / 1000), TimestampStyles.ShortTime); + const cosmicExplorationTimestamp = time(DateHelper.toEpochSeconds(cosmicExploration), TimestampStyles.ShortTime); const cosmicExplorationTimestampRelative = time( - Math.floor(cosmicExploration.valueOf() / 1000), + DateHelper.toEpochSeconds(cosmicExploration), TimestampStyles.RelativeTime, ); @@ -165,14 +165,14 @@ export class WhenIsResetCommandHelper { } private static buildWeeklyContainer( - weeklyReset: moment.Moment, - fashionReport: moment.Moment, - jumboCactpot: moment.Moment, + weeklyReset: Date, + fashionReport: Date, + jumboCactpot: Date, housingLotteryPhase: LotteryPhaseInfo | null, color: number, ): ContainerBuilder { - const weeklyTimestamp = time(Math.floor(weeklyReset.valueOf() / 1000), TimestampStyles.FullDateShortTime); - const weeklyTimestampRelative = time(Math.floor(weeklyReset.valueOf() / 1000), TimestampStyles.RelativeTime); + const weeklyTimestamp = time(DateHelper.toEpochSeconds(weeklyReset), TimestampStyles.FullDateShortTime); + const weeklyTimestampRelative = time(DateHelper.toEpochSeconds(weeklyReset), TimestampStyles.RelativeTime); const weeklyAffects = [ "Adventurer Squadron Priority Missions", @@ -186,11 +186,11 @@ export class WhenIsResetCommandHelper { "Wondrous Tails Journal", ]; - const fashionTimestamp = time(Math.floor(fashionReport.valueOf() / 1000), TimestampStyles.FullDateShortTime); - const fashionTimestampRelative = time(Math.floor(fashionReport.valueOf() / 1000), TimestampStyles.RelativeTime); + const fashionTimestamp = time(DateHelper.toEpochSeconds(fashionReport), TimestampStyles.FullDateShortTime); + const fashionTimestampRelative = time(DateHelper.toEpochSeconds(fashionReport), TimestampStyles.RelativeTime); - const jumboTimestamp = time(Math.floor(jumboCactpot.valueOf() / 1000), TimestampStyles.FullDateShortTime); - const jumboTimestampRelative = time(Math.floor(jumboCactpot.valueOf() / 1000), TimestampStyles.RelativeTime); + const jumboTimestamp = time(DateHelper.toEpochSeconds(jumboCactpot), TimestampStyles.FullDateShortTime); + const jumboTimestampRelative = time(DateHelper.toEpochSeconds(jumboCactpot), TimestampStyles.RelativeTime); const emojiWeeklyReset = DiscordEmojiService.getAsMarkdown("EMOJI_WEEKLY_RESET"); const emojiFashionReport = DiscordEmojiService.getAsMarkdown("EMOJI_FASHION_REPORT"); @@ -263,77 +263,77 @@ export class WhenIsResetCommandHelper { .addSectionComponents(paissaDbSection); } - private static getNextDailyReset(now: moment.Moment): moment.Moment { - const todayReset = now.clone().hour(15).minute(0).second(0).millisecond(0); + static getNextDailyReset(now: Date): Date { + const todayReset = DateHelper.setUtcTime(now, 15); - if (now.isBefore(todayReset)) { + if (now < todayReset) { return todayReset; } else { - return todayReset.add(1, "day"); + return DateHelper.addDays(todayReset, 1); } } - private static getNextGcReset(now: moment.Moment): moment.Moment { - const todayReset = now.clone().hour(20).minute(0).second(0).millisecond(0); + static getNextGcReset(now: Date): Date { + const todayReset = DateHelper.setUtcTime(now, 20); - if (now.isBefore(todayReset)) { + if (now < todayReset) { return todayReset; } else { - return todayReset.add(1, "day"); + return DateHelper.addDays(todayReset, 1); } } - private static getNextWeeklyReset(now: moment.Moment): moment.Moment { - const nextTuesday = now.clone().day(2).hour(8).minute(0).second(0).millisecond(0); + static getNextWeeklyReset(now: Date): Date { + const nextTuesday = DateHelper.setUtcDayOfWeek(now, 2, 8); - if (now.isSameOrAfter(nextTuesday)) { - return nextTuesday.add(1, "week"); + if (now >= nextTuesday) { + return DateHelper.addWeeks(nextTuesday, 1); } return nextTuesday; } - private static getNextFashionReport(now: moment.Moment): moment.Moment { - const nextFriday = now.clone().day(5).hour(8).minute(0).second(0).millisecond(0); + static getNextFashionReport(now: Date): Date { + const nextFriday = DateHelper.setUtcDayOfWeek(now, 5, 8); - if (now.isSameOrAfter(nextFriday)) { - return nextFriday.add(1, "week"); + if (now >= nextFriday) { + return DateHelper.addWeeks(nextFriday, 1); } return nextFriday; } - private static getNextJumboCactpot(now: moment.Moment): moment.Moment { - const nextSaturday = now.clone().day(6).hour(20).minute(0).second(0).millisecond(0); + static getNextJumboCactpot(now: Date): Date { + const nextSaturday = DateHelper.setUtcDayOfWeek(now, 6, 20); - if (now.isSameOrAfter(nextSaturday)) { - return nextSaturday.add(1, "week"); + if (now >= nextSaturday) { + return DateHelper.addWeeks(nextSaturday, 1); } return nextSaturday; } - private static getNextLevequestAllowance(now: moment.Moment): moment.Moment { - const todayNoon = now.clone().hour(12).minute(0).second(0).millisecond(0); - const tomorrowMidnight = now.clone().add(1, "day").hour(0).minute(0).second(0).millisecond(0); - const tomorrowNoon = now.clone().add(1, "day").hour(12).minute(0).second(0).millisecond(0); + static getNextLevequestAllowance(now: Date): Date { + const todayNoon = DateHelper.setUtcTime(now, 12); + const tomorrowMidnight = DateHelper.setUtcTime(DateHelper.addDays(now, 1), 0); + const tomorrowNoon = DateHelper.setUtcTime(DateHelper.addDays(now, 1), 12); - if (now.isBefore(todayNoon)) { + if (now < todayNoon) { return todayNoon; - } else if (now.isBefore(tomorrowMidnight)) { + } else if (now < tomorrowMidnight) { return tomorrowMidnight; } else { return tomorrowNoon; } } - private static getNextCosmicExploration(now: moment.Moment): moment.Moment { - const todayReset = now.clone().hour(9).minute(0).second(0).millisecond(0); + static getNextCosmicExploration(now: Date): Date { + const todayReset = DateHelper.setUtcTime(now, 9); - if (now.isBefore(todayReset)) { + if (now < todayReset) { return todayReset; } else { - return todayReset.add(1, "day"); + return DateHelper.addDays(todayReset, 1); } } } diff --git a/src/index.ts b/src/index.ts index 671ffd3..b0a8e87 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,8 +12,8 @@ import { ModalSubmitInteraction, } from "discord.js"; import { CanvasRenderingContext2D, registerFont } from "canvas"; -import moment from "moment"; import cron from "node-cron"; +import { DateHelper } from "./helper/DateHelper.ts"; import * as log from "@std/log"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; @@ -44,7 +44,7 @@ log.setup({ handlers: { console: new log.ConsoleHandler("DEBUG", { formatter: (logRecord) => { - const formattedDate = `[${moment(logRecord.datetime).format("YYYY-MM-DD HH:mm:ss.SSS Z")}]`; + const formattedDate = `[${DateHelper.formatLog(logRecord.datetime)}]`; return `${formattedDate} [${logRecord.levelName}] ${logRecord.msg}`; }, }), @@ -57,8 +57,6 @@ log.setup({ }, }); -moment.locale("en"); - const fontsPath = join(__dirname, "..", "fonts"); try { registerFont(join(fontsPath, "MiedingerMediumW00-Regular.ttf"), { diff --git a/src/naagostone/dto/CharacterDataDto.ts b/src/naagostone/dto/CharacterDataDto.ts index f73b02b..e7fe669 100644 --- a/src/naagostone/dto/CharacterDataDto.ts +++ b/src/naagostone/dto/CharacterDataDto.ts @@ -1,12 +1,11 @@ -import { Moment } from "moment"; import { Character } from "../../naagostone/type/CharacterTypes.ts"; export class CharacterDataDto { - public latestUpdate: Moment; + public latestUpdate: Date; public character: Character; public isCachedDueToUnavailability: boolean; - constructor(latestUpdate: Moment, character: Character, isCachedDueToUnavailability: boolean = false) { + constructor(latestUpdate: Date, character: Character, isCachedDueToUnavailability: boolean = false) { this.latestUpdate = latestUpdate; this.character = character; this.isCachedDueToUnavailability = isCachedDueToUnavailability; diff --git a/src/service/FetchCharacterService.ts b/src/service/FetchCharacterService.ts index 7f27937..878e023 100644 --- a/src/service/FetchCharacterService.ts +++ b/src/service/FetchCharacterService.ts @@ -8,11 +8,11 @@ import { import { NaagostoneApiService } from "../naagostone/service/NaagostoneApiService.ts"; import { CharacterDataRepository } from "../database/repository/CharacterDataRepository.ts"; import { Character } from "../naagostone/type/CharacterTypes.ts"; -import moment from "moment"; import { VerificationsRepository } from "../database/repository/VerificationsRepository.ts"; import { CharacterDataDto } from "../naagostone/dto/CharacterDataDto.ts"; import { StringManipulationService } from "./StringManipulationService.ts"; import { LodestoneServiceUnavailableError } from "../naagostone/error/LodestoneServiceUnavailableError.ts"; +import { DateHelper } from "../helper/DateHelper.ts"; import * as log from "@std/log"; export class FetchCharacterService { @@ -43,8 +43,8 @@ export class FetchCharacterService { return this.fetchCharacterForced(interaction, characterId); } - const latestUpdate = moment(new Date(characterData.latestUpdate)); - if (latestUpdate.isBefore(moment().subtract(10, "minutes"))) { + const latestUpdate = new Date(characterData.latestUpdate); + if (latestUpdate < DateHelper.subtractMinutes(new Date(), 10)) { return this.fetchCharacterForced(interaction, characterId); } @@ -67,7 +67,7 @@ export class FetchCharacterService { await CharacterDataRepository.set(character); - return new CharacterDataDto(moment(), character); + return new CharacterDataDto(new Date(), character); } catch (error: unknown) { if (error instanceof LodestoneServiceUnavailableError) { log.warn( @@ -77,7 +77,7 @@ export class FetchCharacterService { if (cachedData) { log.info(`[CHARACTER] Using cached data for character ${characterId}`); return new CharacterDataDto( - moment(new Date(cachedData.latestUpdate)), + new Date(cachedData.latestUpdate), JSON.parse(cachedData.jsonString) as Character, true, ); diff --git a/src/service/MaintenanceSenderService.ts b/src/service/MaintenanceSenderService.ts index fcf7da6..610bc0a 100644 --- a/src/service/MaintenanceSenderService.ts +++ b/src/service/MaintenanceSenderService.ts @@ -1,4 +1,3 @@ -import moment from "moment"; import { StringManipulationService } from "./StringManipulationService.ts"; import { NaagostoneApiService } from "../naagostone/service/NaagostoneApiService.ts"; import { Maintenance } from "../naagostone/type/Maintenance.ts"; @@ -35,7 +34,7 @@ export class MaintenanceSenderService { maintenance.tag = maintenance.tag === "[Maintenance]" ? null : maintenance.tag; maintenance.tag = StringManipulationService.convertTag("maintenance", maintenance.tag ?? null); - const date = moment(maintenance.date).tz("Europe/London").toDate(); + const date = new Date(maintenance.date); const existingMaintenance = await MaintenancesRepository.find(maintenance.title, date); itemsWithExisting.push({ maintenance, existing: existingMaintenance }); diff --git a/src/service/NoticeSenderService.ts b/src/service/NoticeSenderService.ts index 326c41d..f0e5453 100644 --- a/src/service/NoticeSenderService.ts +++ b/src/service/NoticeSenderService.ts @@ -1,4 +1,3 @@ -import moment from "moment"; import { StringManipulationService } from "./StringManipulationService.ts"; import { NoticesRepository } from "../database/repository/NoticesRepository.ts"; import { NaagostoneApiService } from "../naagostone/service/NaagostoneApiService.ts"; @@ -37,7 +36,7 @@ export class NoticeSenderService { notice.tag = StringManipulationService.convertTag("notice", notice.tag); - const date = moment(notice.date).tz("Europe/London").toDate(); + const date = new Date(notice.date); const existingNotice = await NoticesRepository.find(notice.title, date); itemsWithExisting.push({ notice, existing: existingNotice }); diff --git a/src/service/ProfileGeneratorService.ts b/src/service/ProfileGeneratorService.ts index 144e44f..355551e 100644 --- a/src/service/ProfileGeneratorService.ts +++ b/src/service/ProfileGeneratorService.ts @@ -1,8 +1,8 @@ import { readFileSync } from "node:fs"; import { CanvasRenderingContext2D, createCanvas, loadImage } from "canvas"; import { ActionRowBuilder, ButtonBuilder } from "discord.js"; -import moment from "moment"; import { Buffer } from "node:buffer"; +import { DateHelper } from "../helper/DateHelper.ts"; declare module "canvas" { interface CanvasRenderingContext2D { @@ -289,7 +289,7 @@ class Profile { "Started", this.character.started === "Private" ? "Private" - : moment(parseInt(this.character.started) * 1000).format("Do MMM Y"), + : DateHelper.formatOrdinalDate(new Date(parseInt(this.character.started) * 1000)), ); await profileBlock.add( "City-state", @@ -1753,8 +1753,8 @@ class Profile { ctx.font = `bold 16px roboto condensed`; if (cleared && ultData.date) { - const date = moment(ultData.date); - ctx.fillText(date.format("MMM D, YYYY"), ux + boxWidth / 2, uy + 33, boxWidth - 10); + const date = new Date(ultData.date); + ctx.fillText(DateHelper.formatMediumDate(date), ux + boxWidth / 2, uy + 33, boxWidth - 10); if (ultData.week !== null && ultData.week !== undefined) { ctx.fillText(`Week ${ultData.week}`, ux + boxWidth / 2, uy + 50, boxWidth - 10); @@ -1892,8 +1892,8 @@ class Profile { ctx.font = `bold 12px roboto condensed`; if (cleared && raidClear?.date) { - const date = moment(raidClear.date); - ctx.fillText(date.format("MMM D, YYYY"), raidX + raidBoxWidth / 2, raidY + 22, raidBoxWidth - 8); + const date = new Date(raidClear.date); + ctx.fillText(DateHelper.formatMediumDate(date), raidX + raidBoxWidth / 2, raidY + 22, raidBoxWidth - 8); if (raidClear?.week !== null && raidClear?.week !== undefined) { ctx.fillText(`Week ${raidClear.week}`, raidX + raidBoxWidth / 2, raidY + 37, raidBoxWidth - 8); diff --git a/src/service/StatusSenderService.ts b/src/service/StatusSenderService.ts index f3e60b8..3374402 100644 --- a/src/service/StatusSenderService.ts +++ b/src/service/StatusSenderService.ts @@ -1,4 +1,3 @@ -import moment from "moment"; import { StringManipulationService } from "./StringManipulationService.ts"; import * as log from "@std/log"; import { NaagostoneApiService } from "../naagostone/service/NaagostoneApiService.ts"; @@ -37,7 +36,7 @@ export default class StatusSenderService { status.tag = StringManipulationService.convertTag("status", status.tag); - const date = moment(status.date).tz("Europe/London").toDate(); + const date = new Date(status.date); const existingStatus = await StatusesRepository.find(status.title, date); itemsWithExisting.push({ status, existing: existingStatus }); diff --git a/src/service/TopicSenderService.ts b/src/service/TopicSenderService.ts index b218408..05e421c 100644 --- a/src/service/TopicSenderService.ts +++ b/src/service/TopicSenderService.ts @@ -1,4 +1,3 @@ -import moment from "moment"; import { Topic } from "../naagostone/type/Topic.ts"; import { NaagostoneApiService } from "../naagostone/service/NaagostoneApiService.ts"; import { TopicsRepository } from "../database/repository/TopicsRepository.ts"; @@ -34,7 +33,7 @@ export class TopicSenderService { for (const topic of latestTopics) { if (!topic) continue; - const date = moment(topic.date).tz("Europe/London").toDate(); + const date = new Date(topic.date); const existingTopic = await TopicsRepository.find(topic.title, date); itemsWithExisting.push({ topic, existing: existingTopic }); diff --git a/src/service/UpdateSenderService.ts b/src/service/UpdateSenderService.ts index fa47bf9..d707500 100644 --- a/src/service/UpdateSenderService.ts +++ b/src/service/UpdateSenderService.ts @@ -1,4 +1,3 @@ -import moment from "moment"; import { NaagostoneApiService } from "../naagostone/service/NaagostoneApiService.ts"; import { Update } from "../naagostone/type/Updates.ts"; import { UpdatesRepository } from "../database/repository/UpdatesRepository.ts"; @@ -34,7 +33,7 @@ export class UpdateSenderService { for (const update of latestUpdates) { if (!update) continue; - const date = moment(update.date).tz("Europe/London").toDate(); + const date = new Date(update.date); const existingUpdate = await UpdatesRepository.find(update.title, date); itemsWithExisting.push({ update, existing: existingUpdate }); diff --git a/tests/helper/DateHelper.test.ts b/tests/helper/DateHelper.test.ts new file mode 100644 index 0000000..a0deff4 --- /dev/null +++ b/tests/helper/DateHelper.test.ts @@ -0,0 +1,118 @@ +import { assertEquals } from "@std/assert"; +import { DateHelper } from "../../src/helper/DateHelper.ts"; + +Deno.test("DateHelper.setUtcTime - sets hours, minutes, seconds, ms", () => { + const base = new Date("2025-03-10T14:22:33.444Z"); + const result = DateHelper.setUtcTime(base, 8, 0, 0, 0); + assertEquals(result.getUTCHours(), 8); + assertEquals(result.getUTCMinutes(), 0); + assertEquals(result.getUTCSeconds(), 0); + assertEquals(result.getUTCMilliseconds(), 0); + assertEquals(result.getUTCDate(), 10); +}); + +Deno.test("DateHelper.setUtcTime - does not mutate the original date", () => { + const base = new Date("2025-03-10T14:22:33.444Z"); + DateHelper.setUtcTime(base, 8); + assertEquals(base.getUTCHours(), 14); +}); + +Deno.test("DateHelper.setUtcDayOfWeek - sets day forward within the same week", () => { + const monday = new Date("2025-03-10T12:00:00Z"); + assertEquals(monday.getUTCDay(), 1); + const friday = DateHelper.setUtcDayOfWeek(monday, 5, 8); + assertEquals(friday.getUTCDay(), 5); + assertEquals(friday.getUTCHours(), 8); + assertEquals(friday.getUTCDate(), 14); +}); + +Deno.test("DateHelper.setUtcDayOfWeek - sets day backward within the same week", () => { + const friday = new Date("2025-03-14T12:00:00Z"); + assertEquals(friday.getUTCDay(), 5); + const tuesday = DateHelper.setUtcDayOfWeek(friday, 2, 8); + assertEquals(tuesday.getUTCDay(), 2); + assertEquals(tuesday.getUTCDate(), 11); +}); + +Deno.test("DateHelper.setUtcDayOfWeek - does not mutate the original date", () => { + const monday = new Date("2025-03-10T12:00:00Z"); + DateHelper.setUtcDayOfWeek(monday, 5, 8); + assertEquals(monday.getUTCDay(), 1); + assertEquals(monday.getUTCDate(), 10); +}); + +Deno.test("DateHelper.addDays - adds positive days", () => { + const base = new Date("2025-03-10T10:00:00Z"); + const result = DateHelper.addDays(base, 3); + assertEquals(result.getUTCDate(), 13); + assertEquals(result.getUTCHours(), 10); +}); + +Deno.test("DateHelper.addDays - handles month boundary", () => { + const base = new Date("2025-03-30T10:00:00Z"); + const result = DateHelper.addDays(base, 3); + assertEquals(result.getUTCMonth(), 3); + assertEquals(result.getUTCDate(), 2); +}); + +Deno.test("DateHelper.addWeeks - adds weeks correctly", () => { + const base = new Date("2025-03-10T10:00:00Z"); + const result = DateHelper.addWeeks(base, 2); + assertEquals(result.getUTCDate(), 24); +}); + +Deno.test("DateHelper.addMilliseconds - adds milliseconds", () => { + const base = new Date("2025-03-10T10:00:00Z"); + const result = DateHelper.addMilliseconds(base, 5000); + assertEquals(result.getUTCSeconds(), 5); +}); + +Deno.test("DateHelper.subtractMinutes - subtracts minutes correctly", () => { + const base = new Date("2025-03-10T10:30:00Z"); + const result = DateHelper.subtractMinutes(base, 10); + assertEquals(result.getUTCHours(), 10); + assertEquals(result.getUTCMinutes(), 20); +}); + +Deno.test("DateHelper.subtractMinutes - crosses hour boundary", () => { + const base = new Date("2025-03-10T10:05:00Z"); + const result = DateHelper.subtractMinutes(base, 10); + assertEquals(result.getUTCHours(), 9); + assertEquals(result.getUTCMinutes(), 55); +}); + +Deno.test("DateHelper.subtractMinutes - does not mutate the original date", () => { + const base = new Date("2025-03-10T10:30:00Z"); + DateHelper.subtractMinutes(base, 10); + assertEquals(base.getUTCMinutes(), 30); +}); + +Deno.test("DateHelper.formatLog - formats date with timezone offset", () => { + const result = DateHelper.formatLog(new Date("2025-03-10T10:30:45.123Z")); + assertEquals(result.length > 0, true); + assertEquals(result.includes("2025"), true); + assertEquals(result.includes(".123"), true); +}); + +Deno.test("DateHelper.formatOrdinalDate - formats with ordinal suffix", () => { + assertEquals(DateHelper.formatOrdinalDate(new Date("2025-01-01T00:00:00Z")), "1st Jan 2025"); + assertEquals(DateHelper.formatOrdinalDate(new Date("2025-01-02T00:00:00Z")), "2nd Jan 2025"); + assertEquals(DateHelper.formatOrdinalDate(new Date("2025-01-03T00:00:00Z")), "3rd Jan 2025"); + assertEquals(DateHelper.formatOrdinalDate(new Date("2025-01-04T00:00:00Z")), "4th Jan 2025"); + assertEquals(DateHelper.formatOrdinalDate(new Date("2025-01-11T00:00:00Z")), "11th Jan 2025"); + assertEquals(DateHelper.formatOrdinalDate(new Date("2025-01-21T00:00:00Z")), "21st Jan 2025"); + assertEquals(DateHelper.formatOrdinalDate(new Date("2025-01-22T00:00:00Z")), "22nd Jan 2025"); + assertEquals(DateHelper.formatOrdinalDate(new Date("2025-06-15T00:00:00Z")), "15th Jun 2025"); + assertEquals(DateHelper.formatOrdinalDate(new Date("2025-12-31T00:00:00Z")), "31st Dec 2025"); +}); + +Deno.test("DateHelper.formatMediumDate - formats as MMM D, YYYY", () => { + assertEquals(DateHelper.formatMediumDate(new Date("2025-01-01T00:00:00Z")), "Jan 1, 2025"); + assertEquals(DateHelper.formatMediumDate(new Date("2025-06-15T12:00:00Z")), "Jun 15, 2025"); + assertEquals(DateHelper.formatMediumDate(new Date("2025-12-31T23:59:59Z")), "Dec 31, 2025"); +}); + +Deno.test("DateHelper.toEpochSeconds - converts to seconds", () => { + const date = new Date("2025-01-01T00:00:00Z"); + assertEquals(DateHelper.toEpochSeconds(date), 1735689600); +}); diff --git a/tests/helper/WhenIsResetCommandHelper.test.ts b/tests/helper/WhenIsResetCommandHelper.test.ts new file mode 100644 index 0000000..4412782 --- /dev/null +++ b/tests/helper/WhenIsResetCommandHelper.test.ts @@ -0,0 +1,125 @@ +import { assertEquals } from "@std/assert"; +import { WhenIsResetCommandHelper } from "../../src/helper/WhenIsResetCommandHelper.ts"; + +Deno.test("getNextDailyReset - before 15:00 UTC returns same day", () => { + const now = new Date("2025-03-10T10:00:00Z"); + const result = WhenIsResetCommandHelper.getNextDailyReset(now); + assertEquals(result.getUTCDate(), 10); + assertEquals(result.getUTCHours(), 15); + assertEquals(result.getUTCMinutes(), 0); +}); + +Deno.test("getNextDailyReset - after 15:00 UTC returns next day", () => { + const now = new Date("2025-03-10T16:00:00Z"); + const result = WhenIsResetCommandHelper.getNextDailyReset(now); + assertEquals(result.getUTCDate(), 11); + assertEquals(result.getUTCHours(), 15); +}); + +Deno.test("getNextDailyReset - exactly at 15:00 UTC returns next day", () => { + const now = new Date("2025-03-10T15:00:00Z"); + const result = WhenIsResetCommandHelper.getNextDailyReset(now); + assertEquals(result.getUTCDate(), 11); + assertEquals(result.getUTCHours(), 15); +}); + +Deno.test("getNextGcReset - before 20:00 UTC returns same day", () => { + const now = new Date("2025-03-10T18:00:00Z"); + const result = WhenIsResetCommandHelper.getNextGcReset(now); + assertEquals(result.getUTCDate(), 10); + assertEquals(result.getUTCHours(), 20); +}); + +Deno.test("getNextGcReset - after 20:00 UTC returns next day", () => { + const now = new Date("2025-03-10T21:00:00Z"); + const result = WhenIsResetCommandHelper.getNextGcReset(now); + assertEquals(result.getUTCDate(), 11); + assertEquals(result.getUTCHours(), 20); +}); + +Deno.test("getNextWeeklyReset - on Monday returns Tuesday 08:00", () => { + const monday = new Date("2025-03-10T10:00:00Z"); + assertEquals(monday.getUTCDay(), 1); + const result = WhenIsResetCommandHelper.getNextWeeklyReset(monday); + assertEquals(result.getUTCDay(), 2); + assertEquals(result.getUTCHours(), 8); + assertEquals(result.getUTCDate(), 11); +}); + +Deno.test("getNextWeeklyReset - on Tuesday after 08:00 returns next Tuesday", () => { + const tuesday = new Date("2025-03-11T10:00:00Z"); + assertEquals(tuesday.getUTCDay(), 2); + const result = WhenIsResetCommandHelper.getNextWeeklyReset(tuesday); + assertEquals(result.getUTCDay(), 2); + assertEquals(result.getUTCDate(), 18); + assertEquals(result.getUTCHours(), 8); +}); + +Deno.test("getNextWeeklyReset - on Wednesday returns next Tuesday", () => { + const wednesday = new Date("2025-03-12T10:00:00Z"); + assertEquals(wednesday.getUTCDay(), 3); + const result = WhenIsResetCommandHelper.getNextWeeklyReset(wednesday); + assertEquals(result.getUTCDay(), 2); + assertEquals(result.getUTCDate(), 18); +}); + +Deno.test("getNextFashionReport - on Thursday returns Friday 08:00", () => { + const thursday = new Date("2025-03-13T10:00:00Z"); + assertEquals(thursday.getUTCDay(), 4); + const result = WhenIsResetCommandHelper.getNextFashionReport(thursday); + assertEquals(result.getUTCDay(), 5); + assertEquals(result.getUTCHours(), 8); + assertEquals(result.getUTCDate(), 14); +}); + +Deno.test("getNextFashionReport - on Friday after 08:00 returns next Friday", () => { + const friday = new Date("2025-03-14T10:00:00Z"); + assertEquals(friday.getUTCDay(), 5); + const result = WhenIsResetCommandHelper.getNextFashionReport(friday); + assertEquals(result.getUTCDay(), 5); + assertEquals(result.getUTCDate(), 21); +}); + +Deno.test("getNextJumboCactpot - on Friday returns Saturday 20:00", () => { + const friday = new Date("2025-03-14T10:00:00Z"); + const result = WhenIsResetCommandHelper.getNextJumboCactpot(friday); + assertEquals(result.getUTCDay(), 6); + assertEquals(result.getUTCHours(), 20); + assertEquals(result.getUTCDate(), 15); +}); + +Deno.test("getNextJumboCactpot - on Saturday after 20:00 returns next Saturday", () => { + const saturday = new Date("2025-03-15T21:00:00Z"); + assertEquals(saturday.getUTCDay(), 6); + const result = WhenIsResetCommandHelper.getNextJumboCactpot(saturday); + assertEquals(result.getUTCDay(), 6); + assertEquals(result.getUTCDate(), 22); +}); + +Deno.test("getNextLevequestAllowance - before noon returns today noon", () => { + const now = new Date("2025-03-10T08:00:00Z"); + const result = WhenIsResetCommandHelper.getNextLevequestAllowance(now); + assertEquals(result.getUTCDate(), 10); + assertEquals(result.getUTCHours(), 12); +}); + +Deno.test("getNextLevequestAllowance - after noon before midnight returns tomorrow midnight", () => { + const now = new Date("2025-03-10T15:00:00Z"); + const result = WhenIsResetCommandHelper.getNextLevequestAllowance(now); + assertEquals(result.getUTCDate(), 11); + assertEquals(result.getUTCHours(), 0); +}); + +Deno.test("getNextCosmicExploration - before 09:00 returns same day", () => { + const now = new Date("2025-03-10T07:00:00Z"); + const result = WhenIsResetCommandHelper.getNextCosmicExploration(now); + assertEquals(result.getUTCDate(), 10); + assertEquals(result.getUTCHours(), 9); +}); + +Deno.test("getNextCosmicExploration - after 09:00 returns next day", () => { + const now = new Date("2025-03-10T10:00:00Z"); + const result = WhenIsResetCommandHelper.getNextCosmicExploration(now); + assertEquals(result.getUTCDate(), 11); + assertEquals(result.getUTCHours(), 9); +}); diff --git a/tests/naagstone/dto/CharacterDataDto.test.ts b/tests/naagstone/dto/CharacterDataDto.test.ts index e2540d6..b8f27e2 100644 --- a/tests/naagstone/dto/CharacterDataDto.test.ts +++ b/tests/naagstone/dto/CharacterDataDto.test.ts @@ -1,12 +1,11 @@ import { assertEquals } from "@std/assert"; -import moment from "moment"; import { CharacterDataDto } from "../../../src/naagostone/dto/CharacterDataDto.ts"; import type { Character } from "../../../src/naagostone/type/CharacterTypes.ts"; const mockCharacter = { id: 12345, name: "Test Character" } as unknown as Character; Deno.test("CharacterDataDto - stores latestUpdate and character", () => { - const now = moment(); + const now = new Date(); const dto = new CharacterDataDto(now, mockCharacter); assertEquals(dto.latestUpdate, now); assertEquals(dto.character, mockCharacter); @@ -14,11 +13,11 @@ Deno.test("CharacterDataDto - stores latestUpdate and character", () => { }); Deno.test("CharacterDataDto - isCachedDueToUnavailability defaults to false", () => { - const dto = new CharacterDataDto(moment(), mockCharacter); + const dto = new CharacterDataDto(new Date(), mockCharacter); assertEquals(dto.isCachedDueToUnavailability, false); }); Deno.test("CharacterDataDto - isCachedDueToUnavailability can be set to true", () => { - const dto = new CharacterDataDto(moment(), mockCharacter, true); + const dto = new CharacterDataDto(new Date(), mockCharacter, true); assertEquals(dto.isCachedDueToUnavailability, true); });