System.DateTime reprezentuje datę i porę dnia, od roku 1 do roku 9999, z dokładnością do 100 nanosekund (jeden "takt"). System.TimeSpan reprezentuje czas trwania: różnicę między dwiema wartościami DateTime. Oba to niemodyfikowalne typy wartościowe (struktury), więc każda operacja zwraca nową wartość.
Wynik:
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
Każdy przykład na tej stronie wypisuje daty z jawnym ciągiem formatu. Domyślne ToString() podąża za bieżącą kulturą (9/24/2026 2:30:00 PM w USA, 24.09.2026 14:30:00 w Niemczech), więc jego wynik zależy od maszyny. Kody formatu są na stronie formatowanie DateTime.
Niepoprawna data rzuca wyjątek: new DateTime(2026, 2, 30) zgłasza ArgumentOutOfRangeException, podobnie jak miesiąc 13 albo godzina 24.
Now, UtcNow i Today
Trzy właściwości statyczne odczytują zegar:
Przykładowy wynik:
Now: 2026-09-24 18:20:41 (Local)
UtcNow: 2026-09-24 16:20:41 (Utc)
Today: 2026-09-24 00:00:00
W tym przykładzie lokalna strefa czasowa wyprzedza UTC o dwie godziny, więc pierwsze dwie linie różnią się o dwie godziny; na maszynie ustawionej na UTC są takie same. Właściwość Kind zapisuje, czy wartość jest Local, Utc czy Unspecified (domyślne dla dat, które tworzysz sam). Używaj DateTime.UtcNow do wszystkiego, co zapisujesz, logujesz, porównujesz lub wysyłasz do innego systemu: nie przeskakuje przy zmianie czasu na letni i zimowy i oznacza ten sam moment na każdym serwerze. Na czas lokalny konwertuj dopiero przy pokazywaniu wartości człowiekowi.
Do mierzenia czasu wykonania kodu używaj System.Diagnostics.Stopwatch zamiast odejmować dwie wartości DateTime.Now; ma znacznie lepszą rozdzielczość i nie wpływają na niego korekty zegara.
Dodawanie i odejmowanie czasu
AddDays, AddHours, AddMinutes, AddSeconds, AddMonths i AddYears zwracają nowy DateTime. Przekaż liczbę ujemną, żeby cofnąć się w czasie. Ponieważ DateTime jest niemodyfikowalny, wynik trzeba przypisać:
Wynik:
2026-01-31
2026-02-03 09:00
2026-01-30 21:00
2026-02-28
2027-01-31
10:30
29
True
AddMonths przycina datę do ostatniego dnia miesiąca, gdy dany dzień nie istnieje: 31 stycznia plus jeden miesiąc to 28 lutego (lub 29 w roku przestępnym), a nie 3 marca. Dwukrotne dodanie jednego miesiąca i jednorazowe dodanie dwóch miesięcy może więc dać różne daty.
Odejmowanie dat: TimeSpan
Odjęcie jednego DateTime od drugiego daje TimeSpan:
Wynik:
3.20:30:00
Days: 3, Hours: 20, Minutes: 30
TotalDays: 3.85
TotalHours: 92.5
TotalMinutes: 5550
Nights: 4
To część API, w której ludzie najczęściej się mylą. Days, Hours, Minutes i Seconds to składniki przedziału (3 dni, 20 godzin, 30 minut). TotalDays, TotalHours i TotalMinutes to cały czas trwania w jednej jednostce, jako double. "Ile godzin gość przebywał w hotelu?" to TotalHours (92.5), a nie Hours (20).
Ostatnia linia pokazuje pokrewną kwestię: minęło 3.85 dnia, ale gość został na 4 noce. Porównanie części .Date liczy dni kalendarzowe, a tego zwykle potrzebują rozliczenia i liczniki "dni do".
Tworzenie i formatowanie wartości TimeSpan
Wynik:
02:15:00
01:30:00
1.12:00:00
True
03:45:00
True
02:15
36h 0m
00:00:00
TimeSpan obsługuje +, -, porównania, Duration() (wartość bezwzględną) i Negate(). Formaty niestandardowe, takie jak @"hh\:mm", wymagają ukośnika wstecznego przed dosłownymi znakami, a hh pokazuje tam tylko składnik godzin (od 0 do 23), więc dla czasów trwania dłuższych niż doba buduj tekst z TotalHours, jak w przedostatniej linii.
Porównywanie dat
DateTime obsługuje ==, !=, <, >, <= i >=, a także CompareTo i DateTime.Compare. Żeby porównać tylko datę i pominąć czas, porównaj właściwości .Date:
Wynik:
True
True
1
True
2026-09-01
Przy pytaniu "czy ten znacznik czasu mieści się w 30 września?" porównuj z początkiem następnego dnia przez <, jak wyżej. Zapis check <= end wykluczyłby wszystko po północy ostatniego dnia, bo end to 2026-09-30 00:00:00.
Porównania patrzą tylko na takty, a nie na Kind: wartość Local i wartość Utc, które wypisują się tak samo, są uznawane za równe, choć to różne momenty. To kolejny powód, żeby przechowywać czas w UTC.
Dzień tygodnia i początek tygodnia
DayOfWeek to enum od Sunday (0) do Saturday (6). Arytmetyka na nim pozwala znaleźć dni robocze i granice tygodni:
Wynik:
Thursday
4
Weekend: False
Week starts 2026-09-21 (Monday)
Next Friday: 2026-09-25
2026-09-01 to 2026-09-30
Nazwy dni wypisywane przez DayOfWeek.ToString() są zawsze po angielsku. Żeby dostać nazwę w danym języku, sformatuj datę przez "dddd" z kulturą.
Parsowanie dat z napisów
Gdy znasz format danych wejściowych, używaj ParseExact lub TryParseExact z CultureInfo.InvariantCulture. Ciąg formatu używa tych samych kodów co formatowanie:
Wynik:
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) bez formatu próbuje zgadnąć na podstawie bieżącej kultury. "03/04/2026" to 4 marca na amerykańskiej maszynie i 3 kwietnia na brytyjskiej, a data, która parsuje się na twoim laptopie, może rzucić FormatException na serwerze. Zostaw Parse dla danych wpisywanych przez lokalnego użytkownika; do plików, API i baz danych używaj ParseExact z kulturą niezmienną. ParseExact rzuca FormatException, gdy tekst nie pasuje; TryParseExact zamiast tego zwraca false.
Obliczanie wieku
Odejmowanie dat urodzenia i dzielenie przez 365 daje złe wyniki w okolicach urodzin i lat przestępnych. Porównaj lata, a potem popraw wynik, jeśli tegoroczne urodziny jeszcze nie nastąpiły:
Wynik:
36
35
18
70 days to go
DateTimeOffset
DateTime nie zapisuje, w jakiej jest strefie czasowej, poza niejasną flagą Kind. DateTimeOffset przechowuje wartość razem z jej przesunięciem względem UTC, więc zawsze wskazuje jeden dokładny moment:
Wynik:
2026-09-24 14:00 +02:00
2026-09-24 12:00
2026-09-24 12:30
00:30:00
21:00 +09:00
Używaj DateTimeOffset (lub wartości DateTime w UTC) dla znaczników czasu: kiedy złożono zamówienie, kiedy wysłano wiadomość. Bazy danych i serializatory JSON dobrze sobie z nim radzą. Do konwersji między nazwanymi strefami czasowymi z regułami czasu letniego używaj TimeZoneInfo.ConvertTime; identyfikatory stref w starszych wersjach .NET różnią się między systemami operacyjnymi ("Europe/Paris" na Linuksie, "Romance Standard Time" na Windows), a .NET 6 i nowsze akceptują oba.
DateOnly i TimeOnly (.NET 6 i nowsze)
Wiele wartości to data bez czasu (urodziny, termin płatności) albo czas bez daty (godziny otwarcia). .NET 6 dodał dla nich dwa typy:
// .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);
Usuwają całą klasę błędów, w których przypadkowa godzina lub strefa czasowa przesuwa datę o jeden dzień. Starszy kod oraz kod dla .NET Framework lub Unity używa DateTime z czasem ustawionym na północ.
Częste błędy
- Odrzucanie wyniku
AddDays.DateTimejest niemodyfikowalny; przypisz zwróconą wartość. - Używanie
HourszamiastTotalHours. Składniki kontra cały czas trwania. - Zapisywanie
DateTime.Now. Zapisuj UTC i konwertuj do wyświetlania. - Wywoływanie
ToString()bez formatu w logach, plikach lub testach, gdzie wynik zależy od kultury maszyny. - Parsowanie danych maszynowych przez
DateTime.Parse. UżyjParseExacti kultury niezmiennej. - Mylenie
mmiMMw ciągach formatu (minuty i miesiące). Zobacz formatowanie DateTime.
Najczęściej zadawane pytania
Czym różni się DateTime.Now od DateTime.UtcNow?
DateTime.Now to bieżący czas w lokalnej strefie czasowej komputera, z Kind ustawionym na Local. DateTime.UtcNow to bieżący czas w UTC, z Kind ustawionym na Utc, i jest też szybszy, bo pomija konwersję strefy czasowej. Przechowuj i porównuj znaczniki czasu w UTC, a na czas lokalny konwertuj tylko do wyświetlania.
Jak obliczyć różnicę między dwiema datami w C#?
Odejmij je: TimeSpan gap = end - start;. Potem odczytaj gap.TotalDays, gap.TotalHours lub gap.TotalMinutes, żeby dostać cały czas trwania jako double, albo gap.Days, żeby dostać część pełnych dni. Dla miesięcy lub lat kalendarzowych nie ma wbudowanej właściwości, bo miesiące mają różną długość; porównaj pola roku i miesiąca samodzielnie.
Czym różni się TimeSpan.Hours od TotalHours?
Hours to tylko składnik godzin, od 0 do 23, po odjęciu pełnych dni. TotalHours to cały czas trwania wyrażony w godzinach, jako double. Dla przedziału 1 dnia i 3 godzin Hours wynosi 3, a TotalHours 27. Użycie Hours tam, gdzie chodziło o TotalHours, to bardzo częsty błąd.
Jak sparsować datę z napisu w C#?
Gdy znasz format, użyj DateTime.ParseExact(text, "yyyy-MM-dd", CultureInfo.InvariantCulture) albo DateTime.TryParseExact, żeby przy błędnych danych dostać false zamiast FormatException. DateTime.Parse zgaduje format na podstawie bieżącej kultury, więc 03/04/2026 oznacza 4 marca w USA i 3 kwietnia w Wielkiej Brytanii.
Dlaczego AddDays nie zmienia mojego DateTime?
DateTime to niemodyfikowalny typ wartościowy. AddDays, AddHours i pozostałe metody zwracają nowy DateTime i zostawiają oryginał bez zmian, więc musisz przypisać wynik: due = due.AddDays(7);.
Kiedy używać DateTimeOffset zamiast DateTime?
Używaj DateTimeOffset dla znaczników czasu, które muszą wskazywać dokładny moment, na przykład złożenie zamówienia albo zapis wpisu w logu, zwłaszcza gdy dane przechodzą między serwerami i strefami czasowymi. Przechowuje on przesunięcie względem UTC razem z wartością. DateTime wystarczy dla znaczników czasu tylko w UTC i dla dat bez znaczącej strefy czasowej.