Skip to content

Conversation

@gpbl
Copy link
Owner

@gpbl gpbl commented Dec 12, 2025

Adds an experimental noonSafe prop to work around historical second-level timezone offset issues that cause calendar rendering glitches.

Root Cause

When using the timeZone prop with historical timezones (e.g., Asia/Dubai in 1900 has offset +03:41:12, or Asia/Tehran for old Persian years), JavaScript's Date and date-fns round offsets to minutes. This rounding can push midnight calculations into the previous day, causing:

This affects both Gregorian and Jalali calendars (Persian calendar in #2842 with Asia/Tehran and old years like 1313).

Solution

The noonSafe prop creates date library overrides that keep all calendar math at 12:00 noon in the target timezone. By performing calculations at noon instead of midnight, we avoid the second-level offset rounding issues that occur across midnight boundaries. This ensures:

  • Calendar grids always display complete weeks with 7 days
  • No duplicate or missing days
  • Correct rendering for all historical timezones with second-level offsets
  • Works with both Gregorian and Jalali calendars

Implementation

  • Added createNoonOverrides() function in noonDateLib.ts that provides timezone-aware date utilities operating at noon
  • Added createJalaliNoonOverrides() in noonJalaliDateLib.ts for Jalali calendar support
  • Integrated into DayPicker component: when noonSafe={true} and timeZone is set, the noon overrides are merged with the dateLib
  • Custom dateLib props take precedence over noonSafe overrides if both are provided
  • Updated Persian locale wrapper to support noonSafe
  • Added comprehensive tests covering navigation, week numbers, and multi-locale scenarios
  • Added documentation and interactive examples

Closes

Fixes #2842

Notes

  • Marked as @experimental and requires explicit opt-in via noonSafe prop
  • No impact on existing code (completely backwards compatible)
Screen.Recording.2025-12-12.at.14.28.50.mov
Screen.Recording.2025-12-12.at.14.22.38.mov

@gpbl gpbl requested a review from rodgobbi December 12, 2025 13:32
@gpbl gpbl marked this pull request as ready for review December 12, 2025 13:32
@gpbl gpbl changed the title fix: add noon-safe timezone helpers for historical offsets feat: add experimental noonSafe prop for timezone offsets Dec 12, 2025
Copy link
Collaborator

@rodgobbi rodgobbi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this approach, we are leaning more into calling TimeZone, which would worsen the performance issues we already have: #2845.

What if we try another approach to handling time zones as you suggested?
We could fix the performance issues and these historical timezone issues altogether.

I can try some new ideas later, but this issue would take time to fix.

import type { DateLib } from "./classes/DateLib.js";

export interface CreateNoonOverridesOptions {
timeZone?: string;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can make timeZone required and skip all internal checks,
this module is dedicated to the timeZone use case.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense - I've simplified a lot the utility so that we don't need the timeZone option anymore.

Comment on lines 40 to 49
if (!timeZone || Number.isNaN(+normalizedDate)) return normalizedDate;
const tzDate = new TZDate(
normalizedDate.getFullYear(),
normalizedDate.getMonth(),
normalizedDate.getDate(),
12,
0,
0,
timeZone,
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I first thought the approach would be to shift + 12 hours the processed date.
This approach hard sets the time to 12:00 the same day.

From the top of my head the issues this approach could have:
01:00 + 1 UTC is a different calendar day from 23:00 - 1 UTC, but are the same time 00:00 UTC.
Imagine the machine timezone is 01:00 + 1 UTC, and the timezone prop is -1 UTC.
This logic would normalize the date to 12:00 +1 UTC, which is 10:00 -1 UTC.
I feel this can cause issues, but it needs to be tested and validated.
If this is handled, lemme know how so we can validate.

The Jest runner timezone is now UTC, and the test cases in this PR are all + UTC timezone, causing the all the calculations and rendering of the calendar to be in the same day.

Could we have more test cases that cross days between the system timezone and the prop timezone?
At least one to cover both ways:

  • one case where system is + UTC and timezone is - UTC, setting the system time to cause the calendar to be one day before in the calendar
  • and vice versa for system in - UTC and timezone in + UTC

@gpbl gpbl merged commit 2a080c5 into main Dec 17, 2025
20 checks passed
@gpbl gpbl deleted the docs-add-asia-saigon branch December 17, 2025 10:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

timeZone prop breaks Persian calendar rendering for old years (e.g. 1313) in Asia/Tehran

3 participants