dayjs icon indicating copy to clipboard operation
dayjs copied to clipboard

The timezone plugin is not compatible with React Native Hermes engine

Open dortamiguel opened this issue 4 years ago • 51 comments

The timezone plugin throws a timezone RangeError error when calling .tz because React Native Hermes engine doesn't has support for Intl timezones and toLocaleString

This two lines are the ones that are throwing the error

https://github.com/iamkun/dayjs/blob/dev/src/plugin/timezone/index.js#L20

https://github.com/iamkun/dayjs/blob/dev/src/plugin/timezone/index.js#L98

Could be posible to update the plugin to not depend on Intl and toLocaleString('en-US', { timeZone: timezone }) ?

dortamiguel avatar Feb 09 '21 13:02 dortamiguel

I've been facing the same issue today.

With the formatjs polyfill that's recommended, I've got it partially working: dayjs.tz("2013-11-18 11:55", "America/Toronto") returns correctly with "2013-11-18T16:55:00.000Z"

However, it's not fully working: dayjs("2013-11-18 11:55").tz("America/Toronto") returns null

So, with a polyfill I'm able to get Parsing in Zone to work, but not Converting to Zone.

sohcah avatar Feb 09 '21 13:02 sohcah

@sohcah what polyfill setup are you using? I tried to use formatjs but I didn't managed to make it work

dortamiguel avatar Feb 09 '21 13:02 dortamiguel

@ellipticaldoor I created an empty polyfill.ts file, as the polyfill isn't required on iOS or Web, and created a polyfill.android.ts file which contains the following: https://gist.github.com/sohcah/c223886c3e40a7b9c0283cd0213e2a54 I've got a few console logs in there to test Converting and Parsing, giving me this output: image

Then I'm just putting import './polyfill.ts'; in my App.tsx file.

sohcah avatar Feb 09 '21 13:02 sohcah

My converting issue seems similar to #1356... so possibly related?

sohcah avatar Feb 09 '21 13:02 sohcah

Looks like Date.toLocaleString() works differently with the Polyfill. Running new Date().toLocaleString("en-US", { timeZone: "America/Toronto" }) in a browser returns "2/9/2021, 9:03:52 AM", whilst with the polyfill it returns 2/9/2021.

sohcah avatar Feb 09 '21 14:02 sohcah

// @ts-ignore
Date.prototype._toLocaleString = Date.prototype.toLocaleString;
// @ts-ignore
Date.prototype.toLocaleString = function (a, b) {
  if (b && Object.keys(b).length === 1 && "timeZone" in b && a === "en-US") {
    return Intl.DateTimeFormat("en-us", {
      year: "numeric",
      month: "2-digit",
      day: "2-digit",
      hour: "2-digit",
      minute: "2-digit",
      second: "2-digit",
      hour12: false,
      timeZone: b.timeZone,
    })
      .format(this)
      .replace(/(\d{2})\/(\d{2})\/(\d{4}),/g, "$3-$1-$2");
  }
  // @ts-ignore
  return this._toLocaleString(a, b);
};

I've ended up, at least temporarily, using this patch in my polyfill.android.ts (plus all the formatjs stuff from before). It's absolutely not perfect... and really not a great solution, but seems to sort the issue for me.

sohcah avatar Feb 09 '21 14:02 sohcah

Had run into same issue as @sohcah. Any update as official fix would be appreciated. Polyfills claim spec compliance https://formatjs.io/docs/polyfills/intl-datetimeformat/#default-timezone

I had to patch dayjs to isolate one call with ^^ workaround

dlebedynskyi avatar Apr 13 '21 23:04 dlebedynskyi

Had run into same issue as @sohcah. Any update as official fix would be appreciated. Polyfills claim spec compliance https://formatjs.io/docs/polyfills/intl-datetimeformat/#default-timezone

I had to patch dayjs to isolate one call with ^^ workaround

Could you provide a code snippet ?

troZee avatar Apr 19 '21 09:04 troZee

It's possible this might solve the problem on Android. In app build.gradle:

