Giải thích về Unix timestamp
Unix timestamp là một con số duy nhất biểu thị một thời điểm. Nó dễ lưu trữ và so sánh, nhưng đơn vị, múi giờ và định dạng văn bản lại gây ra nhiều lỗi khi chuyển đổi.
Thời gian Unix là gì
Thời gian Unix (còn gọi là epoch time hoặc thời gian POSIX) đếm số giây kể từ 1970-01-01 00:00:00 UTC, gọi là mốc epoch Unix. 0 chính là mốc epoch, 1700000000 là 2023-11-14 22:13:20 UTC, và các giá trị âm là những thời điểm trước năm 1970: -1 là 1969-12-31 23:59:59 UTC.
Con số này không chứa múi giờ: nó mô tả cùng một thời điểm ở mọi nơi, nhờ đó rất tiện cho việc lưu trữ, sắp xếp và so sánh các sự kiện.
Thời gian Unix bỏ qua giây nhuận: mỗi ngày có đúng 86.400 giây. Khi một giây nhuận được thêm vào UTC, các hệ thống lặp lại một giây hoặc dàn trải nó ra một khoảng thời gian dài hơn (“smearing”), nên khoảng cách giữa hai thời điểm nằm ở hai phía của một giây nhuận sẽ ngắn hơn một giây so với thời gian thực tế đã trôi qua.
Giây, mili giây, micro giây hay nano giây?
Nhiều hệ thống đếm bằng đơn vị nhỏ hơn, và con số không cho biết đó là đơn vị nào. Cùng một thời điểm có thể xuất hiện dưới dạng bốn con số rất khác nhau:
| Đơn vị | 2023-11-14 22:13:20 UTC | Số chữ số (2001–2286) | Thường gặp trong |
|---|---|---|---|
| Giây | 1700000000 | 10 | date +%s, nhiều API, claim exp của JWT |
| Mili giây | 1700000000000 | 13 | Date.now() trong JavaScript, System.currentTimeMillis() trong Java |
| Micro giây | 1700000000000000 | 16 | Một số cơ sở dữ liệu và hệ thống ghi log |
| Nano giây | 1700000000000000000 | 19 | UnixNano() trong Go, time.time_ns() trong Python |
Đếm số chữ số là cách kiểm tra nhanh nhất với các ngày gần hiện tại. Công cụ chuyển đổi của LeedyBox dùng cùng ý tưởng này: giá trị đến 1011 được đọc là giây, đến 1014 là mili giây, đến 1017 là micro giây và các giá trị lớn hơn là nano giây. Quy tắc kinh nghiệm này không đúng với những ngày cách xa hiện tại, vì vậy hãy kiểm tra đơn vị khi kết quả trông bất thường.
Độ chính xác cũng quan trọng. Số trong JavaScript chỉ biểu diễn chính xác số nguyên đến 253 (khoảng 9 × 1015), nên một giá trị nano giây như 1700000000123456789 sẽ mất các chữ số cuối khi được phân tích như một số thông thường. Hãy giữ các giá trị như vậy dưới dạng chuỗi hoặc dùng BigInt; công cụ chuyển đổi làm điều này ở bên trong. Để đọc claim exp của một token, hãy giải mã nó bằng công cụ Base64 / URL / JWT.
Múi giờ và giờ mùa hè
Để hiển thị một timestamp dưới dạng ngày và giờ, bạn cần một múi giờ. UTC là mốc tham chiếu; giờ địa phương cộng thêm một độ lệch như +09:00 ở Seoul hoặc −05:00 ở New York vào mùa đông.
Hãy ưu tiên tên múi giờ IANA như Asia/Seoul hoặc America/New_York thay vì độ lệch cố định và chữ viết tắt: tên múi giờ mang theo lịch sử và quy tắc giờ mùa hè của múi giờ đó, còn một chữ viết tắt như CST lại chỉ nhiều múi giờ khác nhau. Các quy tắc này lấy từ cơ sở dữ liệu múi giờ, vốn thay đổi vài lần mỗi năm, và trình duyệt, hệ điều hành cũng như ngôn ngữ lập trình đều đi kèm bản sao riêng, nên kết quả cho các ngày trong quá khứ hoặc tương lai có thể khác nhau.
Giờ mùa hè tạo ra hai trường hợp khó xử khi bạn chuyển giờ địa phương thành timestamp:
- Khoảng trống: khi đồng hồ được chỉnh tiến lên, một số thời điểm địa phương không tồn tại. Ở
America/New_York, 2026-03-08 02:30 bị bỏ qua vì đồng hồ nhảy từ 02:00 lên 03:00. - Chồng lấn: khi đồng hồ được chỉnh lùi lại, một số thời điểm địa phương xảy ra hai lần. 2026-11-01 01:30 xảy ra một lần theo EDT (UTC−04:00) và lặp lại một giờ sau đó theo EST (UTC−05:00).
Mỗi thư viện xử lý các trường hợp này theo cách khác nhau. Công cụ chuyển đổi của LeedyBox dùng thời điểm hợp lệ muộn hơn khi rơi vào khoảng trống (ở đây là 03:30) và lần xuất hiện sớm hơn khi rơi vào khoảng chồng lấn, đồng thời giải thích điều này trong một ghi chú. Các tác vụ được lên lịch cũng gặp vấn đề tương tự; xem Cách đọc biểu thức cron.
ISO 8601, RFC 3339 và ngày tháng trong HTTP
Khi một thời điểm cần dễ đọc với con người, hãy viết theo ISO 8601 hoặc RFC 3339, phiên bản chặt chẽ hơn dành cho internet:
2023-11-14T22:13:20Z
2023-11-15T07:13:20+09:00
2023-11-14T22:13:20.500Z
Z nghĩa là UTC. Khi có Z hoặc độ lệch, chuỗi không bị mơ hồ. Một chuỗi không có cả hai, như 2023-11-14T22:13:20, là giờ địa phương ở một múi giờ không xác định, và các trình phân tích không thống nhất cách đọc nó.
Các header HTTP như Last-Modified và Expires dùng một định dạng cũ hơn, bắt nguồn từ định dạng ngày của email (RFC 2822, nay là RFC 5322), viết theo GMT: Tue, 14 Nov 2023 22:13:20 GMT.
Sự cố năm 2038
Nhiều hệ thống cũ lưu thời gian Unix trong một số nguyên có dấu 32 bit. Giá trị lớn nhất của nó, 2147483647, là 2038-01-19 03:14:07 UTC. Một giây sau, bộ đếm bị tràn thành -2147483648, được đọc là 1901-12-13 20:45:52 UTC. Hệ điều hành và ngôn ngữ lập trình hiện nay phần lớn dùng giá trị thời gian 64 bit, đủ cho hàng trăm tỷ năm, nhưng các trường 32 bit vẫn còn xuất hiện trong thiết bị nhúng, định dạng tệp, cột cơ sở dữ liệu và giao thức mạng. Nếu mã hoặc dữ liệu của bạn lưu số giây trong 32 bit, hãy lên kế hoạch thay đổi sớm và kiểm thử các ngày sau năm 2038 ngay từ bây giờ.
Timestamp trong mã nguồn và cơ sở dữ liệu
JavaScript làm việc với mili giây:
Math.floor(Date.now() / 1000); // thời gian Unix hiện tại, tính bằng giây
new Date(1700000000 * 1000).toISOString(); // "2023-11-14T22:13:20.000Z"
Date.parse("2026-10-03T09:00:00+09:00") / 1000; // 1790985600
Hàm time.time() của Python trả về số giây dưới dạng số thực. Hãy truyền tz=timezone.utc, nếu không fromtimestamp() sẽ trả về giờ địa phương của máy tính mà không kèm múi giờ:
import time
from datetime import datetime, timezone
int(time.time()) # giây
datetime.fromtimestamp(1700000000, tz=timezone.utc) # 2023-11-14 22:13:20+00:00
datetime.fromisoformat("2026-10-03T09:00:00+09:00").timestamp() # 1790985600.0
Trong SQL, các hàm khác nhau tùy cơ sở dữ liệu:
-- PostgreSQL
SELECT extract(epoch FROM now()); -- giây, có phần thập phân
SELECT to_timestamp(1700000000); -- timestamp with time zone
-- MySQL
SELECT UNIX_TIMESTAMP();
SELECT FROM_UNIXTIME(1700000000); -- theo múi giờ của phiên
Khi lưu trữ, một vài thói quen sau giúp tránh hầu hết các vấn đề:
- Lưu các thời điểm đã xảy ra theo UTC, trong cột kiểu timestamp có múi giờ hoặc dưới dạng số nguyên 64 bit có đơn vị nằm trong tên cột, như
created_at_ms. - Lưu các sự kiện địa phương trong tương lai, như một cuộc họp lúc 09:00 ở Seoul, dưới dạng ngày giờ địa phương kèm tên múi giờ IANA, và tính timestamp khi cần, để một thay đổi quy tắc sau này không làm xê dịch cuộc họp.
- Không trộn lẫn các đơn vị trong một cột; sai đơn vị sẽ lệch đi 1.000 lần trở lên.
Câu hỏi thường gặp
Vì sao timestamp của tôi được chuyển thành một ngày trong năm 1970?
Có lẽ giá trị đã bị đọc sai đơn vị: giây bị đọc thành mili giây sẽ rơi vào tháng 1 năm 1970, còn mili giây bị đọc thành giây sẽ rơi vào hàng chục nghìn năm sau trong tương lai. Hãy đếm số chữ số hoặc chọn đơn vị trong công cụ chuyển đổi.
Unix timestamp có phụ thuộc vào múi giờ của tôi không?
Không. Nó đếm số giây tính từ một thời điểm cố định theo UTC. Chỉ ngày giờ được hiển thị cho nó mới phụ thuộc vào múi giờ.
Thời gian Unix có biểu diễn được giây nhuận không?
Không. Thời gian Unix coi mỗi ngày có 86.400 giây, nên một thời điểm UTC như 23:59:60 không có timestamp riêng.