JavaScript Temporal sắp ra mắt
(developer.mozilla.org)- Đối tượng Temporal mới của JavaScript bắt đầu xuất hiện trong các bản phát hành thử nghiệm của trình duyệt; đây là thay đổi nhằm xử lý ngày, giờ, khoảng thời gian và lịch chính xác, nhất quán hơn bằng API tích hợp sẵn
Datehiện tại khiến xử lý múi giờ và gỡ lỗi trở nên khó khăn do cấu trúc xoay quanh giờ cục bộ của người dùng và UTC, cơ chế phân tích cú pháp không ổn định, cùng hành vi mutable- Temporal tách bạch thời điểm, giờ địa phương và khoảng thời gian bằng các kiểu như
Temporal.Instant,Temporal.ZonedDateTime,Temporal.PlainDateTime,Temporal.Duration,Temporal.Now, đồng thời cung cấp hơn 200 phương thức tiện ích - Vẫn cần thêm thời gian để có hỗ trợ ổn định trên nhiều trình duyệt; trên Firefox Nightly có thể thử Temporal sau thiết lập
javascript.options.experimental.temporal - Tuần này MDN đã bổ sung hơn 270 trang tài liệu Temporal, và có thể chạy ví dụ mà không cần đổi flag trên trang tài liệu TC39 có
@js-temporal/polyfill
Những hạn chế lâu đời của JavaScript Date
- Khi JavaScript được tạo ra vào năm 1995, đối tượng
Dateđã sao chép triển khaijava.util.Dateban đầu của Java; Java đã thay thế nó vào năm 1997, nhưng JavaScript vẫn duy trì gần như cùng một API trong gần 30 năm - Vấn đề lớn nhất là thiếu hỗ trợ múi giờ và hành vi phân tích cú pháp không ổn định
- Chỉ hỗ trợ giờ cục bộ của người dùng và UTC
- Bản thân
Datelà mutable nên có thể tạo ra các lỗi khó theo dõi - Các phép tính đi qua Daylight Saving Time (DST) và những thay đổi lịch sử của lịch cũng khó xử lý
- Vì những hạn chế này, nhiều nhà phát triển đã dựa vào các thư viện chuyên dụng như Moment.js hoặc date-fns để xử lý ngày và giờ trong ứng dụng
Temporal thay đổi mô hình ngày giờ như thế nào
- Temporal được thiết kế như một sự thay thế toàn diện cho đối tượng
Date, với mục tiêu giúp quản lý ngày giờ đáng tin cậy và dễ dự đoán hơn - Hỗ trợ biểu diễn múi giờ và lịch, đồng thời cung cấp các phương thức tích hợp sẵn để chuyển đổi, so sánh, tính toán và định dạng
- Bề mặt API có hơn 200 phương thức tiện ích, và MDN đã bổ sung phần giải thích cùng ví dụ trong tài liệu Temporal
Các kiểu chính của Temporal
- Temporal phân biệt một thời điểm duy nhất, giờ đồng hồ treo tường tại địa phương và khoảng thời gian thành các kiểu khác nhau
- Các cấu trúc chính như sau
Temporal.Duration: chênh lệch giữa hai thời điểmTemporal.Instant: một thời điểm duy nhất được biểu diễn bằng timestampTemporal.ZonedDateTime: ngày giờ có kèm múi giờTemporal.PlainDateTime: ngày giờ đầy đủ không có múi giờTemporal.PlainDate: chỉ biểu diễn ngàyTemporal.PlainYearMonth: năm và thángTemporal.PlainMonthDay: tháng và ngàyTemporal.PlainTime: chỉ biểu diễn giờ
Temporal.Now: API để lấy thời gian hiện tại dưới dạng instance của nhiều lớp hoặc theo định dạng cụ thể
Cách dùng qua ví dụ
- Có thể lấy ngày giờ hiện tại dưới dạng chuỗi ISO và truyền múi giờ vào nhiều phương thức
Temporal.Now.plainDateTimeISO()trả về ngày giờ hiện tại theo múi giờ hệ thốngTemporal.Now.plainDateTimeISO("America/New_York")trả về ngày giờ hiện tại theo múi giờ New York
- Cũng có thể xử lý các lịch ngoài Gregorian, cho phép tạo ngày trong các hệ lịch như Hebrew, Chinese, Islamic
- Tính Chinese New Year tiếp theo bằng ngày 1 của
M01trong lịch Chinese - Trong kết quả ví dụ tại thời điểm viết bài, Chinese New Year tiếp theo là
1/29/2025
- Tính Chinese New Year tiếp theo bằng ngày 1 của
- Có thể tạo một thời điểm từ timestamp mili giây Unix Epoch bằng
Temporal.Instant.fromEpochMilliseconds(), rồi so sánh vớiTemporal.Now.instant()để tính thời gian còn lại- Với timestamp ví dụ
1851222399924, tính thời gian từ hiện tại bằngsmallestUnit: "hour" - Kết quả từ
@js-temporal/polyfilllà31,600 hr, còn kết quả từ Firefox Nightly làPT31600H
- Với timestamp ví dụ
toLocaleStringtrong triển khai Firefox hiện không xuất ra chuỗi nhạy theo locale- Hành vi này gần với một quyết định thiết kế hơn là giới hạn kỹ thuật, và bản thân việc định dạng duration vẫn khả thi
- Polyfill và triển khai Firefox có thể hội tụ về sau
- Phương thức
compare()có thể dùng để sắp xếp mảng- Dùng
durations.sort(Temporal.Duration.compare)sẽ sắp xếp theo thứ tựPT1H,PT1H30M,PT1H45M,PT2H
- Dùng
Hỗ trợ trình duyệt và cách thử nghiệm
- Hỗ trợ Temporal đang bắt đầu được đưa dần vào các bản phát hành trình duyệt thử nghiệm, và hiện triển khai của Firefox có vẻ trưởng thành nhất
- Trên Firefox, Temporal đang được build trong bản Nightly phía sau thiết lập
javascript.options.experimental.temporal - Các issue trình duyệt chính để theo dõi quá trình triển khai như sau
- Firefox: Build temporal in Nightly by default
- Safari: [JSC] Implement Temporal
- Chrome: Implement the Temporal proposal
- Có thể xem tình trạng tương thích đầy đủ trong phần Browser Compatibility của đối tượng Temporal trên MDN
- Tài liệu Temporal của TC39 có
@js-temporal/polyfill, nên có thể chạy ví dụ trong console của công cụ nhà phát triển trên bất kỳ trình duyệt nào mà không cần đổi flag hay thiết lập
1 bình luận
Ý kiến trên Hacker News
Temporal rất tuyệt. Tôi đã dùng nó trong production một thời gian bằng polyfill, và nó giải quyết được rất nhiều vấn đề tôi từng gặp với API
Date()cũCảm giác như nó lấy cảm hứng từ các thư viện thời gian tốt như
chronocủa Rust hay Joda Time của Java rồi kết hợp lại thành một API dễ dùngNó tách riêng thời gian đơn thuần, thời điểm, và thời gian có kèm múi giờ nên có phần phức tạp hơn, nhưng bản thân thời gian vốn đã phức tạp, nên lập trình viên cần đối diện với sự phức tạp đó
Với những thao tác như “cộng thêm một ngày vào timestamp này”, bạn cũng buộc phải quyết định là theo múi giờ nào; nếu không thì sẽ sinh lỗi do giờ tiết kiệm ánh sáng ban ngày, do người dùng đổi múi giờ, hoặc do chênh lệch múi giờ của máy chủ
Nó cũng xử lý được sự khác biệt khi tuần tự hóa giữa timestamp có offset cố định (
2025-01-01T00:00+02:00) và timestamp theo một múi giờ cụ thể (Europe/Paris)chrono: https://docs.rs/jiff/latest/jiff/_documentation/comparison/i...Cá nhân tôi không nghĩ Temporal chịu ảnh hưởng quá nhiều từ
chronocủa Rust; có vẻ nó bị ảnh hưởng nhiều hơn từ Joda,java.timehiện đại, và các thư viện ngày giờ JavaScript như date-fns hay Moment.jsHọ không muốn đi tra xem
Europe/Parislà chỉ cả một vùng rộng của Pháp hay chỉ riêng Paris, và còn thấy khó chịu vì có cảm giác như đang nói rằng mình sống ở ParisĐiều này từng rất đau đầu khi lên lịch cho các sự kiện cộng đồng trực tuyến. Mọi người muốn dùng offset cố định, và chấp nhận các cách viết như
CET/CEST, nhưng lại không thích kiểuEurope/yourexactcoordinatesThực ra hầu hết múi giờ đều bao phủ khu vực khá rộng, nhưng có rất nhiều người vừa quan tâm đến quyền riêng tư lại vừa không định tìm hiểu sự khác biệt giữa “thời gian” và “múi giờ”
chronovà Joda Time là sản phẩm của hai thời đại khác nhau. Khi Rust 1.0 ra mắt thì Java đã triển khai cách tiếp cận này trong java.time của thư viện chuẩn, và không cần thư viện bên thứ ba cho mục đích này nữaTôi mất khá nhiều thời gian mới tìm được trong tài liệu rằng Temporal.ZonedDateTime.prototype.withTimeZone() dùng để chuyển đổi từ múi giờ này sang múi giờ khác: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
Nếu tạo giá trị bằng
Temporal.ZonedDateTime.from("2021-08-01T12:00[America/New_York]")rồi truyền vàowithTimeZone("Europe/Paris"), thì sẽ nhận được2021-08-01T18:00:00+02:00[Europe/Paris]Chuyển đổi múi giờ và xử lý khoảng thời gian là các tính năng cốt lõi mà thư viện bắt buộc phải xử lý, nên thật tốt khi Temporal làm được cả hai. Tuy vậy, có vẻ
Temporal.Durationkhông có hàmformattùy biến để tạo ra dạng như1h03m, chẳng hạnduration.format('H\hmm\m')1 yr,3 hours,3m: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...Tôi từng triển khai một phần việc này trong thư viện icu4x
Hiện TC39 có khá nhiều đề xuất đang dần chín muồi, và Temporal là một trong những thứ mình rất muốn được phát hành sớm
Mình hiểu rằng để các trình duyệt lớn hỗ trợ tính năng mới thì cần nỗ lực phát triển từ các kỹ sư của v8, JSC và SpiderMonkey, nên thật sự rất biết ơn những người đang tạo ra tiến triển này
Từ góc nhìn sốt ruột thì chỉ muốn cổ vũ kiểu “Làm luôn cả Records và Tuples đi! Thậm chí có thể bỏ qua cuộc chiến cú pháp Pipe cũng được!”
Theo mình hiểu thì các nhà cung cấp trình duyệt hiện đã phản đối khá mạnh việc bổ sung kiểu nguyên thủy mới ở thời điểm này. Độ khó của chuyện cơ chế đồng nhất nên hoạt động ra sao được giải thích trong chuỗi này: https://github.com/tc39/proposal-record-tuple/issues/387
Ban đầu mình không nghĩ BigInt sẽ được dùng nhiều đến vậy, nhưng trên thực tế lại liên tục xuất hiện nhu cầu về các collection có đồng nhất cấu trúc, nên khá tiếc
Mình cũng hiểu là việc phản đối bổ sung kiểu nguyên thủy mới đang ảnh hưởng cả đến pattern matching, nhưng không rõ lý do cụ thể, và mình cũng theo dõi đề xuất đó ít hơn
Pipeline, pattern matching và Records+Tuples đều đã được làm hơn 4 năm mà vẫn chỉ dừng ở giai đoạn 1~2. Trong 1 năm qua hầu như không thấy tiến triển đáng kể nào, ngoại trừ có lẽ là pattern matching
Theo các issue trong kho Records và Tuples thì đã có phản ứng trái chiều về ngữ nghĩa giá trị, tức là liệu
===có thể hoạt động hay không. Nếu bỏ ngữ nghĩa giá trị thì tính hữu ích của đề xuất giảm đi rất nhiềuĐể chúng lên được giai đoạn 3 có lẽ còn cần ít nhất 1~2 năm nữa, hoặc thậm chí có thể không bao giờ tới được. Dù vậy thì ít ra cũng đã có hàm nhóm mảng
Tiếc là nó vẫn mới ở giai đoạn 1, nên có lẽ còn mất thêm vài năm nữa
Dictionary trong AS3 là một kiểu cho phép dùng tham chiếu đối tượng, chuỗi và số làm khóa duy nhất, rồi gắn giá trị thuộc bất kỳ kiểu nào. Việc garbage collection cũng được xử lý để nó không trở thành tham chiếu yếu miễn là chính đối tượng dictionary đó còn tồn tại
Hãy hình dung như
Setcủa JavaScript, nhưng có thể dùng bất kỳ loại khóa nào đã chỉ định với kiểu mạnh, và sẽ báo lỗi ngay ở thời điểm biên dịch. Mình nhớ ngôn ngữ đóNhững cập nhật đó gần như đã làm hỏng hệ sinh thái JavaScript, và giờ có cảm giác chỉ còn lại các ngôn ngữ biên dịch sang JS. Người vẫn còn dùng vanilla JS lúc này giống như người làm ứng dụng bằng assembly vậy
Mình tò mò không biết dữ liệu múi giờ trong Temporal được giữ cập nhật như thế nào
Có phải TC39 cập nhật dữ liệu ở đâu đó rồi mỗi trình duyệt sẽ sao chép dữ liệu đó vào bên trong và phát hành phiên bản mới không, hay nếu người dùng chưa cập nhật lên bản trình duyệt có dữ liệu mới thì website của mình sẽ hiển thị giờ sai?
Ví dụ, Mexico đã bãi bỏ giờ mùa hè vào năm 2022: https://en.wikipedia.org/wiki/Time_in_Mexico
Với các thư viện bên thứ ba như
pytzhaymoment-timezone, chỉ cần cập nhật thư viện phía server là được, còn nếu xử lý trong thư viện chuẩn thì chỉ cần nâng phiên bản ngôn ngữ; nhưng với Temporal và trình duyệt của khách truy cập thì mình không rõ sẽ thế nàoCó vẻ máy tính của người dùng cũng có thể truy cập cơ sở dữ liệu này và xử lý cập nhật, nhưng mình không chắc. Cũng có thể trình duyệt sẽ tự tải và duy trì cơ sở dữ liệu đó riêng, và có lẽ điều này tùy vào từng cách triển khai
Cơ sở dữ liệu liên quan: https://en.wikipedia.org/wiki/Tz_database
Intl, nên nếu tìm hiểu hướng đó thì có thể sẽ ra câu trả lời tương tựtzdbNếu người dùng không cập nhật trình duyệt hoặc hệ thống để nhận dữ liệu múi giờ mới thì khả năng họ thấy giờ sai nhìn chung là đúng. Tuy nhiên điều này cũng không khác mấy so với thông lệ hiện tại
Phương án khác là mọi ứng dụng đều mang theo
tzdbriêng, và một số ứng dụng đúng là làm như vậy trong những trường hợp hợp lý. Nhưng nhìn chung đó là sự lãng phí, làm tăng bất nhất giữa các ứng dụng, và còn có thể khiến chu kỳ cập nhật chậm hơnCần thêm một sự kiện để phát hiện khi ai đó thay đổi múi giờ
Trong danh sách “những điều sai lầm mà lập trình viên tin về thời gian”, có lẽ cũng nên thêm câu “lập trình viên tin rằng múi giờ đang sử dụng là cố định”. Ngoài đời thực, mỗi ngày có hàng triệu người di chuyển qua các múi giờ
Tất nhiên, nó có thể hữu ích cho các thư viện
Việc
.Nowkhông phải là lời gọi hàm thật sự rất đáng tiếc. Nó không nên là một thuộc tínhNếu đánh giá
.Nownhiều lần trong một vòng lặp, mỗi lần có thể cho ra giá trị khác nhau, điều này khá bất ngờC# cũng đã đưa ra lựa chọn tương tự và lẽ ra không nên làm vậy: https://ericlippert.com/2014/05/19/when-should-i-write-a-pro...
Nowlà namespace của các hàm lấy giá trị thực tế, ví dụ nhưNow.instant()Tôi thật sự rất thích đề xuất Temporal, nhưng có một điểm khiến tôi băn khoăn. Nó dựa vào tính bằng nhau theo tham chiếu khi so sánh
Tức là
Temporal.Instant.from('2020-01-01') != Temporal.Instant.from('2020-01-01')Bản thân điều đó không hẳn là xấu, nhưng về thực tế nó gần như loại bỏ khả năng dùng các đối tượng này làm khóa
Maphoặc gom chúng vàoSet. Tôi hiểu vì sao quyết định đó được đưa ra, nhưng vẫn thấy tiếc vì không làm đượcNếu một ngày nào đó Records và Tuples thực sự xuất hiện, có lẽ sẽ có một phiên bản dựa trên chúng
==cho ra tính bằng nhau không dựa trên tham chiếu không?.epochMillisecondslàm khóa/phần tử choMap/Set. Tuy nhiên nếu cần giữ dữ liệu múi giờ trongMaphoặc cần phép toán cộng thêm trênInstantthì phải giữ lạiInstantgốc ở phía valueVới
Set, có thể dùng kiểuMap{v.epochMilliseconds:v}để vẫn giữ đượcInstantKhông phải cách hay ho gì, nhưng đây là vấn đề nên trách bản thân JavaScript hơn là Temporal. Có thể bắt runtime xử lý việc này, nhưng như vậy thì polyfill sẽ thất bại
Map/Setcũng thường là ý tưởng tồi, khá giống với việc dùng số dấu chấm động trongMap/SetString()hoặc'2020-01-01'?Việc cung cấp hàm so sánh hoạt động cùng các API hiện có là một thiết kế rất vững vàng. Thậm chí có thể coi đây là điều bắt buộc phải đọc khi thêm kiểu mới
Ví dụ, truyền
Temporal.Duration.comparevàosortthì có thể sắp xếp theo thứ tựPT1H,PT1H30M,PT1H45M,PT2H. Xin gửi lời khen tới các nhà thiết kếTôi hiểu cái tên
Temporalnhằm tránh xung đột với các đối tượng thời gian phổ biến như Moment hay Datetime, nhưng cái tên này không hayLần đầu nhìn thấy tôi còn tưởng đó là thứ gì như điều khiển garbage collection
temporalcó nghĩa là liên quan đến thời gian đối lập với tính vĩnh cửu, liên quan đến thì ngữ pháp hay sự phân chia thời gian, hoặc liên quan đến thời gian đối lập với không gian: https://www.merriam-webster.com/dictionary/temporalNghe cũng là một cái tên ổn
Trước hết,
Temporalkhông phải danh từ mà là tính từ. Nó có liên quan đến thời gian nhưng không tạo cảm giác trực quan là phù hợpQuan trọng hơn, nếu chọn những cái tên kỳ quặc để giảm khả năng xung đột với mã cũ, thì theo thời gian ngôn ngữ sẽ ngày càng trở nên khó hiểu hơn
Khi thêm
PromisevàGenerator, cũng đã có nhiều thư viện dùng những tên đối tượng đó rồi. Không giống từ khóa, các đối tượng built-in có thể bị ghi đè nên đó không phải vấn đề lớnTôi cho rằng ủy ban tiêu chuẩn nên kiên quyết dùng
Timevì sức khỏe lâu dài của ngôn ngữDĩ nhiên, cuộc tranh luận này có lẽ đã qua rồi và hẳn các nhà làm trình duyệt cùng những người triển khai JS engine đã đi đến đồng thuận
DateV2còn hơnTôi hiểu cần tương thích ngược, nhưng
Temporalnghe rất gượng.DateTimethậm chí còn không phải là tên có trong chuẩn JS, nên tôi không hiểu vì sao họ không dùng nó. Cho đến lúc đó, chắc tôi sẽ đểconst DateTime = TemporalBài liên quan:
JavaScript Temporal Is Coming - https://news.ycombinator.com/item?id=42809834 - tháng 1 năm 2025, 18 bình luận
Mozilla: Temporal (Limited Availability) - https://news.ycombinator.com/item?id=42776548 - tháng 1 năm 2025, 1 bình luận
Is It Time for the JavaScript Temporal API? - https://news.ycombinator.com/item?id=29712118 - tháng 12 năm 2021, 100 bình luận
Temporal: Getting started with JavaScript's new date time API - https://news.ycombinator.com/item?id=27661667 - tháng 6 năm 2021, 194 bình luận
JavaScript Temporal - https://news.ycombinator.com/item?id=27395236 - tháng 6 năm 2021, 1 bình luận
JavaScript Proposal Temporal for Dates is now stage 3 - https://news.ycombinator.com/item?id=26432346 - tháng 3 năm 2021, 1 bình luận
Câu “con tàu lớn đổi hướng thì chậm” đúng là quá chuẩn