1. Làm quen với kiểu DateTime
Ngày và giờ là một viên gạch cơ bản của bất kỳ logic kinh doanh nào, đến mức không thể bỏ qua nó ngay cả trong những dự án đơn giản nhất. Thường xuyên cần biết khi nào user đăng ký, khi nào gửi mail, xử lý lịch trình chuyến bay, múi giờ — ở đâu cũng có ngày tháng! Nhưng thực tế cho thấy, làm việc với ngày và giờ là một trong những chủ đề "khó nhằn" nhất trong lập trình. 😅
C# cung cấp hai kiểu đặc biệt quan trọng để làm việc với thời gian: DateTime và DateTimeOffset. Nói ngắn gọn — một cái cho các tình huống đơn giản, cái còn lại cho các bài toán liên quan đến múi giờ và lưu trữ thời gian chính xác.
DateTime là gì?
Kiểu DateTime là một struct được định nghĩa trong namespace System. Nó dùng để lưu trữ ngày và giờ với độ chính xác đến 100 nano giây (1/10 tỷ giây).
- Kiểu giá trị (struct), tức là copy theo giá trị.
- Lưu cả ngày lẫn giờ.
- Có thể lưu chỉ ngày (2025-05-24) hoặc ngày kèm giờ (2025-05-24 15:17:42).
- "Không biết" về múi giờ, trừ khi bạn chỉ rõ.
2. Tạo DateTime
Có vài cách, mình sẽ nói mấy cái chính.
1. Ngày và giờ hiện tại
DateTime now = DateTime.Now; // Giờ local của máy tính
DateTime utcNow = DateTime.UtcNow; // Giờ theo Greenwich (UTC)
- Now — là "giống như trên máy của bạn", tính theo múi giờ local của Windows.
- UtcNow — "universal", tức là giờ phối hợp quốc tế. Rất tiện để lưu thời gian, ví dụ trong database hoặc các hệ thống chạy toàn cầu.
2. Constructors
// Năm, tháng, ngày
var date1 = new DateTime(2025, 5, 24);
// Năm, tháng, ngày, giờ, phút, giây
var date2 = new DateTime(2025, 5, 24, 12, 0, 0);
// Năm, tháng, ngày, giờ, phút, giây, mili giây
var date3 = new DateTime(2025, 5, 24, 12, 0, 0, 500);
Tháng không bắt đầu từ 0 đâu nhé! Tức là tháng 1 là 1, tháng 12 là 12.
3. Parse từ chuỗi
Rất thường ngày đến dưới dạng chuỗi! Ví dụ, user nhập ngày bằng tay.
string userInput = "2025-05-24 18:30";
DateTime parsed = DateTime.Parse(userInput); // Có thể ném exception!
Để parse an toàn — dùng TryParse:
string input = "2025-05-24 18:30";
if (DateTime.TryParse(input, out DateTime result))
{
Console.WriteLine($"Chuyển đổi thành công: {result}");
}
else
{
Console.WriteLine("Không thể chuyển chuỗi thành ngày.");
}
4. Lấy ngày "không có giờ"
DateTime today = DateTime.Today; // Ngày hiện tại, giờ = 00:00:00
Lấy các thành phần của ngày và giờ
Tất cả property có thể lấy rất nhanh:
var dt = new DateTime(2025, 5, 24, 15, 17, 42);
int year = dt.Year; // 2025
int month = dt.Month; // 5 (tháng 5)
int day = dt.Day; // 24
int hour = dt.Hour; // 15
int minute = dt.Minute; // 17
int second = dt.Second; // 42
DayOfWeek dow = dt.DayOfWeek; // Thứ sáu
3. Làm việc với kiểu DateTime
Cộng và trừ ngày giờ
Thời gian trong lập trình trôi nhanh lắm, để khỏi phải tính "còn bao nhiêu tiếng đến deadline", dùng mấy method của class này:
DateTime now = DateTime.Now;
DateTime tomorrow = now.AddDays(1); // Ngày mai
DateTime afterFiveHours = now.AddHours(5); // Sau 5 tiếng
DateTime inTenMinutes = now.AddMinutes(10); // Sau 10 phút
Hiệu của hai ngày: TimeSpan
Hiệu giữa hai ngày là một giá trị riêng, kiểu TimeSpan:
DateTime deadline = new DateTime(2026, 6, 1);
DateTime now = DateTime.Now;
TimeSpan untilDeadline = deadline - now;
Console.WriteLine($"Còn lại ngày đến deadline: {untilDeadline.Days}");
Console.WriteLine($"Còn lại giờ đến deadline: {untilDeadline.TotalHours:N2}");
TimeSpan — là "khoảng cách", không phải "ngày/giờ cụ thể"!
Kiểm tra ngày trong tương lai/quá khứ
if (deadline > now)
Console.WriteLine("Deadline vẫn còn phía trước!");
else
Console.WriteLine("Deadline qua rồi! Nộp bài gấp!");
4. Dưới "nắp ca-pô" ngày tháng được lưu thế nào
Để tiện cho việc cộng trừ ngày tháng, dev quyết định lưu tất cả ngày tháng dưới dạng số giây đã trôi qua từ đầu kỷ nguyên của chúng ta. Giá trị giới hạn:
| Thuộc tính | Giá trị |
|---|---|
| Ngày nhỏ nhất | = 0001-01-01 00:00:00 |
| Ngày lớn nhất | = 9999-12-31 23:59:59.9999999 |
| Đơn vị đo | 1 tick = 100 nano giây |
DateTime không chỉ lưu số giây, mà là số "tick" (ticks) kể từ một ngày đặc biệt — 1 tháng 1 năm 0001 (ừ thì, dev không biết khủng long là gì, lịch của họ nghiêm ngặt lắm). Một tick = 100 nano giây.
5. Kiểu DateTimeOffset
Đôi khi DateTime là chưa đủ. Lấy ví dụ kinh điển: công ty hoạt động ở hai quốc gia, mỗi nơi một múi giờ, mà tất cả cuộc họp đều lên lịch "theo giờ của tôi". Làm sao bây giờ?
Thời gian lưu bằng DateTime thường là "trừu tượng": chỉ là một dãy số và tick, trừ khi bạn chỉ rõ nó thuộc múi giờ nào. Ví dụ, nếu bạn nhận được "2025-05-24 15:00", bạn không thể chắc — đó là giờ Nairobi? Baghdad? Berlin? (mà nếu còn daylight saving nữa thì xác định luôn!).
Vì vậy, cho các bài toán cần thời điểm chính xác, không chỉ là "ngày trên lịch", có thêm kiểu thứ hai — DateTimeOffset. Tạo DateTimeOffset với offset (ví dụ, Nairobi +3 so với UTC):
var localTime = new DateTimeOffset(2025, 5, 24, 15, 0, 0, TimeSpan.FromHours(3));
Dùng DateTimeOffset nếu:
- Cần biết thời điểm chính xác (ví dụ, cho event trong log, gắn với giờ server).
- App làm việc với user ở nhiều múi giờ khác nhau.
- Cần lưu/truyền thời gian kèm múi giờ (ví dụ, cho giao dịch, event, lịch biểu).
6. Làm sao tạo DateTimeOffset?
- Thời điểm hiện tại
DateTimeOffset now = DateTimeOffset.Now; // Giờ local + offset DateTimeOffset utc = DateTimeOffset.UtcNow; // UTC (offset 0) - Tạo rõ ràng
// Năm, tháng, ngày, giờ, phút, giây, offset (ví dụ, +2 tiếng) DateTimeOffset custom = new DateTimeOffset(2025, 5, 24, 20, 0, 0, TimeSpan.FromHours(2)); - Chuyển từ DateTime
DateTime dt = DateTime.Now; DateTimeOffset dto = new DateTimeOffset(dt); // Offset local sẽ tự động lấy
Lấy các thành phần
DateTimeOffset dto = DateTimeOffset.Now;
var datePart = dto.Date; // Chỉ ngày
var timePart = dto.TimeOfDay; // Chỉ giờ
var offset = dto.Offset; // Offset so với UTC, ví dụ, 03:00:00
Chuyển đổi giữa các múi giờ
Ma thuật của offset:
// Lấy ngày ở UTC
var now = DateTimeOffset.Now;
var utc = now.ToUniversalTime();
Console.WriteLine(utc); // Giờ Greenwich
// Chuyển sang múi giờ khác
var berlinTime = utc.ToOffset(TimeSpan.FromHours(2));
Console.WriteLine(berlinTime); // Cho Berlin (CET +2)
7. Khi nào dùng DateTime, khi nào dùng DateTimeOffset?
Triết lý chọn lựa
Khi nói về cái gì đó đơn giản và local — ví dụ, ngày sinh nhật, ngày đăng bài, hoặc deadline khảo sát đến 15:00, thường chỉ cần DateTime. Trong mấy trường hợp này, thời gian tuyệt đối không quan trọng, chỉ cần biết sự kiện xảy ra vào ngày giờ nào, không cần quan tâm múi giờ.
Nhưng nếu cần ghi nhận thời điểm chính xác kèm múi giờ, ví dụ, trong log, giao dịch hoặc hệ thống phân tán — nên dùng DateTimeOffset. Nó sẽ không làm bạn rối khi truyền thời gian giữa các hệ thống, nhất là khi chúng ở các múi giờ khác nhau.
| Tình huống | Kiểu | Lý do |
|---|---|---|
| Sinh nhật | |
Chỉ cần ngày, không quan tâm múi giờ (thường là vậy) |
| Thời gian đăng nhập | |
Cần biết thời điểm chính xác và offset |
| Hạn sử dụng sản phẩm | |
Ngày hết hạn, không quan tâm múi giờ |
| Thời gian event trong log | |
Nên luôn lưu kèm offset |
GO TO FULL VERSION