date-fns | 如何将格式转换为UTC

96

问题

看起来当我使用format()函数时,它会自动将原始的UTC时间转换为我的时区(UTC+8)。我已经花了几个小时查阅他们的文档,但似乎找不到一种默认为UTC时间的方法。

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!!

我尝试使用 date-fns-tz 包,类似于以下方式:

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

仍没有运气。

请帮忙!

期望的输出

2019-10-25 08:10:00

实际的输出

2019-10-25 16:10:00


我在这里测试了你的代码。看起来运行良好。https://repl.it/repls/RepentantDimFactor - khan
8
@khan - repl.it 运行在协调世界时(UTC),这就是原因。 - Matt Johnson-Pint
1
当解析2019-10-25 08:10:00时,它将被解释为本地时间而不是UTC,并且在某些浏览器中将被视为无效日期。 - RobG
10个回答

61

你差不多做到了。这对我有效:

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

幕后花絮

date-fns[-tz]库坚持使用内置的Date数据类型,该类型不包含TZ信息
一些函数将其视为时间点,但像format这样的函数更像是日历组件的结构 - 年份2019年,...,第25天,第08小时等。

现在的问题是Date在内部仅仅是一个时刻。它的方法提供了一个映射到/从本地时区日历组件的方式。

因此,为了表示不同的时区,date-fns-tz/utcToZonedTime临时生成Date实例,表示错误的时刻 — 仅仅是为了在本地时区中得到我们想要的日历组件!

date-fns-tz/format函数的timeZone输入仅影响打印时区的模板字符(XX..Xxx..xzz..zOO..O)。

有关此“移位”技术(以及激发它们的真实用例)的讨论,请参见https://github.com/marnusw/date-fns-tz/issues/36...
这有点低级和有风险,但我上面组合它们的特定方式 - formatInTimeZone() - 我相信是一个安全的方法。


4
如果有人想知道这是不是黑客攻击——实际上,这是将时间格式化为特定时区(包括UTC)的官方方法:https://www.npmjs.com/package/date-fns-tz#format - nerrons
但是关于官方方式是否是一种hack,仍有争议 :-D 具体来说,我担心的是在某些时区中不存在的日历时间,例如 https://www.bbc.com/news/world-asia-16351377 — 如果您的本地时区是萨摩亚,那么没有时间戳映射到2011年12月30日,因此如果您想从另一个时区格式化该日期,我不确定它是否有效。 - Beni Cherniavsky-Paskin
2
这对我有用。在我意识到format导入必须来自于date-fns-tz而不是核心的date-fns之前,我尝试了几次,但之后就可以工作了。 - User 1058612
1
@nerrons 它的官方性质并不意味着它就不是“黑客行为”。毫无疑问,这仍然是一种黑客行为,而且还是一种很丑陋的黑客行为。可惜在JS中,人们必须采取如此极端的手段来完成这样一个微不足道的事情。 - wscourge

38

我建议使用内置的Date工具:

const date = new Date("2019-10-25T08:10:00Z");
const isoDate = date.toISOString();

console.log(`${isoDate.substring(0, 10)} ${isoDate.substring(11, 19)}`);

输出:

2019-10-25 08:10:00

不是适用于任何格式的一般解决方案,但不需要外部库。


谢谢回复,但不幸的是,我选择使用库的原因是我需要对日期进行计算 :( - Patrick Mao
6
除了输出部分,您仍然可以使用 date-fns 进行所有操作。毕竟它返回的是 Date 对象。 - Matt Johnson-Pint
看起来这是我想要使用 date-fns 的唯一出路。非常好的答案! - Patrick Mao
我喜欢这个解决方案。它是正确的且只有一行代码。 - graup

33

注意
以下解决方案并不能适用于所有时区,因此如果时区准确性对您的应用程序至关重要,则可能需要尝试类似于Beni答案中提供的内容。有关更多信息,请参见此链接

今天我有完全相同的问题,并进行了一些研究,以查看自从这个问题被提出以来是否有人提出更好的解决方法。我发现了这个解决方案,它符合我的需求和风格偏好:

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

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

说明

getTimezoneOffset返回将该日期转换为UTC所需的分钟数。在PST(-0800小时)中,它会返回480,而对于CST(+0800小时)中的某个人,它会返回-480。


2
请注意,正如您所提到的线程中指出的那样,此解决方案并不适用于所有时区:https://github.com/date-fns/date-fns/issues/1401#issuecomment-621897094 - tilo
@Sherwin,对我来说,它不是在做+5.30,而是在做-5.30。那么我该如何纠正它呢? - Komal Khatkole
@KomalKhatkole getTimezoneOffset 返回将本地时间转换为 UTC 时间所需的分钟数,因此如果您的偏移量为 +5:30,则它将返回 -330 分钟。这是正在发生的吗? - Sherwin F

12

我曾经遇到过同样的问题。我的解决方法是先从ISO字符串中删除时区,然后将该时间与date-fns一起使用:

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

上述是一个无时区的时间,因为没有时区,date-fns会假定本地时区,所以当你进行以下操作时:

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

你会得到:8:10 AM,或者你喜欢的任何格式。你只需要注意要切割的字符串是否始终具有相同的格式,这样它就能正常工作。


9

我使用date/fns和本地日期方法做了类似的事情

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')
}


我目前卡在1.30.1版本,这对我有效。 - Mitchell Brooks
我不知道是否还有更多的解决方案,但肯定这是最简单的一个。 - undefined

3

以下是我的做法:

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

我刚刚从ISO字符串中移除了Z。 我不确定是否解决了这个问题


0

时间戳的解决方案

format(utcToZonedTime(timestamp, 'UTC'), 'MM/dd/yyyy hh:mm a', { timeZone: 'UTC' })

0
这个功能只使用了date-fns提供的功能。
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);

-3

我猜

在解析之前将日期构建为UTC格式会很有帮助。

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");

像这样。


1
你的代码片段中的 parsedTime 返回了无效日期。 - Alex Buznik
1
答案部分正确,但他错过了parseISO的用法。我编辑了答案。const parsedTime = parseISO(new Date(Date.UTC(time))); - cyberfly

-3

尝试

const formatDate = new Date().toISOString().substr(0, 19).replace('T', ' ');

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接