diff --git a/package.json b/package.json index 197034a..b1b8eee 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,6 @@ "dependencies": { "@permafrost-dev/pretty-format": "^1.1.5", "axios": "^1.6.8", - "dayjs": "^1.11.10", "stacktrace-js": "^2.0.2", "uuid": "^9.0.1", "xml-formatter": "^3.6.2" diff --git a/src/Payloads/DatePayload.ts b/src/Payloads/DatePayload.ts index 85f49e5..11aeb42 100644 --- a/src/Payloads/DatePayload.ts +++ b/src/Payloads/DatePayload.ts @@ -1,5 +1,5 @@ +import { formatDateExtended } from '@/lib/utils'; import { Payload } from '@/Payloads/Payload'; -import dayjs from 'dayjs'; export type PayloadType = 'date'; @@ -28,27 +28,27 @@ export class DatePayload extends Payload { } protected getTimestamp(): number | null { - if (this.date === null) return null; + if (!this.date) { + return null; + } - return dayjs(this.date.toISOString()).unix(); + //remove the last '000' to get the timestamp in seconds: + return this.date.getTime() / 1000; } protected getFormatted(): string { - if (this.date === null) { + if (!this.date) { return '--'; } - return dayjs(this.date.toISOString()).format(this.format); + return formatDateExtended(this.date, this.format); } protected getTimezoneName(): string { - if (this.date === null) { + if (!this.date) { return '--'; } - const dateObj = this.date ? this.date : new Date(); - const matches = /\((.*)\)/.exec(dateObj.toString()); - - return matches ? matches[1] : '--'; + return formatDateExtended(this.date, 'T'); } } diff --git a/src/Support/CacheStore.ts b/src/Support/CacheStore.ts index e60d941..a873690 100644 --- a/src/Support/CacheStore.ts +++ b/src/Support/CacheStore.ts @@ -27,7 +27,7 @@ export class CacheStore { } public countLastSecond(): number { - const lastSecond = this.clock.now().modify('-1 second'); + const lastSecond = this.clock.now().subSeconds(1); let amount = 0; this.store.forEach(item => { diff --git a/src/Support/DateImmutable.ts b/src/Support/DateImmutable.ts index 4b317e2..e97441e 100644 --- a/src/Support/DateImmutable.ts +++ b/src/Support/DateImmutable.ts @@ -1,17 +1,10 @@ -/* eslint-disable no-unused-vars */ -import dayjs from 'dayjs'; - -interface DateImmutableModifyPart { - value: number; - unit: string; -} - export class DateImmutable { public dateStr: string; public dateTs: number; + protected _date!: Date; public get date() { - return dayjs(this.dateStr).toDate(); + return this._date; } public set date(value: Date) { @@ -24,7 +17,8 @@ export class DateImmutable { } constructor(date: Date | null = null) { - this.date = date ?? new Date(); + this._date = date ?? new Date(); + this.date = this._date; this.dateStr = this.date.toISOString(); this.dateTs = this.date.getTime(); } @@ -33,25 +27,11 @@ export class DateImmutable { return Math.floor(this.dateTs / 1000); } - public modify(str: string): DateImmutable { - const strParts = str.split(' '); - const parts: DateImmutableModifyPart[] = []; - - for (let idx = 0; idx < strParts.length; idx++) { - parts.push({ - value: Number(strParts[idx]), - unit: strParts[idx + 1], - }); - - idx++; - } - - let tempDate = dayjs(this.getTimestamp() * 1000); - - parts.forEach(part => { - tempDate = tempDate.add(part.value * 1000); - }); + public addSeconds(seconds: number): DateImmutable { + return DateImmutable.createFrom(new Date(this.dateTs + seconds * 1000)); + } - return DateImmutable.createFrom(tempDate.toDate()); + public subSeconds(seconds: number): DateImmutable { + return DateImmutable.createFrom(new Date(this.dateTs - seconds * 1000)); } } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 593a0be..0ae9103 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -248,3 +248,97 @@ export function fileURLToPath(url): string { throw new Error('Invalid URL.'); } } + +/** + * Returns a formatted date string using dayjs format strings. + * @param date Date + * @param format string + * @returns {string} + */ +export function formatDateExtended(date: Date, format = 'YYYY-MM-DD HH:mm:ss'): string { + const pad = number => (number < 10 ? `0${number}` : number); + const getTzName = date => { + const tzNameMatch = new Intl.DateTimeFormat('en', { timeZoneName: 'short' }) + .formatToParts(date) + .find(part => part.type === 'timeZoneName'); + return tzNameMatch ? tzNameMatch.value : ''; + }; + + const tokens = { + // Year + YYYY: () => date.getFullYear(), + YY: () => String(date.getFullYear()).slice(-2), + + // Month + MM: () => pad(date.getMonth() + 1), + M: () => date.getMonth() + 1, + + // Day + DD: () => pad(date.getDate()), + D: () => date.getDate(), + + // Hours + HH: () => pad(date.getHours()), + H: () => date.getHours(), + hh: () => pad(date.getHours() % 12 || 12), + h: () => date.getHours() % 12 || 12, + + // Minutes + mm: () => pad(date.getMinutes()), + m: () => date.getMinutes(), + + // Seconds + ss: () => pad(date.getSeconds()), + s: () => date.getSeconds(), + + // AM/PM + A: () => (date.getHours() < 12 ? 'AM' : 'PM'), + a: () => (date.getHours() < 12 ? 'am' : 'pm'), + + T: () => getTzName(date), + Z: () => { + const offset = date.getTimezoneOffset(); + return `${(offset > 0 ? '-' : '+') + pad(Math.floor(Math.abs(offset) / 60))}:${pad(Math.abs(offset) % 60)}`; + }, + z: () => { + const offset = date.getTimezoneOffset(); + return `${(offset > 0 ? '-' : '+') + pad(Math.floor(Math.abs(offset) / 60)) + pad(Math.abs(offset) % 60)}`; + }, + + // Escape character is handled below in the loop + }; + + let formattedDate = ''; + let escapeNext = false; + for (let i = 0; i < format.length; i++) { + if (format[i] === '[') { + escapeNext = true; + continue; + } + + if (format[i] === ']') { + escapeNext = false; + continue; + } + + if (escapeNext) { + formattedDate += format[i]; + continue; + } + + const twoCharToken = format.substring(i, i + 2); + const fourCharToken = format.substring(i, i + 4); + + if (tokens[fourCharToken]) { + formattedDate += tokens[fourCharToken](); + i += 3; // Skip next 3 characters + } else if (tokens[twoCharToken]) { + formattedDate += tokens[twoCharToken](); + i += 1; // Skip next character + } else { + formattedDate += format[i]; + } + } + + return formattedDate; +} diff --git a/tests/Support/CacheStore.test.ts b/tests/Support/CacheStore.test.ts index 0915963..fb09d3d 100644 --- a/tests/Support/CacheStore.test.ts +++ b/tests/Support/CacheStore.test.ts @@ -14,11 +14,11 @@ it('can count per second', () => { expect(store.countLastSecond()).toBe(3); - clock.moveForward('1 second'); + clock.moveForwardInSeconds(1); expect(store.countLastSecond()).toBe(3); - clock.moveForward('1 second'); + clock.moveForwardInSeconds(1); expect(store.countLastSecond()).toBe(0); }); diff --git a/tests/Support/DateImmutable.test.ts b/tests/Support/DateImmutable.test.ts index f90c9da..b74915c 100644 --- a/tests/Support/DateImmutable.test.ts +++ b/tests/Support/DateImmutable.test.ts @@ -2,30 +2,29 @@ import { expect, it } from 'vitest'; import { DateImmutable } from '../../src/Support/DateImmutable'; -import dayjs from 'dayjs'; it('returns a timestamp without milliseconds', () => { - const now = DateImmutable.createFrom(dayjs('2021-06-22T18:11:03.967Z').toDate()); + const now = DateImmutable.createFrom(new Date('2021-06-22T18:11:03.000Z')); expect(now.getTimestamp()).toBe(1624385463); }); it('modifies the date by adding time', () => { - let now = DateImmutable.createFrom(dayjs('2021-06-22T18:11:03.967Z').toDate()); + let now = DateImmutable.createFrom(new Date('2021-06-22T18:11:03.000Z')); expect(now.getTimestamp()).toBe(1624385463); - now = now.modify('5 seconds'); + now = now.addSeconds(5); expect(now.getTimestamp()).toBe(1624385468); }); it('modifies the date by subtracting time', () => { - let now = DateImmutable.createFrom(dayjs('2021-06-22T18:11:03.000Z').toDate()); + let now = DateImmutable.createFrom(new Date('2021-06-22T18:11:03.000Z')); expect(now.getTimestamp()).toBe(1624385463); - now = now.modify('-2 seconds'); + now = now.subSeconds(2); expect(now.getTimestamp()).toBe(1624385461); }); diff --git a/tests/TestClasses/FakeClock.ts b/tests/TestClasses/FakeClock.ts index 472723e..53c3f06 100644 --- a/tests/TestClasses/FakeClock.ts +++ b/tests/TestClasses/FakeClock.ts @@ -16,9 +16,9 @@ export class FakeClock implements Clock { this.fixedNow = now ?? new DateImmutable(); } - public moveForward(modifier: string): void { + public moveForwardInSeconds(modifier: number): void { const currentTime = this.now(); - const modifiedTime = currentTime.modify(`${modifier}`); + const modifiedTime = currentTime.addSeconds(modifier); this.freeze(modifiedTime); } diff --git a/tests/lib/utils.test.ts b/tests/lib/utils.test.ts index f36255b..b3b9f13 100644 --- a/tests/lib/utils.test.ts +++ b/tests/lib/utils.test.ts @@ -11,6 +11,7 @@ import { end, nonCryptoUuidV4, md5, + formatDateExtended, } from '../../src/lib/utils'; it.concurrent('sleeps for 0.1 sec', async () => { @@ -79,3 +80,11 @@ it('calculates an md5 hash', () => { const data = 'test'; expect(md5(data)).toBe('098f6bcd4621d373cade4e832627b4f6'); }); + +it('formats a date', () => { + const date = new Date('2021-01-01T12:34:56'); + expect(formatDateExtended(date)).toBe('2021-01-01 12:34:56'); + expect(formatDateExtended(date, 'YYYY-MM-DD')).toBe('2021-01-01'); + expect(formatDateExtended(date, 'hh:mm:ss')).toBe('12:34:56'); + expect(formatDateExtended(date, 'T').length).greaterThan(1); +});