Nếu bạn đã từng lưu ngày giờ vào database, xây dựng API, hoặc debug một lỗi lập lịch chỉ xuất hiện ở một số quốc gia nhất định, chắc chắn bạn đã gặp phải mối quan hệ giữa Unix timestamp UTC và giờ địa phương. Sự nhầm lẫn này rất phổ biến và gây ra những lỗi thực sự trên môi trường production. Bài viết này giải thích chi tiết UTC là gì, tại sao Unix timestamp được định nghĩa theo UTC, cách UTC offset hoạt động, và cách chuyển đổi timestamp đúng cách trong JavaScript, Python và PHP. Bạn cũng sẽ tìm thấy những lỗi phổ biến nhất mà developer hay mắc phải và cách tránh chúng.
Mục lục
Điểm mấu chốt:
- Unix timestamp luôn đếm số giây kể từ ngày 1 tháng 1 năm 1970, 00:00:00 UTC - nó không mang theo thông tin múi giờ nào.
- UTC là mốc tham chiếu chung của toàn thế giới; giờ địa phương chỉ là UTC cộng hoặc trừ một offset.
- Để chuyển đổi timestamp sang giờ địa phương đúng cách, bạn cần biết tên múi giờ đích, không chỉ là một con số offset.
- DST (Daylight Saving Time - giờ mùa hè) làm thay đổi offset của một múi giờ, đó là lý do tại sao hardcode offset gây ra lỗi.
UTC là gì và tại sao nó quan trọng?
UTC là viết tắt của Coordinated Universal Time (Giờ Phối hợp Quốc tế). Đây là tiêu chuẩn thời gian chính mà thế giới dùng để điều chỉnh đồng hồ và thời gian. Khác với các múi giờ khác, UTC không có offset - nó là điểm gốc bằng 0. UTC không áp dụng Daylight Saving Time và không thay đổi theo bất kỳ quốc gia hay khu vực nào.
Trước UTC, người ta dùng GMT (Greenwich Mean Time), và nhiều người vẫn dùng hai thuật ngữ này thay thế cho nhau. Về mặt kỹ thuật, UTC và GMT có sự khác biệt nhỏ, nhưng trong hầu hết các ứng dụng phần mềm chúng được xem là tương đương. UTC được định nghĩa chính thức theo khuyến nghị ITU-R TF.460 và được duy trì bằng đồng hồ nguyên tử.
Với developer, UTC quan trọng vì nó cung cấp một mốc tham chiếu duy nhất, không mơ hồ. Nếu server của bạn ở Frankfurt và người dùng ở Los Angeles đều ghi lại một sự kiện theo UTC, bạn luôn có thể so sánh hai timestamp đó một cách chính xác. Nếu mỗi bên ghi theo giờ địa phương mà không có metadata kèm theo, bạn sẽ gặp vấn đề ngay.
Tại sao Unix timestamp luôn là UTC
Unix timestamp (còn gọi là epoch time hoặc POSIX time) được định nghĩa là số giây đã trôi qua kể từ ngày 1 tháng 1 năm 1970, 00:00:00 UTC. Mốc thời gian đó - nửa đêm ngày 1 tháng 1 năm 1970 theo UTC - được gắn chặt vào chính định nghĩa của Unix time. Không có phiên bản nào của Unix time bắt đầu từ giờ địa phương của New York hay Tokyo.
Điều này có nghĩa là câu trả lời cho câu hỏi "Unix timestamp có luôn là UTC không?" là có, theo đúng định nghĩa. Con số 1700000000 đại diện cho cùng một thời điểm chính xác đối với mọi người trên hành tinh này. Điều thay đổi giữa các người dùng là cách thời điểm đó được hiển thị theo múi giờ địa phương của họ.
Để hiểu sâu hơn về nền tảng của khái niệm này, hãy đọc bài viết của chúng tôi về Epoch Time: Nền tảng của Unix Timestamp.
Thiết kế lấy UTC làm gốc này là một trong những tính chất hữu ích nhất của Unix time. Vì bản thân con số không mang thông tin múi giờ, hai hệ thống ở hai quốc gia khác nhau có thể trao đổi một timestamp và cả hai đều biết chính xác thời điểm nào được đề cập, mà không cần bất kỳ thỏa thuận thêm nào.
UTC so với giờ địa phương và múi giờ
UTC là mốc tham chiếu. Giờ địa phương là kết quả khi bạn áp dụng quy tắc múi giờ lên trên UTC. Múi giờ không chỉ là một offset cố định - đó là một vùng được đặt tên với tập hợp các quy tắc có thể thay đổi theo thời gian (chủ yếu do Daylight Saving Time).
Ví dụ, "Eastern Time" ở Hoa Kỳ là UTC-5 vào mùa đông và UTC-4 vào mùa hè. Tên múi giờ là "America/New_York" trong cơ sở dữ liệu múi giờ IANA, đây là nguồn tham chiếu chính thức cho các quy tắc múi giờ được hầu hết các ngôn ngữ lập trình và hệ điều hành sử dụng.
Điểm thực tế cần ghi nhớ: khi bạn lưu một Unix timestamp vào database, bạn đang lưu một giá trị UTC. Khi hiển thị cho người dùng, bạn chuyển đổi nó sang múi giờ địa phương của họ. Đừng bao giờ lưu giờ địa phương dưới dạng số thô rồi giả định đó là UTC. Đó là nơi lỗi bắt đầu xuất hiện.
UTC offset hoạt động như thế nào
UTC offset mô tả một múi giờ cách UTC bao nhiêu. Nó được viết dưới dạng +HH:MM hoặc -HH:MM. Một số ví dụ:
- UTC+02:00 - Central European Summer Time (CEST), dùng ở Đức và Pháp vào mùa hè.
- UTC-05:00 - Eastern Standard Time (EST), dùng ở bờ Đông nước Mỹ vào mùa đông.
- UTC+05:30 - India Standard Time (IST), đây là offset nửa tiếng.
- UTC+00:00 - chính là UTC, cũng được dùng ở Anh vào mùa đông (GMT).
Để chuyển đổi một epoch time UTC sang giờ địa phương theo cách thủ công, bạn cộng hoặc trừ offset tính bằng giây. Với UTC+02:00, đó là 2 * 3600 = 7200 giây. Vậy nếu Unix timestamp của bạn là 1700000000, giờ địa phương ở UTC+02:00 sẽ tương ứng với 1700000000 + 7200 trước khi định dạng.
Tuy nhiên, làm thủ công như vậy rất rủi ro vì offset thay đổi theo DST. Hãy luôn dùng thư viện múi giờ phù hợp thay vì hardcode offset. Xem hướng dẫn của chúng tôi về Cách chuyển đổi Unix Timestamp sang ngày tháng để tìm hiểu sâu hơn về các phương pháp chuyển đổi.
Chuyển đổi Unix timestamp sang giờ địa phương
Dưới đây là ví dụ cụ thể sử dụng Unix timestamp 1700000000, tương ứng với ngày 14 tháng 11 năm 2023, 22:13:20 UTC. Chúng ta sẽ chuyển đổi nó sang "America/New_York" (UTC-5 vào tháng 11) trong ba ngôn ngữ lập trình.
JavaScript
const ts = 1700000000;
// JavaScript Date nhận giá trị millisecond
const date = new Date(ts * 1000);
// Hiển thị theo giờ địa phương New York
const options = {
timeZone: 'America/New_York',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'short'
};
console.log(date.toLocaleString('en-US', options));
// Output: November 14, 2023 at 05:13:20 PM ESTLưu ý rằng object Date của JavaScript lưu trữ thời gian nội bộ dưới dạng UTC millisecond. Phương thức toLocaleString với tùy chọn timeZone sẽ xử lý offset và các quy tắc DST cho bạn.
Python
from datetime import datetime, timezone
import zoneinfo # Python 3.9+
ts = 1700000000
# Tạo một datetime có timezone UTC từ timestamp
utc_dt = datetime.fromtimestamp(ts, tz=timezone.utc)
# Chuyển đổi sang giờ địa phương New York
ny_tz = zoneinfo.ZoneInfo('America/New_York')
local_dt = utc_dt.astimezone(ny_tz)
print(local_dt.strftime('%Y-%m-%d %H:%M:%S %Z'))
# Output: 2023-11-14 17:13:20 ESTĐiểm mấu chốt ở đây là sử dụng datetime.fromtimestamp(ts, tz=timezone.utc). Nếu bạn bỏ qua tham số tz, Python sẽ dùng múi giờ địa phương của server để diễn giải timestamp - đây là nguồn gốc phổ biến của nhiều lỗi.
PHP
$ts = 1700000000;
// Tạo object DateTime từ Unix timestamp (luôn là UTC)
$dt = new DateTime('@' . $ts);
// Đặt múi giờ đích
$dt->setTimezone(new DateTimeZone('America/New_York'));
echo $dt->format('Y-m-d H:i:s T');
// Output: 2023-11-14 17:13:20 ESTTrong PHP, tiền tố @ khi khởi tạo object DateTime báo cho PHP biết phải xử lý giá trị đó như một Unix timestamp (UTC). Nếu không có tiền tố này, PHP có thể diễn giải chuỗi đó theo cài đặt múi giờ mặc định của server.
Những lỗi phổ biến developer hay mắc phải
Ngay cả những developer giàu kinh nghiệm cũng dễ mắc bẫy khi xử lý múi giờ. Dưới đây là những vấn đề thường gặp nhất:
1. Giả định giờ địa phương của server là UTC
Nếu server của bạn được cài đặt theo "Europe/Berlin" và bạn gọi time() trong PHP hoặc datetime.now() trong Python mà không truyền tham số timezone, bạn sẽ nhận được giờ địa phương - không phải UTC. Hãy luôn chỉ định rõ ràng UTC khi ghi lại timestamp.
2. Hardcode UTC offset thay vì dùng tên múi giờ
Dùng +02:00 làm offset cố định cho nước Đức sẽ bị sai vào mùa đông khi Đức chuyển sang +01:00. Hãy luôn dùng tên múi giờ như Europe/Berlin để thư viện có thể tự động áp dụng các quy tắc DST chính xác.
3. Lưu timestamp dưới dạng chuỗi giờ địa phương mà không có metadata múi giờ
Một chuỗi như 2023-11-14 17:13:20 trong database là mơ hồ. Đó là UTC? EST? IST? Nếu bạn lưu timestamp dưới dạng số nguyên Unix hoặc chuỗi ISO 8601 có UTC offset (ví dụ: 2023-11-14T22:13:20Z), ý nghĩa sẽ không còn mơ hồ nữa. Xem bài so sánh của chúng tôi về Unix Timestamp Format so với ISO 8601 để được hướng dẫn lựa chọn.
4. Bỏ qua DST khi lập lịch
Nếu bạn lập lịch một job chạy vào "09:00 mỗi ngày" bằng cách cộng thêm 86400 giây vào lần chạy trước, nó sẽ bị lệch một tiếng vào những ngày DST thay đổi. Hãy dùng thư viện cron hoặc scheduler hiểu được các quy tắc múi giờ.
5. Nhầm lẫn giữa millisecond và giây
JavaScript dùng millisecond cho object Date, nhưng hầu hết Unix timestamp được tính bằng giây. Truyền một timestamp tính bằng giây vào new Date() mà không nhân với 1000 sẽ cho bạn một ngày nào đó vào tháng 1 năm 1970. Vấn đề này được trình bày chi tiết trong bài viết của chúng tôi về Giây, Millisecond hay Microsecond: Nên dùng Unix Timestamp nào?.
Kết luận
Unix timestamp và UTC gắn liền với nhau theo đúng thiết kế. Timestamp luôn là một giá trị UTC - số giây đếm từ một mốc UTC cố định. Giờ địa phương chỉ là định dạng hiển thị, không phải một loại timestamp khác. Cách tiếp cận an toàn nhất là lưu mọi thứ theo UTC, chỉ chuyển đổi sang giờ địa phương tại thời điểm hiển thị, và luôn dùng tên múi giờ thay vì hardcode offset. Tuân theo những nguyên tắc này và phần lớn các lỗi liên quan đến múi giờ sẽ không còn xảy ra nữa. Để biết các best practice khi lưu trữ các giá trị này, hãy xem hướng dẫn của chúng tôi về Unix Timestamp trong Database.
Chuyển đổi bất kỳ Unix Timestamp nào sang UTC hoặc giờ địa phương - ngay lập tức
Dán bất kỳ Unix timestamp nào và xem nó được chuyển đổi sang UTC và múi giờ địa phương của bạn chỉ với một cú nhấp. Miễn phí, không cần đăng ký, hỗ trợ cả giây lẫn millisecond.
Dùng thử công cụ miễn phí của chúng tôi →
Có. Theo định nghĩa, Unix epoch timestamp đếm số giây kể từ ngày 1 tháng 1 năm 1970, 00:00:00 UTC. Bản thân con số không mang thông tin múi giờ - nó là một giá trị UTC. Múi giờ chỉ trở nên liên quan khi bạn chuyển đổi timestamp thành ngày giờ dạng con người đọc được để hiển thị.
Hãy dùng thư viện timezone tích hợp sẵn của ngôn ngữ bạn đang dùng. Trong JavaScript, dùng toLocaleString với tùy chọn timeZone. Trong Python, dùng datetime.fromtimestamp(ts, tz=timezone.utc).astimezone() với tên múi giờ. Trong PHP, tạo DateTime từ timestamp và gọi setTimezone(). Luôn dùng tên múi giờ, không dùng offset thô.
Điều này thường xảy ra vì một hàm nào đó đang dùng múi giờ mặc định của server thay vì UTC khi diễn giải timestamp. Trong Python, bỏ qua tham số tz trong datetime.fromtimestamp() sẽ dùng giờ hệ thống địa phương. Trong PHP, không dùng tiền tố @ cũng có hiệu ứng tương tự. Hãy luôn chỉ định rõ ràng UTC tại thời điểm khởi tạo.
UTC offset là số giờ (và đôi khi cả phút) mà một múi giờ đi trước hoặc đi sau UTC. Ví dụ, UTC-05:00 là năm tiếng sau UTC. Khi chuyển đổi Unix timestamp sang giờ địa phương, offset được áp dụng lên giá trị UTC. Vì offset thay đổi theo DST, bạn nên dùng tên múi giờ thay vì hardcode offset.
Có, và đây là một trong những ưu điểm chính của Unix timestamp. Vì mọi timestamp đều là giá trị UTC, bạn có thể so sánh bất kỳ hai timestamp nào trực tiếp dưới dạng số nguyên. Số lớn hơn luôn có nghĩa là thời điểm muộn hơn, bất kể timestamp được tạo ra ở đâu. Không cần chuyển đổi múi giờ nào khi so sánh.