Javascirpt时区——脱坑指南

alt

最近业务反馈了一个约课功能的问题,澳大利亚的用户反馈,无法进行选课。排查之后发现是时区不对引起的,由于时区的偏差已经超过时间,导致无法选课。

这里对js中处理时区的问题做一些总结。

时区

时区(Time Zone)是指地球上基于经度划分的时间区域,用于协调全球各地的时间。每个时区有一个标准时间,通常以协调世界时(UTC)为基准来定义偏移量,例如 UTC+8 就表示比 UTC 快 8 个小时。

基础概念

  • UTC(协调世界时):UTC 是全世界的标准时间,用作所有时区的基准。
  • GMT(格林威治标准时间):GMT 曾经是世界标准时间,但目前 UTC 更常用。两者基本相同,但 UTC 是更加精确的原子时间。
  • 时区偏移(Offset):时区通常以 UTC 偏移量来表示,比如中国标准时间(CST)为 UTC+8,表示比 UTC 快 8 个小时。
  • 时区名称:每个时区有一个标准名称,比如 EST(Eastern Standard Time,美国东部标准时间),PST(Pacific Standard Time,美国太平洋标准时间)等。

夏令时&冬令时

夏令时(Daylight Saving Time)是某些国家或地区在夏季将时钟调快一小时的制度,以充分利用日照时间。夏令时通常从春季开始,到秋季结束。时区在进入和退出夏令时时会有时间上的切换。

概念最早由美国政治家本杰明·富兰克林提出,他在1784年就曾建议人们在夏季更早起床,以充分利用自然光,从而节约蜡烛的使用。然而,直到20世纪初,随着工业化的发展,夏令时才得到了广泛的实施。尤其是在两次世界大战期间,许多国家为了节约能源,开始实行夏令时。通过提前将时间拨快一小时,夏季的日照时间可以更长地照射到工作时段,降低了电力消耗。

随着夏令时的普及,它逐渐成为了很多国家应对季节性日照变化、提高效率的一种措施。然而,夏令时并非在所有国家或地区普遍适用。不同的国家根据自身的地理位置、气候条件和历史背景,决定是否实行夏令时。例如,热带地区和赤道附近的国家由于日照时间变化不大,通常不实施夏令时,而温带国家和高纬度国家则更加依赖这一制度。

时区数据库(IANA 时区)

计算机系统使用 IANA 时区数据库(例如 "Asia/Shanghai"、"America/New_York")来管理时区。IANA 时区数据库标准化了世界各地的时区信息,并包括夏令时切换信息。

例如:

  • Asia/Shanghai:代表中国的北京时间
  • America/New_York:代表美国纽约的时间
  • Europe/London:代表英国伦敦的时间

在多时区应用中进行时间转换时需要考虑以下因素:

  • 时间偏移:每个时区的偏移可能不同,需要考虑到目标时区的偏移量。
  • 夏令时切换:夏令时会影响偏移量,不同地区的夏令时规则不同。
  • 时区数据库更新:有时某些国家会临时调整夏令时或时区规则,因此需要确保时区数据库是最新的。

如何处理时区

为了排除干扰因素,在处理时区我们要遵循一个原则:

在存储或处理时间时,以 UTC 格式储存(避免受到用户所在地的时区影响),然后在需要显示的地方将 UTC 转换为用户所在的时区

JavaScript 中的 Date 对象基于 UTC 时间,可以使用 Intl.DateTimeFormat 和 toLocaleString 方法格式化为特定时区时间。此外,还可以使用 Intl API 的 timeZone 选项处理时区差异。

由于 JavaScript 中的时间和时区处理较为复杂,很多开发者使用第三方库(如 Moment.js 和 Luxon)来简化时间管理。

  • Moment.js:支持丰富的时间操作和时区处理,但已停止维护,推荐使用 Day.js 或 Luxon。
  • Luxon:由 Moment 团队开发的现代库,支持 IANA 时区数据库,适用于多时区应用。
// 使用 Luxon 格式化为特定时区的时间
const { DateTime } = require("luxon");
const dt = DateTime.now().setZone("America/New_York");
console.log(dt.toString()); // 输出纽约时间

获取当前时区

获取当前时区有不同的方法,最直接和推荐的方法是利用 Intl.DateTimeFormat 对象,它可以直接返回用户所在的时区

const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
console.log(timeZone); // 输出类似 "Asia/Shanghai"

