Windows 节点中 process.env.TZ = 'UTC' 的解决方案?

Solution for process.env.TZ = 'UTC' in Node for Windows?

我正在尝试测试我的 Postgres 数据库以确保所有内容都已正确添加,并且会包含这种格式的硬编码日期 2029-01-22T16:28:32.000Z

这是它的 table:

CREATE TABLE blogful_articles (
    id INTEGER PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
    title TEXT NOT NULL,
    content TEXT,
    date_published TIMESTAMP DEFAULT now() NOT NULL
);

我希望回来 2029-01-22T16:28:32.000Z 但是回来 2029-01-23T00:28:32.000Z 因为 Windows 会根据时区差异自动调整它,我的测试会失败。

它应该自动调整夏令时,但这是我的解决方案:

function adjustingForTimezone(dateToAdjust) {
  const offsetInMilliseconds = dateToAdjust.getTimezoneOffset() * 60000;
  const theDateWithOffsetAdded =
    Date.parse(dateToAdjust) - offsetInMilliseconds;
  const adjustedTime = new Date(theDateWithOffsetAdded);
  return adjustedTime;
}

以下是我的使用方法:

app.get("/articles", (req, res, next) => {
  const knexInstance = req.app.get("db");
  ArticlesService.getAllArticles(knexInstance)
    .then(articles => {
      const os = process.platform

      function adjustingForTimezone(dateToAdjust) {
        const offsetInMilliseconds = dateToAdjust.getTimezoneOffset() * 60000
        const theDateWithOffsetAdded = Date.parse(dateToAdjust) - offsetInMilliseconds
        const adjustedTime = new Date(theDateWithOffsetAdded)
        return adjustedTime
      }

      res.json(
        articles.map(article => ({
          id: article.id,
          title: article.title,
          style: article.style,
          content: article.content,
          date_published: os === "win32" ? adjustingForTimezone(article.date_published) : new Date(article.date_published)
        }))
      );
    })
    .catch(next);
});

我做了 adjustingForTimezone 功能,应该自动调整它,有什么办法可以改进它吗?在我的代码中使用它有哪些潜在的陷阱?

用于“调整”时区的方法实际上并没有调整。相反,它创建了一个不同的时间点。人们通常认为可以只减去一个偏移量来说明时区,但这是两个截然不同的概念。

Date 对象只存储了一个实际值,即您在调用 getTimevalueOfparse 等时看到的数字时间戳. 任何其他使用本地时区(例如 toString)或命名时区(例如 toLocaleStringtimeZone 选项)的函数都期望内部时间戳是 UTC -基于。如果不是,那么您可能会得到不正确的结果。

这在您的代码中重要的地方是您调用 getTimezoneOffset 的地方。您要求的时间点应该是基于 UTC 的,但是由于您需要调整,所以它已经是错误的时间点。您可以在 DST 转换的 date/time 附近对此进行测试,您会发现它会稍微过早或稍微过晚地切换偏移量。

那么让我们回到最初的问题——您从 Postgres 中获取的时间戳已被本地时区偏移。这是罪魁祸首:

date_published TIMESTAMP DEFAULT now() NOT NULL

这是可能发生的事情:

  • now() 函数 returns a TIMESTAMP WITH TIME ZONE(又名 TIMESTAMPTZ),使用插入行时有效的会话时区。在您的示例中,它返回 2029-01-23T00:28:32.000+08:00。因此,会话时区可能是默认值,它来自服务器的时区设置。 (UTC+8 - 在中国、澳大利亚和其他一些地方使用。)

  • 然后你将它传递给 TIMESTAMP,在 Postgres 中它 知道时区。所以它去掉偏移量并存储 2029-01-23T00:28:32.000。因此,您的时间戳不是以 UTC 而是以本地时间存储的。

  • 后面查询时,会话时区设置为UTC,所以返回2029-01-23T00:28:32.000Z,不正确

有几种不同的方法可以解决此问题:

  • 您可以确保在行插入期间将会话时区设置为 UTC

      SET TIME ZONE 'UTC'; INSERT INTO ...
    
  • 在定义 table:

    时,您可以在存储时间戳之前显式转换为 UTC
      date_published TIMESTAMP DEFAULT (now() AT TIME ZONE 'UTC') NOT NULL
    
  • 您可以将 date_published 字段定义为 TIMESTAMPTZ 而不是 TIMESTAMP。 (这可以说是最好的方法。)

      date_published TIMESTAMPTZ DEFAULT now() NOT NULL
    
  • 您可以确保查询时的会话时区与插入时的本地时区相同。但是,我不推荐这个,因为如果服务器的时区发生变化,或者如果您使用的时区在夏令时的偏移量之间转换,就会出现差异。

有了其中任何一个,您可以删除 adjustingForTimezone 函数并直接使用从查询返回的时间戳。