ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JavaScript Date对象详解:时间戳、时区与格式化避坑指南

JavaScript Date对象详解:时间戳、时区与格式化避坑指南 1. 先把Date对象“到底是什么”彻底说透写JS的人几乎没有不用到Date的但我觉得很多朋友对它其实一直处于“会用但不完全懂”的状态。拿我自己的经历来说早年做后台管理系统遇到一个需求把用户上次登录时间存到localStorage里下次登录时做对比。我当时满脑子想的都是“把时间转成字符串存起来”结果取出来的时候发现类型对不上、比较大小全是NaN排查老半天才发现是Date对象在作祟。这里必须先说一个最核心的认知JS里的Date对象本质上存的是一个数字——从1970年1月1日00:00:00 UTC到目标时间的毫秒数。我们平时写new Date()、new Date(2023-07-01)、new Date(2023, 6, 1)这些参数只是构造时的“输入方式”对象内部真正持有的值是那个毫秒数。这就像你银行卡里存的是钱不是存了一堆“取款凭证”。你存取款时可以用现金、刷卡、扫码但账户余额始终是一个数字。Date对象也一样用new Date()创建出来之后你对它做的获取年份、月份、星期、格式化字符串等操作本质上都是从那个毫秒数反推出来的“视图”。我见过不少新手用typeof去判断一个Date对象结果得到object一下就懵了为什么不是date因为Date是引用类型typeof只能区分出基本类型和引用类型具体是数组还是日期要靠instanceof或Object.prototype.toString.call()来判断。这些都是基础中的基础但确实容易绕晕。还有一个经常被我拿来举例子的小细节new Date(2023, 0, 1)和new Date(2023-01-01)看起来都是2023年1月1日但两者相差可能不止8小时。为什么因为前者是按本地时间解析的后者是按UTC时间解析的。在中国UTC8跑前者显示的是北京时间1月1日零点后者显示的是北京时间1月1日早上8点。这个坑太常见了不信你可以自己试一下。Date对象还自带一个很实用的小特性比较大小可以直接用、、、。因为它内部是数字JS会做隐式转换。但这里必须注意不要用等号或直接比较两个Date对象那比较的是“引用地址”不是“毫秒数”。如果你要判断两个时间是否相等正确姿势是拿getTime()或valueOf()去比。再补充一个经验之谈如果你需要把Date对象存到localStorage或者传给后端最好的做法是存date.getTime()这个纯数字。这样既避免了时区问题也避免了“字符串解析成Date再解析”这一层的折腾。我第一次做“记住用户上次操作时间”的功能时就是吃了这个亏后来统一改成毫秒时间戳问题彻底消失。2. 核心API的坑与实用套路从年月日到格式化2.1 getMonth为什么从0开始以及怎么优雅地规避Date对象的方法很多但最让初学者抓狂的绝对是getMonth()。它返回的月份是从0开始计数的也就是说一月返回0十二月返回11。当初设计者是为了对应new Date(year, month, day)构造参数但到了业务层这个设定非常反直觉。我个人的习惯是封装一个小工具函数统一做月份1的转换function getMonthNumber(date) { return date.getMonth() 1; }这样写出来的代码阅读性好很多不至于在业务代码里到处都是 1和- 1的魔法数字。另外getDate()返回的是“几号”getDay()返回的是“星期几”周日是0这两个名字太像了我见过不下十个同事在这里写错过。我的建议是遇到“几号”就用getDate遇到“星期几”就用getDay两者根本没有可比性。2.2 用原生方法拼出“YYYY-MM-DD HH:mm:ss”的通用写法现在很多项目的日期格式化都交给dayjs或者date-fns来做但如果只是拼一个字符串模板手写也就两三行的事情function formatDateTime(date) { const y date.getFullYear(); const m String(date.getMonth() 1).padStart(2, 0); const d String(date.getDate()).padStart(2, 0); const hh String(date.getHours()).padStart(2, 0); const mm String(date.getMinutes()).padStart(2, 0); const ss String(date.getSeconds()).padStart(2, 0); return ${y}-${m}-${d} ${hh}:${mm}:${ss}; }这里用padStart(2, 0)做补零比传统写法里的三元判断(m 10 ? 0 m : m)要清爽太多。如果你担心ES2017的padStart语法在旧浏览器上不兼容可以自己写一个简单补零函数function padZero(num, len 2) { return String(num).padStart(len, 0); }我在做报表导出的时候经常需要把日期转成表格里的字符串格式这个方法几乎是万能的。只要注意一点业务上到底需要“本地时间”还是“UTC时间”。如果拿getUTCFullYear、getUTCMonth这一套去格式化输出的结果和本地的getFullYear这一套相差8个小时尤其在跨时区的项目中这个区分至关重要。2.3 toISOString和toLocaleString的适用边界toISOString()会返回一个标准ISO格式的字符串比如2023-07-01T08:00:00.000Z末尾的Z表示UTC时间。这个格式非常适合传给后端因为它自带了时区信息不会产生歧义。但它也有一个缺点显示出来的是UTC时间不是本地时间。如果你直接把它扔给用户看用户会发现自己本来中午12点提交的数据显示成了凌晨4点。toLocaleString()则相反它会按用户本地的时区和语言习惯输出字符串。看代码的话看着很友好但坑在于不同浏览器甚至不同操作系统输出格式可能不一样。Chrome里是2023/7/1 08:00:00Safari里可能是2023年7月1日 08:00:00。这种不确定性在自动化测试里是灾难。所以我的经验是存数据和传参优先用getTime()毫秒数或toISOString()。展示给用户优先用Intl.DateTimeFormat后面会说或你自己拼的模板字符串。日志打印直接用toLocaleString()其实没问题自己能看懂就行。3. 时区问题为什么你的时间在用户电脑上“变了”聊到Date对象不可避免要聊时区。很多人写代码时完全不考虑时区直到某天发现海外用户看到的日期和国内用户不一样才开始痛苦地排查。这个问题的根源在于Date对象本身只存储了一个UTC毫秒数而所有本地的getFullYear、getMonth、getHours等方法都是按照运行环境所在的时区去解析的。举个例子你存了一个时间戳1688140800000在中国UTC8它对应的是2023年7月1日08:00:00但在英国UTC0它对应的是2023年7月1日00:00:00。同一个数字不同时区的人看到的时间文字完全不同。这个问题解决起来有几种思路3.1 纯前端展示场景让浏览器按本地时区渲染绝大多数情况下我们只需要“用户在哪个时区就按哪个时区展示”。这时根本不需要做任何转换直接new Date(timestamp)然后用本地时间方法去渲染即可。浏览器的运行环境会帮我们处理好一切。比如一个活动倒计时后端把活动结束时间存成UTC时间戳前端只需const endTime new Date(serverTimestamp); const now new Date(); const diff endTime - now;这样拿到的diff是毫秒差不需要关心时区。倒计时本身也不涉及“年月日”的文字显示只涉及小时分钟秒天然免疫时区问题。3.2 固定时区的会议/排期场景需要用到偏移量或指定时区库但如果你做的是一个“全球统一的直播排期表”比如无论用户在哪里都希望看到“北京时间20:00开播”那就不能依赖浏览器本地时区了。这时有几个方案后端在返回时间字段时直接带上时区偏移前端用偏移量做手动换算。前端直接用dayjs配合utc插件把指定时区的时间格式化输出。用Luxon库的fromObject配合zone参数直接指定时区。我实际项目中用得比较多的是Luxon的DateTime.fromISO配合setZone因为它在处理夏令时上比手动算偏移靠谱得多。这里特别提醒一下夏令时不是一个可以靠“固定偏移量”解决的问题它会因为地区和日期而变化。如果你自己写偏移量调整逻辑大概率会在10月某个周日凌晨踩坑。3.3 toLocaleString里的timeZone参数toLocaleString还有一种高级用法可以指定输出哪个时区的时间new Date().toLocaleString(zh-CN, { timeZone: Asia/Shanghai }); new Date().toLocaleString(zh-CN, { timeZone: America/New_York });这个API非常强大它会根据你指定的时区去计算时间而不是受运行环境影响。我在做后台国际化的时候经常用它来调试“某个时区当前几点”这种问题。唯一的注意点是timeZone参数必须是IANA时区标识符不是简单的“UTC8”这种写法写错了会直接抛异常。4. 解析字符串的那些坑new Date(...)并不总是那么听话用字符串构造Date是日常操作但这里隐藏着好几个坑。4.1 连字符“-”与斜杠“/”在不同浏览器里的差异先给个结论new Date(2023-07-01 08:00:00)这种带空格和连字符的写法在Safari里大概率返回Invalid Date但在Chrome里能正常工作。这个差异其实很久了而且至今仍然存在。为什么因为ECMA规范只要求实现YYYY-MM-DDTHH:mm:ss.sssZ这种ISO 8601格式而YYYY-MM-DD HH:mm:ss是各种浏览器厂商自己额外支持的。Chrome宽宏大量地把它当成本地时间解析了Safari则比较严格直接不认。所以凡是把后端传来的“2023-07-01 08:00:00”直接new Date()的代码都有潜在兼容性风险。我的建议是遇到这种格式先做个正则替换const raw 2023-07-01 08:00:00; const safe raw.replace(/-/g, /); const date new Date(safe);替换成斜杠之后各浏览器都能正确解析了。这个土办法虽然看着不太高级但在传统项目里救了我很多次。4.2 ISO字符串末尾的Z代表什么当后端返回一个类似2023-07-01T08:00:00.000Z的字符串时很多前端朋友会忽略末尾的Z。这个Z表示“UTC时间”如果没有Z按ISO规范会被解析为“本地时间”。这两种解析结果最多能差出十几个小时。我记得有次对接第三方接口后端返回的是2023-07-01T00:00:00没有Z前端直接new Date()解析把客户存的“当天零点”当成了“当天零点”看着没什么问题。但客户在海外他实际的“当天零点”和UTC零点并不是一回事。最后还是我通过对比数据库中时间戳的偏移量才定位到问题。当你看到不带时区标识的ISO字符串时第一反应应该是去确认它到底代表哪个时区的时间而不是直接拿去解析。4.3 为什么不建议裸用new Date()去比较两个字符串时间还有一类常见写法是if (new Date(start) new Date(end)) { ... }如果你确定start和end都是能被稳健解析的格式那没什么问题。但如果你不能保证来源格式统一我强烈建议先解析成时间戳再做比较const startTs parseDateSafe(start); const endTs parseDateSafe(end); if (startTs endTs) { ... }这样即使其中某个字符串解析失败也能通过安全函数返回一个NaN或者特殊值不至于让整个逻辑在静默中跑偏。我之前就是因为贪图省事用裸的new Date()去比较两个由不同部门接口返回的时间结果其中一个字段带上了时区后缀、另一个没带最后比较结果完全反了。5. 时间戳、valueOf和原型方法那些面试官爱问但日常没人讲的细节Date的原型链上挂了一堆方法但其中有几个很容易被忽略细究起来却很有意思。5.1 valueOf与getTime的关系Date.prototype.valueOf()返回的其实就是内部毫秒数和getTime()完全等价。但为什么会有两个方法因为valueOf是JS所有对象都有的一个约定方法它定义了对象在做隐式类型转换时怎么变成基本类型。Date自己重写了valueOf让它返回毫秒数。这个重写带来了一个非常实用的效果Date对象做减法运算时会隐式调用valueOf得到毫秒数。所以date2 - date1的结果是一个数字而不是一个字符串或对象const d1 new Date(2023, 0, 1); const d2 new Date(2023, 0, 2); console.log(d2 - d1); // 86400000但如果用date2 date1结果就是拼起来的字符串了。因为加法运算符在遇到对象时会先调用valueOf但valueOf返回的是数字加法就直接把两个数字相加了结果是毫秒数之和。这个特性既方便又容易引发误解我记得以前排查过一个bug就是某位同事拿两个时间做加法想“合并时间”结果得到了一个巨大的数字。5.2 比较时间戳而不是比较字符串判断两个日期是否同一天不要直接比较“年月日字符串”更不要逐字段去getFullYear、getMonth、getDate三个分别比这样代码又长又容易在某个月份边界出错。最稳的做法是取一天的开始时间戳来比function isSameDay(d1, d2) { return new Date(d1.getFullYear(), d1.getMonth(), d1.getDate()).getTime() new Date(d2.getFullYear(), d2.getMonth(), d2.getDate()).getTime(); }原理是用本地时区把两个日期归一到当天零点然后比较零点的时间戳。这样无论d1和d2的时分秒差多少只要还在同一天结果就是true。这个方法我在写日历组件、签到功能的时候用了无数次。5.3 valueOf和时间戳的加减法还有一个小技巧如果你想给某个时间加一天最简洁的写法不是date.setDate(date.getDate() 1)而是直接在时间戳上加毫秒数const tomorrow new Date(date.getTime() 24 * 60 * 60 * 1000);这两种方式效果一样但后者在“只关心时间数值不关心本地时区”的场景下更不容易出错。比如计算两个时间戳之间隔了多少天const diffDays Math.floor((d2 - d1) / (24 * 60 * 60 * 1000));这里直接用毫秒差除以一天的毫秒数简洁明了。6. 业务场景实操从签到、倒计时到图表统计Date的真实用法6.1 签到连续天数统计做签到功能时最核心的一个判断是“用户今天是否已经签到过”。这个逻辑说白了就是比对“最后签到日期”是否等于“今天”。function hasCheckedToday(lastCheckTimestamp) { if (!lastCheckTimestamp) return false; const lastDate new Date(lastCheckTimestamp); const today new Date(); return isSameDay(lastDate, today); }如果你后端存的是一个“纯日期字符串”比如2023-07-01那你要注意它可能是UTC日期也可能是本地日期两者在极端情况下会差异。我在用Java后端的时候遇到过类似的后端用LocalDate.now()生成的日期字符串前端直接new Date(2023-07-01)解析得到的是北京时间上午8点。如果用户在当天0点到8点之间签到比较结果永远对不上。后来后端统一返LocalDateTime的毫秒时间戳这个问题才彻底终结。6.2 图表统计里按日/月分桶做数据报表时经常会遇到“按天统计PV/UV”的需求。最简单的方式是把每个事件的时间戳取toDateString()或者getFullYear() - getMonth() - getDate()作为分桶键const bucketKey ${d.getFullYear()}-${padZero(d.getMonth() 1)}-${padZero(d.getDate())};这个键在Map里做累加非常合适。需要注意的是如果你的项目是跨时区的比如服务端在新加坡、用户在欧洲那么按“本地日期”分桶和按“UTC日期”分桶的结果会不一样。有些统计报表需要统一按某个固定时区分桶这时就要用getUTCFullYear这一套方法。我记得有次给客户做欧洲访问统计如果按北京时间分桶每天的高峰时段会被切到第二天的凌晨整个曲线看起来非常诡异后来统一改成UTC日期分桶才恢复正常。6.3 倒计时与超时判断倒计时本质上就是不断去计算“目标时间戳 - 当前时间戳”的差值。但有一个细节容易被忽略setInterval并不是精确的它可能会因为浏览器标签页被切到后台而节流导致倒计时越来越不准。我的做法是在每次render间隔里都用Date.now()重新计算真实剩余时间而不是用上一个时间点减去固定的间隔。比如let remainMs targetTime - Date.now(); setInterval(() { remainMs targetTime - Date.now(); // 重新渲染 }, 1000);这样即使setInterval被拖到两三秒才执行一次渲染出来的剩余时间也不会累积误差。超时判断也是同样的道理判断用户是否登录超时最快的方式就是const isExpired Date.now() expireTimestamp;千万不要去解析expireTimestamp字符串再比较纯属多绕一圈。6.4 日期选择器里的禁用逻辑做日期选择器时禁用过去日期、限制未来时间这些需求核心也就是一次时间戳比较。比如禁用早于今天的日期const todayStart new Date(); todayStart.setHours(0, 0, 0, 0); const disabled date todayStart;这里要注意setHours(0, 0, 0, 0)会把时分秒都清零拿到当天的开始时间戳。如果不做清零直接用当前时间比较那么“今天”的所有时间点都会被判定成过去用户就没法选今天了。7. 数据持久化和类型转换这可能是你踩过最深的坑7.1 JSON序列化后Date变成了字符串你知道JSON.stringify一个含Date对象的对象时Date会被转成什么吗答案是ISO格式字符串。比如JSON.stringify({ time: new Date() }); // {time:2023-07-01T08:00:00.000Z}这本身没什么但如果后端把这个字符串存到数据库下次返回给前端时前端if判断里可能就会把它当成字符串而不是Date。这时候再做new Date(str)当然可以转换回来但如果你忘了转换直接拿它和new Date()比较结果永远是false。所以我的经验是在后端接口返回时间字段时尽量统一返回毫秒时间戳。即使返回字符串也必须是带时区信息的ISO格式。最怕的就是“看起来是字符串其实是Date实际上又是字符串”这种不确定性。7.2 从localStorage读时间戳的注意点localStorage里只能存字符串所以你存date.getTime()时实际存的是字符串形式的数字。读取出来之后如果你直接拿去做加法会得到字符串拼接而不是数字相加const saved localStorage.getItem(lastTime); // 1690000000000 const diff saved - Date.now(); // 啊这里居然是数值运算没问题加法和减法在JS里的表现不同。减法会转成数字加法会转成字符串。这个特性真的坑死过很多人。安全起见读取时主动转一下类型const saved Number(localStorage.getItem(lastTime)) || 0;7.3 new Date()参数个数不同行为不同但别混淆new Date(2023, 6, 1)传三个数字按本地时间解析new Date(2023-07-01)传字符串按UTC解析new Date(2023/07/01)传带斜杠的字符串在不同浏览器下按本地时间解析。同一个2023年7月1日算出来的时间戳可能差8小时。这类问题我在面试时经常拿来考候选人几乎十有八九答不对。如果你确实需要“本地时间”的效果最明确的方式是function localDate(year, month, day) { return new Date(year, month - 1, day); }月份记得减一因为Date构造参数里月份从0开始。这样一行代码就能消除歧义。8. 原生日期格式化方案Intl.DateTimeFormat与toLocaleString的深入对比8.1 Intl.DateTimeFormat的基本使用如果你不想引入dayjs这类库又想要可靠的本地化日期输出Intl.DateTimeFormat是很好的选择const formatter new Intl.DateTimeFormat(zh-CN, { year: numeric, month: 2-digit, day: 2-digit, hour: 2-digit, minute: 2-digit, second: 2-digit, hour12: false }); formatter.format(new Date());这段代码会输出2023/07/01 08:00:00这种格式具体分隔符和显示方式由浏览器根据语言环境决定。你用en-US会得到07/01/2023, 08:00:00用de-DE会得到01.07.2023, 08:00:00。对于多语言站点的全局时间显示来说非常方便。8.2 为什么我不推荐直接用date.toLocaleString()拼字符串虽然toLocaleString()也能输出类似效果但它有几个问题不同浏览器默认的分隔符不一致。部分浏览器会忽略你传入的hour12参数。拼出来的字符串不好做进一步解析。而Intl.DateTimeFormat的格式化结果是基于相对稳定的规则生成的在大多数现代浏览器里输出差异较小。当然它也不是100%一致但对于展示用途完全够用。8.3 把Intl.DateTimeFormat封装成全局日期格式化方法我在项目里通常会做一个统一的dateUtilconst dateFormatters { full: new Intl.DateTimeFormat(zh-CN, { year: numeric, month: 2-digit, day: 2-digit, hour: 2-digit, minute: 2-digit, second: 2-digit, hour12: false }), date: new Intl.DateTimeFormat(zh-CN, { year: numeric, month: 2-digit, day: 2-digit }) }; function formatFull(date) { return dateFormatters.full.format(date); } function formatDate(date) { return dateFormatters.date.format(date); }这样做的好处是formatter实例只需要创建一次后续直接复用性能好很多。如果你每次都调Intl.DateTimeFormat创建新实例在高频渲染场景下会白白浪费不少CPU。我当时在一个图表库里做tooltip的日期显示刚开始没优化一天下来肉眼可见地卡后来用这种复用方式做了近一倍压测优化。9. 我踩过的Date相关的坑和排查思路这一节我把自己这些年工作中真正踩过的坑罗列一下每个都有一个清晰的复现场景方便你对照。9.1 “2023-07-01 08:00:00”在iOS里的Invalid Date这个坑已经提过一次但值得重点强调。之前做移动端H5后端返回预约时间字段格式是2023-07-01 08:00:00iOS Safari上直接显示Invalid Date安卓上一切正常。我当初排查了半天最终用replace(/-/g, /)解决了。这个问题的根因在于iOS上的JavaScriptCore引擎对非标准的日期时间字符串支持得极其有限它基本只认ISO 8601。而Android上的V8引擎则宽容得多甚至能解析英文月份的日期。所以在所有涉及“从字符串转Date”的前端代码里最好统一先做一次规范化处理不要指望浏览器会原谅你的不严谨。9.2 用getMonth做月份累加时跨年出错有一次做会员到期时间计算需求是“当前月份加3个月”我直接写const month date.getMonth() 3; const newDate new Date(date.getFullYear(), month, date.getDate());如果当前是11月getMonth()是10加3得到13然后传给new Date(year, 13, date.getDate())。神奇的是Date构造器会自己进位把13月当作下一年的1月。看起来好像没问题但如果当前是1月31日加1个月后应该是2月29日或2月28日可new Date(2023, 1, 31)会解析成3月3日。这又带来了一个“溢出”坑。处理月加减业务时要么先转到月底最后一天再算要么直接依赖dayjs的add(3, month)因为dayjs内部处理了月末的修正逻辑。你要自己做也不是不行但一定要先理解“溢出”行为。9.3 当成UTC解析的午夜时间串还有一种很隐蔽的坑后端返回一个日期字符串2023-07-01没有时间部分。在ISO规范里没有时间部分的日期字符串会被视为UTC午夜。前端如果直接new Date(2023-07-01)在中国时区会得到2023-07-01 08:00:00本地时间。结果当你显示“开始日期”时会发现用户在7月1日凌晨看的时候系统告诉他“还没到7月1号”。这是因为8点才会进入“本地7月1日”。如果你希望2023-07-01被解释为“本地日期的零点”比较稳妥的方式是const parts raw.split(-); const date new Date(parts[0], parts[1] - 1, parts[2]);这种拆分再拼接的方式虽然繁琐却能准确表达“我是想按本地时间创建这个日期”的意图。9.4 toISOString在不同时区会输出不同日期还有一个容易混淆的点toISOString()输出UTC时间但因为UTC和本地时间存在时差同一个“本地日期”在不同时区转成ISO字符串时日期字段可能不一样。比如在北京时间2023年7月1日上午8点new Date().toISOString()出来是2023-07-01T00:00:00.000Z日期还是7月1日。但如果现在是北京时间晚上20点toISOString()出来的会是2023-07-01T12:00:00.000Z也是7月1日没问题。可如果把时间再往后推到北京时间7月2日凌晨1点toISOString()出来的日期就是2023-07-01T17:00:00.000Z变成了7月1日。这个现象在只看日期不看时分秒的业务里很容易产生误解。我有次做国际订单统计按toISOString().slice(0, 10)做日期分组结果订单在UTC日期和本地日期之间反复横跳最后看后台数据才反应过来。9.5 setInterval与倒计时精度问题前面提过setInterval不精确这里再补充一个排查实例。某次活动页倒计时总是慢半拍用户反馈“明明到点了我还能抢”。排查后发现页面在后台标签页时浏览器会降低定时器的执行频率。而我的代码是在每次setInterval回调里减固定值所以进度落后了。修复方式就是每次都用Date.now()重新计算目标时间与当前时间的差值不依赖定时器的累积计数。这种写法在性能上没有任何负担因为Date.now()非常便宜而且不受时区影响。10. 推荐工具库与选型思考到底该不该引入dayjs聊了很多原生Date的用法最后说说工具库选型。现在前端生态里最常用的日期库无非就是dayjs、date-fns、moment.js和luxon。moment.js已经进入维护模式不再推荐新项目使用。date-fns按函数式风格导出对tree-shaking友好。dayjs则是轻量、插件化的典型代表。luxon则是在时区处理上最强的。10.1 我的实际选型经验如果项目只是简单地格式化、加减日期、比较日期我用dayjs就足够了安装体积小很多API也非常接近原生习惯import dayjs from dayjs; dayjs().format(YYYY-MM-DD HH:mm:ss); dayjs(2023-07-01).add(1, day).format(YYYY-MM-DD);如果项目里有强时区需求比如全球排期、夏令时切换我更倾向于用luxon因为它原生支持IANA时区不需要额外的插件。如果你的项目里只是零星几处需要日期格式化完全不需要引库直接封装几个函数就够用了。为了一个format函数去增加好几个依赖性价比其实很低。10.2 工具库不能解决所有问题无论用什么库底层仍是对Date对象的封装它依然受制于Date本身的解析差异和时区行为。也就是说当你写dayjs(2023-07-01 08:00:00)时它在iOS上同样可能解析失败因为dayjs的内部也是调用了new Date()。所以即使引了库输入数据的规范化和统一时区处理仍然是必修课。我的几点实操建议最后不在末尾放那种“综上所述”的大道理只说我个人的习惯第一写任何和日期相关的工具函数之前先明确你要处理的是“本地时间”还是“UTC时间”。这个定了后面的代码基本不会出大问题。第二凡是用户输入的日期字符串一律先规范成“YYYY/MM/DD HH:mm:ss”这种能被所有浏览器稳定解析的格式再传给new Date()。这个操作虽然土但能省下很多兼容性排查的时间。第三凡是后端返回的时间字段如果可能让后端给毫秒时间戳。联调的时候争取把这件事定好后面断言、比较、存储都会清爽很多。第四如果你的项目对时间要求比较苛刻比如倒计时、定时任务核心逻辑直接用Date.now()和时间戳计算不要绕来绕去做字符串拼接。JS的Date对象就是这样一个东西看起来简单到一句话能说完真正用起来却有无穷细节。你把它当成“一个存着毫秒数的对象”去理解很多坑都能从根上避开。希望这篇笔记能帮你少踩几个我已经踩过的坑。
返回列表