83 lines
2.5 KiB
TypeScript
83 lines
2.5 KiB
TypeScript
function extractDateParts(date: Date, timeZone: string) {
|
|
const formatter = new Intl.DateTimeFormat("en-CA", {
|
|
timeZone,
|
|
year: "numeric",
|
|
month: "2-digit",
|
|
day: "2-digit",
|
|
});
|
|
|
|
const parts = formatter.formatToParts(date);
|
|
const year = Number(parts.find((part) => part.type === "year")?.value);
|
|
const month = Number(parts.find((part) => part.type === "month")?.value);
|
|
const day = Number(parts.find((part) => part.type === "day")?.value);
|
|
|
|
return { year, month, day };
|
|
}
|
|
|
|
export function getDateStringInTimeZone(
|
|
date: Date,
|
|
timeZone: string,
|
|
): string {
|
|
const { year, month, day } = extractDateParts(date, timeZone);
|
|
return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
}
|
|
|
|
export function getShiftedDateStringInTimeZone(
|
|
daysOffset: number,
|
|
timeZone: string,
|
|
baseDate: Date = new Date(),
|
|
): string {
|
|
const { year, month, day } = extractDateParts(baseDate, timeZone);
|
|
const shifted = new Date(Date.UTC(year, month - 1, day));
|
|
shifted.setUTCDate(shifted.getUTCDate() + daysOffset);
|
|
return shifted.toISOString().split("T")[0];
|
|
}
|
|
|
|
function getTimeZoneOffsetMs(date: Date, timeZone: string): number {
|
|
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
timeZone,
|
|
timeZoneName: "shortOffset",
|
|
});
|
|
|
|
const offsetLabel =
|
|
formatter.formatToParts(date).find((part) => part.type === "timeZoneName")
|
|
?.value || "GMT+0";
|
|
|
|
const match = offsetLabel.match(/GMT([+-])(\d{1,2})(?::?(\d{2}))?/);
|
|
if (!match) return 0;
|
|
|
|
const sign = match[1] === "-" ? -1 : 1;
|
|
const hours = Number(match[2] || "0");
|
|
const minutes = Number(match[3] || "0");
|
|
|
|
return sign * (hours * 60 + minutes) * 60 * 1000;
|
|
}
|
|
|
|
export function getDayBoundsForTimeZone(
|
|
dateString: string,
|
|
timeZone: string,
|
|
): { startMs: number; endMs: number } {
|
|
const [year, month, day] = dateString.split("-").map(Number);
|
|
const startGuess = new Date(Date.UTC(year, month - 1, day, 0, 0, 0));
|
|
const nextDayGuess = new Date(Date.UTC(year, month - 1, day + 1, 0, 0, 0));
|
|
|
|
const startOffsetMs = getTimeZoneOffsetMs(startGuess, timeZone);
|
|
const nextDayOffsetMs = getTimeZoneOffsetMs(nextDayGuess, timeZone);
|
|
|
|
const startMs = Date.UTC(year, month - 1, day, 0, 0, 0) - startOffsetMs;
|
|
const nextDayStartMs =
|
|
Date.UTC(year, month - 1, day + 1, 0, 0, 0) - nextDayOffsetMs;
|
|
|
|
return {
|
|
startMs,
|
|
endMs: nextDayStartMs - 1,
|
|
};
|
|
}
|
|
|
|
export function getDateOnlyValueForTimeZone(
|
|
timeZone: string,
|
|
date: Date = new Date(),
|
|
): Date {
|
|
return new Date(`${getDateStringInTimeZone(date, timeZone)}T00:00:00.000Z`);
|
|
}
|