System.DateTime biểu diễn một ngày và một thời điểm trong ngày, từ năm 1 đến năm 9999, với độ chính xác 100 nano giây (một "tick"). System.TimeSpan biểu diễn một khoảng thời gian: hiệu giữa hai giá trị DateTime. Cả hai đều là kiểu giá trị bất biến (struct), nên mọi thao tác đều trả về một giá trị mới.
Đầu ra:
2026-09-24 00:00:00
2026-09-24 14:30:00
14:30:05.250
2026 9 24
14:30
Thursday
267
2026-09-24 00:00
14:30:00
Mọi ví dụ trên trang này đều in ngày bằng chuỗi định dạng tường minh. ToString() mặc định theo culture hiện tại (9/24/2026 2:30:00 PM ở Mỹ, 24.09.2026 14:30:00 ở Đức), nên đầu ra của nó phụ thuộc vào máy. Các mã định dạng có ở trang định dạng DateTime.
Ngày không hợp lệ sẽ ném ngoại lệ: new DateTime(2026, 2, 30) ném ra ArgumentOutOfRangeException, tháng 13 hay giờ 24 cũng vậy.
Now, UtcNow và Today
Ba property tĩnh đọc đồng hồ:
Ví dụ đầu ra:
Now: 2026-09-24 18:20:41 (Local)
UtcNow: 2026-09-24 16:20:41 (Utc)
Today: 2026-09-24 00:00:00
Trong ví dụ này, múi giờ địa phương sớm hơn UTC hai giờ, nên hai dòng đầu lệch nhau hai giờ; trên máy đặt múi giờ UTC thì chúng trùng nhau. Property Kind cho biết một giá trị là Local, Utc hay Unspecified (mặc định cho ngày bạn tự tạo). Hãy dùng DateTime.UtcNow cho mọi thứ bạn lưu, ghi log, so sánh hay gửi sang hệ thống khác: nó không nhảy khi giờ mùa hè bắt đầu hay kết thúc, và biểu thị cùng một thời điểm trên mọi máy chủ. Chỉ chuyển sang giờ địa phương khi hiển thị giá trị cho người đọc.
Để đo thời gian chạy của code, hãy dùng System.Diagnostics.Stopwatch thay vì trừ hai giá trị DateTime.Now; nó có độ phân giải cao hơn nhiều và không bị ảnh hưởng khi đồng hồ được chỉnh.
Cộng và trừ thời gian
AddDays, AddHours, AddMinutes, AddSeconds, AddMonths và AddYears trả về một DateTime mới. Truyền số âm để lùi lại. Vì DateTime bất biến, kết quả phải được gán:
Đầu ra:
2026-01-31
2026-02-03 09:00
2026-01-30 21:00
2026-02-28
2027-01-31
10:30
29
True
AddMonths lấy ngày cuối tháng khi ngày đó không tồn tại: ngày 31 tháng 1 cộng một tháng là ngày 28 tháng 2 (hoặc 29 trong năm nhuận), không phải ngày 3 tháng 3. Vì vậy cộng một tháng hai lần và cộng hai tháng một lần có thể cho ra hai ngày khác nhau.
Trừ hai ngày: TimeSpan
Lấy một DateTime trừ cho một DateTime khác sẽ cho ra một TimeSpan:
Đầu ra:
3.20:30:00
Days: 3, Hours: 20, Minutes: 30
TotalDays: 3.85
TotalHours: 92.5
TotalMinutes: 5550
Nights: 4
Đây là phần API mà mọi người hay dùng sai nhất. Days, Hours, Minutes và Seconds là các thành phần của khoảng thời gian (3 ngày, 20 giờ, 30 phút). TotalDays, TotalHours và TotalMinutes là toàn bộ khoảng thời gian quy ra một đơn vị, dưới dạng double. "Khách ở bao nhiêu giờ?" là TotalHours (92,5), không phải Hours (20).
Dòng cuối cho thấy một điểm liên quan: đã trôi qua 3,85 ngày, nhưng khách ở 4 đêm. So sánh phần .Date sẽ đếm theo ngày trên lịch, thường là thứ mà việc tính tiền hay hiển thị "còn bao nhiêu ngày" cần.
Tạo và định dạng giá trị TimeSpan
Đầu ra:
02:15:00
01:30:00
1.12:00:00
True
03:45:00
True
02:15
36h 0m
00:00:00
TimeSpan hỗ trợ +, -, phép so sánh, Duration() (giá trị tuyệt đối) và Negate(). Định dạng tùy chỉnh như @"hh\:mm" cần dấu gạch chéo ngược trước các ký tự nguyên văn, và hh ở đó chỉ hiện thành phần giờ (từ 0 đến 23), nên với khoảng thời gian dài hơn một ngày, hãy tạo văn bản từ TotalHours như ở dòng áp chót.
So sánh ngày
DateTime hỗ trợ ==, !=, <, >, <= và >=, cùng với CompareTo và DateTime.Compare. Để chỉ so sánh ngày và bỏ qua giờ, hãy so sánh property .Date:
Đầu ra:
True
True
1
True
2026-09-01
Với câu hỏi "mốc thời gian này có nằm trong ngày 30 tháng 9 không?", hãy so sánh với đầu ngày hôm sau bằng <, như trên. Viết check <= end sẽ loại mọi thứ sau nửa đêm của ngày cuối, vì end là 2026-09-30 00:00:00.
Phép so sánh chỉ xét tick, không xét Kind: một giá trị Local và một giá trị Utc in ra giống nhau được coi là bằng nhau dù chúng là hai thời điểm khác nhau. Thêm một lý do để lưu thời gian theo UTC.
Thứ trong tuần và đầu tuần
DayOfWeek là một enum từ Sunday (0) đến Saturday (6). Tính toán trên nó giúp tìm ngày trong tuần và ranh giới tuần:
Đầu ra:
Thursday
4
Weekend: False
Week starts 2026-09-21 (Monday)
Next Friday: 2026-09-25
2026-09-01 to 2026-09-30
Tên thứ do DayOfWeek.ToString() in ra luôn là tiếng Anh. Để có tên theo ngôn ngữ địa phương, hãy định dạng ngày bằng "dddd" kèm một culture.
Phân tích ngày từ chuỗi
Khi bạn biết định dạng của đầu vào, hãy dùng ParseExact hoặc TryParseExact với CultureInfo.InvariantCulture. Chuỗi định dạng dùng cùng các mã như khi định dạng:
Đầu ra:
2026-09-24 00:00
2026-09-24 18:05
'2026-02-28' -> Saturday, February 28
'2026-02-30' -> invalid
'28.02.2026' -> invalid
'' -> invalid
2026-02-28
2026-09-24 10:00 Utc
DateTime.Parse(text) không có định dạng sẽ thử đoán theo culture hiện tại. "03/04/2026" là ngày 4 tháng 3 trên máy ở Mỹ và ngày 3 tháng 4 trên máy ở Anh, và một ngày phân tích được trên laptop của bạn có thể ném FormatException trên máy chủ. Chỉ dùng Parse cho đầu vào do người dùng địa phương gõ; dùng ParseExact với invariant culture cho tệp, API và cơ sở dữ liệu. ParseExact ném FormatException khi văn bản không khớp; TryParseExact thì trả về false.
Tính tuổi
Trừ ngày sinh rồi chia cho 365 sẽ sai quanh ngày sinh nhật và năm nhuận. Hãy so sánh năm, rồi điều chỉnh nếu sinh nhật năm nay chưa tới:
Đầu ra:
36
35
18
70 days to go
DateTimeOffset
DateTime không ghi lại múi giờ của nó, ngoài cờ Kind khá mơ hồ. DateTimeOffset lưu giá trị cùng độ lệch so với UTC, nên nó luôn xác định đúng một thời điểm:
Đầu ra:
2026-09-24 14:00 +02:00
2026-09-24 12:00
2026-09-24 12:30
00:30:00
21:00 +09:00
Hãy dùng DateTimeOffset (hoặc giá trị DateTime theo UTC) cho mốc thời gian: lúc một đơn hàng được đặt, lúc một tin nhắn được gửi. Cơ sở dữ liệu và bộ tuần tự hóa JSON xử lý nó tốt. Để chuyển đổi giữa các múi giờ có tên với quy tắc giờ mùa hè, hãy dùng TimeZoneInfo.ConvertTime; trên các phiên bản .NET cũ, ID múi giờ khác nhau tùy hệ điều hành ("Europe/Paris" trên Linux, "Romance Standard Time" trên Windows), còn .NET 6 trở lên chấp nhận cả hai.
DateOnly và TimeOnly (.NET 6 trở lên)
Nhiều giá trị là ngày không có giờ (ngày sinh, hạn chót) hoặc giờ không có ngày (giờ mở cửa). .NET 6 thêm hai kiểu cho chúng:
// .NET 6 and later
DateOnly birthday = new DateOnly(1990, 9, 24);
DateOnly due = DateOnly.FromDateTime(DateTime.Today).AddDays(14);
int daysLeft = due.DayNumber - DateOnly.FromDateTime(DateTime.Today).DayNumber;
TimeOnly opens = new TimeOnly(9, 0);
TimeOnly closes = new TimeOnly(17, 30);
bool isOpen = TimeOnly.FromDateTime(DateTime.Now).IsBetween(opens, closes);
Chúng loại bỏ cả một nhóm lỗi trong đó một giờ hay múi giờ lạc chỗ làm ngày bị lệch đi một ngày. Code cũ hơn, và code nhắm tới .NET Framework hoặc Unity, dùng DateTime với giờ để ở nửa đêm.
Lỗi thường gặp
- Bỏ qua kết quả của
AddDays.DateTimebất biến; hãy gán giá trị trả về. - Dùng
Hoursthay choTotalHours. Thành phần so với toàn bộ khoảng thời gian. - Lưu
DateTime.Now. Hãy lưu UTC và chuyển đổi khi hiển thị. - Gọi
ToString()không có định dạng trong log, tệp hoặc test, nơi đầu ra phụ thuộc vào culture của máy. - Phân tích dữ liệu máy bằng
DateTime.Parse. Hãy dùngParseExactvà invariant culture. - Nhầm
mmvớiMMtrong chuỗi định dạng (phút và tháng). Xem định dạng DateTime.
Câu hỏi thường gặp
DateTime.Now và DateTime.UtcNow khác nhau thế nào?
DateTime.Now là thời điểm hiện tại theo múi giờ địa phương của máy, với Kind là Local. DateTime.UtcNow là thời điểm hiện tại theo UTC, với Kind là Utc, và nó còn nhanh hơn vì bỏ qua bước chuyển đổi múi giờ. Hãy lưu và so sánh mốc thời gian theo UTC, và chỉ chuyển sang giờ địa phương khi hiển thị.
Làm sao tính khoảng cách giữa hai ngày trong C#?
Trừ chúng cho nhau: TimeSpan gap = end - start;. Sau đó đọc gap.TotalDays, gap.TotalHours hoặc gap.TotalMinutes để có toàn bộ khoảng thời gian dưới dạng double, hoặc gap.Days để lấy phần số ngày nguyên. Với số tháng hay số năm theo lịch thì không có property có sẵn, vì các tháng dài ngắn khác nhau; hãy tự so sánh các trường năm và tháng.
TimeSpan.Hours và TotalHours khác nhau thế nào?
Hours chỉ là thành phần giờ, từ 0 đến 23, sau khi đã tách các ngày nguyên ra. TotalHours là toàn bộ khoảng thời gian quy ra giờ, dưới dạng double. Với khoảng 1 ngày 3 giờ, Hours là 3 còn TotalHours là 27. Dùng Hours ở chỗ cần TotalHours là một lỗi rất phổ biến.
Làm sao phân tích chuỗi ngày tháng trong C#?
Khi bạn biết định dạng, hãy dùng DateTime.ParseExact(text, "yyyy-MM-dd", CultureInfo.InvariantCulture), hoặc DateTime.TryParseExact để nhận false thay vì FormatException khi đầu vào sai. DateTime.Parse đoán định dạng theo culture hiện tại, nên 03/04/2026 là ngày 4 tháng 3 ở Mỹ và ngày 3 tháng 4 ở Anh.
Vì sao AddDays không làm thay đổi DateTime của tôi?
DateTime là kiểu giá trị bất biến. AddDays, AddHours và các phương thức khác trả về một DateTime mới và giữ nguyên giá trị gốc, nên bạn phải gán kết quả: due = due.AddDays(7);.
Khi nào nên dùng DateTimeOffset thay cho DateTime?
Dùng DateTimeOffset cho các mốc thời gian phải xác định một thời điểm chính xác, như lúc một đơn hàng được đặt hay một dòng log được ghi, nhất là khi dữ liệu di chuyển giữa các máy chủ và múi giờ. Nó lưu độ lệch so với UTC cùng với giá trị. DateTime vẫn ổn cho mốc thời gian chỉ dùng UTC và cho ngày không có múi giờ cụ thể.