JavaScript – Temporal API & Date
前言
Temporal API 是 JS 的新东西,用来取代 Date。虽然现在 (12-09-2024) 依然没有任何游览器支持 Temporal API,但它已经是 stage 3 了,而且有完整的 polyfill,所以还是非常推荐大家积极去使用它。
如果你对日期不熟悉,可以先看看这篇文章 -- Time Zone, Leap Year, Date Format, Epoch Time 时区, 闰年, 日期格式。
参考
YouTube – Learn Temporal API In 17 Minutes
Temporal Date API Ultimate Guide
吐槽 Date
Date 非常的烂,它是 1995 年,JavaScript 模仿 Java 设计的出来的。后来 1997年 Java 丢弃了这个设计,但 JavaScript 却一直沿用至今。从这一点你就知道它有多烂了。
month starts with zero
const date = new Date(2023, 1, 1);
console.log(date.toDateString()); // Wed Feb 01 2023
你以为创建的是 1月1号?不,JS 的 Date 月份是从 0 开始的,1月 = 0,2月 = 1...
parse string 倒是正确的,但 getMonth 依旧是返回 0 哦。
const date2 = new Date('2023-01-01');
console.log(date2.toDateString()); // Sun Jan 01 2023
console.log(date2.getMonth()); // 0
alwasy time zone & only locale time zone
JS 的 Date 一定包含 time zone,而且这个 time zone 是无法用 JS 控制的,它总是依据游览器的设定。
创建一个早上八点的 Date
const date = new Date(2023, 0, 15, 8);
console.log(date.toLocaleString()); // 1/15/2023, 8:00:00 AM
查看 time zone offset,马来西亚的 offset 是 +08.00
console.log(date.getTimezoneOffset()); // -480
480 是 minutes,换成 hours 就是 8 小时。前面的 (-) 减符号让人匪夷所思,+08:00 不应该是 positive 吗?
查看 UTC 时间
console.log(date.toUTCString()); // "Sun, 15 Jan 2023 00:00:00 GMT"
果然 8 小时没了,所以 Date 的 time zone 就是 +08:00。
Force UTC ?
有没有办法让它变成 UTC time zone +00:00 呢?没有!
const date = new Date(Date.UTC(2023, 0, 15, 8));
console.log(date.toUTCString()); // Sun, 15 Jan 2023 08:00:00 GMT
用 Date.UTC 看上去好像是 UTC time zone,但其实不是。
Date.UTC 只是把日期变成了 number 然后交给 Date,最终出来的 Date 依然是 locale time zone。
不信,请查看 offset 和 locale date
console.log(date.getTimezoneOffset()); // -480
console.log(date.toLocaleString()); // 1/15/2023, 4:00:00 PM
locale date 从早上 8 点变成了下午 4 点,多了 8 小时。
总结:Date 总是有 time zone 概念的,而且 time zone 是依据游览器 locale 设定的。
不直观的 Date edit function
想添加 15 天到一个 datetime 是这样的
const now = new Date(2023, 0, 29);
console.log(now.toDateString()); // Sun Jan 29 2023
now.setDate(30); // mutable
console.log(now.toDateString()); // Mon Jan 30 2023
now.setDate(now.getDate() + 15); // add 15 days
console.log(now.toDateString()); // Tue Feb 14 2023
通过 setDate 而且利用了它自动进位的功能来完成 add 15 days 操作。
直观的操作应该是这样的
const now = new Date();
const newDate = now.addDays(15); // immutable
sort date
我在 JavaScript – Sort 里提过 sort date 的方式,利用 date.getTime 得到 epoch time 然后对减。
这个方法很间接,应该要提供一个类似 String.prototype.localeCompare 的方法用于 Date 排序才对。
Parse Date
console.log(new Date('2023-01-15').getHours()); // 8
由于 time zone 作祟,莫名其妙 hour 就变成 8 了...
总结
总之 Date 有一万个不好,不要用它,宁愿用 dayjs / date-fns 之类得 Library,或者 Temporal API with polyfill。
Temporal Polyfill
目前没有任何游览器支持 Temporal API,所以一定要安装 Polyfill。
yarn add @js-temporal/polyfill
或者
yarn add temporal-polyfill
这两个 Polyfill 都不错用,后者比较轻,比较新,前者是权威维护的。
它们都自带 TypeScript 哦
import { Temporal, Intl, toTemporalInstant } from 'temporal/polyfill';
Date.prototype.toTemporalInstant = toTemporalInstant;
有三个主要 exports. Temporal 就是 Temporal API 的接口。
Intl 是为了让 Intl.DateTimeFormat 支持 Temporal 实例,所以也需要 polyfill。
最后是 toTemporalInstant,它是一个方便把 Date 转换成 Temporal 实例的小扩展。
Date, Time, DateTime, Duration, TimeZone 概念
传统的 Date 对象包含了 Date (日期),Time (时间),Time Zone (时区)。这种 always all in 的对象并不友好。
很多时候我们只想表达日期,不需要时间也不需要 Time Zone。有时候则只想表达几点几分,不需要日期,这时用 Date 都很变扭。
Temporal 允许我们只定义日期 Date,或者只定义时间 TIme,或者两者 DateTime。而 DateTIme 也可以选择要不要有 Time Zone 概念。
所以我们会有许多不同的对象,对象的方法也都不一样。比如 Temporal 的 Date 对象没有操控时间的 API,Time 对象则没有操控日期的 API。
另外 Temporal 还引入了 Duration 概念。它表示一个时间单位,比如 10 分钟、两天、一星期等等,这个在日常生活很常见。
总之,Temporal 把时间分的很细,而不像传统的 Date 对象,完全不符合我们直观的理解。
创建 Temporal
Temporal.now 可以依据此时此刻创建一个 Temporal 对象
如同上面提到的,它有很多种对象可以选择,Date、Time、DateTime、zonedDateTIme 等等。我们依据需求范围决定使用哪个对象就可以了。
Temporal.Now.plainDateISO().toString(); // 2023-01-15
Temporal.Now.plainTimeISO().toString(); // 21:16:35.06659506
Temporal.Now.plainDateTimeISO().toString(); // 2023-01-15T21:16:35.069595066
Temporal.Now.zonedDateTimeISO().toString(); // 2023-01-15T21:16:35.072595069+08:00[Asia/Kuala_Lumpur]
Temporal.Now.instant().toString(); // 2023-01-15T13:16:35.075595072Z
Temporal.Now.zonedDateTimeISO('America/Adak').toString(); // 2023-01-15T04:16:35.072595069-09:00[America/Adak]
这里有一个知识点要知道.
date、time、datetime 都缺少了 time zone,它们是算不出 epoch time 的。只有拥有 time zone 的 zonedDateTime 和 instant (它是 UTC) 才可以算出 epoch time 哦。
注:epoch time 是 1970年1月1号 (UTC +00:00) 到某时间的纳秒数,而这个某时间一定要明确表明 time zone / UTC,不然秒数肯定不准。
Temporal.Now.zonedDateTimeISO().epochMilliseconds; // 1673789585487
Temporal.Now.instant().epochMilliseconds; // 1673789585487
另外,ISO 指的是 ISO-8601 calendar,plainDateISO 使用的是 ISO-8601 calendar,而 plainDate(‘Islamic’) 则可以选择使用其它 calendar,比如 Islamic。
from
想创建任意时间 / 时区,可以用 from 方法,可以用 string format 或者传 config 对象
const date = Temporal.ZonedDateTime.from('2012-01-01[Asia/Kuala_Lumpur]'); // 2012-01-01T00:00:00+08:00[Asia/Kuala_Lumpur] const date = Temporal.ZonedDateTime.from({
year: 2023,
month: 1,
day: 1,
timeZone: 'Asia/Kuala_Lumpur',
});
// 2023-01-01T00:00:00+08:00[Asia/Kuala_Lumpur]
注:一月是 1 而不是 0 哦。
完整的 time zone id 列表可以参考:Wikipedia – List of tz database time zones
另外它也不会像传统 Date 对象那样,parse 的时候会被 time zone 影响
Temporal.PlainDateTime.from('2023-01-15').hour; // 0
new Date('2023-01-15').getHours(); // 8
日期格式
输出日期格式不是 Temporal 的职责,它只有少量的 API 可以控制。
Temporal.Now.zonedDateTimeISO().toString(); // 2023-01-15T21:49:51.000590989+08:00[Asia/Kuala_Lumpur]
Temporal.Now.zonedDateTimeISO().toLocaleString('en-US'); // 1/15/2023, 9:49:51 PM GMT+8
Temporal.Now.zonedDateTimeISO().toLocaleString('ms-MY'); // 15/1/2023, 9:49:51 PTG MYT
Temporal.Now.zonedDateTimeISO().toLocaleString('zh-TW'); // 2023/1/15 下午9:49:51 [GMT+8]
Temporal.Now.zonedDateTimeISO().toLocaleString('zh-CN'); // 2023/1/15 GMT+8 21:49:51
Temporal.Now.zonedDateTimeISO().toInstant().toString(); // 2023-01-15T13:49:51.015591013Z
Temporal.Now.zonedDateTimeISO().toInstant().toString({ smallestUnit: 'millisecond' }); // 2023-01-15T13:49:51.017Z
两个点值得注意
最小的单位是纳秒 nanosecond,可以通过 options smallestUnit 控制最小单位输出,(millisecond > microsecond > nanosecond) 每一个级别差 1000 (1ms = 1000micro = 1000000ns)
instant 才可以输出程序员最常见的格式
Inlt.DateTimeFormat
想要配置多一点需要使用 Inlt.DateTimeFormat
const date = Temporal.Now.instant();
new Intl.DateTimeFormat('en-MY', {
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
hour12: true,
minute: '2-digit',
second: '2-digit',
weekday: 'short',
timeZoneName: 'longOffset',
timeZone: 'Asia/Kuala_Lumpur',
}).format(date); // Thu, 12 Sept 2024, 03:02:41 pm GMT+08:00
它已经可以配置很多东西了,但依然没有完全自定义 format 的能力,比如你不能像 C# 那样直接指定一个 format yyyy-MMM-dd HH:mm:ss tt %K 作为输出。
另外,讲一下 time zone 和 format。
下面这样会报错
const date = Temporal.Now.zonedDateTimeISO();
const dateString = new Intl.DateTimeFormat('en-MY', {
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
hour12: true,
minute: '2-digit',
second: '2-digit',
weekday: 'short',
timeZoneName: 'longOffset',
}).format(date); // Error: Temporal.ZonedDateTime not supported in DateTimeFormat methods. Use toLocaleString() instead.
Intl.DateTimeFormat 不支持 zonedDateTime,date time 不能包含 time zone,只能像上一个例子那样使用 UTC date time,然后在调用 Intl.DateTimeFormat 时设置指定的 time zone (它会自动把时间换算过去)。
改成下面这样就可以
const date = Temporal.Now.zonedDateTimeISO(); const dateString = new Intl.DateTimeFormat('en-MY', {
timeZone: date.timeZoneId, // 指定 time zone
}).format(date.toInstant()); // toInstant 转成 UTC console.log(dateString);
最后,补上一个 format list
const date = Temporal.ZonedDateTime.from('2024-09-09T16:09:09.123[America/Chicago]');
const dateString = new Intl.DateTimeFormat('en-US', {
// year: '2-digit', // 24 YY
// year: 'numeric', // 2024 YYYY // month: 'numeric', // 9 M
// month: '2-digit', // 09 MM
// month: 'short', // Sep MMM
// month: 'long', // September MMMM // day: 'numeric', // 9 D
// day: '2-digit', // 09 DD
// weekday: 'short', // Mon ddd
// weekday: 'long', // Monday dddd // hour12: false,
// hour: 'numeric', // 16 H
// hour: '2-digit', // 16 HH // hour12: true,
// hour: 'numeric', // 4 PM h A
// hour: '2-digit', // 04 PM hh A // minute: 'numeric', // 9 m
// minute: '2-digit', // 09 mm // second: 'numeric', // 9 s
// second: '2-digit', // 09 ss // second: 'numeric',
// fractionalSecondDigits: 3, // 9.123 SSS // second: 'numeric',
// timeZoneName: 'longOffset', // 9 GMT-05:00 Z
timeZone: date.timeZoneId,
}).format(date.toInstant());
console.log(dateString);
傍边注释的是比对 dayjs format 的格式,大部分都可以对的上,或则有一些微差。
Duration
它类似 C# 的 TimeSpan, 玩法很简单
const duration1 = Temporal.Duration.from({ minutes: 5, seconds: 30 });
console.log(duration1.toString()); // PT5M30S
const duration2 = Temporal.Duration.from('PT10M35S');
console.log(duration2.minutes); // 10
console.log(duration2.seconds); // 35
console.log(duration2.total('second')); // 635
再来一个
Temporal.Duration.from({
years: 1,
months: 1,
days: 1,
hours: 1,
minutes: 1,
seconds: 1,
}).toString() // P1Y1M1DT1H1M1S
拆开来看 P 1Y 1M 1DT 1H 1M 1S.
P 应该是 stand for Plain
PT 是 PlainTime (PT for 只有 hours 和以下, 有 days 或以上一律 starts with P 没有 T)
切换加减
Temporal.Duration.from({ minutes: 5, seconds: 30 }).negated().abs();
获取时间细节
const date = Temporal.Now.zonedDateTimeISO();
const values = [
date.year, // 2023
date.month, // 1 这里 1 代表 一月,JS Date 0 代表一月,不一样
date.day, // 15
date.dayOfWeek, // 7 这里星期日是 7 哦,JS Date 星期日是 0,不一样
date.hour, // 22
date.minute, // 35
date.second, // 37
date.millisecond, // 663
date.microsecond, // 337
date.nanosecond, // 652
date.offset, // '+08:00' 这是 string 哦
date.offsetNanoseconds / 1000 / 1000 / 1000 / 60 / 60, // 8 要自己换算成 offset 小时
];
date.toString() // 2023-01-15T22:37:14.513434495+08:00[Asia/Kuala_Lumpur]
算 ago
const commentDate = Temporal.ZonedDateTime.from('2023-01-02[Asia/Kuala_Lumpur]');
const now = Temporal.Now.zonedDateTimeISO();
console.log(now.since(commentDate).round({ smallestUnit: 'day' }).total('day') + 'days ago'); // 14 days ago
用了 since, round, total 技巧. 还有一个方法叫 until. 它和 since 是同个原理, 只是方向不一样.
now.since(previous) 是说从过往某天到现在的时间. now.until(future) 是从现在到未来某天的时间.
DateTime Adjustment
const date = Temporal.Now.plainDateISO();
const oneDay = Temporal.Duration.from({ days: 1 });
const newDate = date.add(oneDay);
console.log(date.toString()); // 2023-01-15
console.log(newDate.toString()); // 2023-01-16
它是 immutable 的.
直接传对象也是可以的
const newDate = date.add({ days: 1 });
减法
const newDate = date.subtract({ days: 1 });
或者使用 add days: -1 也是可以的
直接修改
const date = Temporal.Now.plainDateISO();
const newDate = date.with({ day: 18 });
console.log(date.toString()); // 2023-01-15
console.log(newDate.toString()); // 2023-01-18
注: Temporal 的 with 和 传统 Date 对象的 setDate 不同, 如果给的数超过当月份, 那么它会停在最后一天, 比如 31号. 而 setDate 则会自动进位去修改月份.
Date Comparison for Sort
const today = Temporal.Now.plainDateISO();
const yesterday = today.subtract('P1D');
const tomorrow = today.add('P1D');
const days = [tomorrow, today, yesterday];
console.log(days.sort(Temporal.PlainDate.compare).map(d => d.toString())); // ['2023-01-14', '2023-01-15', '2023-01-16']
Convert Between Date and Temporal
Date to Temporal 很简单,有 built-in 好的方法可以使用
import { Intl, Temporal, toTemporalInstant } from 'temporal-polyfill'; const date = new Date();
const temporalInstant = toTemporalInstant.call(date);
Date 一定是包含日期,时间,time zone (UTC) 的,所以转换过去就成了 TemporalInstant。
TemporalInstant to Date 也不难
const date = new Date(Temporal.Now.instant().toString());
console.log(date);
直接 toString 就可以了,因为它会返回 ISO string,而 new Date 可以 parse 这个 string。
唯一一个比较麻烦,或者需要注意的地方是 Temporal.PlainDate 和 time zone 的关系。
const today = Temporal.PlainDate.from('2024-09-12');
const date = new Date(today.toString());
console.log(date); // Thu Sep 12 2024 08:00:00 GMT+0800 (Malaysia Time)
注意,PlainDate 是没有 time zone 的,但是 Date 是有 time zone 的,所以最终的结果多了 8 hours。
转 UTC 也没用
const date = new Date(today.toZonedDateTime('UTC').toInstant().toString());
console.log(date); // Thu Sep 12 2024 08:00:00 GMT+0800 (Malaysia Time)
我们需要转成当前游览器的 time zone 才正确。
const today = Temporal.PlainDate.from('2024-09-12');
const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; // Asia/Kuala_Lumpur
const date = new Date(today.toZonedDateTime(userTimeZone).toInstant().toString());
console.log(date); // Thu Sep 12 2024 00:00:00 GMT+0800 (Malaysia Time)
不再多出 8 hours 了。
JavaScript – Temporal API & Date的更多相关文章
- Spring Boot 框架@Temporal(TemporalType.DATE)
使用spring boot框架开发项目时,遇到这样一个问题: 查询pgSQL数据库中表A中某date数据类型的列B,想得到YYYY-MM-DD格式的日期,结果返回的为时间戳(长整型数据). 解决办法: ...
- javascript的api设计原则
前言 本篇博文来自一次公司内部的前端分享,从多个方面讨论了在设计接口时遵循的原则,总共包含了七个大块.系卤煮自己总结的一些经验和教训.本篇博文同时也参考了其他一些文章,相关地址会在后面贴出来.很难做到 ...
- 深入理解javascript选择器API系列第三篇——h5新增的3种selector方法
× 目录 [1]方法 [2]非实时 [3]缺陷 前面的话 尽管DOM作为API已经非常完善了,但是为了实现更多的功能,DOM仍然进行了扩展,其中一个重要的扩展就是对选择器API的扩展.人们对jQuer ...
- 使用 JavaScript File API 实现文件上传
概述 以往对于基于浏览器的应用而言,访问本地文件都是一件头疼的事情.虽然伴随着 Web 2.0 应用技术的不断发展,JavaScript 正在扮演越来越重要的角色,但是出于安全性的考虑,JavaScr ...
- hibernate jpa 注解 @Temporal(TemporalType.DATE) 格式化时间日期,页面直接得到格式化类型的值
1.日期: @Temporal(TemporalType.DATE) @Column(name = "applyDate", nullable = false, length = ...
- 【译】JavaScript Promise API
原文地址:JavaScript Promise API 在 JavaScript 中,同步的代码更容易书写和 debug,但是有时候出于性能考虑,我们会写一些异步的代码(代替同步代码).思考这样一个场 ...
- JavaScript高级编程——Date类型
JavaScript高级编程——Date类型 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" ...
- 深入理解javascript选择器API系列第三篇——HTML5新增的3种selector方法
前面的话 尽管DOM作为API已经非常完善了,但是为了实现更多的功能,DOM仍然进行了扩展,其中一个重要的扩展就是对选择器API的扩展.人们对jQuery的称赞,很多是由于jQuery方便的元素选择器 ...
- JavaScript 字符串 & Math & Date
字符串 字符串就是零个或多个排在一起的字符,放在单引号或双引号之中. 'abc' "abc" 单引号字符串的内部,可以使用双引号.双引号字符串的内部,可以使用单引号. 'key=& ...
- Javascript学习之Date对象详解
1.定义 创建 Date 实例用来处理日期和时间.Date 对象基于1970年1月1日世界协调时起的毫秒数 2.语法 构造函数 new Date() new Date(value) value代表自世 ...
随机推荐
- 可视化—AntV G6实现节点连线及展开收缩分组
AntV 是蚂蚁金服全新一代数据可视化解决方案,主要包含数据驱动的高交互可视化图形语法G2,专注解决流程与关系分析的图表库 G6.适于对性能.体积.扩展性要求严苛的场景. demo使用数字模拟真实的节 ...
- ctfshow sql-labs(笔记)
这是当时做题的时候记得笔记有些乱看不懂的可以私我 判断闭合方式: id=1' and 1=1–+ *正常回显* id=1' and 1=2–+ *异常回显* id=1 and 1=1 *正常回显* i ...
- [oeasy]python0052_ raw格式字符串_单引号_双引号_反引号_ 退格键
转义字符 回忆上次内容 最近玩的是\n.\r 之外的转义序列 \a是 ␇ (bell) \t是 水平制表符 \v是 换行不回车 通过 16 进制数值转义 \xhh 把(hh)16 进制对应的 asci ...
- vue进阶一~数据响应式,数据响应到视图层,手写v-model,订阅发布者模式,
1,数据响应式 当数据发生改变的时候,我们立即知道数据发生改变,并做出相关的操作:发送请求,打印文字,操作DOM等. 1.1,vue实现数据响应的原理 vue中使用了两种模式来实现数据响应式,分别是v ...
- Python elasticsearch-py类库基础用法
实践环境 https://pypi.org/project/elasticsearch/ pip install elasticsearch==7.6.0 离线安装包及依赖包下载地址: https:/ ...
- CF1915B Not Quite Latin Square 题解
CF1915B 题意 给出一个 \(3\) 行 \(3\) 列的字符矩形,其中每行都有字符 ABC 各一个组成,现有一个字符未知,求出未知字符. 思路 就是说每个字符都应该出现 \(3\) 次,所以我 ...
- 新做了一个MySQL 数据库 DDL 差异对比的网站
MySQL 数据库 DDL 差异对比的网站 摘要 新做了个网站,用来对比不同环境下的 DDL 差异,生成变更点和 迁移 DDL 网站地址:https://ddlcompare.com/ 对比过程中如果 ...
- OpenGL 三角形颜色插值
1.最懒的方法--Nearest Neighbor对于三角形内的点,离三个顶点谁最近,就赋值为那个顶点对应的颜色. 2.最天真的方法--Distance三角形内一点的值应该来自于三个顶点. 计算距离: ...
- 阅读翻译Mathematics for Machine Learning之2.8 Affine Subspaces
阅读翻译Mathematics for Machine Learning之2.8 Affine Subspaces 关于: 首次发表日期:2024-07-24 Mathematics for Mach ...
- Cesium 实现可视域分析
*前言:尝试了网上好多个版本的可视域分析,感觉都有一些问题,我这个也可能不是最完美的,但是我觉得对我来说够用了,实现效果如下* 此示例基于vue3上实现,cesium版本1.101.0 ,vite-p ...