- Chỉ ra tính không hoàn chỉnh và thiếu nhất quán của đối tượng Date hiện có trong JavaScript, đồng thời giới thiệu Temporal API sẽ thay thế nó
- Date hoạt động như một đối tượng có thể thay đổi (mutable object), lệch với khái niệm ngày tháng thực tế, đồng thời có các vấn đề cấu trúc như lỗi phân tích cú pháp và giới hạn trong xử lý múi giờ
- Temporal cung cấp mô hình xử lý ngày·giờ mới dựa trên tính bất biến, bao gồm các lớp được phân chia chi tiết như
PlainDate, ZonedDateTime, Duration
- Các phương thức của Temporal không sửa đổi đối tượng hiện có mà trả về đối tượng mới, cho phép phép toán chaining rõ ràng và an toàn
- Temporal hiện đang ở giai đoạn chuẩn hóa 3 (Stage 3) và được hỗ trợ thử nghiệm trên các trình duyệt hiện đại như Chrome và Firefox
Vấn đề của đối tượng Date trong JavaScript
- Hàm khởi tạo
Date gây nhầm lẫn vì quy tắc phân tích cú pháp không nhất quán và đánh chỉ số thiếu trực quan
- Ví dụ: tháng (month) bắt đầu từ 0, nhưng ngày (day) và năm (year) lại bắt đầu từ 1
- Chuỗi
"99" được hiểu là năm 1999, còn "100" lại được hiểu là năm 0100, cho thấy sự thiếu nhất quán
Date được thiết kế xoay quanh thời gian (time) và được lưu trữ nội bộ dưới dạng Unix timestamp (đơn vị mili giây)
- Hỗ trợ múi giờ (time zone) bị hạn chế và không nhận biết được giờ mùa hè (DST) hay lịch phi Gregory
- Vì những giới hạn này, việc phụ thuộc vào các thư viện bên thứ ba lớn như Moment.js, date-fns là chuyện phổ biến, và điều đó dẫn tới suy giảm hiệu năng
Xung đột giữa tính bất biến và khái niệm tham chiếu
- Giá trị nguyên thủy (primitive) trong JavaScript là bất biến và được lưu trực tiếp theo giá trị, còn đối tượng (object) được lưu bằng tham chiếu (reference) nên có thể bị thay đổi
Date là một đối tượng được tạo thông qua constructor, vì vậy nó có thể thay đổi
- Ví dụ: khi gọi
setMonth() hoặc setDate(), đối tượng gốc sẽ bị sửa đổi trực tiếp
- Điều này dẫn đến những thay đổi giá trị ngoài dự kiến giữa các biến cùng tham chi chiếu tới một đối tượng
- Ví dụ: nếu một hàm nhận
today làm đối số và sửa ngày bên trong, thì today gốc cũng sẽ bị thay đổi
Temporal: API ngày·giờ mới
Temporal là namespace object chứ không phải constructor, có cấu trúc tương tự Math
- Các thành phần chính:
PlainDate, PlainDateTime, PlainTime, ZonedDateTime, Duration, Now
Temporal.Now trả về thời điểm hiện tại dưới nhiều dạng khác nhau
plainDateISO() → ngày theo định dạng ISO
zonedDateTimeISO() → thời điểm có bao gồm múi giờ
- Các đối tượng Temporal cung cấp hệ thống phương thức rõ ràng
- Có thể thực hiện phép toán theo đơn vị một cách tường minh như
add({ days: 1 }), subtract({ years: 2 })
- Không sửa đổi đối tượng hiện có mà trả về đối tượng mới, duy trì tính bất biến
Cách Temporal hoạt động và ưu điểm
- Các đối tượng Temporal vẫn là kiểu object, nhưng tuân theo mẫu sử dụng bất biến được thiết kế có chủ đích
- Ví dụ:
today.add({ days: 1 }) sẽ trả về một đối tượng ngày mới, còn today gốc không thay đổi
- Cung cấp cú pháp ngắn gọn và rõ ràng hơn so với
Date
- Phù hợp với các nhu cầu hiện đại như chỉ định múi giờ, tính khoảng thời gian, duy trì định dạng ISO
- Có thể diễn đạt ngắn gọn các phép tính ngày phức tạp thông qua method chaining như
add, subtract, since, until
Tình hình chuẩn hóa và triển vọng sắp tới
Temporal đã đạt tới giai đoạn đề xuất ECMAScript 3 (Stage 3), tức là đã ở trạng thái khuyến nghị triển khai trên trình duyệt
- Chrome và Firefox đã bắt đầu hỗ trợ thử nghiệm, các trình duyệt khác cũng dự kiến sẽ triển khai
- Các nhà phát triển có thể tham gia cải thiện đặc tả ngay từ bây giờ thông qua kiểm thử và phản hồi
Date vẫn sẽ tiếp tục tồn tại, nhưng trong tương lai Temporal được kỳ vọng sẽ trở thành cách xử lý ngày mặc định
- Bài viết kết lại rằng: “Lẽ ra phải thay thế từ năm 1995, nhưng dù muộn thì Temporal.Now vẫn là thời điểm tốt nhất”
1 bình luận
Ý kiến trên Hacker News
Bài viết này nói về nhiều hành vi kỳ quặc của constructor Date trong JavaScript
Đặc biệt, nó giải thích vấn đề định dạng
'YYYY-MM-DD'được diễn giải là nửa đêm UTC, khiến ngày bị lệch một ngày trong múi giờ cục bộTheo ISO 8601 gốc, nếu không chỉ định múi giờ thì phải được coi là giờ địa phương, nhưng do một sai sót khi viết đặc tả ES5 nên nó lại được xử lý thành “Z”(UTC)
Sau đó đã từng định sửa trong ES2015, nhưng vì vô số website phụ thuộc vào hành vi sai cũ nên nó bị hoàn tác vì lý do tương thích web
Xem thêm ở phần Broken Parser
'strict datetime'giống như'use strict'Khi đó có thể áp dụng có chọn lọc hành vi đúng mà không gặp vấn đề không tương thích với mã cũ
Hoặc cũng có thể dùng cách nhập một đối tượng toàn cục đã được sửa qua module nội bộ như
import Date from 'browser:date'Với những giá trị chỉ mang ý nghĩa ngày tháng như sinh nhật, việc thay đổi vì múi giờ là hoàn toàn vô lý
Tôi nhớ trước kia Outlook lưu sinh nhật kèm múi giờ, nên mỗi lần đổi quốc gia thì sinh nhật lại bị xê dịch một ngày
Nhưng liệu có lựa chọn nào khác không? Ép mọi người phải phân nhánh theo phiên bản trình duyệt như thời IE5 có lẽ còn tệ hơn
Tôi thực sự ghen tị với cách Rails và Ruby xử lý thời gian
API như
Time.current.in_time_zone('America/Los_Angeles') + 3.days - 4.months + 1.hourvừa trực quan vừa mạnh mẽRuby overload đối tượng Time thành một đối tượng nhất quán, nên hầu như không phải băn khoăn chuyện chuyển đổi hay ép kiểu
Tôi vẫn nghĩ sẽ tuyệt biết bao nếu trong JS cũng có thể viết đơn giản như
new Date().add({ days: 1 })Ngoài ra, việc overload thư viện lõi có thật sự là cách tiếp cận tốt hay không cũng còn gây tranh cãi
Thật tiếc là Safari vẫn chưa hỗ trợ Temporal API
Hy vọng khoảng năm sau sẽ có hỗ trợ
Date của JavaScript có nhiều vấn đề, nhưng bản thân việc nó là một đối tượng thì có lẽ không phải vấn đề lớn
Giá như nó là đối tượng bất biến thì tốt hơn, nhưng một đối tượng có thể thay đổi thì thay đổi cũng không có gì đáng ngạc nhiên
Nguy cơ thực sự của tính khả biến xuất hiện ở thay đổi phi cục bộ, chứ không phải thay đổi tại chỗ
Việc Temporal API hoàn toàn không xử lý thông tin về giây nhuận (leap second) gây bất tiện
Tôi muốn tạo công cụ JS cho tính toán thiên văn, nhưng việc chuyển đổi UTC cần dữ liệu giây nhuận
Có cách lách như
temporal-tai, nhưng phải duy trì tệp giây nhuận ở phía client nên rất phiềnVì SOP(chính sách CORS), cũng không thể lấy trực tiếp tệp từ site bên ngoài
Trình duyệt vốn được cập nhật định kỳ, nên thật khó hiểu vì sao chúng không nhúng sẵn thông tin giây nhuận
Nếu server đặt header
Access-Control-Allow-Originhoặc cung cấp dưới dạng tệp JS thì vẫn làm đượcDù vậy, để trình duyệt tự chứa và duy trì dữ liệu giây nhuận có thể là một công việc tốn kém
UTC vốn là thang thời gian có bao gồm giây nhuận, nên đúng hơn phải nói là nó chỉ xử lý thời gian POSIX
Trong ví dụ mã, phải nói là từ
"50"chứ không phải"33"mới bị xử lý thành những năm 1900 — đây chỉ là một lỗi gõ đơn giảnTôi đang dùng Temporal polyfill và đến giờ thì rất hài lòng
Với server hoặc ứng dụng lớn thì ổn, nhưng với app nhỏ có thể là gánh nặng
Bài viết kỳ lạ ở chỗ hoàn toàn không nhắc đến
Date.now()Nếu muốn so sánh với Temporal thì lẽ ra nên giải thích dựa trên
Date.now()Hàm này trả về thời gian đã trôi qua tính bằng mili giây kể từ ngày 1 tháng 1 năm 1970
Temporal đúng là có API thân thiện hơn, nhưng về bản chất mục tiêu của nó vẫn là biểu diễn khoảng cách tương đối của thời gian
Vậy thì làm sao chuyển đổi sang định dạng mong muốn mà không phải đi qua Date, điều đó khiến người ta thắc mắc
Có người để lại một chỉnh sửa nhỏ nhưng quan trọng rằng không phải “daylight savings time” mà là “daylight saving time”
Từ trước đến nay tôi không hề biết JS Date lại tệ đến vậy
Chỉ cần cẩn trọng hơn một chút vào thời điểm đó thì đã có thể giúp vô số lập trình viên tránh được những cái bẫy này