也可以使用使用 Date 对象和 UTC 偏移量,用 Date 对象的 getTimezoneOffset() 方法。这个方法返回用户所在时区与 UTC 的分钟差。

const offset = new Date().getTimezoneOffset();
console.log(offset); // 例如 -480,表示 UTC+8

const offsetHours = -offset / 60;
console.log(offsetHours); // 输出 8 表示 UTC+8

是否存在夏令时

可以通过检查日期对象的 UTC 偏移量来判断是否处于夏令时。

  1. 检查一年中不同月份的 getTimezoneOffset()

夏令时期间的 UTC 偏移量通常不同于标准时间。例如,某些时区在夏令时期间会比标准时间少 60 分钟。通过对比一年中不同月份的偏移量,我们可以判断当前是否在夏令时。

function isDaylightSavingTime() {
    const january = new Date(new Date().getFullYear(), 0, 1); // 一月
    const july = new Date(new Date().getFullYear(), 6, 1);    // 七月
    const current = new Date();

    // 比较一月和七月的时区偏移
    const stdTimezoneOffset = Math.max(january.getTimezoneOffset(), july.getTimezoneOffset());
    const isDST = current.getTimezoneOffset() < stdTimezoneOffset;

    return isDST;
}

console.log(isDaylightSavingTime()); // 如果当前处于夏令时,则返回 true,否则返回 false

在这里,一月和七月分别代表通常不使用夏令时的月份。如果当前月份的偏移量小于这两个月份中的最大偏移量,则说明当前是夏令时。

  1. 使用 Intl.DateTimeFormat 检查夏令时信息

虽然 Intl.DateTimeFormat 没有直接给出夏令时的信息,但在一些系统中可以通过时区名称和日期来获取大致信息:

function isDaylightSavingTimeIntl() {
    const date = new Date();
    const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
    
    // 获取当前时区偏移和标准时间偏移
    const dtf = new Intl.DateTimeFormat('en-US', { timeZone, timeZoneName: 'short' });
    const parts = dtf.formatToParts(date);
    
    // 查看短时区名称是否包含夏令时标记 (例如 "PDT" 而不是 "PST")
    const timeZoneName = parts.find(part => part.type === 'timeZoneName');
    return timeZoneName ? timeZoneName.value.includes('D') : false;
}

console.log(isDaylightSavingTimeIntl()); // 如果当前处于夏令时,则返回 true,否则返回 false

在一些时区中,短时区名称会用 D 表示夏令时,例如 “PDT” 表示太平洋夏令时,而 “PST” 表示标准时间。如果名字包含 D,则表示夏令时启用。

使用第三方库

如果项目中使用了 moment-timezone 库,可以直接使用它来检查当前是否为夏令时。这个方法适合需要精准时区信息的应用。

const moment = require('moment-timezone');

function isDaylightSavingTimeMoment() {
    const timeZone = moment.tz.guess();
    return moment.tz(timeZone).isDST();
}

console.log(isDaylightSavingTimeMoment()); // 如果当前处于夏令时,则返回 true,否则返回 false

在moment-timezone 库里,本质上也是使用了上面的方法。

alt

关于Inel

前面的例子中提到了Inel对象,Intl 对象是 JavaScript 的国际化 API,提供了一系列工具,用于处理语言敏感的字符串、数字、日期、货币等内容的格式化。它帮助开发者根据用户的本地设置自动调整格式,适用于多语言、多地区应用。

  • 日期和时间:Intl.DateTimeFormat,支持时区和多种格式选项。
  • 数字和货币:Intl.NumberFormat,支持千分位分隔符、货币和百分比格式。
  • 字符串排序:Intl.Collator,支持多语言排序。
  • 复数规则:Intl.PluralRules,根据语言规则选择单复数形式。
  • 相对时间:Intl.RelativeTimeFormat,格式化相对时间描述。
  • 列表格式:Intl.ListFormat,将数组格式化为自然的列表形式。

Intl 对象是随着 ECMAScript 2015(ES6)标准推出的,首次在主流浏览器中出现大约是在 2015 年。自那以后,Intl 的功能不断扩展,以支持更多的国际化特性。

总体来说,Intl 对象的基本功能(如 Intl.DateTimeFormat, Intl.NumberFormat, 和 Intl.Collator)在现代浏览器中的兼容性非常好。但需要注意的是,一些较新的特性(如 Intl.RelativeTimeFormat, Intl.ListFormat, 和 Intl.PluralRules)是在 ECMAScript 2018 及之后的版本中引入的,因此它们的兼容性在早期浏览器和旧设备上可能不如基本功能。

