marnusw / date-fns-tz

Complementary library for date-fns v2 adding IANA time zone support
MIT License
1.08k stars 116 forks source link

zonedTimeToUtc is not converting Utc time #174

Open Frans-L opened 2 years ago

Frans-L commented 2 years ago

It seems like that zonedTimeToUtc is not converting to UTC time, instead, it's converting to zoned time into your local time.

Here's an example, in which you try to convert something from your timezone to UTC. However, nothing is changed, since the zoned is the same as your local time.

So, it seems like zonedTimeToUtc should be called as zonedTimeToLocal.

import { zonedTimeToUtc } from "date-fns-tz";

const zonedTime = "2022-02-02T17:00:00.000Z";
const timezone = "Europe/Helsinki"; // YOUR TIMEZONE (e.g. Helsinki +2)

const org = new Date(zonedTime);
console.log("org", org.toISOString());
// output: 2022-02-02T17:00:00.000Z ✅

const converted = zonedTimeToUtc(zonedTime, timezone);
console.log("new", converted.toISOString());
// output: 2022-02-02T17:00:00.000Z ❌
// should be: 2022-02-02T15:00:00.000Z

Tested with:

marnusw commented 2 years ago

Merits of function names aside, this is intended behaviour. When you are working with a zoned time you shouldn't be specifying that using a UTC time via an ISO string as you're doing, that defeats the purpose. Create a new date using the constructor directly, i.e. new Date(...), then pass that to zonedTimeToUtc with the time zone and you'll get the correct UTC date out.

Put another way, if you have the correct UTC time to start with, why use date-fns-tz at all?

mPyth commented 2 years ago

@marnusw : I have many problems with understanding what this f-on does. Please, put somewhere in documentation what f-on does: it adds locale timezone offset and subtracts specified timezone offset to specified date. With this explanation misunderstanding will be impossible.

In order to be more clear I' suggestion next changes in documentation:

Current: Given a date and any time zone, returns a Date with the equivalent UTC time. More precise: Given a date, any time zone and local time zone (as a third hidden param), returns a Date with the equivalent UTC time.

Current: (picked in any time zone) More precise: (picked in whatever local time zone)

neillindberg commented 2 years ago

@marnusw I'm here because of material ui's x-datetime/picker. It is enforcing editing in a user's locale. What I'm building is a scientific app and it must be in UTC. So, I start with UTC, but the picker forces locale. I'm trying to override that back to UTC using date-fns-tz. I can't believe how much I'm struggling to do this. I thought, by the name of this function, that I had found a path forward. I can't believe this picker doesn't have a UTC or ISO flag.

MrRainesE commented 2 years ago

any update on this ?? facing the same issue: expected: "2022-11-07T15:30:00.000Z" result: Mon Nov 07 2022 16:30:00 GMT+0100 (Midden-Europese standaardtijd) code below add in codesanbox

import * as React from "react"; import TextField from "@mui/material/TextField"; import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns"; import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { DateTimePicker } from "@mui/x-date-pickers/DateTimePicker"; import { utcToZonedTime, zonedTimeToUtc } from "date-fns-tz";

export default function BasicDateTimePicker() { const [value, setValue] = React.useState( new Date("Mon Nov 07 2022 15:34:00 GMT+0100 (Midden-Europese standaardtijd)"));

return ( <LocalizationProvider dateAdapter={AdapterDateFns}> <DateTimePicker ampm={false} renderInput={(props) => <TextField {...props} />} label="DateTimePicker" value={value} onChange={(newValue) => { console.log(newValue,"\n", newValue.toISOString(),"\n", zonedTimeToUtc(newValue, Intl.DateTimeFormat().resolvedOptions().timeZone ) ); setValue(newValue); }} /> </LocalizationProvider> ); }

lamuertepeluda commented 1 year ago

I agree that this function purpose is not understandable and the name is ambiguous. I though it was something useful to convert local dates to UTC, which is useful in several use cases...

Is there anything like that in date-fns?

sopilkar commented 1 year ago

It looks like need to remove the latest "Z" from "zonedTime" string:


test('zonedTimeToUtc', () => {
    const zonedTime = '2022-02-02T17:00:00.000Z';
    const timezone = 'Europe/Helsinki'; // YOUR TIMEZONE (e.g. Helsinki +2)

    const converted = zonedTimeToUtc(zonedTime.replace('Z', ''), timezone);
    expect(converted.toISOString()).toEqual('2022-02-02T15:00:00.000Z');
  });
richardoSGSI commented 10 months ago

i'm also struggling with this, though my problem isn't exactly the same as OP

i'm trying to get the local EST date object and transform it to UTC

    const local = new Date();
    const utc = zonedTimeToUtc(local, "America/New_York");
    console.log("🚀 ~ file: AddModal.tsx:149 ~ handleSubmit ~ local:", local);
    console.log("🚀 ~ file: AddModal.tsx:150 ~ handleSubmit ~ utc:", utc);

output:

🚀 ~ file: AddModal.tsx:150 ~ handleSubmit ~ utc: Fri Feb 02 2024 20:03:59 GMT-0500 (Eastern Standard Time)
🚀 ~ file: AddModal.tsx:150 ~ handleSubmit ~ utc: Fri Feb 02 2024 20:03:59 GMT-0500 (Eastern Standard Time)

i'm pulling my hair out because of this. am i doing something wrong?

pbaern commented 1 week ago

I am pretty sure that this problem (the original problem as well as this) is related to https://github.com/marnusw/date-fns-tz/issues/302.

I have been working around that other issue by recreating internal methods like so

import { toDate, type OptionsWithTZ } from "date-fns-tz";
// @ts-expect-error
import tzParseTimezone from "@@/node_modules/date-fns-tz/_lib/tzParseTimezone";
// @ts-expect-error
import tzPattern from "@@/node_modules/date-fns-tz/_lib/tzPattern";

export const fromZonedTime = (
  date: string | Date,
  timeZone: string,
  options?: OptionsWithTZ,
) => {
  if (typeof date === "string" && !date.match(tzPattern)) {
    return toDate(
      date,
      Object.assign(Object.assign({}, options), { timeZone }),
    );
  }
  date = toDate(date, options);
  const offsetMilliseconds = tzParseTimezone(timeZone, date);
  return new Date(date.getTime() + offsetMilliseconds);
};

export const toZonedTime = (
  date: string | Date,
  timeZone: string,
  options?: OptionsWithTZ,
) => {
  date = toDate(date, options);
  const offsetMilliseconds = tzParseTimezone(timeZone, date, true);
  return new Date(date.getTime() - offsetMilliseconds);
};

and with that also the problems above vanished.