mirror of
https://github.com/ManInDark/HabitTrove.git
synced 2026-01-21 22:54:29 +01:00
refactor: replace moment library with luxon
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { expect, test, describe, beforeAll, afterAll } from "bun:test";
|
||||
import { cn, getDateInTimezone, getTodayInTimezone } from './utils'
|
||||
import { cn, getTodayInTimezone, getNow, getNowInMilliseconds, t2d, d2t, d2s, d2sDate, d2n, isSameDate } from './utils'
|
||||
import { DateTime } from "luxon";
|
||||
|
||||
describe('cn utility', () => {
|
||||
test('should merge class names correctly', () => {
|
||||
@@ -10,41 +11,86 @@ describe('cn utility', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('timezone utilities', () => {
|
||||
describe('getDateInTimezone', () => {
|
||||
test('should convert date to specified timezone', () => {
|
||||
const date = new Date('2024-01-01T00:00:00Z')
|
||||
|
||||
// Test with specific timezones
|
||||
const nyDate = getDateInTimezone(date, 'America/New_York')
|
||||
expect(nyDate.toISOString()).toBe('2023-12-31T19:00:00.000Z') // NY is UTC-5
|
||||
describe('datetime utilities', () => {
|
||||
let fixedNow: DateTime;
|
||||
|
||||
const tokyoDate = getDateInTimezone(date, 'Asia/Tokyo')
|
||||
expect(tokyoDate.toISOString()).toBe('2024-01-01T09:00:00.000Z') // Tokyo is UTC+9
|
||||
})
|
||||
|
||||
test('should handle string dates', () => {
|
||||
const dateStr = '2024-01-01T00:00:00Z'
|
||||
const nyDate = getDateInTimezone(dateStr, 'America/New_York')
|
||||
expect(nyDate.toISOString()).toBe('2023-12-31T19:00:00.000Z')
|
||||
})
|
||||
beforeAll(() => {
|
||||
// Fix the current time to 2024-01-01T00:00:00Z
|
||||
fixedNow = DateTime.fromISO('2024-01-01T00:00:00Z');
|
||||
DateTime.now = () => fixedNow;
|
||||
})
|
||||
|
||||
describe('getTodayInTimezone', () => {
|
||||
let originalDate: Date;
|
||||
|
||||
beforeAll(() => {
|
||||
originalDate = new Date();
|
||||
globalThis.Date.now = () => new Date('2024-01-01T00:00:00Z').getTime();
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
globalThis.Date.now = () => originalDate.getTime();
|
||||
})
|
||||
|
||||
test('should return today in YYYY-MM-DD format for timezone', () => {
|
||||
expect(getTodayInTimezone('America/New_York')).toBe('2023-12-31')
|
||||
expect(getTodayInTimezone('Asia/Tokyo')).toBe('2024-01-01')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getNow', () => {
|
||||
test('should return current datetime in specified timezone', () => {
|
||||
const nyNow = getNow({ timezone: 'America/New_York' });
|
||||
expect(nyNow.zoneName).toBe('America/New_York')
|
||||
expect(nyNow.year).toBe(2023)
|
||||
expect(nyNow.month).toBe(12)
|
||||
expect(nyNow.day).toBe(31)
|
||||
})
|
||||
|
||||
test('should default to UTC', () => {
|
||||
const utcNow = getNow({});
|
||||
expect(utcNow.zoneName).toBe('UTC')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getNowInMilliseconds', () => {
|
||||
test('should return current time in milliseconds', () => {
|
||||
expect(getNowInMilliseconds()).toBe('1704067200000')
|
||||
})
|
||||
})
|
||||
|
||||
describe('timestamp conversion utilities', () => {
|
||||
const testTimestamp = '2024-01-01T00:00:00.000Z';
|
||||
const testDateTime = DateTime.fromISO(testTimestamp);
|
||||
|
||||
test('t2d should convert ISO timestamp to DateTime', () => {
|
||||
const result = t2d({ timestamp: testTimestamp });
|
||||
// Normalize both timestamps to handle different UTC offset formats (Z vs +00:00)
|
||||
expect(DateTime.fromISO(result.toISO()!).toMillis())
|
||||
.toBe(DateTime.fromISO(testTimestamp).toMillis())
|
||||
})
|
||||
|
||||
test('d2t should convert DateTime to ISO timestamp', () => {
|
||||
const result = d2t({ dateTime: testDateTime });
|
||||
expect(result).toBe(testTimestamp)
|
||||
})
|
||||
|
||||
test('d2s should format DateTime for display', () => {
|
||||
const result = d2s({ dateTime: testDateTime });
|
||||
expect(result).toBeString()
|
||||
|
||||
const customFormat = d2s({ dateTime: testDateTime, format: 'yyyy-MM-dd' });
|
||||
expect(customFormat).toBe('2024-01-01')
|
||||
})
|
||||
|
||||
test('d2sDate should format DateTime as date string', () => {
|
||||
const result = d2sDate({ dateTime: testDateTime });
|
||||
expect(result).toBeString()
|
||||
})
|
||||
|
||||
test('d2n should convert DateTime to milliseconds string', () => {
|
||||
const result = d2n({ dateTime: testDateTime });
|
||||
expect(result).toBe('1704067200000')
|
||||
})
|
||||
})
|
||||
|
||||
describe('isSameDate', () => {
|
||||
test('should compare dates correctly', () => {
|
||||
const date1 = DateTime.fromISO('2024-01-01T12:00:00Z');
|
||||
const date2 = DateTime.fromISO('2024-01-01T15:00:00Z');
|
||||
const date3 = DateTime.fromISO('2024-01-02T12:00:00Z');
|
||||
|
||||
expect(isSameDate(date1, date2)).toBe(true)
|
||||
expect(isSameDate(date1, date3)).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
53
lib/utils.ts
53
lib/utils.ts
@@ -1,16 +1,57 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import moment from "moment-timezone"
|
||||
import { DateTime } from "luxon"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export function getDateInTimezone(date: Date | string, timezone: string): Date {
|
||||
const m = moment.tz(date, timezone);
|
||||
return new Date(m.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
|
||||
// get today's date string for timezone
|
||||
export function getTodayInTimezone(timezone: string): string {
|
||||
const now = getNow({ timezone });
|
||||
return d2s({ dateTime: now, format: 'yyyy-MM-dd' });
|
||||
}
|
||||
|
||||
export function getTodayInTimezone(timezone: string): string {
|
||||
return moment.tz(timezone).format('YYYY-MM-DD');
|
||||
// get datetime object of now
|
||||
export function getNow({ timezone = 'utc' }: { timezone?: string }) {
|
||||
return DateTime.now().setZone(timezone);
|
||||
}
|
||||
|
||||
// get current time in epoch milliseconds
|
||||
export function getNowInMilliseconds() {
|
||||
const now = getNow({});
|
||||
return d2n({ dateTime: now });
|
||||
}
|
||||
|
||||
// iso timestamp to datetime object, most for storage read
|
||||
export function t2d({ timestamp, timezone }: { timestamp: string; timezone?: string }) {
|
||||
return DateTime.fromISO(timestamp).setZone(timezone);
|
||||
}
|
||||
|
||||
// convert datetime object to iso timestamp, mostly for storage write
|
||||
export function d2t({ dateTime, timezone = 'utc' }: { dateTime: DateTime, timezone?: string }) {
|
||||
return dateTime.setZone(timezone).toISO()!;
|
||||
}
|
||||
|
||||
// convert datetime object to string, mostly for display
|
||||
export function d2s({ dateTime, format }: { dateTime: DateTime, format?: string }) {
|
||||
if (format) {
|
||||
return dateTime.toFormat(format);
|
||||
}
|
||||
return dateTime.toLocaleString(DateTime.DATETIME_MED);
|
||||
}
|
||||
|
||||
// convert datetime object to date string, mostly for display
|
||||
export function d2sDate({ dateTime }: { dateTime: DateTime }) {
|
||||
return dateTime.toLocaleString(DateTime.DATE_MED);
|
||||
}
|
||||
|
||||
// convert datetime object to epoch milliseconds string, mostly for storage write
|
||||
export function d2n({ dateTime }: { dateTime: DateTime }) {
|
||||
return dateTime.toMillis().toString();
|
||||
}
|
||||
|
||||
// compare the date portion of two datetime objects (i.e. same year, month, day)
|
||||
export function isSameDate(a: DateTime, b: DateTime) {
|
||||
return a.hasSame(b, 'day');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user