涉及日期 fns 格式()的时区问题

Posted

技术标签:

【中文标题】涉及日期 fns 格式()的时区问题【英文标题】:Time zone issue involving date fns format() 【发布时间】:2018-06-18 18:12:25 【问题描述】:
const dt = new Date('2017-12-12');
console.log(format(dt, 'YYYY-MM-DD'));

上述代码在美国记录 2017-12-11,但在印度记录 2017-12-12。

我关注了这个 github 线程 here 并尝试了一些东西,但没有得到想要的结果。

我的期望是打印相同的日期而不考虑时区

为什么我需要这个: 考虑一个涉及生日的场景。如果我给出一些输入日期,它必须在所有地区显示为相同的日期,而不管它们的时区。

【问题讨论】:

据我了解,问题在于您将 ISO 日期格式解析为 ISO,但它们是本地时间(大概您不希望用户以 UTC 输入他们的出生日期) .您可以通过查看new Date().getTimezoneOffset() 来推断用户的时区,但这可能并不准确。也许用户出生在印度但现在住在美国,所以你知道使用正确时区的唯一方法是他们告诉你他们出生在印度(更糟糕的是多-时区国家)。 是的。我也一整天都在探索,但找不到跳过时区并显示单个恒定输出的方法 据我所知,获得一致输出并且仍然能够将日期视为不透明字符串的唯一方法是询问用户他们的出生地。但也许你的应用程序可以接受不透明字符串选项。 (不透明,我的意思是您将用户的生日存储为字符串,与他们输入的内容完全相同,永远不要尝试将其传递给 Date 构造函数或解析器;不要费心计算用户的年龄或任何东西;那么你知道你总是可以正确显示它) 没有。我只是举了出生日期作为例子之一。我也在构建更多场景,所以我还没有想到使用字符串!不过谢谢你的建议!! 【参考方案1】:

在将date-fns 传递给format 之前,您需要从Date 实例中减去本地时区的时区偏移量。例如:

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"

问题

您只想处理Date 实例的日期部分,因为时间部分对于生日没有意义。但是,Date 对象不提供任何“仅日期”模式。您可以在本地时区或 UTC 中访问其日期和时间部分。问题是,来自date-fnsformat 总是在本地时区打印输出。

当您仅使用日期部分执行构造函数时:

const dt = new Date('2017-12-12');

javascript 引擎实际上假定了一个不完整的 ISO 8601 格式的字符串并执行了以下操作:

const dt = new Date('2017-12-12T00:00:00.000Z');

在您看来它可能仍然“无害”,但 date 实例不仅以 UTC 形式公开该值,而且还以本地时区公开该值。如果您在美国东海岸构建Date 实例,您将看到以下输出:

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

解决方案

如果您知道,date-fns 中的 format 从本地时区的 date 实例中读取日期和时间部分,您需要让您的日期“看起来像”本地时区的午夜而不是 UTC,您将其传递给 Date 构造函数。然后您将看到保留的年、月和日期数字。这意味着,您需要减去指定日期的本地时区的时区偏移量。 Date.prototype.getTimezoneOffset 返回偏移量,但带有一个倒号和以分钟为单位。

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

但是,这样的Date 实例只能用于格式化仅日期值。您不能使用它来计算日期差异,例如,这需要原始且正确的 UTC 值。

另类

如果您始终需要相同的仅日期格式而不是特定于当前语言环境的格式,则不需要date-fns。您可以通过连接填充数字来格式化字符串:

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

【讨论】:

谢谢,我已经为这个解决方案寻找了几个小时。有点黑客,但工作得很好! ? 请注意查看此调用会将其转换回包含时区:const dtDateOnly = new Date(dt.valueOf() + dt.getTimezoneOffset() * 60 * 1000); 您实际上需要返回以下内容:const dtDateOnly = dt.valueOf() + dt.getTimezoneOffset() * 60 * 1000; Ferdinand Prantl 在我的例子中不是添加偏移量,而是减去偏移量。如何使它添加?我已将其更改为“-”而不是“+”,然后它已添加到我的偏移时区。无论如何谢谢 @KomalKhatkole,dt.getTimezoneOffset() 对于 EST (-05:00) 的结果为正 (+300)。该符号不同于格式化日期的 TZ 后缀。这就是为什么在计算 UTC 时将其添加到本地 TZ 日期而不是减去它的原因。你有你的代码可以查看吗?例如,作为一个要点?

以上是关于涉及日期 fns 格式()的时区问题的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 date-fns 格式化日期?

date-fns - 有没有办法获取短日期格式字符串

时刻 vs date-fns 语言环境日期格式

使用 ISO-8601 格式的 date-fns 获取当前日期

使用 date-fns 2.x 版时,如何将日期时间格式化为 am/pm,不带句点?

获取 date-fns 中两个日期之间的持续时间,以天:小时:分钟:秒格式