date-fns | How do I format to UTC
Asked Answered
K

11

104

Problem

It looks like when I use the format() function, it automatically convert the original UTC time into my timezone (UTC+8). I have been digging through their docs for hours and couldn't seem to find a way to default it to UTC time.

import { parseISO, format } from "date-fns";

const time = "2019-10-25T08:10:00Z";

const parsedTime = parseISO(time);
console.log(parsedTime); // 2019-10-25T08:10:00.000Z

const formattedTime = format(parsedTime, "yyyy-MM-dd kk:mm:ss");
console.log(formattedTime); // 2019-10-25 16:10:00 <-- 8 HOURS OFF!!

I have tried to use the package data-fns-tz and use something like

format(parsedTime, "yyyy-MM-dd kk:mm:ss", {timeZone: "UTC"});

still no luck.

Please help!

Expected Output

2019-10-25 08:10:00

Actual Output

2019-10-25 16:10:00

Kinzer answered 25/10, 2019 at 15:8 Comment(4)
I have tested your code here. This seems to work fine. repl.it/repls/RepentantDimFactorRooster
@khan - repl.it runs in UTC, that's why.Seften
When parsed, 2019-10-25 08:10:00 will be interpreted as local, not UTC, and will be treated as an invalid date in some browsers.Defeat
UPDATE: they have a new package github.com/date-fns/utc with UTCDate class which might be simpler/safer?Marsupium
T
41

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.