以下是主要特性和它们的兼容性:

    1. 基础 Intl 对象(DateTimeFormat, NumberFormat, Collator):
    • 支持情况:大部分现代浏览器,包括 Chrome、Firefox、Edge、Safari 以及 Node.js 都支持,IE11 也部分支持。
    • 最低版本:
    • Chrome 24+
    • Firefox 29+
    • Safari 10+
    • IE 11+(但支持较有限)
    • Node.js 0.12+
    1. Intl.RelativeTimeFormat(相对时间格式):
    • 支持情况:主流浏览器中较新版本支持,包括 Chrome、Firefox、Safari 和 Edge,但 IE 不支持。
    • 最低版本:
    • Chrome 71+
    • Firefox 65+
    • Safari 14+
    • Node.js 12+
    1. Intl.ListFormat(列表格式化):
    • 支持情况:Chrome、Firefox、Edge 和 Safari 的较新版本支持,不支持 IE。
    • 最低版本:
    • Chrome 72+
    • Firefox 78+
    • Safari 13+
    • Node.js 12+
    1. Intl.PluralRules(复数规则):
    • 支持情况:主流浏览器中较新版本支持,包括 Chrome、Firefox、Safari 和 Edge,不支持 IE。
    • 最低版本:
    • Chrome 63+
    • Firefox 58+
    • Safari 11+
    • Node.js 10+
    1. Intl.DisplayNames(地区名称格式化):
    • 支持情况:仅在较新的 Chrome 和 Firefox 浏览器上支持,不支持 IE 和 Safari。
    • 最低版本:
    • Chrome 81+
    • Firefox 78+
    • Node.js 14+

如果需要在不支持 Intl 对象的环境中运行代码,可以考虑以下解决方案:

    1. Polyfill:使用诸如 @formatjs/intl 的 polyfill,为旧环境添加对 Intl 的支持。这个库包含了 DateTimeFormat, NumberFormat, RelativeTimeFormat 等多个 Intl 扩展。
    1. 后端预处理:对于不支持 Intl 的客户端环境,可以考虑将格式化操作放到后端处理,后端将格式化后的数据传输给客户端。

时区处理的注意事项

在处理时区问题时,我们需要注意一些关键点,以避免因时区差异导致的数据不一致或逻辑错误。

1、理解 Date 对象的 UTC 基准

JavaScript 的 Date 对象内部存储的时间是基于 UTC 时间的,而不是本地时间。Date 的各类方法,如 getTime(),返回的是相对于 UTC 的时间戳。因此,在存储或传输时间时,建议使用 UTC 格式,确保时区转换的一致性。

const now = new Date(); 
console.log(now.getTime()); // 返回 UTC 时间戳

但当我们使用new Date()或者getHours()这些方法时,是基于本地时区的,而不是UTC。

getHours() 返回本地时区的小时。如果需要 UTC 时间,则需要使用 getUTCHours() 方法。

const date = new Date(); // 当前时间

console.log("本地时间的小时数:", date.getHours());       // 返回本地时区的小时数
console.log("UTC 时间的小时数:", date.getUTCHours());    // 返回 UTC 时区的小时数

当通过 new Date(timestamp) 创建一个 Date 实例时,JavaScript 会根据本地时区来显示时间

const timestamp = 1700000000000;
const date = new Date(timestamp);
console.log("本地时间:", date.toString());       // 输出本地时间
console.log("UTC 时间:", date.toUTCString());    // 输出 UTC 时间

在本地时间(例如 UTC+8 时区)下可能会显示为 2023-11-14 22:13:20,而 UTC 显示为 2023-11-14 14:13:20。

2、避免直接操作 Date 对象来转换时区

Date 对象提供了一些转换本地和 UTC 时间的方法,但直接操作可能会导致复杂的代码,尤其是在涉及夏令时(DST)转换时。使用 toLocaleString 和 Intl.DateTimeFormat 等内置方法更为安全。

// 不建议手动转换偏移量,可能导致错误
const localDate = new Date();
const utcDate = new Date(localDate.getTime() + localDate.getTimezoneOffset() * 60000);

夏令时会导致某些地区的时区偏移量在夏季改变(例如纽约从 UTC-5 转为 UTC-4)。处理夏令时切换时,不建议依赖硬编码的时区偏移值,而是使用 Intl.DateTimeFormat 或第三方库,如 Luxon,它们可以根据当前日期自动调整时区偏移。

PS:中国也曾经在上个世纪实行过几年夏令时。

