Improve timezone handling (#18257)

* Ensure review activity calendar uses correct timezone

react-day-picker 9.x adds a timeZone prop and a TZDate() handler to show the calendar based on a timezone and better handle dates passed to it in timezones

* Ensure calendar range uses correct timezone

* clean up

* ensure range is timezone aware

* ensure export dates are timezone aware
This commit is contained in:
Josh Hawkins
2025-05-15 17:13:32 -05:00
committed by GitHub
parent f48356cbee
commit 2f9b373c1a
5 changed files with 74 additions and 25 deletions
+43 -18
View File
@@ -12,6 +12,7 @@ import {
import { Switch } from "./switch";
import { cn } from "@/lib/utils";
import { LuCheck } from "react-icons/lu";
import { TZDate } from "react-day-picker";
import { t } from "i18next";
export interface DateRangePickerProps {
@@ -32,19 +33,24 @@ export interface DateRangePickerProps {
locale?: string;
/** Option for showing compare feature */
showCompare?: boolean;
/** timezone */
timezone?: string;
}
const getDateAdjustedForTimezone = (dateInput: Date | string): Date => {
const getDateAdjustedForTimezone = (
dateInput: Date | string,
timezone?: string,
): Date => {
if (typeof dateInput === "string") {
// Split the date string to get year, month, and day parts
const parts = dateInput.split("-").map((part) => parseInt(part, 10));
// Create a new Date object using the local timezone
// Note: Month is 0-indexed, so subtract 1 from the month part
const date = new Date(parts[0], parts[1] - 1, parts[2]);
const date = new TZDate(parts[0], parts[1] - 1, parts[2], timezone);
return date;
} else {
// If dateInput is already a Date object, return it directly
return dateInput;
return new TZDate(dateInput, timezone);
}
};
@@ -73,7 +79,12 @@ const PRESETS: Preset[] = [
/** The DateRangePicker component allows a user to select a range of dates */
export function DateRangePicker({
initialDateFrom = new Date(new Date().setHours(0, 0, 0, 0)),
timezone,
initialDateFrom = (() => {
const date = new TZDate(new Date(), timezone);
date.setHours(0, 0, 0, 0);
return date;
})(),
initialDateTo,
initialCompareFrom,
initialCompareTo,
@@ -84,18 +95,27 @@ export function DateRangePicker({
const [isOpen, setIsOpen] = useState(false);
const [range, setRange] = useState<DateRange>({
from: getDateAdjustedForTimezone(initialDateFrom),
from: getDateAdjustedForTimezone(initialDateFrom, timezone),
to: initialDateTo
? getDateAdjustedForTimezone(initialDateTo)
: getDateAdjustedForTimezone(initialDateFrom),
? getDateAdjustedForTimezone(initialDateTo, timezone)
: getDateAdjustedForTimezone(initialDateFrom, timezone),
});
const [rangeCompare, setRangeCompare] = useState<DateRange | undefined>(
initialCompareFrom
? {
from: new Date(new Date(initialCompareFrom).setHours(0, 0, 0, 0)),
from: new TZDate(
new Date(initialCompareFrom).setHours(0, 0, 0, 0),
timezone,
),
to: initialCompareTo
? new Date(new Date(initialCompareTo).setHours(0, 0, 0, 0))
: new Date(new Date(initialCompareFrom).setHours(0, 0, 0, 0)),
? new TZDate(
new Date(initialCompareTo).setHours(0, 0, 0, 0),
timezone,
)
: new TZDate(
new Date(initialCompareFrom).setHours(0, 0, 0, 0),
timezone,
),
}
: undefined,
);
@@ -128,8 +148,8 @@ export function DateRangePicker({
const getPresetRange = (presetName: string): DateRange => {
const preset = PRESETS.find(({ name }) => name === presetName);
if (!preset) throw new Error(`Unknown date range preset: ${presetName}`);
const from = new Date();
const to = new Date();
const from = new TZDate(new Date(), timezone);
const to = new TZDate(new Date(), timezone);
const first = from.getDate() - from.getDay();
switch (preset.name) {
@@ -191,16 +211,18 @@ export function DateRangePicker({
setRange(range);
if (rangeCompare) {
const rangeCompare = {
from: new Date(
from: new TZDate(
range.from.getFullYear() - 1,
range.from.getMonth(),
range.from.getDate(),
timezone,
),
to: range.to
? new Date(
? new TZDate(
range.to.getFullYear() - 1,
range.to.getMonth(),
range.to.getDate(),
timezone,
)
: undefined,
};
@@ -212,16 +234,18 @@ export function DateRangePicker({
for (const preset of PRESETS) {
const presetRange = getPresetRange(preset.name);
const normalizedRangeFrom = new Date(range.from);
const normalizedRangeFrom = new TZDate(range.from, timezone);
normalizedRangeFrom.setHours(0, 0, 0, 0);
const normalizedPresetFrom = new Date(
const normalizedPresetFrom = new TZDate(
presetRange.from.setHours(0, 0, 0, 0),
timezone,
);
const normalizedRangeTo = new Date(range.to ?? 0);
const normalizedRangeTo = new TZDate(range.to ?? new Date(0), timezone);
normalizedRangeTo.setHours(0, 0, 0, 0);
const normalizedPresetTo = new Date(
const normalizedPresetTo = new TZDate(
presetRange.to?.setHours(0, 0, 0, 0) ?? 0,
timezone,
);
if (
@@ -401,6 +425,7 @@ export function DateRangePicker({
),
)
}
timeZone={timezone}
/>
</div>
</div>