Unstable timezone parsing with dayjs.tz()
- Dominant language
- JavaScript
- Stars
- 48.7k
- Forks
- 2.5k
- PR merge metrics
- No merged PRs in 30d
Description
### **Bug Report: `dayjs.tz()` produces incorrect and inconsistent results on React Native iOS with Hermes**
- **Package Name and Version:** `dayjs` v1.11.13 (or latest)
- **React Native / Expo Version:** React Native v0.76.9+ (with New Architecture/Fabric), Expo SDK 52.0.46+
- **OS and Environment:** iOS 18.2 iPhone 13 Device and iPhone 16 Pro iOS 18.2 Simulator
#### Step 1: Describe the problem
When parsing a timezone-naive date string with an IANA timezone name (e.g., `Asia/Seoul`), `dayjs.tz()` produces an incorrect and highly inconsistent result **only on some React Native iOS environments**. The same code works perfectly on Android and in web browsers.
The most critical symptom is that **the parsed result changes over time**. For the exact same input string, the `minutes` part of the parsed `Date` object changes as the device's real-world clock ticks. This suggests that the parsing logic is being "contaminated" by the current system time.
**Example of the bug:**
- **Input:** `dayjs.tz("2025-06-16T14:07:00", "Asia/Seoul")`
- **Expected UTC ISO String:** `2025-06-16T05:07:00.000Z`
- **Actual UTC ISO String at 10:30 AM:** `2025-06-16T05:16:00.000Z` (Incorrect hour and minute)
- **Actual UTC ISO String at 10:31 AM:** A different, also incorrect value. The `minutes` part keeps changing.
This issue persists even when the `customParseFormat` plugin is used with an explicit format string.
---
#### Step 2: How to reproduce
This bug appears to be specific to the **React Native iOS + Hermes engine** environment, especially when the New Architecture (Fabric) is enabled.
**1. Set up a React Native project with Hermes enabled for iOS.**
A new project created with `npx create-expo-app` and then prebuilt with `npx expo prebuild` can be used. Ensure `hermes_enabled` is `true` in `ios/Podfile`.
**2. Install `dayjs` and its plugins.**
```bash
npm install dayjs
```
**3. Use the following code snippet in any component.**
```jsx
import React, { useState, useEffect } from 'react';
import { View, Text } from 'react-native';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import customParseFormat from 'dayjs/plugin/customParseFormat';
// Extend plugins
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(customParseFormat);
const BugReproductionComponent = () => {
const [parsedTime, setParsedTime] = useState(null);
const dateString = "2025-06-16T14:07:00";
const timeZone = "Asia/Seoul";
const format = "YYYY-MM-DDTHH:mm:ss";
const parseDate = () => {
// This is the problematic line
const result = dayjs.tz(dateString, format, timeZone);
setParsedTime(result);
console.log(`[${new Date().toLocaleTimeString()}] Parsed with dayjs.tz:`, result.toISOString());
};
useEffect(() => {
parseDate(); // Initial parse
const interval = setInterval(parseDate, 5000); // Re-parse every 5 seconds
return () => clearInterval(interval);
}, []);
return (
Input String: {dateString}
Timezone: {timeZone}
Expected UTC: 2025-06-16T05:07:00.000Z
Actual UTC: {parsedTime ? parsedTime.toISOString() : 'Parsing...'}
(Check console logs to see the value changing over time)
);
};
export default BugReproductionComponent;
```
**4. Run the app on an iOS Simulator or a physical iOS device.**
Observe the console logs. You will see that the parsed UTC string is incorrect and its value, especially the `minutes`, changes with each new log entry.
---
#### Step 3: Investigation and Suspected Cause
The investigation suggests this is not a simple bug but a deep interaction issue between `dayjs`'s parsing logic and the underlying JavaScript engine's `Intl` API implementation on iOS.
**1. Alternative Library `date-fns-tz` Works Correctly:**
Using `date-fns-tz` with the same input string and IANA timezone name produces the correct and stable result in the exact same environment.
```javascript
import { zonedTimeToUtc } from 'date-fns-tz';
const utcDate = zonedTimeToUtc("2025-06-16T14:07:00", "Asia/Seoul");
console.log(utcDate.toISOString()); // Correctly logs "2025-06-16T05:07:00.000Z"
```
This strongly indicates the issue lies within `dayjs`'s specific implementation of IANA timezone parsing.
**2. A Stable Workaround for `dayjs`:**
The bug can be completely avoided by providing a numeric UTC offset instead of an IANA name. This forces `dayjs` to bypass its problematic timezone resolution logic.
```javascript
const stableResult = dayjs("2025-06-16T14:07:00+09:00");
console.log(stableResult.toISOString()); // Correctly logs "2025-06-16T05:07:00.000Z"
```
**3. Suspected Implementation Difference:**
The core of the problem seems to be how `dayjs` and `date-fns-tz` handle timezone-naive strings.
- **`dayjs`'s Approach (Suspected):** It appears to use an indirect, inference-based algorithm. It might first parse the string using the system's local time, and then ask the native `Intl` API for an offset based on this ambiguous, intermediate `Date` object. This process is susceptible to bugs in the native `Intl` implementation, where the **current system time** contaminates the offset calculation for a **future date**.
- **`date-fns-tz`'s Approach (Suspected):** It seems to use a more robust, direct calculation method. It likely parses the string into pure date components, creates a UTC-based `Date` object, and then uses the `Intl` API in a more constrained way to *calculate* the precise offset for that specific point in time, avoiding any reliance on the current system clock.
---
#### Step 4: Expected behavior
`dayjs.tz("2025-06-16T14:07:00", "Asia/Seoul")` should consistently and correctly produce a `dayjs` object that represents `2025-06-16T05:07:00.000Z` in UTC, regardless of the platform, JavaScript engine, or the current system time. The result should be stable and not fluctuate.
---
#### **Final thoughts and request for a fix**
Considering that `date-fns-tz` handles this case correctly in the same environment and that providing a direct UTC offset bypasses the issue, the evidence strongly points to a defect in `dayjs`'s internal algorithm for parsing timezone-naive strings with IANA zone names.
This bug severely impacts the reliability of `dayjs` for cross-platform development, especially in a modern stack like React Native with the Hermes engine. It can lead to critical, hard-to-debug data inconsistencies.
We kindly request that the `dayjs` team investigate this parsing logic. A fix would be greatly appreciated by the React Native community and would restore confidence in using `dayjs` for time-critical applications. Please let us know if a minimal reproducible repository or any further information would be helpful.
Contributor guide
Assessment
This issue has not been assessed yet.