Trong kinh nghiệm gần đây khi viết một trình thông dịch JS, tôi đã vật lộn rất nhiều với các hoạt động bên trong của ngày ECMA / JS. Vì vậy, tôi nghĩ rằng tôi sẽ ném 2 xu của mình vào đây. Hy vọng việc chia sẻ nội dung này sẽ giúp những người khác có bất kỳ câu hỏi nào về sự khác biệt giữa các trình duyệt trong cách họ xử lý ngày.
Phía đầu vào
Tất cả các cài đặt đều lưu trữ giá trị ngày của chúng bên trong dưới dạng số 64 bit đại diện cho số mili giây (ms) kể từ 1970-01-01 UTC (GMT giống với UTC). Ngày này là kỷ nguyên ECMAScript cũng được sử dụng bởi các ngôn ngữ khác như các hệ thống Java và POSIX như UNIX. Ngày xảy ra sau kỷ nguyên là số dương và ngày trước là âm.
Mã sau đây được hiểu là cùng một ngày trong tất cả các trình duyệt hiện tại, nhưng với phần bù múi giờ cục bộ:
Date.parse('1/1/1970'); // 1 January, 1970
Trong múi giờ của tôi (EST, là -05: 00), kết quả là 18000000 vì đó là bao nhiêu ms trong 5 giờ (chỉ 4 giờ trong các tháng tiết kiệm ánh sáng ban ngày). Giá trị sẽ khác nhau ở các múi giờ khác nhau. Hành vi này được chỉ định trong ECMA-262 vì vậy tất cả các trình duyệt thực hiện theo cùng một cách.
Mặc dù có một số phương sai trong các định dạng chuỗi đầu vào mà các trình duyệt chính sẽ phân tích thành ngày, nhưng về cơ bản, chúng diễn giải chúng giống như liên quan đến múi giờ và tiết kiệm ánh sáng ban ngày mặc dù phân tích cú pháp chủ yếu phụ thuộc vào việc thực hiện.
Tuy nhiên, định dạng ISO 8601 là khác nhau. Đây là một trong hai định dạng được nêu trong ECMAScript 2015 (ed 6) cụ thể phải được phân tích cú pháp theo cùng một cách bởi tất cả các triển khai (định dạng còn lại là định dạng được chỉ định cho Date.prototype.toString ).
Nhưng, ngay cả đối với các chuỗi định dạng ISO 8601, một số triển khai đã hiểu sai. Đây là kết quả so sánh của Chrome và Firefox khi câu trả lời này ban đầu được viết cho 1/1/1970 (kỷ nguyên) trên máy của tôi bằng cách sử dụng các chuỗi định dạng ISO 8601 nên được phân tích cú pháp thành chính xác cùng một giá trị trong tất cả các lần triển khai:
Date.parse('1970-01-01T00:00:00Z'); // Chrome: 0 FF: 0
Date.parse('1970-01-01T00:00:00-0500'); // Chrome: 18000000 FF: 18000000
Date.parse('1970-01-01T00:00:00'); // Chrome: 0 FF: 18000000
- Trong trường hợp đầu tiên, bộ xác định "Z" chỉ ra rằng đầu vào ở thời gian UTC nên không được bù từ epoch và kết quả là 0
- Trong trường hợp thứ hai, chỉ định "-0500" chỉ ra rằng đầu vào nằm trong GMT-05: 00 và cả hai trình duyệt diễn giải đầu vào là ở múi giờ -05: 00. Điều đó có nghĩa là giá trị UTC được bù từ kỷ nguyên, có nghĩa là thêm 18000000ms vào giá trị thời gian nội bộ của ngày.
- Trường hợp thứ ba, nơi không có specifier, nên được coi là cục bộ cho hệ thống máy chủ. FF coi chính xác đầu vào là giờ địa phương trong khi Chrome coi nó là UTC, do đó tạo ra các giá trị thời gian khác nhau. Đối với tôi điều này tạo ra sự khác biệt 5 giờ trong giá trị được lưu trữ, đó là vấn đề. Các hệ thống khác có độ lệch khác nhau sẽ nhận được kết quả khác nhau.
Sự khác biệt này đã được sửa chữa vào năm 2020, nhưng các quirks khác tồn tại giữa các trình duyệt khi phân tích chuỗi định dạng ISO 8601.
Nhưng nó trở nên tồi tệ hơn. Một điều khó hiểu của ECMA-262 là định dạng chỉ ngày ISO 8601 (YYYY-MM-DD) được yêu cầu phân tích thành UTC, trong khi ISO 8601 yêu cầu nó phải được phân tích cú pháp thành cục bộ. Đây là đầu ra từ FF với các định dạng ngày ISO dài và ngắn không có chỉ định múi giờ.
Date.parse('1970-01-01T00:00:00'); // 18000000
Date.parse('1970-01-01'); // 0
Vì vậy, lần đầu tiên được phân tích cú pháp là cục bộ vì đó là ngày và thời gian ISO 8601 không có múi giờ và lần thứ hai được phân tích cú pháp là UTC vì đó chỉ là ngày ISO 8601.
Vì vậy, để trả lời trực tiếp câu hỏi ban đầu, "YYYY-MM-DD"
ECMA-262 yêu cầu phải được hiểu là UTC, trong khi câu hỏi còn lại được hiểu là cục bộ. Đó là lý do:
Điều này không tạo ra kết quả tương đương:
console.log(new Date(Date.parse("Jul 8, 2005")).toString()); // Local
console.log(new Date(Date.parse("2005-07-08")).toString()); // UTC
Cái này không
console.log(new Date(Date.parse("Jul 8, 2005")).toString());
console.log(new Date(Date.parse("2005-07-08T00:00:00")).toString());
Dòng dưới cùng là để phân tích chuỗi ngày. Chuỗi CHỈ ISO 8601 mà bạn có thể phân tích cú pháp một cách an toàn trên các trình duyệt là dạng dài có độ lệch (± HH: mm hoặc "Z"). Nếu bạn làm điều đó, bạn có thể quay lại một cách an toàn giữa giờ địa phương và giờ UTC.
Điều này hoạt động trên các trình duyệt (sau IE9):
console.log(new Date(Date.parse("2005-07-08T00:00:00Z")).toString());
Hầu hết các trình duyệt hiện tại đều đối xử bình đẳng với các định dạng đầu vào khác, bao gồm '1/1/1970' (M / D / YYYY) và '1/1/1970 00:00:00 AM' (M / D / YYYY hh : mm: ss ap) định dạng. Tất cả các định dạng sau (ngoại trừ định dạng cuối cùng) được coi là đầu vào thời gian cục bộ trong tất cả các trình duyệt. Đầu ra của mã này giống nhau trong tất cả các trình duyệt trong múi giờ của tôi. Cái cuối cùng được coi là -05: 00 bất kể múi giờ của máy chủ vì phần bù được đặt trong dấu thời gian:
console.log(Date.parse("1/1/1970"));
console.log(Date.parse("1/1/1970 12:00:00 AM"));
console.log(Date.parse("Thu Jan 01 1970"));
console.log(Date.parse("Thu Jan 01 1970 00:00:00"));
console.log(Date.parse("Thu Jan 01 1970 00:00:00 GMT-0500"));
Tuy nhiên, do việc phân tích cú pháp ngay cả các định dạng được chỉ định trong ECMA-262 là không nhất quán, nên không bao giờ dựa vào bộ dữ liệu được xây dựng trong trình phân tích cú pháp và luôn luôn phân tích chuỗi theo cách thủ công, sử dụng thư viện và cung cấp định dạng cho trình phân tích cú pháp.
Ví dụ: trong khoảnh khắc.js bạn có thể viết:
let m = moment('1/1/1970', 'M/D/YYYY');
Phía đầu ra
Về phía đầu ra, tất cả các trình duyệt dịch các múi giờ theo cùng một cách nhưng chúng xử lý các định dạng chuỗi khác nhau. Dưới đây là các toString
chức năng và những gì họ đầu ra. Lưu ý toUTCString
vàtoISOString
chức năng đầu ra 5:00 AM trên máy của tôi. Ngoài ra, tên múi giờ có thể là viết tắt và có thể khác nhau trong các triển khai khác nhau.
Chuyển đổi từ UTC sang Giờ địa phương trước khi in
- toString
- toDateString
- toTimeString
- toLocaleString
- toLocaleDateString
- toLocaleTimeString
In trực tiếp thời gian UTC được lưu trữ
- toUTCString
- toISOString
Trong Chrome
toString Thu Jan 01 1970 00:00:00 GMT-05:00 (Eastern Standard Time)
toDateString Thu Jan 01 1970
toTimeString 00:00:00 GMT-05:00 (Eastern Standard Time)
toLocaleString 1/1/1970 12:00:00 AM
toLocaleDateString 1/1/1970
toLocaleTimeString 00:00:00 AM
toUTCString Thu, 01 Jan 1970 05:00:00 GMT
toISOString 1970-01-01T05:00:00.000Z
Trong Firefox
toString Thu Jan 01 1970 00:00:00 GMT-05:00 (Eastern Standard Time)
toDateString Thu Jan 01 1970
toTimeString 00:00:00 GMT-0500 (Eastern Standard Time)
toLocaleString Thursday, January 01, 1970 12:00:00 AM
toLocaleDateString Thursday, January 01, 1970
toLocaleTimeString 12:00:00 AM
toUTCString Thu, 01 Jan 1970 05:00:00 GMT
toISOString 1970-01-01T05:00:00.000Z
Tôi thường không sử dụng định dạng ISO cho đầu vào chuỗi. Thời gian duy nhất sử dụng định dạng đó có lợi cho tôi là khi ngày cần được sắp xếp thành chuỗi. Định dạng ISO có thể sắp xếp như hiện tại trong khi các định dạng khác thì không. Nếu bạn phải có khả năng tương thích giữa nhiều trình duyệt, hãy chỉ định múi giờ hoặc sử dụng định dạng chuỗi tương thích.
Mã new Date('12/4/2013').toString()
đi qua chuyển đổi giả nội bộ sau đây:
"12/4/2013" -> toUCT -> [storage] -> toLocal -> print "12/4/2013"
Tôi hy vọng câu trả lời này có ích.