3、存储时间的格式

在跨时区应用中存储时间数据时,使用 UTC 时间戳可以确保在不同地区显示一致。例如,将时间存储为 UTC 格式,并在前端使用用户所在时区显示。

const utcNow = new Date().toISOString(); // 推荐将时间存储为 UTC ISO 格式
console.log(utcNow); // 例如:"2024-11-15T10:30:00.000Z"

这里解析一下ISO格式和时间戳的区别。

时间戳,通常指 Unix 时间戳,表示从 1970 年 1 月 1 日(UTC 时间)到特定时间的秒或毫秒数(如 1700000000000,以毫秒计)。它不包含任何时区信息。

ISO 格式 一般指 ISO 8601 标准,它是一种国际标准化的日期和时间表示方法,提供了一种全球通用的、易读的时间格式。它广泛用于数据库、API、前端和后端系统等,具有良好的跨平台兼容性。ISO 格式的时间字符串按标准化的顺序排列,方便排序和解析。

以下是 ISO 8601 格式的一些常见表示形式: (YYYY-MM-DDTHH:mm:ss.sssZ) 是一种标准化的日期时间格式,被广泛支持并易于解析。

4、不要写死时区偏移量

Date.getTimezoneOffset() 可以返回本地时间与 UTC 的差值,单位为分钟。虽然它提供了当前时区的偏移信息,但在夏令时的切换期间,偏移量会发生变化。避免依赖固定的偏移量,而应根据需求动态获取。

const offset = new Date().getTimezoneOffset();
console.log(offset); // 当前时区与 UTC 的差值,例如 -480 表示 UTC+8

另外,部分国家或地区可能更改时区或夏令时的切换规则。保持时区数据库(如 Intl API 使用的 IANA 数据库)最新可以避免时间转换错误。如果使用 Node.js,可以通过 full-icu 包更新 ICU 数据库,确保支持最新的时区数据。

总结

这里就是关于时区上的处理,掌握这些时区处理技巧不仅能够提升程序的准确性,还能有效避免时区相关的错误。

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/17269.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

不用来回切换,一个界面管理多个微信

你是不是也有多个微信号需要管理&#xff1f; 是不是也觉得频繁切换账号很麻烦&#xff1f; 是不是也想提升多账号管理的效率&#xff1f; 在工作中&#xff0c;好的辅助工具&#xff0c;能让我们的效率加倍增长&#xff01; 今天&#xff0c; 就给大家分享一个多微管理工具…

每日OJ题_牛客_AB32【模板】哈夫曼编码_C++_Java

目录 牛客_AB32【模板】哈夫曼编码 题目解析 C代码 Java代码 牛客_AB32【模板】哈夫曼编码 【模板】哈夫曼编码_牛客题霸_牛客网 描述&#xff1a; 给出一个有n种字符组成的字符串&#xff0c;其中第ii种字符出现的次数为ai​。请你对该字符串应用哈夫曼编码&#xff0c;…

UDP协议

​ UDP协议 前置知识一、应用层的进程为什么要bind端口号二、如何确定网络中的一个进程三、进程 服务 协议 端口之间的关系四、常见的协议对应的端口五、一些命令六、一个进程能不能绑定多个端口号&#xff0c;一个端口号能不能被多个进程绑定七、对任何一个协议报文的认识 UD…

KkFileView4.1.0部署文档--linux

先看下官方文档&#xff1a;kkFileView - 在线文件预览 环境要求中的JDK8如果没有的&#xff0c;需先安装JDK8&#xff0c;这里不做展示。 第二个office相关环境要求在linux中会自动下载安装&#xff0c;不用管。 1、下载地址 Linux 或 MacOS 版&#xff1a; https://kkfil…

[论文笔记]An LLM Compiler for Parallel Function Calling

引言 今天带来一篇优化函数调用的论文笔记——An LLM Compiler for Parallel Function Calling。 为了简单&#xff0c;下文中以翻译的口吻记录&#xff0c;比如替换"作者"为"我们"。 当前的函数(工具)调用方法通常需要对每个函数进行顺序推理和操作&…

基于JAVA的资源检索系统(源码+定制+开发)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

展望:多模态融合与marker推断

技术进步使得利用高维、高通量、多尺度的生物医学数据从多个角度研究患者和疾病成为可能。在肿瘤学中&#xff0c;正在生成大量数据&#xff0c;从分子、组织病理学到临床记录。深度学习的引入极大地促进了生物医学数据的分析。然而&#xff0c;大多数方法都侧重于单一模态&…

