Skip to content
Draft
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
20 changes: 20 additions & 0 deletions docs/timezone.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,26 @@ function TokyoCalendar() {

- **Consistency**: When using the `timeZone` prop, ensure all date-related props (like `minDate`, `maxDate`, `excludeDates`, etc.) are provided in a consistent manner.

### Using with Vite (or other bundlers that don't support dynamic require)

If you're using Vite or another bundler that doesn't support dynamic `require()`, you'll need to explicitly provide the `date-fns-tz` module using `setDateFnsTzModule`:

```jsx
import DatePicker, { setDateFnsTzModule } from "react-datepicker";
import * as dateFnsTz from "date-fns-tz";

// Call once at app initialization (e.g., in your main entry point)
setDateFnsTzModule(dateFnsTz);

function MyComponent() {
const [startDate, setStartDate] = useState(new Date());

return <DatePicker selected={startDate} onChange={(date) => setStartDate(date)} timeZone="America/New_York" />;
}
```

This workaround is necessary because react-datepicker normally loads `date-fns-tz` dynamically using `require()` to keep it as an optional dependency. Since Vite doesn't support `require()` in ES modules or dynamic `require()`, you need to import and provide the module explicitly.

---

## The "Date is One Day Off" Problem
Expand Down
23 changes: 23 additions & 0 deletions src/date_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,32 @@ export function __setDateFnsTzNull(): void {
dateFnsTzLoadAttempted = true;
}

/**
* Sets the date-fns-tz module externally.
* This is useful for environments where dynamic require doesn't work (e.g., Vite).
*
* @example
* ```typescript
* import * as dateFnsTz from 'date-fns-tz';
* import { setDateFnsTzModule } from 'react-datepicker';
*
* // Call once at app initialization
* setDateFnsTzModule(dateFnsTz);
* ```
*
* @param module - The date-fns-tz module containing toZonedTime, fromZonedTime, and formatInTimeZone
*/
export function setDateFnsTzModule(module: DateFnsTz): void {
dateFnsTz = module;
dateFnsTzLoadAttempted = true;
}

/**
* Attempts to load date-fns-tz module.
* Returns null if the module is not installed.
*
* If the module was set externally via setDateFnsTzModule(), that will be used.
* Otherwise, attempts to load via dynamic require.
*/
function getDateFnsTz(): DateFnsTz | null {
if (dateFnsTzLoadAttempted) {
Expand Down
8 changes: 7 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
isSameMinute,
toZonedTime,
fromZonedTime,
setDateFnsTzModule,
safeToDate,
type HighlightDate,
type HolidayItem,
Expand All @@ -67,7 +68,12 @@ import type { ClickOutsideHandler } from "./click_outside_wrapper";

export { default as CalendarContainer } from "./calendar_container";

export { registerLocale, setDefaultLocale, getDefaultLocale };
export {
registerLocale,
setDefaultLocale,
getDefaultLocale,
setDateFnsTzModule,
};

export {
ReactDatePickerCustomHeaderProps,
Expand Down
97 changes: 97 additions & 0 deletions src/test/timezone_test.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import { render, fireEvent } from "@testing-library/react";
import * as realDateFnsTz from "date-fns-tz";
import DatePicker from "../index";
import * as dateUtils from "../date_utils";

Expand All @@ -10,6 +11,7 @@ const {
nowInTimeZone,
__resetDateFnsTzCache,
__setDateFnsTzNull,
setDateFnsTzModule,
} = dateUtils;

describe("Timezone utility functions", () => {
Expand Down Expand Up @@ -928,3 +930,98 @@ describe("Timezone fallback behavior (when date-fns-tz is not installed)", () =>
consoleSpy.mockRestore();
});
});

describe("setDateFnsTzModule - for environments where dynamic require doesn't work (e.g., Vite)", () => {
beforeEach(() => {
__resetDateFnsTzCache();
});

afterEach(() => {
__resetDateFnsTzCache();
});

it("should use the externally provided module for toZonedTime", () => {
const testDate = new Date("2024-06-15T12:00:00Z");

// Create a mock module that returns a predictable result
const mockModule = {
toZonedTime: jest.fn().mockReturnValue(new Date("2024-06-15T08:00:00")),
fromZonedTime: jest.fn(),
formatInTimeZone: jest.fn(),
};

setDateFnsTzModule(mockModule);

const result = toZonedTime(testDate, "America/New_York");

expect(mockModule.toZonedTime).toHaveBeenCalledWith(
testDate,
"America/New_York",
);
expect(result.getHours()).toBe(8);
});

it("should use the externally provided module for fromZonedTime", () => {
const testDate = new Date("2024-06-15T08:00:00");

const mockModule = {
toZonedTime: jest.fn(),
fromZonedTime: jest
.fn()
.mockReturnValue(new Date("2024-06-15T12:00:00Z")),
formatInTimeZone: jest.fn(),
};

setDateFnsTzModule(mockModule);

const result = fromZonedTime(testDate, "America/New_York");

expect(mockModule.fromZonedTime).toHaveBeenCalledWith(
testDate,
"America/New_York",
);
expect(result.getUTCHours()).toBe(12);
});

it("should use the externally provided module for formatInTimeZone", () => {
const testDate = new Date("2024-06-15T12:00:00Z");

const mockModule = {
toZonedTime: jest.fn(),
fromZonedTime: jest.fn(),
formatInTimeZone: jest.fn().mockReturnValue("08:00"),
};

setDateFnsTzModule(mockModule);

const result = formatInTimeZone(testDate, "HH:mm", "America/New_York");

expect(mockModule.formatInTimeZone).toHaveBeenCalledWith(
testDate,
"America/New_York",
"HH:mm",
{ locale: undefined },
);
expect(result).toBe("08:00");
});

it("should work with the real date-fns-tz module when provided", () => {
// Import the real module
setDateFnsTzModule(realDateFnsTz);

const testDate = new Date("2024-06-15T12:00:00Z");

// Test toZonedTime
const zonedResult = toZonedTime(testDate, "America/New_York");
expect(zonedResult.getHours()).toBe(8); // 12:00 UTC is 08:00 EDT

// Test fromZonedTime
const nyDate = new Date("2024-06-15T08:00:00");
const utcResult = fromZonedTime(nyDate, "America/New_York");
expect(utcResult.getUTCHours()).toBe(12);

// Test formatInTimeZone
const formatted = formatInTimeZone(testDate, "HH:mm", "America/New_York");
expect(formatted).toBe("08:00");
});
});