Menu

Ngày giờ trong JavaScript: new Date, định dạng và so sánh

Đối tượng Date trong JavaScript thực sự hoạt động thế nào: tạo ngày, định dạng, tính toán, xử lý múi giờ và những điểm dễ khiến bạn vấp.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Đối tượng Date là một thời điểm

Một Date trong JavaScript biểu diễn một thời điểm duy nhất: bên trong, nó chỉ là số mili giây tính từ ngày 1 tháng 1 năm 1970 theo UTC ("Unix epoch"). Mọi thứ khác (năm, tháng, ngày, múi giờ, định dạng) chỉ là cách nhìn đặt lên trên con số đó.

now.getTime() là số mili giây gốc. Mọi việc một Date làm (so sánh, cộng ngày, định dạng) rốt cuộc đều là thao tác trên con số đó rồi diễn giải lại nó.

Hãy luôn nhớ mô hình này. Một Date không phải là "ngày 14 tháng 3 ở Paris". Nó là một thời điểm chung cho cả thế giới, có thể được hiển thị thành ngày 14 tháng 3 ở Paris hoặc ngày 13 tháng 3 ở Los Angeles, tùy múi giờ bạn dùng để nhìn nó.

Tạo ngày

Có bốn cách chính để tạo một Date:

Hai điều cần để ý:

  • Hàm khởi tạo theo từng phần dùng tháng đánh số từ 0. 2 là tháng 3. Tháng 1 là 0. Đây là nguồn gây lỗi lệch một không bao giờ cạn; ở mọi chỗ khác trong API, tháng cũng đánh số từ 0, nên ít ra nó còn nhất quán.
  • new Date("2026-03-14") (không có giờ) được phân tích thành nửa đêm theo UTC. new Date("2026-03-14T09:30") (không có Z) được phân tích theo giờ cục bộ. Sự bất đối xứng này là một cái bẫy kinh điển.

Để lấy "ngay lúc này" dưới dạng số, hãy ưu tiên Date.now() vì nó không phải tạo đối tượng:

Date.now() là công cụ phù hợp để đo thời gian đã trôi qua, đặt timeout, và mọi việc không cần tính toán theo lịch.

Đọc từng phần của một ngày

Khi đã có một Date, bạn lấy từng thành phần bằng các getter. Mỗi getter có hai phiên bản: giờ cục bộ và UTC.

Các getter cục bộ phụ thuộc vào máy đang chạy code. Nếu bạn lưu hoặc so sánh ngày giữa nhiều người dùng và máy chủ, hãy chọn UTC một cách rõ ràng, nếu không bạn sẽ phải đuổi theo những bug ma. Quy tắc chung: dùng getter UTC cho mọi thứ đi vào cơ sở dữ liệu hoặc log; dùng getter cục bộ cho những gì sắp hiển thị cho người xem.

Đừng dùng getYear(). Đây là phương thức cũ trả về year - 1900 và chỉ tồn tại để tương thích ngược. Hãy luôn dùng getFullYear().

Định dạng cho người đọc

Tránh date.toString() cho bất cứ thứ gì quan trọng, vì kết quả của nó phụ thuộc vào ngôn ngữ và engine. Có hai cách định dạng đáng biết.

Để có một chuỗi chuẩn cho máy đọc, hãy dùng toISOString():

Đó là định dạng nên dùng khi ghi log, lưu vào JSON hoặc gửi qua mạng. Nó luôn theo UTC và không bao giờ mơ hồ.

Để có chuỗi cho người đọc, hãy dùng Intl.DateTimeFormat hoặc các phương thức toLocale* vốn được xây trên nó:

Intl.DateTimeFormat xử lý ngôn ngữ, múi giờ và mọi tổ hợp trường bạn cần. Hãy dùng nó trước khi tự viết định dạng kiểu ${year}-${month}-${day}, vì kiểu ghép chuỗi đó chính là nơi bug lệch tháng sinh sôi.

So sánh ngày

Hai đối tượng Date biểu diễn cùng một thời điểm không bằng nhau theo ===, vì === kiểm tra có phải cùng một đối tượng không chứ không so giá trị. Hãy so sánh timestamp của chúng:

Để sắp xếp thứ tự, các toán tử so sánh dùng trực tiếp được vì chúng ép kiểu thành số:

Phép trừ cho bạn khoảng cách tính bằng mili giây. Chia cho 1000 * 60 * 60 * 24 để ra số ngày. Lần đầu hãy viết đầy đủ hằng số đó; dần dần bạn sẽ nhận ra 86_400_000 ngay khi nhìn thấy.

Tính toán với ngày

Không có phương thức addDays. Cách viết quen thuộc là dùng setDate, setMonth và các phương thức tương tự: chúng nhận cả giá trị vượt phạm vi và tự chuyển sang tháng hoặc năm kế tiếp cho đúng:

Hai điều đáng nói:

  • new Date(date) sao chép ngày. setDate thay đổi trực tiếp đối tượng, nên luôn sao chép trước, nếu không bạn sẽ sửa luôn giá trị của nơi gọi.
  • setDate(35) trên một tháng có 31 ngày sẽ tự chuyển sang tháng sau. setMonth(14) cũng vậy: nó tăng năm lên. Nhờ đó việc tính toán đỡ vất vả hơn nhiều so với vẻ ngoài.

Với những việc phức tạp (ngày làm việc, sự kiện lặp lại, khoảng thời gian tính theo tháng), hãy dùng thư viện (date-fns, Luxon, hoặc API Temporal sắp ra mắt). Tự viết phép tính lịch vượt quá mức "cộng vài ngày" là một vũng lầy.

Sự thật về múi giờ

Múi giờ là nguồn gây bug ngày tháng lớn nhất. Những quy tắc nên thuộc lòng:

  • Một Date lưu một thời điểm theo UTC. Múi giờ chỉ được áp dụng khi bạn đọc từng phần hoặc định dạng nó.
  • Múi giờ mà getHours(), getDate() và các getter khác dùng là múi giờ cục bộ của máy đang chạy code. Máy chủ và trình duyệt thường không khớp nhau.
  • new Date("2026-03-14") (chỉ có ngày) được phân tích theo UTC. new Date("2026-03-14T00:00") (có giờ, không có múi giờ) được phân tích theo giờ cục bộ. new Date(2026, 2, 14) (theo từng phần) là giờ cục bộ.

Khi cần hiển thị theo một múi giờ cụ thể, hãy truyền timeZone vào Intl.DateTimeFormat:

Cùng một thời điểm, hai cách nhìn. Bản thân đối tượng Date không hề thay đổi.

Một ví dụ nhỏ hoàn chỉnh

Ghép tất cả lại: một hàm định dạng xem một việc đã xảy ra cách đây bao lâu:

Timestamp đi vào, chuỗi dễ đọc đi ra. Đây là dáng dấp của 90% code xử lý ngày tháng ngoài thực tế: trừ hai thời điểm, chia cho một đơn vị, làm tròn, định dạng.

Những điều cần nhớ

  • Một Date là một thời điểm theo UTC. Múi giờ chỉ xuất hiện khi bạn đọc hoặc định dạng nó.
  • Dùng Date.now() cho timestamp, new Date() cho các việc liên quan đến lịch.
  • Dùng toISOString() để lưu trữ và ghi log, Intl.DateTimeFormat cho người dùng.
  • So sánh bằng getTime() hoặc </>. Không bao giờ dùng ===.
  • Tháng đánh số từ 0. Cẩn thận với bẫy phân tích chuỗi chỉ có ngày.
  • Với phép tính ngày tháng nghiêm túc, hãy dùng thư viện.

Tiếp theo: URL và query string

Ngày tháng thường xuất hiện trong URL: lọc theo khoảng ngày, truyền timestamp làm tham số truy vấn. Tự phân tích và tạo URL cũng dễ sinh bug như tự định dạng ngày, và thư viện chuẩn có đối tượng URL xử lý việc này gọn gàng. Đó là bài tiếp theo.

Câu hỏi thường gặp

Làm sao lấy ngày hiện tại trong JavaScript?

Gọi new Date() không kèm đối số. Nó trả về một đối tượng Date biểu diễn đúng thời điểm hàm khởi tạo chạy. Nếu bạn chỉ cần một timestamp dạng số (số mili giây kể từ năm 1970), hãy dùng Date.now(): nhanh hơn và không phải tạo cả một đối tượng.

Làm sao so sánh hai ngày trong JavaScript?

So sánh timestamp của chúng, đừng so sánh chính các đối tượng Date. a.getTime() < b.getTime() chạy đúng, a < b cũng vậy vì < ép kiểu ngày thành số. Nhưng a === b thì không: === kiểm tra xem có phải cùng một đối tượng không, nên hai đối tượng Date biểu diễn cùng một thời điểm không bao giờ bằng nhau nghiêm ngặt.

Làm sao định dạng ngày trong JavaScript?

Với mọi thứ hiển thị cho người dùng, hãy dùng Intl.DateTimeFormat hoặc date.toLocaleDateString(), vì chúng xử lý đúng ngôn ngữ và múi giờ. Với đầu ra cho máy đọc, date.toISOString() cho bạn một chuỗi chuẩn như 2026-03-14T09:30:00.000Z. Tránh dùng date.toString() để lưu trữ, vì định dạng của nó phụ thuộc vào ngôn ngữ.

Tại sao ngày trong JavaScript của tôi bị lệch một ngày?

Thường là do múi giờ. new Date('2026-03-14') được phân tích thành nửa đêm theo UTC, nhưng date.getDate() trả về ngày theo múi giờ cục bộ, có thể là ngày hôm trước. Dùng getUTCDate() để lấy ngày theo UTC, hoặc tạo ngày bằng new Date(year, month, day), cách này dùng giờ cục bộ ngay từ đầu.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu