(
+ "@internationalized/date"
+ );
+ return {
+ ...mod,
+ // Note: Restoring mocks will cause the function to return 'undefined'.
+ // Consider providing a default implementation to be restored instead.
+ getLocalTimeZone: vi.fn().mockReturnValue("Europe/Paris"),
+ };
+});
+describe("", () => {
const expectCalendarToBeClosed = () => {
expect(screen.queryByRole("application")).toBeNull();
};
@@ -525,7 +533,7 @@ describe("", () => {
expectCalendarToBeClosed();
// Make sure value is selected.
- screen.getByText(`Value = 2023-04-12|`);
+ screen.getByText(`Value = 2023-04-11T22:00:00.000Z|`);
// Clear value.
const clearButton = screen.getByRole("button", {
@@ -633,7 +641,7 @@ describe("", () => {
expect(monthSegment).toHaveFocus();
// Type date's value.
- await user.keyboard("{1}{2}{5}{2}{0}{2}{3}");
+ await user.keyboard("{5}{1}{2}{2}{0}{2}{3}");
// Submit form being filled with a date.
await user.click(submitButton);
@@ -642,7 +650,7 @@ describe("", () => {
// Make sure form's value matches.
expect(formData).toEqual({
- datepicker: "2023-12-05",
+ datepicker: "2023-05-11T22:00:00.000Z",
});
// Clear picked date.
diff --git a/packages/react/src/components/Forms/DatePicker/DatePicker.tsx b/packages/react/src/components/Forms/DatePicker/DatePicker.tsx
index d5a01be..2366f84 100644
--- a/packages/react/src/components/Forms/DatePicker/DatePicker.tsx
+++ b/packages/react/src/components/Forms/DatePicker/DatePicker.tsx
@@ -12,6 +12,7 @@ import { Calendar } from ":/components/Forms/DatePicker/Calendar";
import DateFieldBox from ":/components/Forms/DatePicker/DateField";
import { StringOrDate } from ":/components/Forms/DatePicker/types";
import {
+ convertDateValueToString,
getDefaultPickerOptions,
parseCalendarDate,
} from ":/components/Forms/DatePicker/utils";
@@ -41,7 +42,7 @@ export const DatePicker = (props: DatePickerProps) => {
: parseCalendarDate(props.value),
defaultValue: parseCalendarDate(props.defaultValue),
onChange: (value: DateValue | null) => {
- props.onChange?.(value?.toString() || "");
+ props.onChange?.(convertDateValueToString(value));
},
};
const pickerState = useDatePickerState(options);
diff --git a/packages/react/src/components/Forms/DatePicker/DatePickerAux.tsx b/packages/react/src/components/Forms/DatePicker/DatePickerAux.tsx
index ecda078..832a1d8 100644
--- a/packages/react/src/components/Forms/DatePicker/DatePickerAux.tsx
+++ b/packages/react/src/components/Forms/DatePicker/DatePickerAux.tsx
@@ -21,6 +21,7 @@ import {
Calendar,
CalendarRange,
} from ":/components/Forms/DatePicker/Calendar";
+import { convertDateValueToString } from ":/components/Forms/DatePicker/utils";
export type DatePickerAuxSubProps = FieldProps & {
label?: string;
@@ -111,19 +112,19 @@ const DatePickerAux = forwardRef(
>
) : (
)}
diff --git a/packages/react/src/components/Forms/DatePicker/DateRangePicker.spec.tsx b/packages/react/src/components/Forms/DatePicker/DateRangePicker.spec.tsx
index 9020993..dae4cb7 100644
--- a/packages/react/src/components/Forms/DatePicker/DateRangePicker.spec.tsx
+++ b/packages/react/src/components/Forms/DatePicker/DateRangePicker.spec.tsx
@@ -6,6 +6,18 @@ import { CunninghamProvider } from ":/components/Provider";
import { DateRangePicker } from ":/components/Forms/DatePicker/DateRangePicker";
import { Button } from ":/components/Button";
+vi.mock("@internationalized/date", async () => {
+ const mod = await vi.importActual(
+ "@internationalized/date"
+ );
+ return {
+ ...mod,
+ // Note: Restoring mocks will cause the function to return 'undefined'.
+ // Consider providing a default implementation to be restored instead.
+ getLocalTimeZone: vi.fn().mockReturnValue("Europe/Paris"),
+ };
+});
+
describe("", () => {
const expectDatesToBeEqual = (
firstDate: Date | string | undefined | null,
@@ -938,7 +950,9 @@ describe("", () => {
expectCalendarToBeClosed();
// Make sure value is selected.
- screen.getByText(`Value = 2023-04-12 2023-04-14|`);
+ screen.getByText(
+ `Value = 2023-04-11T22:00:00.000Z 2023-04-13T22:00:00.000Z|`
+ );
// Clear value.
const clearButton = screen.getByRole("button", {
@@ -1004,10 +1018,10 @@ describe("", () => {
expect(startMonthSegment).toHaveFocus();
// Type start date's value.
- await user.keyboard("{1}{0}{5}{2}{0}{2}{3}");
+ await user.keyboard("{5}{1}{0}{2}{0}{2}{3}");
// Type end date's value.
- await user.keyboard("{1}{2}{5}{2}{0}{2}{3}");
+ await user.keyboard("{5}{1}{2}{2}{0}{2}{3}");
// Submit form being filled with a date.
await user.click(submitButton);
@@ -1016,8 +1030,8 @@ describe("", () => {
// Make sure form's value matches.
expect(formData).toEqual({
- datepickerStart: "2023-10-05",
- datepickerEnd: "2023-12-05",
+ datepickerStart: "2023-05-09T22:00:00.000Z",
+ datepickerEnd: "2023-05-11T22:00:00.000Z",
});
// Clear picked date.
diff --git a/packages/react/src/components/Forms/DatePicker/DateRangePicker.tsx b/packages/react/src/components/Forms/DatePicker/DateRangePicker.tsx
index 3f762e6..fbd8275 100644
--- a/packages/react/src/components/Forms/DatePicker/DateRangePicker.tsx
+++ b/packages/react/src/components/Forms/DatePicker/DateRangePicker.tsx
@@ -12,6 +12,7 @@ import DatePickerAux, {
import DateFieldBox from ":/components/Forms/DatePicker/DateField";
import { StringsOrDateRange } from ":/components/Forms/DatePicker/types";
import {
+ convertDateValueToString,
getDefaultPickerOptions,
parseRangeCalendarDate,
} from ":/components/Forms/DatePicker/utils";
@@ -44,7 +45,10 @@ export const DateRangePicker = ({
onChange: (value: DateRange) => {
props.onChange?.(
value?.start && value.end
- ? [value.start.toString(), value.end.toString()]
+ ? [
+ convertDateValueToString(value.start),
+ convertDateValueToString(value.end),
+ ]
: null,
);
},
diff --git a/packages/react/src/components/Forms/DatePicker/utils.spec.ts b/packages/react/src/components/Forms/DatePicker/utils.spec.ts
index 6b3ca1c..cb86013 100644
--- a/packages/react/src/components/Forms/DatePicker/utils.spec.ts
+++ b/packages/react/src/components/Forms/DatePicker/utils.spec.ts
@@ -1,5 +1,12 @@
-import { CalendarDate, DateValue, parseDate } from "@internationalized/date";
import {
+ CalendarDate,
+ DateValue,
+ parseAbsolute,
+ parseDate,
+} from "@internationalized/date";
+import { vi } from "vitest";
+import {
+ convertDateValueToString,
parseCalendarDate,
parseRangeCalendarDate,
} from ":/components/Forms/DatePicker/utils";
@@ -17,6 +24,18 @@ const expectDateToBeEqual = (
expect(parsedDate?.day === expectedDay);
};
+vi.mock("@internationalized/date", async () => {
+ const mod = await vi.importActual(
+ "@internationalized/date",
+ );
+ return {
+ ...mod,
+ // Note: Restoring mocks will cause the function to return 'undefined'.
+ // Consider providing a default implementation to be restored instead.
+ getLocalTimeZone: vi.fn().mockReturnValue("Europe/Paris"),
+ };
+});
+
describe("parseCalendarDate", () => {
it.each([
[2023, 4, 12],
@@ -161,3 +180,24 @@ describe("parseRangeCalendarDate", () => {
expectDateToBeEqual(range?.end, 2023, 4, 22);
});
});
+
+describe("convertDateValueToString", () => {
+ it("should return an empty string for null date", () => {
+ const date: DateValue | null = null;
+ const result = convertDateValueToString(date);
+ expect(result).toBe("");
+ });
+
+ it("should return a UTC ISO 8601 string from a CalendarDate", async () => {
+ const date = parseDate("2023-05-25");
+ const result = convertDateValueToString(date);
+ expect(result).eq("2023-05-24T22:00:00.000Z");
+ });
+
+ it("should return a UTC ISO 8601 string from a ZonedDateTime", async () => {
+ // Europe/Paris is the mocked local timezone.
+ const date = parseAbsolute("2023-05-25T00:00:00.000+02:00", "Europe/Paris");
+ const result = convertDateValueToString(date);
+ expect(result).eq("2023-05-24T22:00:00.000Z");
+ });
+});
diff --git a/packages/react/src/components/Forms/DatePicker/utils.ts b/packages/react/src/components/Forms/DatePicker/utils.ts
index 731901d..3cd2bc1 100644
--- a/packages/react/src/components/Forms/DatePicker/utils.ts
+++ b/packages/react/src/components/Forms/DatePicker/utils.ts
@@ -1,7 +1,11 @@
import {
CalendarDate,
+ DateValue,
parseAbsoluteToLocal,
toCalendarDate,
+ ZonedDateTime,
+ toZoned,
+ getLocalTimeZone,
} from "@internationalized/date";
import { DateRange } from "react-aria";
import {
@@ -38,6 +42,19 @@ export const parseRangeCalendarDate = (
};
};
+export const convertDateValueToString = (date: DateValue | null): string => {
+ try {
+ const localTimezone = getLocalTimeZone();
+ // If timezone is already set, it would be kept, else the selection is set at midnight
+ // on the local timezone, then converted to a UTC offset.
+ return date ? toZoned(date, localTimezone).toAbsoluteString() : "";
+ } catch (e) {
+ throw new Error(
+ "Invalid date format when converting date value on DatePicker component"
+ );
+ }
+};
+
export const getDefaultPickerOptions = (props: DatePickerAuxSubProps): any => ({
minValue: parseCalendarDate(props.minValue),
maxValue: parseCalendarDate(props.maxValue),