Time zone issue involving date fns format()
Asked Answered
D

2

53
const dt = new Date('2017-12-12');
console.log(format(dt, 'YYYY-MM-DD'));

The above code logs 2017-12-11 in the US, but 2017-12-12 in India.

I followed this github thread here and tried out things but am not getting the desired results.

My expectation is to print the same date irrespective of time zone

Why I need this : Consider a scenario involving birthdates. If i am giving some input date, it has to be displayed as same date in all regions irrespective of their timezones.

Dunagan answered 9/1, 2018 at 16:38 Comment(6)
From what I understand, the issue is that you are parsing the ISO date format as ISO, but they are of local time (presumably you don't expect the users to input their birth date in UTC). You can infer the user's timezone by looking at new Date().getTimezoneOffset() but that may not be accurate. Maybe the user was born in India but are now living in the US, so the only way you'd know to use the correct timezone is if they tell you they were born in India (even worse with multi-timezone countries).Trichome
Yeah. I too was exploring throughout the day , but found no way to skip the time zone and to display a single constant outputDunagan
As far as I can see, the only way to have a consistent output and still be able to treat the date as more than an opaque string is to ask the user for their place of birth. But maybe the opaque string option is acceptable for your application.Trichome
(by opaque, I mean that you store the user's birthdate as a string with exactly what they typed in, don't ever try to pass it to a Date constructor or parser ; don't bother computing the user's age or anything ; then you know you can always display it correctly)Trichome
No. I just gave date of birth as one of the example. I am framing some more scenarios too so using string was not into my mind yet ! However thanks for the suggestion !!Dunagan
Oh, I think I got exactly this issue when filling some forms on German Government website: I entered my date of birth, but the pre-confirmation summary was giving me shifted date, so I went back to "fix" it and get correct summary, however when I submitted, it transferred the wrong dates.... that's insane...Benzoin
A
75

You will need to subtract the time zone offset of your local time zone from the Date instance, before you pass it to format from date-fns. For example:

const dt = new Date('2017-12-12');
const dtDateOnly = new Date(dt.valueOf() + dt.getTimezoneOffset() * 60 * 1000);
console.log(format(dtDateOnly, 'YYYY-MM-DD')); // Always "2017-12-12"

Problem

You want to handle only the date part of the Date instance, because the time part does not make sense for birthdates. However, the Date object does not offer any "date-only" mode. You can access both its date and time parts in the local time zone or UTC. The problem is, that format from date-fns prints the output always in the local time zone.

When you executed the constructor only with the date part:

const dt = new Date('2017-12-12');

The JavaScript engine actually assumed a string in the incomplete ISO 8601 format and perfomed this:

const dt = new Date('2017-12-12T00:00:00.000Z');

It may still look "harmless" to you, but the date instance exposes the value not only in UTC, but also in the local time zone. If you construct the Date instance on the East Coast of the US, you will see the following output:

> const dt = new Date('2017-12-12');
> dt.toISOString()
'2017-12-12T00:00:00.000Z'
> dt.toString()
'Tue Dec 11 2017 19:00:00 GMT-0500 (EST)'
> d.toLocaleString()
'12/11/2017 7:00:00 PM'

Solution

If you know, that format from date-fns reads date and time parts from the date instance in the local time zone, you will need to make your date "looking like" the midnight in your local time zone and not in UTC, which you passed to the Date constructor. Then you will see the year, month and date numbers preserved. It means, that you need to subtract the time zone offset of your local time zone for the specified day. Date.prototype.getTimezoneOffset returns the offset, but with an inverted sign and in minutes.

const dt = new Date('2017-12-12');
// Tue Dec 11 2017 19:00:00 GMT-0500 (EST)
const dtDateOnly = new Date(dt.valueOf() + dt.getTimezoneOffset() * 60 * 1000);
// Tue Dec 12 2017 00:00:00 GMT-0500 (EST)
console.log(format(dtDateOnly, 'YYYY-MM-DD'));
// Prints always "2017-12-12", regardless the time zone it executed in

However, such Date instance can be used only to format the date-only value. You cannot use it for computing date differences, for example, which would need the original and correct UTC value.

Alternative

If you need always the same date-only format and not the format specific to the current locale, you do not need date-fns. You can format the string by the concatenation of padded numbers:

const dt = new Date('2017-12-12');

const year = dt.getUTCFullYear()
const month = dt.getUTCMonth() + 1 // Date provides month index; not month number
const day = dt.getUTCDate()

// Print always "2017-12-12", regardless the time zone it executed in
console.log(year + '-' + padToTwo(month) + '-', padToTwo(day));
// Or use a template literal
console.log(`${year}-${padToTwo(month)}-${padToTwo(day)}`);

function padToTwo (number) {
  return number > 9 ? number : '0' + number
}
Adolphus answered 16/9, 2018 at 8:51 Comment(4)
Thanks, I have been searching for a couple of hours for this solution. Kind of hack, but works just fine! 🚀Pieter
Please note on review this call will convert it back to include the timezone: const dtDateOnly = new Date(dt.valueOf() + dt.getTimezoneOffset() * 60 * 1000); you actually need to return the following: const dtDateOnly = dt.valueOf() + dt.getTimezoneOffset() * 60 * 1000;Gereron
Ferdinand Prantl instead of adding offset in my case, it is subtracting the offset. How to make it add? I have changed it to '-' instead of '+' then it has been added my offset timezone. Anyways ThanksRosemonde
@KomalKhatkole, the result of dt.getTimezoneOffset() for EST (-05:00) is positive (+300). The sign is different than in the TZ suffix of a formatted date. That is why you have add it and not subtract it to a local TZ date, when you compute UTC. Do you have your code somewhere to look at? As a gist, for example?Adolphus
U
8

Only adding the @ferdinand-prantl answer. If you are using the date-fns, you can parse the string date ('2017-12-12') using the parseISO(here) fn from date-fns, which will complete the missing ISO 8601 format with your local time zone. When you use the format fn, you are going to keep the date.

const strDate = '2017-12-12';
const isoDate = parseISO(strDate);
const formattedDate = format(isoDate, 'YYYY-MM-DD');
console.log({strDate, isoDate, formattedDate})
//{
//  strDate: '2017-12-12',
//  isoDate: 2017-12-12T02:00:00.000Z,
//  formattedDate: '2017-12-12'
//}
Untie answered 18/2, 2023 at 13:29 Comment(1)
Nice. This is a simple and intuitive answer the the OP's question.Eyla

© 2022 - 2025 — McMap. All rights reserved.