interface IRowWithTimezone { TimezoneGmtOffset?: number; TimezoneDstOffset?: number; } export default class TimezoneHelper { public static isDST(targetDate: Temporal.PlainDateTime): boolean { const currMonth = targetDate.month; if (currMonth < 3) { return false; } else if (currMonth > 3 && currMonth < 10) { return true; } else if (currMonth == 3) { if (targetDate.day >= this.getLastSunday(targetDate.month, targetDate.year).day) { return true; } else { return false; } } else if (currMonth == 10) { if (targetDate.day >= this.getLastSunday(targetDate.month, targetDate.year).day) { return false; } else { return true; } } else { return false; } } public static getLocalDateFromUTC(dateUTC: Temporal.PlainDateTime, dataRow: IRowWithTimezone): Temporal.PlainDateTime; public static getLocalDateFromUTC(dateUTC: Temporal.PlainDateTime, dstOffset: number, gmtOffset: number): Temporal.PlainDateTime; public static getLocalDateFromUTC( dateUTC: Temporal.PlainDateTime, dstOffsetOrRow: number | IRowWithTimezone, gmtOffset?: number, ): Temporal.PlainDateTime { if (dateUTC == null) { return null as any; } const tzRow = this.getRowWithTimezone(dstOffsetOrRow, gmtOffset); return dateUTC.add({ milliseconds: ((this.isDST(dateUTC) ? tzRow.TimezoneDstOffset : tzRow.TimezoneGmtOffset) as any) * -1 * 60 * 1000, }); } public static getUTCFromLocalDate(dateUTC: Temporal.PlainDateTime, dataRow: IRowWithTimezone): Temporal.PlainDateTime; public static getUTCFromLocalDate(dateUTC: Temporal.PlainDateTime, dstOffset: number, gmtOffset: number): Temporal.PlainDateTime; public static getUTCFromLocalDate( dateUTC: Temporal.PlainDateTime, dstOffsetOrRow: number | IRowWithTimezone, gmtOffset?: number, ): Temporal.PlainDateTime { if (dateUTC == null) { return null as any; } const tzRow = this.getRowWithTimezone(dstOffsetOrRow, gmtOffset); return dateUTC.add({ milliseconds: ((this.isDST(dateUTC) ? tzRow.TimezoneDstOffset : tzRow.TimezoneGmtOffset) as any) * 60 * 1000, }); } private static getRowWithTimezone(dstOffsetOrRow: number | IRowWithTimezone, gmtOffset?: number): IRowWithTimezone { const tzRow = dstOffsetOrRow as IRowWithTimezone; if (tzRow.TimezoneDstOffset != null) { return tzRow; } return { TimezoneDstOffset: dstOffsetOrRow as number, TimezoneGmtOffset: gmtOffset, }; } private static getLastSunday(month: number, year: number): Temporal.PlainDateTime { // first day of next month const firstOfNextMonth = Temporal.PlainYearMonth.from({ year, month }) .add({ months: 1 }) .toPlainDate({ day: 1 }); // last day of target month const lastOfMonth = firstOfNextMonth.subtract({ days: 1 }); // Temporal dayOfWeek: 1=Mon ... 7=Sun const daysToSubtract = lastOfMonth.dayOfWeek % 7; // 0 if already Sunday const lastSunday = lastOfMonth.subtract({ days: daysToSubtract }); // return as PlainDateTime at 00:00 return lastSunday.toPlainDateTime({ hour: 0, minute: 0, second: 0, millisecond: 0 }); } }