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
}
Answer from Ferdinand Prantl on Stack OverflowYou 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
}
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'
//}
Hey! is there any way of setting the default timezone in date-fns? Im currently +1 locally but the server is also hosted where the timezone is +1. But I need all the time to refer to UCT 0 time. Anyone know how I can do this?
» npm install date-fns-tz
UPDATE: date-fns v4 finally has builtin TZ support
I haven't tried it.
UPDATE: For date-fns v3, use date-fns-tz's formatInTimeZone()
A formatInTimeZone() helper has been added several years ago, but some bugs were fixed recently, best use at least v3.1.2.
<script type="importmap">{ "imports": { "date-fns": "https://esm.run/[email protected]", "date-fns/": "https://esm.run/[email protected]/", "date-fns-tz": "https://esm.run/[email protected]" } }</script>
<script type="module">
import { parseISO } from 'date-fns';
import { enGB } from 'date-fns/locale/en-GB';
import { fr } from 'date-fns/locale/fr';
import { formatInTimeZone } from 'date-fns-tz';
const parsedTime = parseISO("2019-10-25T07:10:00Z");
console.log('parsedTime:', parsedTime);
const formattedTime = formatInTimeZone(
parsedTime, "UTC", "yyyy-MM-dd kk:mm:ss zzzz",
// IF using 'zzzz', can optionally pass locale for better TZ names
{ locale: fr });
console.log('formattedTime:', formattedTime); // 2019-10-25 07:10:00 temps universel coordonné
</script>
The implementation is quite similar to the following (but now it also compensates for shifted time falling on wrong side of DST — not an issue for UTC but can be for many TZs):
Previous "official hack"
You were almost there, but format() only uses timeZone to show TZ name; you also need to shift the time correspondingly. Here is the combo date-fns-tz used to recommend:
<script type="importmap">{ "imports": { "date-fns": "https://esm.run/[email protected]", "date-fns/": "https://esm.run/[email protected]/", "date-fns-tz": "https://esm.run/[email protected]" } }</script>
<script type="module">
import { parseISO } from 'date-fns';
import { enGB } from 'date-fns/locale/en-GB';
import { fr } from 'date-fns/locale/fr';
import { format, toZonedTime } from 'date-fns-tz';
const time = "2019-10-25T08:10:00+01:00"; // = 07:10Z
const parsedTime = parseISO("2019-10-25T07:10:00Z");
console.log('parsedTime:', parsedTime);
const formatInTimeZone = (date, timeZone, fmt, locale) =>
// called `toZonedTime` since 3.0.0, was `utcToZonedTime` before.
format(toZonedTime(date, timeZone),
fmt,
// timeZone required! locale is optional, helps 'zzzz' names.
{ timeZone, locale });
const formattedTime = formatInTimeZone(parsedTime, "UTC", "yyyy-MM-dd kk:mm:ss zzzz", fr);
console.log('formattedTime:', formattedTime); // 2019-10-25 07:10:00 temps universel coordonné
</script>
Behind the scenes
The date-fns[-tz] libraries stick to the built-in Date data type that carries no TZ info.
Some functions treat it as a moment-in-time, but some like format treat it more like a struct of calendaric components — year 2019, ..., day 25, hour 08, ....
Now the trouble is a Date is internally only a moment in time. Its methods provide a mapping to/from calendaric components in local time zone.
So to represent a different time zone, date-fns-tz/utcToZonedTime temporarily produces Date instances which represent the wrong moment in time — just to get its calendaric components in local time to be what we want!
And the date-fns-tz/format function's timeZone input affects only the template chars that print the time zone (XX..X, xx..x, zz..z, OO..O).
See https://github.com/marnusw/date-fns-tz/issues/36 for some discussion of this "shifting" technique (and of real use cases that motivated them)...
It's a bit low-level & risky, but the specific way I composed them above — formatInTimeZone() — is I believe a safe recipe? UPDATE: except TZ name could be wrong around DST boundary !
I would suggest using the built-in Date util:
const date = new Date("2019-10-25T08:10:00Z");
const isoDate = date.toISOString();
console.log(`${isoDate.substring(0, 10)} ${isoDate.substring(11, 19)}`);
Outputs:
2019-10-25 08:10:00
Not a general solution for any format, but no external libraries required.
My country has restored Daylight Saving Time after a long pause. My app now is not working properly like before because of the DST.
Currently, I am using date-fns and date-fns-tz to handle the zoned scheduled events.
I have just tried some libraries like luxon and moment-timezone to figure out if they actually consider the Daylight Saving Time.
I found that luxon does not consider it, and, moment-timezone considers it properly.
Perhaps, all these libraries are considering the DST, but for my country, they don't.
What is your opinion regarding this situation? Should I replace the date-fns ? and if not so, what should I do to make date-fns to consider Daylight Saving Time?