消除 Javascript 夏令时差距,一种跨浏览器的解决方案
经过一番折腾,我终于找到了真正的问题.这是夏令时造成的差距,如果时区设置为 UTC+3:30(我不确定其他时区),不同浏览器的行为会有所不同.
After lots of hassle I finally found the actual problem. It's the gap induced by daylight saving and the fact that different browsers act differently if timezone is set on UTC+3:30 (I'm not sure of other timezones).
这是产生问题的片段(如果您的系统的 TZ 设置为 UTC+3:30,则该问题是可重现的):
Here's a snippet to generate the problem (the problem is reproducible if your system's TZ is set to UTC+3:30):
function zeroPad(n) {
n = n + '';
return n.length >= 2 ? n : new Array(2 - n.length + 1).join('0') + n;
}
document.write("<table border='1' cellpadding='3'><tr><td>Input date</td><td>Parsed timestamp</td><td>Output date</td></tr>");
var m = 22 * 60;
for (var i=0; i<8; i++) {
var input = "3/21/2015 " + zeroPad(Math.floor(m / 60)) + ":" + zeroPad(m % 60) + ":00";
var d = new Date(input);
var output = d.getFullYear()
+'-'+zeroPad(d.getMonth()+1)
+'-'+zeroPad(d.getDate())
+' '+zeroPad(d.getHours())
+':'+zeroPad(d.getMinutes())
+':'+zeroPad(d.getSeconds());
document.write("<tr><td>" + input + "</td><td>" + d.getTime() + "</td><td>" + output + "</td></tr>");
m = m + 15;
}
m = 0;
for (var i=0; i<7; i++) {
var input = "3/22/2015 " + zeroPad(Math.floor(m / 60)) + ":" + zeroPad(m % 60) + ":00";
var d = new Date(input);
var output = d.getFullYear()
+'-'+zeroPad(d.getMonth()+1)
+'-'+zeroPad(d.getDate())
+' '+zeroPad(d.getHours())
+':'+zeroPad(d.getMinutes())
+':'+zeroPad(d.getSeconds());
document.write("<tr><td>" + input + "</td><td>" + d.getTime() + "</td><td>" + output + "</td></tr>");
m = m + 15;
}
document.write("</table>");
我在 Firefox 和 Chromium 上运行过它,他们是这样说的:
I've run it on Firefox and Chromium and here are what they say:
红框内的部分是差距发生的时间范围.我的问题是,像日历这样的组件通常依赖于时间部分设置为00:00:00"的日期对象,并且它们有一个循环,通过在前一个日期添加一天的时间戳来生成新日期.因此,一旦一个对象落入 3/22/2015 00:00:00
它将被视为 3/22/2015 01:00:00
或 21/3/2015 23:00:00
(取决于浏览器),因此生成的日期将从该时间点开始失效!
The parts within the red boxes are the range of times in which the gap happens. My problem is that components like calendars usually depend on date objects with time part set to "00:00:00" and they've got a loop generating new dates by adding a day worth of timestamp to the previous date. So once an object falls into 3/22/2015 00:00:00
it will be considered 3/22/2015 01:00:00
or 21/3/2015 23:00:00
(depending on the browser) and hence the generated dates will be invalid from that point of time forth!
问题是如何检测此类日期对象以及如何处理它们?
The question is how to detect such date objects and how to treat them?
推荐答案
使用 moment.js 可以省去很多麻烦,而且是最简单的为这类事情实现跨浏览器兼容性的方法.
Using moment.js will save you lots of headache, and is the easiest way to achieve cross-browser compatibility for this sort of thing.
var m = moment.utc("3/22/2015","M/D/YYYY")
var s = m.format("YYYY-MM-DD HH:mm:ss")
为此使用 UTC 很重要,因为您不想受到用户时区的影响.否则,如果您的日期进入 DST 转换,则可以将其调整为其他值.(你真的对 UTC 不感兴趣,你只是为了稳定而使用它.)
Using UTC for this is important since you don't want to be affected by the user's time zone. Otherwise, if your date fell into a DST transition, it could be adjusted to some other value. (You're not really intersted in UTC, you're just using it for stability.)
针对您更新问题的这一部分:
In response to this part of your updated question:
为了简化问题,我正在寻找这样的函数:
To simplify the question, I'm looking for a function like this:
function date_decomposition(d) {
...
}
console.log(date_decomposition(new Date("3/22/2015 00:00:00")));
=> [2015, 3, 22, 0, 0, 0]
虽然现在很清楚您的要求是什么,但您必须了解不可能达到您的确切要求,至少不能以跨浏览器、跨区域、跨时区的方式实现.
While it's now clear what you are asking for, you must understand it is not possible to achieve your exact requirements, at least not in a cross-browser, cross-region, cross-timezone manner.
每个浏览器都有自己的实现字符串到日期解析的方式.当您使用构造函数
new Date(string)
或Date.parse(string)
方法时,您调用的是 特定于实现的功能.
Each browser has it's own way of implementing the string-to-date parsing. When you use either the constructor
new Date(string)
or theDate.parse(string)
method, you're invoking functionality that is implementation specific.
这里有一张图表显示了许多格式差异.
即使实施在所有环境中都是一致的,您也需要应对区域格式和时区差异.
Even if the implementation were consistent across all environments, you'd have regional formatting and time zone differences to contend with.
如果出现区域格式问题,请考虑
01/02/2015
.一些地区使用mm/dd/yyyy
排序并将其视为 1 月 2 日,而其他地区使用dd/mm/yyyy
排序并将其视为 2 月 1 日.(此外,世界上的某些地方使用yyyy/mm/dd
格式.)
In the case of regional formatting issues, consider
01/02/2015
. Some regions usemm/dd/yyyy
ordering and will treat this as January 2nd, while other regions usedd/mm/yyyy
ordering and will treat this as February 1st. (Also, some parts of the world useyyyy/mm/dd
formatting.)
维基百科有一份世界各地使用不同日期格式的列表和地图.
对于时区,请考虑 10 月 19 日,2014 年午夜 (00:00) 在巴西 不存在,并且 2014 年 11 月 2 日午夜 (00:00) 在古巴存在两次.
In the case of time zones, consider that October 19th, 2014 at Midnight (00:00) in Brazil did not exist, and November 2nd, 2014 at Midnight (00:00) in Cuba existed twice.
同样的事情发生在不同时区的其他日期和时间.根据您提供的信息,我可以推断您在伊朗时区,标准时间使用 UTC+03:30,白天使用 UTC+04:30.事实上,2105 年 3 月 22 日午夜 (00:00)不存在 在伊朗.
The same thing happens on other dates and times in different time zones. From the information you provided, I can deduce that you are in Iran time zone, which uses UTC+03:30 during standard time, and UTC+04:30 during daylight time. Indeed, March 22, 2105 at Midnight (00:00) did not exist in Iran.
当您尝试解析这些无效或不明确的值时,每个浏览器都有自己的行为,并且浏览器之间确实存在差异.
When you try to parse these invalid or ambiguous values, each browser has its own behavior, and there indeed differences between the browsers.
对于无效时间,一些浏览器会向前跳一个小时,而其他浏览器会向后跳一个小时.
For invalid times, some browsers will jump forward an hour, while others will jump backwards an hour.
对于模棱两可的时间,一些浏览器会假设您指的是第一个(白天)实例,而其他浏览器会假设您指的是第二个(标准时间)实例.
For ambiguous times, some browsers will assume you meant the first (daylight-time) instance, while others will assume you meant the second (standard-time) instance.
现在说了这么多,你当然可以取一个 Date
object 并解构它的各个部分,非常简单:
Now with all of that said, you can certainly take a Date
object and deconstruct its parts, quite simply:
function date_decomposition(d) {
return [d.getFullYear(), d.getMonth()+1, d.getDate(),
d.getHours(), d.getMinutes(), d.getSeconds()];
}
但这将始终基于代码运行的本地时区.您会看到,在 Date
对象内部,只有一个值 - 一个表示自 1970-01-01T00:00:00Z
以来经过的毫秒数(不考虑闰秒)).该数字基于 UTC.
But this will always be based on the local time zone where the code is running. You see, inside the Date
object, there is just one value - a number representing the elapsed milliseconds since 1970-01-01T00:00:00Z
(without leap seconds being considered). That number is UTC-based.
因此,总而言之,您遇到的所有问题都与将字符串解析为 Date
对象的方式有关.再多的关注输出函数都不会帮助您以完全安全的方式解决这个问题.无论您使用库还是编写自己的代码,都需要获取原始数据字符串才能获得所需的结果.当它在 Date
对象中时,您已经丢失了完成这项工作所需的信息.
So, in recap, all of the issues you are having are related to the way the string was parsed into the Date
object to begin with. No amount of focusing on the output functions will help you to resolve that in a completely safe manner. Whether you use a library or write your own code, you'll need to obtain that original string of data to get the result you are looking for. By the time it's in a Date
object, you've lost the information you need to make this work.
顺便说一句,您可以考虑观看我的 Pluralsight 课程,日期和时间基础知识,其中涵盖其中大部分内容更详细.第 7 单元完全是关于 JavaScript 和这些问题的.
By the way, you might consider watching my Pluralsight course, Date and Time Fundamentals, which covers much of this in even greater detail. Module 7 is entirely about JavaScript and these sorts of gotchas.
相关文章