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.