时间格式化在SSR下的偏差

TOC

背景

最近在处理nextjs服务端渲染时,遇到时间格式化后在,客户端展示晚了8小时的问题,刚开始想着是如何处理服务器时区问题,因此查找了很多关于修服务器配置,以及更改nodejs环境配置的资料,但收效甚微。后来在与做后端的朋友聊及此问题时,发现从一开始我的方向都错了。因此在这里总结一下,以供需要的同学处理类似问题。

为什么是晚了8小时?

在计算机中时间的生成都是一个唯一值,用UNIX表示则为一个毫秒级整数,我们平常所看见的是格式化后的时间字符串,例如:2022-22-22 22:22:22。这个字符会因为我们本地计算机配置的时区不同,而格式化后有所差异,例如,在国内个人计机上时间为: 2022-22-22 22:22:22,在英国个人计算机上则为: 2022-22-22 14:22:22,这是因为国内使用的是东8区时区时间,英国为0时区时间,所以时间上相应偏差8时间。

那么由于,我们程序在服务器上的时间配置为0时区,所以在服务器端格式化时间时以0时区为基准,而我们在本地计算机访问时,本地计算机时区为东8区,所以就出现了格式化后的时间字符串晚8小时的情况。

Tip

全球共分24个时区,以0时区为基准,向东增加,向西减少,增减为地区对应时区。

注意:时间是唯一的,本地时间(格式化后时间)是相对的(需要加上时区)

直接查看:解决方案

时间相关概念

时间标准

  • GMT(Greenwich Mean Time)全名是格林威治平时(也称格林威治标准时间),于1884年确立。它规定太阳每天经过位于英国伦敦郊区的皇家格林威治天文台的时间为中午12点。1972年之前GMT一直是世界时间的标准。

  • UT:Universal Time 世界时。根据原子钟计算出来的时间。

  • UTC:Coordinated Universal Time 协调世界时。因为地球自转越来越慢,每年都会比前一年多出零点几秒,每隔几年协调世界时组织都会给世界时+1秒,让基于原子钟的世界时和基于天文学(人类感知)的格林尼治标准时间相差不至于太大。并将得到的时间称为UTC,这是现在使用的世界标准时间。

Tip

协调世界时不与任何地区位置相关,也不代表此刻某地的时间,所以在说明某地时间时要加上时区,也就是说GMT并不等于UTC,而是等于UTC+0,只是格林尼治刚好在0时区上。即:GMT = UTC+0
详细介绍请查看:参考

时区

随着火车铁路与其他交通和通讯工具的发展,以及全球化贸易的推动,各地使用各自的当地太阳时间带来了时间不统一的问题,在19世纪催生了统一时间标准的需求,时区由此诞生。

从格林威治本初子午线起,经度每向东或者向西间隔15°,就划分一个时区,在这个区域内,大家使用同样的标准时间。但实际上,为了照顾到行政上的方便,常将1个国家或1个省份划在一起。所以时区并不严格按南北直线来划分,而是按自然条件来划分。另外:由于目前,国际上并没有一个批准各国更改时区的机构。一些国家会由于特定原因改变自己的时区。全球共分为24个标准时区,相邻时区的时间相差一个小时。

timezone]

解决方案

搞明白时间时区的关系,那么在处理时间是也就变得相对简单的多了。为了计算方便,首先,我们需要将服务器时间统一为零时区(当然也可以其它时区)。后续在做服务端渲染时会传入客户端所在时区参数。且数据库存储数据时存的为世界标准时间,而非本地时间,除非在存储时,已格式化为本地时间字符串。

客户端处理

此处以nextjs为例,其它服务端渲染方案可参考此解决方案。封装一个客户端渲染的时间格式化组件,用来替换时间格式化部分。原理是交由客户端渲染后,时间插件处理在格式化时间时,默认采用的是客户端时区来格式化处理。此方案最为简单

"use client";
import dayjs from "dayjs";

type Format = "YYYY-MM-DD HH:mm:ss" | "YYYY-MM-DD";

type DateFormatProps = {
  value?: Date | string | number;
  format?: Format;
  short?: boolean;
};

export default function DateFormat({ short, value, format }: DateFormatProps) {
  const f: Format = short ? "YYYY-MM-DD" : "YYYY-MM-DD HH:mm:ss";

  return value ? dayjs(value).format(format || f) : "-";
}

服务端处理

该方案未经验证,此处仅提供一个参考解决方案。方案相对于交由客户端渲染来说较为复杂。推荐还是使用客户端渲染方式。

  1. 服务端可以通过客户端发送的 HTTP 请求头中的 "Accept-Timezone" 字段获取客户端的时区信息。这个字段是由客户端的浏览器自动生成并发送的,通常是形如 "GMT+08:00" 的字符串。

Note

不是所有的浏览器都会发送这个字段,因此服务端需要特判是否存在该字段,并在必要时采取其他方法获取客户端时区信息。

  1. 更简单(也可能更准确),在客户端中运行new Date().getTimezoneOffset()得到的值即为时差偏移量(零时区减于当前时区),单位为分钟。将该值取反除以60即可得到当前客户端时区,然后将其作为Web请求的一部分发送给服务端即可。例如添加到请求头:Accept-Timezone: GMT+08:00

  2. 在服务端,取出时区信息。然后在格式化时间时加上客户端传来的时区信息即可。

参考

最近修改时间:2024-05-12 02:25:30