/**
 * The preferred build flavor of JavaScriptCore.
 *
 * For example, to use the international variant, you can use:
 * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
 *
 * The international variant includes ICU i18n library and necessary data
 * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
 * give correct results when using with locales other than en-US.  Note that
 * this variant is about 6MiB larger per architecture than default.
 */
def jscFlavor = 'org.webkit:android-jsc-intl:+'

devinjameson avatar Apr 23 '21 20:04 devinjameson

@troZee

create gist with patch we are using now https://gist.github.com/dlebedynskyi/81ff281b0db6aa69cdb360a1b609fb8a

this is essentially your suggested change, but as patch

+                // HACK https://github.com/iamkun/dayjs/issues/1377
+                i = Intl.DateTimeFormat('en-us', {
+                    year: 'numeric',
+                    month: '2-digit',
+                    day: '2-digit',
+                    hour: '2-digit',
+                    minute: '2-digit',
+                    second: '2-digit',
+                    hour12: false,
+                    timeZone: t,
+                })

@devinjameson

It's possible this might solve the problem on Android. In app build.gradle:

We are trying to avoid this exactly. There are bugs in current JSC used in RN related to DST (in Brazil for example). You also need to use https://formatjs.io/docs/polyfills for Hermes (RN 0.64) Last adding JSC increases your apk size by >3mb

dlebedynskyi avatar Apr 29 '21 16:04 dlebedynskyi

@troZee

create gist with patch we are using now https://gist.github.com/dlebedynskyi/81ff281b0db6aa69cdb360a1b609fb8a

this is essentially your suggested change, but as patch

+                // HACK https://github.com/iamkun/dayjs/issues/1377
+                i = Intl.DateTimeFormat('en-us', {
+                    year: 'numeric',
+                    month: '2-digit',
+                    day: '2-digit',
+                    hour: '2-digit',
+                    minute: '2-digit',
+                    second: '2-digit',
+                    hour12: false,
+                    timeZone: t,
+                })

@devinjameson

It's possible this might solve the problem on Android. In app build.gradle:

We are trying to avoid this exactly. There are bugs in current JSC used in RN related to DST (in Brazil for example). You also need to use https://formatjs.io/docs/polyfills for Hermes (RN 0.64) Last adding JSC increases your apk size by >3mb

Patched it and now I'm getting this error.

ReferenceError: Property 'Intl' doesn't exist

Rc85 avatar Jul 30 '21 02:07 Rc85

Patched it and now I'm getting this error.

you are missing Intl library, probably on React Native Android - make sure you have JSC with Intl flavor, or latest Hermes with Intl, or add polyfill.

dlebedynskyi avatar Aug 20 '21 18:08 dlebedynskyi

@dlebedynskyi Intl has been added in the latest version(0.8.1) of Hermes.

HenriqueeLoopes avatar Aug 20 '21 19:08 HenriqueeLoopes

It's possible this might solve the problem on Android. In app build.gradle:

/**
 * The preferred build flavor of JavaScriptCore.
 *
 * For example, to use the international variant, you can use:
 * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
 *
 * The international variant includes ICU i18n library and necessary data
 * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
 * give correct results when using with locales other than en-US.  Note that
 * this variant is about 6MiB larger per architecture than default.
 */
def jscFlavor = 'org.webkit:android-jsc-intl:+'

You are a life saver <3

DeveloperTheExplorer avatar Nov 12 '21 21:11 DeveloperTheExplorer

@dlebedynskyi Intl has been added in the latest version(0.8.1) of Hermes.

But it doesn't come with timezone conversion support. When using Hermes on iOS, Intl is not available entirely.

mattijsf avatar Nov 30 '21 08:11 mattijsf

The above patch causes midnight dates to be formatted as 2010-01-01 24:00:00 after applying .startOf("day") (see here) once fed into the Date constructor by the .tz implementation (see here).

24:00 marks the end of the day, leading the Date constructor to parse it as Jan 02 2010 00:00:00 GMT+0100. By replacing hour12: false with hourCycle: "h23" in the patch above the correct date is formatted / converted when using .startOf("day") in combination with timezones.

Before:

  const originalDay = dayjs("2010-01-01T00:00:00Z").tz("UTC")
  const startOfDay = originalDay.startOf("day")
  console.log(startOfDay.valueOf() === originalDay.valueOf()) // false using above patch