AI在电商平台中的创新应用:提升销售效率与用户体验的数字化转型

1. 引言 AI技术在电商平台的应用已不仅仅停留在基础的数据分析和自动化推荐上。随着人工智能的迅速发展&#xff0c;越来越多的电商平台开始将AI技术深度融合到用户体验、定价策略、供应链优化、客户服务等核心业务中&#xff0c;从而显著提升运营效率和用户满意度。在这篇文章…

基于Java Springboot餐厅点餐系统(加入商家版)

一、作品包含 源码数据库设计文档万字全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA 数据库&#xff1a;MySQL5.7…

NeRF在农业领域的应用-------------(1)

一、Exploring Accurate 3D Phenotyping in Greenhouse through Neural Radiance Fields&#xff08;通过神经辐射场探索温室中精确的三维表型分析&#xff09; 1.摘要 在精准农业中&#xff0c;准确收集植物表型对于优化可持续农业实践至关重要。 在受控实验室环境中进行的传…

pico-sdk(零)

pico-sdk&#xff08;零&#xff09; 项目概述license相关文档 依赖三方库链接 项目概述 Raspberry Pi Pico SDK&#xff08;以下简称 SDK&#xff09;提供了为 RP 系列微控制器设备&#xff08;如 Raspberry Pi Pico 或 Raspberry Pi Pico 2&#xff09;编写 C、C 或汇编语言…

基于java+SpringBoot+Vue的视频网站系统设计与实现

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis Maven mysql5.7或8.0等等组成&#x…

vue注册全局组件,其他地方可以直接方便的调用

文章目录 问题注册全局组件完结 问题 本来我们想使用某个组件&#xff0c;需要在各个地方引入对应的参数&#xff0c;并配置好components内容&#xff0c;才可以使用 但是随着用的越来越多&#xff0c;这种方法变得重复且易出错 注册全局组件 修改main.js文件&#xff0c;放…

javaScript交互补充(元素的三大系列)

1、元素的三大系列 1.1、offset系列 1.1.1、offset初相识 使用offset系列相关属性可以动态的得到该元素的位置&#xff08;偏移&#xff09;、大小等 获得元素距离带有定位祖先元素的位置获得元素自身的大小&#xff08;宽度高度&#xff09;注意&#xff1a;返回的数值都不…

基于SSM的特色美食推荐平台+LW示例参考

1.项目介绍 系统角色&#xff1a;管理员、普通用户功能模块&#xff1a;管理员&#xff08;用户管理、店铺管理、美食类型、美食收录管理、论坛交流管理等&#xff09;、普通用户&#xff08;登录注册、论坛交流、信息查看、美食收藏、美食资讯等&#xff09;技术栈&#xff1…

【javascript从零单排】变量let、var、const

&#x1f308;"It always seems impossible until it’s done." — Nelson Mandela 种一棵树最好是机会是十年前&#xff0c;其次是现在。 &#x1f4d7;概念 在 JavaScript 中&#xff0c;变量是用于存储数据值的容器。可以使用变量来保存不同类型的数据&#xff0…

Marp for VScode插件 PPT无法预览的问题

优质好文&#xff1a;https://blog.csdn.net/lyuhaochina/article/details/141527208 这是因为很多人在VScode中安装markdown插件时都会安装插件Markdown Preview Enhanced,这个插件会和Marp插件的预览功能产生冲突,导致用Marp插件做的PPT无法预览 找到设置选项Markdown-previe…

响应时间指标的探索

响应时间指标的探索 最近又看到响应时间的一些讨论&#xff0c;就顺着这个响应时间的一些资料整理了如下内容 1968年 目前能够追溯的最早定义响应时间的文章应该是Rober B.Miller于1968年在AFIPS 68 (Fall, part I): Proceedings of the December 9-11, 1968, fall joint comp…

VRT: 关于视频修复的模型

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;编程探索专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年11月15日14点34分 神秘男子影, 秘而不宣藏。 泣意深不见, 男子自持重, 子夜独自沉。 论文链接 点击开启你的论文编程之旅…

从基础到进阶,Dockerfile 如何使用环境变量

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 什么是 Dockerfile 环境变量?🔖1. `ENV` 指令🔖2. `ARG` 指令🔖语法:🔖使用 `ARG` 的例子:📝 如何使用环境变量提高 Dockerfile 的灵活性🔖1. 动态配置环境🔖2. 配置不同的运行环境🔖3. 多…