1. Giới thiệu
Nếu bạn nghĩ múi giờ chỉ là "cộng/trừ vài tiếng", thì sớm thôi bạn sẽ cảm nhận được nỗi đau của đa số coder trên hành tinh này. Múi giờ, chuyển đổi giờ mùa hè/đông, lỗi bất ngờ khi thử "chuyển thời gian sang vùng khác" — tất cả có thể dẫn đến bug khó tìm và còn khó giải thích cho user hơn nữa.
Điều kinh khủng nhất: trên thế giới không có và chưa từng có khái niệm "thời gian chung" — ví dụ, hai nước láng giềng có thể xử lý chuyển đổi giờ mùa hè khác nhau, và đôi khi quyết định chuyển đổi được đưa ra giữa năm (đúng đấy, thật luôn!). Ví dụ: năm 2011 Samoa "nhảy cóc" qua cả một ngày và đổi bên so với đường đổi ngày quốc tế.
Nhưng C# và .NET cho bạn công cụ mạnh để ít nhất còn chống đỡ được. Hôm nay tụi mình sẽ làm quen với vị cứu tinh — kiểu TimeZoneInfo và các chức năng của nó, giúp bạn và app của bạn sống sót.
Khi nào bạn cần làm việc với múi giờ
- Khi user/server ở các vùng khác nhau trên thế giới
- Khi bạn lưu ngày giờ trong database ở UTC, nhưng lại hiển thị cho user theo giờ địa phương của họ
- Để tính toán lịch biểu, nhắc nhở, sự kiện online ("Webinar bắt đầu lúc 19:00 ở Berlin — còn ở vùng của bạn thì sao?")
- Khi làm hệ thống doanh nghiệp/toàn cầu (ví dụ kế toán hoặc CRM cho business quốc tế)
Tổng quan về kiểu TimeZoneInfo
TimeZoneInfo — là class được tạo riêng trong .NET, dùng để biểu diễn thông tin về múi giờ: lệch so với UTC, tên, hỗ trợ chuyển đổi giờ mùa hè và vân vân.
Khác với class cũ TimeZone, cái mà chỉ biết về múi giờ "local" và "UTC", TimeZoneInfo cho bạn truy cập đầy đủ tất cả múi giờ có trong hệ thống (thậm chí cả tự tạo thủ công).
2. Lấy thông tin về múi giờ
Múi giờ local và UTC
Lấy info về múi giờ local cực dễ:
// Local (tức là nơi app của bạn đang chạy)
TimeZoneInfo local = TimeZoneInfo.Local;
// Luôn luôn là UTC
TimeZoneInfo utc = TimeZoneInfo.Utc;
Console.WriteLine(local.DisplayName); // Ví dụ: (UTC+01:00) Berlin, Vienna
Console.WriteLine(utc.DisplayName); // (UTC) Coordinated Universal Time
Tất cả múi giờ có sẵn
Thường bạn cần show cho user danh sách tất cả múi giờ (ví dụ khi đăng ký tài khoản):
foreach (var tz in TimeZoneInfo.GetSystemTimeZones())
{
Console.WriteLine($"{tz.Id} | {tz.DisplayName}");
}
Kết quả sẽ kiểu như:
- Central European Standard Time | (UTC+01:00) Berlin, Vienna
- Pacific Standard Time | (UTC-08:00) Pacific Time (US & Canada)
- v.v...
Minh họa: các thuộc tính chính của TimeZoneInfo
| Thuộc tính | Mô tả |
|---|---|
|
Id hệ thống của múi giờ (dùng để tìm kiếm) |
|
Mô tả cho user (có giờ và thành phố) |
|
Tên "giờ chuẩn" |
|
Tên giờ khi chuyển sang giờ mùa hè |
|
Lệch so với UTC (ví dụ, +01:00 cho Berlin) |
|
True nếu múi giờ này hỗ trợ chuyển đổi giờ mùa hè/đông |
3. Chuyển đổi thời gian giữa các múi giờ
Đây là lý do tụi mình phải làm mấy thứ này! Bạn cần:
- Chuyển thời gian từ một vùng sang vùng khác (ví dụ, server lưu UTC, còn hiển thị cho user theo múi giờ của họ)
- Xử lý đúng chuyển đổi giờ mùa hè/đông
Cú pháp tổng quát
DateTime utcNow = DateTime.UtcNow;
// Ví dụ, chuyển thời gian này sang múi giờ Trung Âu
TimeZoneInfo europeZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time");
DateTime europeTime = TimeZoneInfo.ConvertTimeFromUtc(utcNow, europeZone);
Console.WriteLine($"UTC now: {utcNow}"); // 2024-06-20 10:30:00
Console.WriteLine($"Europe now: {europeTime}"); // 2024-06-20 11:30:00
Lưu ý: Luôn dùng UTC cho tính toán và lưu trữ nội bộ, chỉ chuyển sang múi giờ khác khi hiển thị cho user thôi nhé!
Làm sao biết Id của múi giờ?
Trên Windows, Id (Id) của múi giờ là đặc trưng (ví dụ, "Central European Standard Time"), còn trên Linux/Unix — thường là IANA/Olson Id ("Europe/Berlin", "America/New_York").
Lấy danh sách Id trên hệ thống của bạn bằng TimeZoneInfo.GetSystemTimeZones().
4. Chuyển đổi giữa các múi giờ bất kỳ
Giả sử bạn có thời gian ở London (GMT/UTC+0) và muốn chuyển sang Tokyo (UTC+9):
DateTime londonTime = new DateTime(2024, 6, 20, 12, 0, 0, DateTimeKind.Unspecified);
TimeZoneInfo londonZone = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
TimeZoneInfo tokyoZone = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
// Bước 1: chuyển sang UTC (nếu là local)
DateTime utc = TimeZoneInfo.ConvertTimeToUtc(londonTime, londonZone);
// Bước 2: chuyển UTC sang giờ Tokyo
DateTime tokyoTime = TimeZoneInfo.ConvertTimeFromUtc(utc, tokyoZone);
Console.WriteLine($"London: {londonTime} | UTC: {utc} | Tokyo: {tokyoTime}");
Xử lý chuyển đổi giờ mùa hè và đông
TimeZoneInfo tự động xử lý chuyển đổi giờ mùa hè/đông — nếu múi giờ đó có hỗ trợ.
TimeZoneInfo eastern = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTime dateWinter = new DateTime(2024, 1, 1, 12, 0, 0, DateTimeKind.Unspecified);
DateTime dateSummer = new DateTime(2024, 7, 1, 12, 0, 0, DateTimeKind.Unspecified);
Console.WriteLine( TimeZoneInfo.ConvertTimeToUtc(dateWinter, eastern)); // Lệch UTC-5
Console.WriteLine( TimeZoneInfo.ConvertTimeToUtc(dateSummer, eastern)); // Lệch UTC-4 — giờ mùa hè!
Dù Mỹ (trong ví dụ trên) có bỏ chuyển đổi giờ mùa hè (và năm nào cũng có đề xuất như vậy!), database múi giờ trên OS của bạn vẫn sẽ được update, và code của bạn vẫn chạy đúng.
5. Mẹo thực tế và lỗi thường gặp
Exception và bẫy thường gặp
- Ở thời điểm chuyển đổi giờ (ví dụ, 2:30 sáng ngày đổi giờ mùa hè/đông), một số thời điểm có thể "không tồn tại" hoặc "bị lặp lại":
Ví dụ, ngày đổi "lùi lại" 2:30 xuất hiện hai lần — và mỗi lần là "khác nhau". - Đừng quên: danh sách múi giờ có thể thay đổi khi OS update! Ví dụ: một số nước bỏ/thêm chuyển đổi giờ mùa hè.
GO TO FULL VERSION