Tailband answered 25/10, 2019 at 15:24 Comment(4)
Thank for the reply, but sadly, the reason I chose to use a library is that I need to do calculation on dates :(Kinzer
You can still use date-fns for everything except that last part for output. It does return, afterall, Date objects.Seften
Looks like this is the only way out if I want to use date-fns. Excellent answer!Kinzer
I like this solution. It's correct and a one-liner.Magruder
M
65

You were almost there. This works for me:

import { parseISO } from "date-fns";
import { format, utcToZonedTime } from "date-fns-tz";

const time = "2019-10-25T08:10:00Z";

const parsedTime = parseISO(time);
console.log(parsedTime); // 2019-10-25T08:10:00.000Z

const formatInTimeZone = (date, fmt, tz) =>
  format(utcToZonedTime(date, tz), 
         fmt, 
         { timeZone: tz });

const formattedTime = formatInTimeZone(parsedTime, "yyyy-MM-dd kk:mm:ss xxx", "UTC");
console.log(formattedTime); // 2019-10-25 08:10:00 +00:00

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.

Marsupium answered 3/8, 2020 at 9:58 Comment(5)
For anyone wondering if this is a hack or not -- It's in fact the official way of formatting to a certain timezone, including UTC: npmjs.com/package/date-fns-tz#formatSilicium
But the jury is still out on whether the official way is a hack or not :-D Specifically I'm worried about calendaric times that do not exist in some time zones e.g. bbc.com/news/world-asia-16351377 — if your local TZ is Samoa, no timestamp maps to 30 December 2011, so if you want to format that date from another TZ I'm not sure it'd work.Marsupium
This works for me. Took me a few tries before I realized that the format import must come from date-fns-tz and not the core date-fns, but worked after that.Eelpout
@Silicium it being official does not make it "not a hack". It is certainly still a hack, and an ugly one, too. Shame that in JS one must go to such lengths to perform something this trivial.Jannjanna
Note that date-fns-tz now has a formatInTimeZone function, so no need for the helper function.Salivation
T
41

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.

Tailband answered 25/10, 2019 at 15:24 Comment(4)
Thank for the reply, but sadly, the reason I chose to use a library is that I need to do calculation on dates :(Kinzer
You can still use date-fns for everything except that last part for output. It does return, afterall, Date objects.Seften
Looks like this is the only way out if I want to use date-fns. Excellent answer!Kinzer
I like this solution. It's correct and a one-liner.Magruder
S
34

Note
The following solution will not work for all time zones, so if timezone accuracy is critical for your application you might want to try something like the answer from Beni. See this link for more info

I had the exact same question today and did some research to see if anyone has come up with anything better since this question was posed. I came across this solution which fit my needs and stylistic preference:

import { format, addMinutes } from 'date-fns';

function formatDate(date) {
  return format(addMinutes(date, date.getTimezoneOffset()), 'yyyy-MM-dd HH:mm:ss');
}

Explanation

getTimezoneOffset returns the number of minutes needed to convert that date to UTC. In PST (-0800 hours) it would return 480 whereas for somebody on CST (+0800 hours) it would return -480.

Shuntwound answered 27/4, 2020 at 22:30 Comment(3)
You should note that this solution does not work for all timezones, as pointed out in the thread you referred to: github.com/date-fns/date-fns/issues/1401#issuecomment-621897094Alderney
@Sherwin, for me instead of doing +5.30 , it is doing -5.30 . So how should I correct it.?Hydrothorax
@KomalKhatkole getTimezoneOffset returns the number of minutes needed to convert your local time to a UTC time, so if your offset is +5:30 it will return -330 minutes. Is this what's happening?Shuntwound
K
12

I had the same problem. What I do is remove the timezone from the ISO string and then use that time with date-fns:

let time = "2019-10-25T08:10:00Z".slice(0, -1)

The above is a time with no time zone, and because there is no timezone date-fns assumes the local timezone, so when you do:

format(parseISO(time), 'h:mm a')

you get: 8:10 AM, or whatever format you prefer. You just have to be careful with the string that you are slicing. If its always the same format then it should work.

Kezer answered 15/12, 2019 at 17:51 Comment(0)
B
10

I did something like this using date/fns and native date methods

import format from 'date-fns/format';
import parseISO from 'date-fns/parseISO';

export const adjustForUTCOffset = date => {
  return new Date(
    date.getUTCFullYear(),
    date.getUTCMonth(),
    date.getUTCDate(),
    date.getUTCHours(),
    date.getUTCMinutes(),
    date.getUTCSeconds(),
  );
};

const formatDate = (dateString) = > {
    const date = parseISO(dateString);
    const dateWithOffset = adjustForUTCOffset(date)
    return format(dateWithOffset, 'LLL dd, yyyy HH:mm')
}
Bensen answered 17/3, 2021 at 11:47 Comment(2)
I am stuck on 1.30.1 for now and this worked for meDunnage
I don't if there are more solutions but definitely this is the easiestMillen
D
3

Here is how I did it

const now = new Date()
  const date = format(
    new Date(now.toISOString().slice(0, -1)),
    'yyyy-MM-dd HH:mm:ss'
  )

I just removed the Z from the ISO string. I'm not sure if it solves this issue though

Director answered 27/5, 2021 at 22:21 Comment(0)
R
1

solution for timestamp

format(utcToZonedTime(timestamp, 'UTC'), 'MM/dd/yyyy hh:mm a', { timeZone: 'UTC' })
Rosana answered 3/2, 2023 at 11:55 Comment(0)
D
0

This works using only the date-fns provided functionality.

import { format } from 'date-fns';
import { utcToZonedTime } from 'date-fns-tz';

const date = new Date(); // This could be any date

// Convert the local date to UTC
const utcDate = utcToZonedTime(date, 'Etc/UTC');

// Format the UTC date
const formattedDate = format(utcDate, 'yyyy-MM-dd HH:mm:ss');

console.log(formattedDate);
Delinquent answered 18/10, 2023 at 21:37 Comment(0)
A
0

If you only care about UTC, you can use @date-fns/utc:

import { UTCDateMini } from "@date-fns/utc";
import { format } from 'date-fns';

const given = "2019-10-25T08:10:00Z";
const format = "yyyy-MM-dd kk:mm:ss";

const utcDate = new UTCDateMini(given);
const formatted = format(utcDate, format); // 2019-10-25 08:10:00
Alderney answered 13/3, 2024 at 13:19 Comment(0)
F
-3

try

const formatDate = new Date().toISOString().substr(0, 19).replace('T', ' ');
Francinafrancine answered 4/12, 2020 at 20:20 Comment(0)
E
-3

I guess

To construct the date as UTC before parsing would be helpful.

import { parseISO, format } from "date-fns";

const time = "2019-10-25T08:10:00Z";

const parsedTime = parseISO(new Date(Date.UTC(time)));
const formattedTime = format(parsedTime, "yyyy-MM-dd kk:mm:ss");

like this.

Elin answered 18/6, 2021 at 17:18 Comment(2)
The parsedTime from your snippet returns InvalidDateInvite
The answer is partially correct, but he missed the usage of parseISO. I edited the answer ` const parsedTime = parseISO(new Date(Date.UTC(time))); `Jesusa

© 2022 - 2025 — McMap. All rights reserved.