Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions packages/core/src/__tests__/time.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import { type Timestamp } from '../classes'
import { getDay, convertToDay } from '..'

const dayTimeZone = 'Europe/London'

const supportedTimezones: string[] = [
'Europe/Andorra',
'Asia/Dubai',
Expand Down Expand Up @@ -348,18 +350,22 @@ const supportedTimezones: string[] = [
'Africa/Johannesburg'
]

function localeDate (date: Date, timezone: string): string {
return date.toLocaleDateString('en-US', { timeZone: timezone })
}

function testGetDay (date: Date, timezone: string): void {
const timestamp: Timestamp = getDay(date.getTime())
const convertedDate: Date = new Date(timestamp)
const originalLocaleDate: string = date.toLocaleDateString('en-US', { timeZone: 'Europe/London' })
const convertedLocaleDate: string = convertedDate.toLocaleDateString('en-US', { timeZone: timezone })
const originalLocaleDate: string = localeDate(date, dayTimeZone)
const convertedLocaleDate: string = localeDate(convertedDate, timezone)
expect(convertedLocaleDate).toEqual(originalLocaleDate)
}

function testConvertToDay (date: Date, timezone: string): void {
const convertedDate: Date = convertToDay(date)
const originalLocaleDate: string = date.toLocaleDateString('en-US', { timeZone: 'Europe/London' })
const convertedLocaleDate: string = convertedDate.toLocaleDateString('en-US', { timeZone: timezone })
const originalLocaleDate: string = localeDate(date, dayTimeZone)
const convertedLocaleDate: string = localeDate(convertedDate, timezone)
expect(convertedLocaleDate).toEqual(originalLocaleDate)
}

Expand Down Expand Up @@ -389,10 +395,23 @@ describe('time', () => {
'dates are matched for time [h: %p, m: %p, s: %p, ms: %p]',
(hours: number, minutes: number, seconds: number, milliSeconds: number) => {
const date: Date = new Date()
const expectedDay: number = date.getDate()
date.setHours(hours, minutes, seconds, milliSeconds)
const convertedDate: Date = convertToDay(date)
expect(convertedDate.getDate()).toEqual(expectedDay)
expect(localeDate(convertedDate, dayTimeZone)).toEqual(localeDate(date, dayTimeZone))
expect(convertedDate.getUTCHours()).toEqual(12)
expect(convertedDate.getUTCMinutes()).toEqual(0)
expect(convertedDate.getUTCSeconds()).toEqual(0)
expect(convertedDate.getUTCMilliseconds()).toEqual(0)
}
)

it.each([
[new Date('2025-01-25T00:00:00.000Z'), '2025-01-25T12:00:00.000Z'],
[new Date('2024-06-12T23:30:00.000Z'), '2024-06-13T12:00:00.000Z']
])('normalizes the London calendar day to noon UTC for %p', (date: Date, expected: string) => {
const expectedTimestamp = Date.parse(expected)

expect(convertToDay(date).getTime()).toEqual(expectedTimestamp)
expect(getDay(date.getTime())).toEqual(expectedTimestamp)
})
})
42 changes: 35 additions & 7 deletions packages/core/src/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,48 @@
//
import { type Timestamp } from './classes'

const dayTimeZone = 'Europe/London'
const dayFormatter = new Intl.DateTimeFormat('en-US', {
timeZone: dayTimeZone,
year: 'numeric',
month: 'numeric',
day: 'numeric'
})

function parseCalendarPart (partName: string, partValue: string): number {
const value = Number(partValue)
if (!Number.isInteger(value)) {
throw new Error(`Unable to parse ${dayTimeZone} calendar ${partName}`)
}
return value
}

export function getDay (time: Timestamp): Timestamp {
const date: Date = new Date(time)
return convertToDay(date).getTime()
}

export function convertToDay (date: Date): Date {
const originalDay: number = date.getDate()
const convertedDate: Date = new Date(date)
// Set 12 AM UTC time, since it will be the same day in most timezones
convertedDate.setUTCHours(12, 0, 0, 0)
if (convertedDate.getDate() !== originalDay) {
convertedDate.setDate(originalDay)
let year: number | undefined
let month: number | undefined
let day: number | undefined

for (const part of dayFormatter.formatToParts(date)) {
if (part.type === 'year') {
year = parseCalendarPart(part.type, part.value)
} else if (part.type === 'month') {
month = parseCalendarPart(part.type, part.value)
} else if (part.type === 'day') {
day = parseCalendarPart(part.type, part.value)
}
}

if (year === undefined || month === undefined || day === undefined) {
throw new Error(`Unable to resolve ${dayTimeZone} calendar day`)
}
return convertedDate

// Set noon UTC, since it will be the same day in most timezones.
return new Date(Date.UTC(year, month - 1, day, 12, 0, 0, 0))
}

export function getHour (time: Timestamp): Timestamp {
Expand Down