Unix timestamp 2038 và giờ Việt Nam (UTC+7) | int32, int64, JWT exp

(Cập nhật: 19 tháng 7, 2026 ) Unix timestamp 2038 UTC+7 timezone JWT int32 int64 epoch
Kết luận

Epoch là giây (hoặc milli) kể từ 1970-01-01 UTC — không chứa múi giờ. Giờ Việt Nam = UTC+7 chỉ khi hiển thị. Vấn đề 2038 = trần int32 signed (2_147_483_647). Trước khi lưu expiry / JWT exp: chọn BIGINT/int64 hoặc timestamptz, thống nhất giây vs milli, đọc nhanh bằng Unix Timestamp.

JWT exp: 2147483647 trông “xa”. Đổi sang ngày: 2038-01-19 03:14:07 UTC — tức 10:14 sáng 19/01/2038 giờ Hà Nội. Đó cũng là max int32. Cột MySQL INT lưu exp sẽ không chứa được hạn sau mốc này; firmware IoT cũ và một số serializer nhị phân vẫn sống trong thế giới 32-bit.

Bài này cho backend / freelancer VN hay debug log epoch, JWT, và DB legacy — kết hợp 2038, UTC+7, và checklist trước khi ship.

Bài viết này giúp bạn

  • Hiểu Unix timestamp và vì sao không “mang” +7 bên trong
  • Đọc 2038 đúng (int32 vs int64)
  • Phân giây / milli
  • Checklist lưu expiry / JWT exp
  • Dùng Unix Timestamp Kawa để soi số thành giờ VN

Unix timestamp là gì

Một số nguyên: số giây (hoặc milli) đã trôi từ:

1970-01-01 00:00:00 UTC

Cùng một khoảnh khắc → cùng một số trên mọi máy (bỏ qua lệch đồng hồ). Chuỗi "19/07/2026 17:00" thì phụ thuộc format và TZ — epoch thì không.

const sec = Math.floor(Date.now() / 1000); // giây
const ms = Date.now();                     // milli — JS hay dùng

Dán số vào công cụ chuyển timestamp khi log chỉ toàn 1719….


Giờ Việt Nam (UTC+7) — đừng nhét vào epoch

Việt Nam không dùng DST: UTC+7 quanh năm (Asia/Ho_Chi_Minh).

Cách đúngCách sai
Lưu / truyền epoch UTCCộng 7*3600 vào epoch rồi lưu “cho dễ”
Format UI: Intl / lib với timeZone: 'Asia/Ho_Chi_Minh'Giả sử server Node “đã là giờ VN” nên trừ bớt 7 khi ghi
Log ghi cả UTC và VN khi điều traSo sánh JWT exp (UTC) với new Date() local mà quên offset

Ví dụ đọc exp:

exp (giây) = 1721376000
UTC        ≈ 2024-07-19 08:00:00
VN (UTC+7) ≈ 2024-07-19 15:00:00

Nhầm “số epoch đã là giờ VN” → lệch đúng 7 giờ khi đối chiếu với khách ở Hà Nội — triệu chứng kinh điển ticket “token hết hạn sớm”.


Vấn đề năm 2038

Cơ chế

Signed int32 max = 2_147_483_647.

2_147_483_647 giây sau epoch
= 2038-01-19 03:14:07 UTC
= 2038-01-19 10:14:07 giờ VN

Giây tiếp theo không biểu diễn được trong int32 → wrap (thường về âm ≈ năm 1901) tùy ngôn ngữ / CPU.

int32 vs int64

Kiểu lưu epoch giây
Kiểu Phạm vi thực tế Gợi ý
int32 / INT Tới ~2038-01-19 Tránh cho expiry dài, lease, JWT dài hạn
int64 / BIGINT Xa vượt tuổi hệ mặt trời Chuẩn cho epoch giây hiện đại
timestamptz Kiểu thời gian DB Tốt nếu ORM/SQL — vẫn cẩn thận driver map sang int32

Nơi vẫn gặp int32:

  • Schema MySQL cũ: created_at INT
  • Protobuf / struct C int32 time
  • Một số hệ nhúng, thiết bị cửa, đồng hồ chấm công
  • “Tối ưu” lưu 4 byte khi đĩa đắt — năm 2010

Hệ 64-bit userland (Node, Go, JVM hiện đại) tính toán thường an toàn; cột DB và wire format mới là chỗ chết.


Giây vs milli — bảng nhận diện

Độ dài gần đúngĐơn vịVí dụ nguồn
~10 chữ sốgiâyJWT exp, PHP time(), nhiều API Unix
~13 chữ sốmilliDate.now(), Java Instant.toEpochMilli()
~16 chữ sốmicro (hiếm hơn)Một số DB / telemetry
// SAI — coi giây như milli
new Date(1721376000);        // ~1970 (sai đơn vị)

// ĐÚNG
new Date(1721376000 * 1000); // giây → Date

Checklist so sánh:

nowSec  = Math.floor(Date.now() / 1000)
if (payload.exp < nowSec) token hết hạn

Đừng so exp (giây) với Date.now() (milli).


Checklist trước khi lưu expiry / JWT exp

In ra và tick trước merge:

  1. Đơn vị — giây hay milli? Ghi trong comment / OpenAPI.
  2. Kiểu cộtINT? Đổi BIGINT hoặc TIMESTAMPTZ nếu hạn có thể ≥ 2038.
  3. Giá trị mẫu — dán vào Unix Timestamp, đọc UTC và VN (+7) có khớp product spec không (ví dụ “hết hạn cuối ngày 31/12/2030 giờ VN”).
  4. So sánh — cùng đơn vị với now.
  5. Không nhân đôi +7 — epoch UTC; offset chỉ lúc format.
  6. JWTexp là NumericDate (giây). Đừng nhét milli vào exp.
  7. Legacy import — migrate INT → BIGINT trước khi insert hạn sau 2038; test với 21474836472147483648.
-- Nguy hiểm nếu còn INT
ALTER TABLE sessions MODIFY exp_at BIGINT NOT NULL; -- ví dụ migrate

(Chỉnh theo dialect; ý tưởng: thoát int32.)


Case study

1. Freelancer nhận Laravel cũ

password_resets.expires kiểu INT. Product muốn link reset “không hết hạn trong 5 năm” từ 2036 → cận 2038. Insert fail hoặc wrap. Fix: BIGINT + test biên.

2. Nest JWT “hết hạn lúc 10h sáng dù set midnight”

Team set “midnight VN” bằng cách lấy Date local server (UTC trên VPS) rồi /1000. User VN thấy lệch. Fix: tính midnight theo Asia/Ho_Chi_Minh rồi lấy epoch UTC — không cộng tay 7*3600 lung tung.

3. Log 1710000000000 bị đọc như giây

Tool/UI nhân thêm 1000 → năm cực đại. Nhìn 13 số → milli; đừng nhân.


Cách dùng tool trong điều tra

  1. Copy số từ JWT / log / DB
  2. Dán Unix Timestamp
  3. Xác nhận giây vs milli
  4. Đọc giờ VN (+7) so với ticket khách
  5. Nếu gần 2147483647 → cảnh báo int32 / 2038

Tóm tắt

Chủ đềNhớ một câu
EpochĐếm từ UTC 1970, không chứa +7
VN+7 chỉ khi hiển thị / schedule local
2038Trần int32 giây
JWT expGiây; đừng nhầm milli
Lưu trữBIGINT / timestamptz cho hạn xa

Số epoch không đáng sợ — đáng sợ là đơn vị sai, TZ cộng tay, và cột 32-bit im lặng tới 2038.

Liên kết liên quan