After:

  const originalDay = dayjs("2010-01-01T00:00:00Z").tz("UTC")
  const startOfDay = originalDay.startOf("day")
  console.log(startOfDay.valueOf() === originalDay.valueOf()) // true, using above patch with hourCycle: "h23" instead of hour12: false

mattijsf avatar Dec 21 '21 15:12 mattijsf

The above patch causes midnight dates to be formatted as 2010-01-01 24:00:00 after applying .startOf("day") (see here) once fed into the Date constructor by the .tz implementation (see here).

24:00 marks the end of the day, leading the Date constructor to parse it as Jan 02 2010 00:00:00 GMT+0100. By replacing hour12: false with hourCycle: "h23" in the patch above the correct date is formatted / converted when using .startOf("day") in combination with timezones.

Before:

  const originalDay = dayjs("2010-01-01T00:00:00Z").tz("UTC")
  const startOfDay = originalDay.startOf("day")
  console.log(startOfDay.valueOf() === originalDay.valueOf()) // false using above patch

After:

  const originalDay = dayjs("2010-01-01T00:00:00Z").tz("UTC")
  const startOfDay = originalDay.startOf("day")
  console.log(startOfDay.valueOf() === originalDay.valueOf()) // true, using above patch with hourCycle: "h23" instead of hour12: false

Great find!

santhoshvai avatar Dec 22 '21 05:12 santhoshvai

It's possible this might solve the problem on Android. In app build.gradle:

/**
 * The preferred build flavor of JavaScriptCore.
 *
 * For example, to use the international variant, you can use:
 * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
 *
 * The international variant includes ICU i18n library and necessary data
 * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
 * give correct results when using with locales other than en-US.  Note that
 * this variant is about 6MiB larger per architecture than default.
 */
def jscFlavor = 'org.webkit:android-jsc-intl:+'

I have enable this line, but always getting error timeZone is not supported

anggihnurh avatar Jan 26 '22 07:01 anggihnurh

@anggihnurh Im also still getting that error... did you figure it out?

Aryk avatar Apr 26 '22 19:04 Aryk

There may still be some hair on this after reading through everyones comments, but here is a stitched together polyfill implementation to include in your top index.js file to get things working. Confirmed on iOS - I have not tried android yet.

Its important to note, the order you import the polyfills matters!!

Install things

yarn add @formatjs/intl-datetimeformat
yarn add @formatjs/intl-displaynames
yarn add @formatjs/intl-getcanonicallocales
yarn add @formatjs/intl-listformat
yarn add @formatjs/intl-locale
yarn add @formatjs/intl-numberformat
yarn add @formatjs/intl-pluralrules
yarn add @formatjs/intl-relativetimeformat

Add to your entry index.js file

import '@formatjs/intl-getcanonicallocales/polyfill';
import '@formatjs/intl-locale/polyfill';

import '@formatjs/intl-displaynames/polyfill';
import '@formatjs/intl-displaynames/locale-data/en'; // locale-data for en

import '@formatjs/intl-listformat/polyfill';
import '@formatjs/intl-listformat/locale-data/en'; // locale-data for en

import '@formatjs/intl-pluralrules/polyfill';
import '@formatjs/intl-pluralrules/locale-data/en'; // locale-data for en

import '@formatjs/intl-numberformat/polyfill';
import '@formatjs/intl-numberformat/locale-data/en'; // locale-data for en

import '@formatjs/intl-relativetimeformat/polyfill';
import '@formatjs/intl-relativetimeformat/locale-data/en'; // locale-data for en

import '@formatjs/intl-datetimeformat/polyfill';
import '@formatjs/intl-datetimeformat/locale-data/en'; // locale-data for en
import '@formatjs/intl-datetimeformat/add-all-tz'; // Add ALL tz data

// @ts-ignore
Date.prototype._toLocaleString = Date.prototype.toLocaleString;
Date.prototype.toLocaleString = function (a, b) {
  if (b && Object.keys(b).length === 1 && 'timeZone' in b && a === 'en-US') {
    return Intl.DateTimeFormat('en-us', {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
      hour12: false,
      timeZone: b.timeZone,
    })
      .format(this)
      .replace(/(\d{2})\/(\d{2})\/(\d{4}),/g, '$3-$1-$2');
  }
  // @ts-ignore
  return this._toLocaleString(a, b);
};

This is just a bump/summary of the folks above - they deserve the thank you if it works for you!

dorthwein avatar Jul 21 '22 20:07 dorthwein

There may still be some hair on this after reading through everyones comments, but here is a stitched together polyfill implementation to include in your top index.js file to get things working. Confirmed on iOS - I have not tried android yet.

Its important to note, the order you import the polyfills matters!!

Install things

yarn add @formatjs/intl-datetimeformat
yarn add @formatjs/intl-displaynames
yarn add @formatjs/intl-getcanonicallocales
yarn add @formatjs/intl-listformat
yarn add @formatjs/intl-locale
yarn add @formatjs/intl-numberformat
yarn add @formatjs/intl-pluralrules
yarn add @formatjs/intl-relativetimeformat

Add to your entry index.js file

import '@formatjs/intl-getcanonicallocales/polyfill';
import '@formatjs/intl-locale/polyfill';

import '@formatjs/intl-displaynames/polyfill';
import '@formatjs/intl-displaynames/locale-data/en'; // locale-data for en

import '@formatjs/intl-listformat/polyfill';
import '@formatjs/intl-listformat/locale-data/en'; // locale-data for en

import '@formatjs/intl-pluralrules/polyfill';
import '@formatjs/intl-pluralrules/locale-data/en'; // locale-data for en

import '@formatjs/intl-numberformat/polyfill';
import '@formatjs/intl-numberformat/locale-data/en'; // locale-data for en

import '@formatjs/intl-relativetimeformat/polyfill';
import '@formatjs/intl-relativetimeformat/locale-data/en'; // locale-data for en

import '@formatjs/intl-datetimeformat/polyfill';
import '@formatjs/intl-datetimeformat/locale-data/en'; // locale-data for en
import '@formatjs/intl-datetimeformat/add-all-tz'; // Add ALL tz data

// @ts-ignore
Date.prototype._toLocaleString = Date.prototype.toLocaleString;
Date.prototype.toLocaleString = function (a, b) {
  if (b && Object.keys(b).length === 1 && 'timeZone' in b && a === 'en-US') {
    return Intl.DateTimeFormat('en-us', {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
      hour12: false,
      timeZone: b.timeZone,
    })
      .format(this)
      .replace(/(\d{2})\/(\d{2})\/(\d{4}),/g, '$3-$1-$2');
  }
  // @ts-ignore
  return this._toLocaleString(a, b);
};

This is just a bump/summary of the folks above - they deserve the thank you if it works for you!

Unfortunately on android when I call dayjs.tz.guess() return 'UTC' ...

Does anyone know what the problem is?

VictorPulzz avatar Aug 26 '22 13:08 VictorPulzz

Heyo, I'm on RN 0.70.1 and facing issues with timezones on iOS (haven't tested Android yet).

I would like to get the timezone in relative GMT format, I've done the following:

dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(advancedFormat);

dayjs.tz.setDefault(RNLocalize.getTimeZone()); // Sets timezone to Europe/Berlin

dayjs().format('z') => prints 'GMT' expect 'GMT +2'

I've tried adding the polyfills and the code posted by @dorthwein but when I start the app I get the following error:

simulator_screenshot_61FC24CB-1CED-48E3-9F93-A3F8F3C0AB8B

Have any of you got timezone support working on the latest RN versions?

ospfranco avatar Sep 23 '22 13:09 ospfranco

There may still be some hair on this after reading through everyones comments, but here is a stitched together polyfill implementation to include in your top index.js file to get things working. Confirmed on iOS - I have not tried android yet. Its important to note, the order you import the polyfills matters!! Install things

yarn add @formatjs/intl-datetimeformat
yarn add @formatjs/intl-displaynames
yarn add @formatjs/intl-getcanonicallocales
yarn add @formatjs/intl-listformat
yarn add @formatjs/intl-locale
yarn add @formatjs/intl-numberformat
yarn add @formatjs/intl-pluralrules
yarn add @formatjs/intl-relativetimeformat

Add to your entry index.js file

import '@formatjs/intl-getcanonicallocales/polyfill';
import '@formatjs/intl-locale/polyfill';

import '@formatjs/intl-displaynames/polyfill';
import '@formatjs/intl-displaynames/locale-data/en'; // locale-data for en

import '@formatjs/intl-listformat/polyfill';
import '@formatjs/intl-listformat/locale-data/en'; // locale-data for en

import '@formatjs/intl-pluralrules/polyfill';
import '@formatjs/intl-pluralrules/locale-data/en'; // locale-data for en

import '@formatjs/intl-numberformat/polyfill';
import '@formatjs/intl-numberformat/locale-data/en'; // locale-data for en

import '@formatjs/intl-relativetimeformat/polyfill';
import '@formatjs/intl-relativetimeformat/locale-data/en'; // locale-data for en

import '@formatjs/intl-datetimeformat/polyfill';
import '@formatjs/intl-datetimeformat/locale-data/en'; // locale-data for en
import '@formatjs/intl-datetimeformat/add-all-tz'; // Add ALL tz data

// @ts-ignore
Date.prototype._toLocaleString = Date.prototype.toLocaleString;
Date.prototype.toLocaleString = function (a, b) {
  if (b && Object.keys(b).length === 1 && 'timeZone' in b && a === 'en-US') {
    return Intl.DateTimeFormat('en-us', {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
      hour12: false,
      timeZone: b.timeZone,
    })
      .format(this)
      .replace(/(\d{2})\/(\d{2})\/(\d{4}),/g, '$3-$1-$2');
  }
  // @ts-ignore
  return this._toLocaleString(a, b);
};

This is just a bump/summary of the folks above - they deserve the thank you if it works for you!

Unfortunately on android when I call dayjs.tz.guess() return 'UTC' ...

Does anyone know what the problem is?

.guess is probably using some intl API that is not supported in hermes, you need to use the same that I'm using 'react-native-localize' and get the correct timezone from the native APIs

ospfranco avatar Sep 23 '22 14:09 ospfranco

At least on RN 0.70 the Intl API has been implemented for Hermes on iOS. I don't know if there is some incompatibility with RN or not, but all the poly-fills and workarounds did not do anything for me. I gave up on trying to make .format('z') work the way I wanted and resorted to using a third-party package to get the timezone abbreviation.

ospfranco avatar Sep 24 '22 10:09 ospfranco

@dlebedynskyi Working with your patch thanks!

mi-mazouz avatar Nov 25 '22 11:11 mi-mazouz

Any update on timezone plugin support for Hermes engine?

akirchmyer avatar Jan 10 '23 23:01 akirchmyer

Just adding info here that dayjs.utc(timestamp).tz(timezone).format('myformat') does not work with hermes-enabled on iOS RN 0.71. it returns 'Invalid date'. Disabling hermes enginge fixes this problem for now.

teeserted avatar Feb 18 '23 23:02 teeserted

Has there been any update on this? I am still seeing this issue on iOS

lindan4 avatar May 09 '23 13:05 lindan4

@troZee

create gist with patch we are using now https://gist.github.com/dlebedynskyi/81ff281b0db6aa69cdb360a1b609fb8a

this is essentially your suggested change, but as patch

+                // HACK https://github.com/iamkun/dayjs/issues/1377
+                i = Intl.DateTimeFormat('en-us', {
+                    year: 'numeric',
+                    month: '2-digit',
+                    day: '2-digit',
+                    hour: '2-digit',
+                    minute: '2-digit',
+                    second: '2-digit',
+                    hour12: false,
+                    timeZone: t,
+                })

@devinjameson

It's possible this might solve the problem on Android. In app build.gradle:

We are trying to avoid this exactly. There are bugs in current JSC used in RN related to DST (in Brazil for example). You also need to use https://formatjs.io/docs/polyfills for Hermes (RN 0.64) Last adding JSC increases your apk size by >3mb

thanks! you save my life 😭

duckhoa-uit avatar Jun 02 '23 18:06 duckhoa-uit

Can confirm that this is still a problem.

IncrediblePony avatar Jul 10 '23 09:07 IncrediblePony