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 Overflow
Top answer
1 of 2
79

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
}
2 of 2
12

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'
//}
🌐
Date-fns
blog.date-fns.org › v40-with-time-zone-support
v4.0 is out with first-class time zones support!
September 16, 2024 - In 2022, I came up with @date-fns/utc that immediately felt right. You get an option to make all the calculations in UTC and remove the local time zone from the equation without adding any extra weight to the core library.
Discussions

Specify a time zone for format()?
There was an error while loading. Please reload this page · I recently encountered a phenomenon in date-fns that broke a Node.js script of mine: More on github.com
🌐 github.com
26
May 18, 2017
Add a disclaimer or remove Timezone page
Given marnusw/date-fns-tz#260 It would be nice to remove or add a disclaimer on the current docs: https://date-fns.org/v3.3.1/docs/Time-Zones More on github.com
🌐 github.com
3
February 20, 2024
Date-fns and UTC time
Set the "TZ" environment variable to "UTC" More on reddit.com
🌐 r/node
3
8
February 8, 2023
JS: parse time to pull out hours and minutes using date-fns, ignroing timezones
There has to be an easy way to just ignore timezones? ... @iPhoenix Not really. That answers how to get a Date object into an ISO string and then perform calculations on the string. In my question my date/timestamp is already in an ISO string. What I am looking for is to use date-fns for easy formatting of that string. I can parse the ISO string also, but it just seems hackish if I am using date-fns. ... I am. I am doing something similar now where I remove ... More on stackoverflow.com
🌐 stackoverflow.com
🌐
date-fns
date-fns.org › v2.0.0-alpha.27 › docs › Time-Zones
date-fns - modern JavaScript date utility library
date-fns provides the most comprehensive yet simple and consistent toolset for manipulating JavaScript dates in a browser & Node.js.
🌐
LogSnag
logsnag.com › blog › handling-timezones-in-javascript-with-date-fns-tz
How to Handle Time-Zones in JavaScript with date-fns-tz | LogSnag
October 20, 2023 - Now, combined with the date-fns-tz extension for time-zone support, we can easily handle time-zone specific tasks with lower overhead than ever before.
🌐
Mastering JS
masteringjs.io › tutorials › date-fns › tz
Working with Timezones using date-fns and date-fns-tz - Mastering JS
Instead of using utcToZonedTime(), store the time and the timezone you want to display, and use formatInTimeZone(). While we often use date-fns for convenience, you don't need date-fns to do basic tasks like formatting dates in common timezones. In modern JavaScript runtimes, dates have a toLocaleString() function that has good support for timezones.
🌐
GitHub
github.com › date-fns › date-fns › issues › 489
Specify a time zone for format()? · Issue #489 · date-fns/date-fns
May 18, 2017 - I recently encountered a phenomenon in date-fns that broke a Node.js script of mine: const d = new Date('2017-01-23'); console.log(format(d, 'YYYY-MM-DD')); This code logs 2017-01-23 in Germany, but 2017-01-22 in Seattle. How do I best fix this to always log 2017-01-23?
Published   May 18, 2017
Author   rauschma
🌐
GitHub
github.com › date-fns › date-fns › issues › 3713
Add a disclaimer or remove Timezone page · Issue #3713 · date-fns/date-fns
February 20, 2024 - Given marnusw/date-fns-tz#260 It would be nice to remove or add a disclaimer on the current docs: https://date-fns.org/v3.3.1/docs/Time-Zones
Author   alfonsoar
🌐
npm
npmjs.com › package › date-fns-tz
date-fns-tz - npm
The format function exported from this library is used under the hood by formatInTimeZone and extends date-fns/format with full time zone support for: The z..zzz Unicode tokens: short specific non-location format · The zzzz Unicode token: long specific non-location format · When using those tokens with date-fns/format it falls back to the GMT time zone format, and always uses the current system's local time zone.
      » npm install date-fns-tz
    
Published   Sep 30, 2024
Version   3.2.0
Author   Marnus Weststrate
Find elsewhere
🌐
GitHub
github.com › date-fns › date-fns › blob › main › docs › timeZones.md
date-fns/docs/timeZones.md at main · date-fns/date-fns
You can safely mix and match regular Date instances, as well as UTCDate or TZDate in different time zones and primitive values (timestamps and strings). date-fns will normalize the arguments, taking the first object argument (Date or a Date extension instance) as the reference and return the result in the reference type:
Author   date-fns
🌐
YouTube
youtube.com › watch
How to Remove Timezone from Date Values in JavaScript Using date-fns - YouTube
Learn how to eliminate timezone impacts from your date values in JavaScript with date-fns, ensuring accurate first and last day calculations.---This video is...
Published   February 21, 2025
Views   13
🌐
Snyk
snyk.io › advisor › date-fns-tz › date-fns-tz code examples
Top 5 date-fns-tz Code Examples | Snyk
const {format} = require('date-fns-tz') const parseWithLuxon = require('.')(DateTime) const parseWithDateFns = require('./date-fns')(dateFns) // Europe/Berlin switched to DST at 31st of March at 2am. const withoutDST = '2019-03-31T01:59+01:00' const timeZone = 'Europe/Berlin' const rel = 'in 2 minutes' const dt = DateTime.fromISO(withoutDST).setZone(timeZone) const withDST1 = parseWithLuxon(rel, dt) console.log(withDST1.toFormat('HH:mm ZZZZ')) // 03:01 GMT+2 const withDST2 = parseWithDateFns(rel, new Date(withoutDST)) console.log(format(withDST2, 'HH:mm zz', {timeZone: 'Europe/Berlin'})) // 03:01 GMT+2 ·
🌐
GitHub
github.com › prantlf › date-fns-timezone
GitHub - prantlf/date-fns-timezone: Parsing and formatting date strings using IANA time zones for date-fns.
2018-09-19 v0.1.0 Add parseString without a time zone to cover a gap in date-fns
Starred by 136 users
Forked by 18 users
Languages   JavaScript 96.1% | TypeScript 3.9% | JavaScript 96.1% | TypeScript 3.9%
🌐
DEV Community
dev.to › raerpo › my-mistakes-with-dates-on-javascript-35el
My mistakes with Dates on JavaScript - DEV Community
February 21, 2020 - This bug was hard to find. date-fns-tz is a library that allows you to add timezone support to date-fns dates. I had a date defined in UTC and I need it to be in multiple timezones.
🌐
UNPKG
unpkg.com › browse › date-fns-tz@1.0.7 › README.md
date-fns-tz
Modern browsers all support the [necessary features](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat#Browser_compatibility), and for those that don't a [polyfill](https://github.com/yahoo/date-time-format-timezone) can be used. If you do not wish to use a polyfill the time zone option can still be used, but only with time zone offsets such as '-0200' or '+04:00' and not IANA time zone names. *This work was initially proposed in PR [date-fns/#707](https://github.com/date-fns/date-fns/pull/707), but won't be considered until `date-fns` version 2 has been released.
🌐
GitHub
github.com › marnusw › date-fns-tz › issues › 55
Format method ignores the timeZone option · Issue #55 · marnusw/date-fns-tz
June 3, 2020 - import format from "date-fns-tz/format"; import parseISO from "date-fns/paseISO"; function getTimeString(dateInput: string): string { return format(parseISO(dateInput), "HH:mm", { timeZone: 'GMT' }); } getTimeString("2020-02-11T18:48Z"); // => `01:48` but must be `18:48` in GMT getTimeString("2020-02-11T18:48"); // => `18:48` - it's correct if I remove timezone info from the input string ·
Author   vic08
🌐
GitHub
github.com › date-fns › date-fns › issues › 556
Set a timezone · Issue #556 · date-fns/date-fns
September 12, 2017 - const TODAY = new Date(); const TZ = 'America/New_York' // set the date in TODAY to America/New_York timezone // or const TZ_OFFSET = -240 // set the date in TODAY to America/New_York using the offset
Author   ahmedelgabri
🌐
GitHub
github.com › marnusw › date-fns-tz
GitHub - marnusw/date-fns-tz: Complementary library for date-fns v2 adding IANA time zone support · GitHub
The format function exported from this library is used under the hood by formatInTimeZone and extends date-fns/format with full time zone support for: The z..zzz Unicode tokens: short specific non-location format · The zzzz Unicode token: long specific non-location format · When using those tokens with date-fns/format it falls back to the GMT time zone format, and always uses the current system's local time zone.
Starred by 1.2K users
Forked by 116 users
Languages   JavaScript 60.7% | TypeScript 39.3%
🌐
GitHub
github.com › date-fns › tz
GitHub - date-fns/tz: date-fns timezone utils · GitHub
Using it makes date-fns operate in given time zone but can also be used without it.
Starred by 246 users
Forked by 19 users
Languages   TypeScript 82.2% | JavaScript 14.9% | Shell 2.1%