1. Poznajemy typ DateTime
Data i czas — to taki podstawowy klocek każdej logiki biznesowej, że nie da się go olać nawet w najprostszych projektach. Często trzeba wiedzieć, kiedy użytkownik się zarejestrował, kiedy wysłać maila, dobrze obsłużyć rozkład lotów, strefy czasowe — wszędzie daty! Ale, jak pokazuje praktyka, praca z datą i czasem to jeden z najbardziej podstępnych tematów w programowaniu. 😅
C# daje dwa szczególnie ważne typy do pracy z czasem: DateTime i DateTimeOffset. W skrócie — jeden do prostych scenariuszy, drugi do zadań związanych ze strefami czasowymi i dokładnym przechowywaniem czasu.
Co to jest DateTime?
Typ DateTime — to struktura zdefiniowana w przestrzeni nazw System. Służy do przechowywania daty i czasu z dokładnością do 100 nanosekund (1/10 miliardowej sekundy).
- Typ wartości (struct), czyli kopiowany przez wartość.
- Zawiera zarówno datę, jak i czas.
- Pozwala przechowywać tylko datę (2025-05-24), jak i datę z czasem (2025-05-24 15:17:42).
- „Nie wie” o strefach czasowych, jeśli nie powiesz mu wprost.
2. Tworzymy DateTime
Opcji jest kilka, zobaczmy najważniejsze.
1. Aktualna data i czas
DateTime now = DateTime.Now; // Lokalny czas komputera
DateTime utcNow = DateTime.UtcNow; // Czas UTC (Greenwich)
- Now — to "jak na twoim kompie", z uwzględnieniem lokalnej strefy Windows.
- UtcNow — "uniwersalny", czyli światowy czas koordynowany. Mega wygodne do trzymania czasu np. w bazach danych albo systemach działających globalnie.
2. Konstruktory
// Rok, miesiąc, dzień
var date1 = new DateTime(2025, 5, 24);
// Rok, miesiąc, dzień, godziny, minuty, sekundy
var date2 = new DateTime(2025, 5, 24, 12, 0, 0);
// Rok, miesiąc, dzień, godziny, minuty, sekundy, milisekundy
var date3 = new DateTime(2025, 5, 24, 12, 0, 0, 500);
Miesiące nie są numerowane od zera! Czyli styczeń — to 1, grudzień — 12.
3. Parsowanie ze stringa
Bardzo często data przychodzi jako string! Na przykład, użytkownik wpisuje datę ręcznie.
string userInput = "2025-05-24 18:30";
DateTime parsed = DateTime.Parse(userInput); // Może rzucić wyjątek!
Dla bezpiecznego parsowania — używamy TryParse:
string input = "2025-05-24 18:30";
if (DateTime.TryParse(input, out DateTime result))
{
Console.WriteLine($"Przekształcono pomyślnie: {result}");
}
else
{
Console.WriteLine("Nie udało się przekształcić stringa na datę.");
}
4. Pobieranie daty "bez czasu"
DateTime today = DateTime.Today; // Bieżący dzień, ale czas = 00:00:00
Wyciąganie komponentów daty i czasu
Wszystkie właściwości można pobrać szybko:
var dt = new DateTime(2025, 5, 24, 15, 17, 42);
int year = dt.Year; // 2025
int month = dt.Month; // 5 (maj)
int day = dt.Day; // 24
int hour = dt.Hour; // 15
int minute = dt.Minute; // 17
int second = dt.Second; // 42
DayOfWeek dow = dt.DayOfWeek; // Piątek
3. Praca z typem DateTime
Dodawanie i odejmowanie dat
Czas w programowaniu leci szybko, więc żeby nie liczyć "ile godzin do deadline'u", używamy metod klasy:
DateTime now = DateTime.Now;
DateTime tomorrow = now.AddDays(1); // Jutro
DateTime afterFiveHours = now.AddHours(5); // Za 5 godzin
DateTime inTenMinutes = now.AddMinutes(10); // Za 10 minut
Różnica dwóch dat: TimeSpan
Różnica między dwiema datami — to osobna wartość, typ TimeSpan:
DateTime deadline = new DateTime(2026, 6, 1);
DateTime now = DateTime.Now;
TimeSpan untilDeadline = deadline - now;
Console.WriteLine($"Do deadline'u zostało dni: {untilDeadline.Days}");
Console.WriteLine($"Do deadline'u zostało godzin: {untilDeadline.TotalHours:N2}");
TimeSpan — to "różnica", a nie "konkretna data/czas"!
Sprawdzanie daty w przyszłości/przeszłości
if (deadline > now)
Console.WriteLine("Deadline jeszcze przed nami!");
else
Console.WriteLine("Deadline już minął! Szybko oddaj pracę!");
4. Jak daty są przechowywane pod maską
Dla wygody dodawania i odejmowania dat, programiści postanowili trzymać wszystkie daty jako liczbę sekund od początku naszej ery. Zakres wartości:
| Właściwość | Wartość |
|---|---|
| Minimalna data | = 0001-01-01 00:00:00 |
| Maksymalna data | = 9999-12-31 23:59:59.9999999 |
| Jednostka miary | 1 tick = 100 nanosekund |
DateTime przechowuje nie tylko liczbę sekund, ale liczbę "ticków" (ticks) od specjalnej daty — 1 stycznia 0001 roku (tak, programiści nie znają dinozaurów, ich kalendarz jest bardziej surowy). Jeden tick = 100 nanosekund.
5. Typ DateTimeOffset
Czasem typ DateTime to za mało. Klasyczny przypadek: firma działa w dwóch krajach, mają różne strefy czasowe, a wszystkie spotkania zaplanowane są "wg mojego czasu". Co robić?
Czas zapisany jako DateTime jest często "abstrakcyjny": to po prostu zestaw cyferek i tick, dopóki nie powiesz wprost, w jakiej strefie to było. Na przykład, jeśli dostaniesz "2025-05-24 15:00", nie możesz być pewien — to czas Nairobi? Bagdadu? Berlina? (a jak jeszcze jest czas letni — trzymaj się mocno!).
Dlatego do zadań związanych z dokładnym momentem w czasie, a nie tylko "datą w kalendarzu", pojawił się drugi typ — DateTimeOffset. Tworzenie DateTimeOffset z podaniem przesunięcia (np. Nairobi +3 do UTC):
var localTime = new DateTimeOffset(2025, 5, 24, 15, 0, 0, TimeSpan.FromHours(3));
Używaj DateTimeOffset, jeśli:
- Musisz znać dokładny moment w czasie (np. dla zdarzeń w logu powiązanych z czasem serwera).
- Program działa z użytkownikami w różnych strefach czasowych.
- Trzeba przechowywać/przekazywać czas z podaniem strefy (np. dla transakcji, zdarzeń, rozkładów).
6. Jak stworzyć DateTimeOffset?
- Aktualny moment czasu
DateTimeOffset now = DateTimeOffset.Now; // Lokalny czas + przesunięcie DateTimeOffset utc = DateTimeOffset.UtcNow; // UTC (przesunięcie 0) - Jawne tworzenie
// Rok, miesiąc, dzień, godzina, minuta, sekunda, przesunięcie (np. +2 godziny) DateTimeOffset custom = new DateTimeOffset(2025, 5, 24, 20, 0, 0, TimeSpan.FromHours(2)); - Konwersja z DateTime
DateTime dt = DateTime.Now; DateTimeOffset dto = new DateTimeOffset(dt); // Lokalna strefa zostanie pobrana automatycznie
Wyciąganie komponentów
DateTimeOffset dto = DateTimeOffset.Now;
var datePart = dto.Date; // Tylko data
var timePart = dto.TimeOfDay; // Tylko czas
var offset = dto.Offset; // Przesunięcie względem UTC, np. 03:00:00
Konwersja między strefami czasowymi
Magia przesunięć:
// Pobieramy datę w UTC
var now = DateTimeOffset.Now;
var utc = now.ToUniversalTime();
Console.WriteLine(utc); // Czas Greenwich
// Przekładamy na inną strefę
var berlinTime = utc.ToOffset(TimeSpan.FromHours(2));
Console.WriteLine(berlinTime); // Dla Berlina (CET +2)
7. Kiedy używać DateTime, a kiedy — DateTimeOffset?
Filozofia wyboru
Gdy chodzi o coś prostego i lokalnego — np. urodziny, datę publikacji artykułu czy deadline na ankietę do 15:00, najczęściej wystarczy DateTime. W takich przypadkach absolutny czas nie jest krytyczny, ważne tylko, że zdarzenie miało miejsce w określony dzień i godzinę, bez powiązania ze strefą czasową.
Ale jeśli trzeba zapisać konkretny moment z uwzględnieniem strefy czasowej, np. w logach, transakcjach czy systemach rozproszonych — lepiej użyć DateTimeOffset. Dzięki temu nie pogubisz się przy przekazywaniu czasu między systemami, szczególnie jeśli są w różnych strefach.
| Scenariusz | Typ | Dlaczego |
|---|---|---|
| Urodziny | |
Liczy się tylko data, strefa nieistotna (zwykle) |
| Czas logowania | |
Ważny dokładny moment i przesunięcie |
| Termin ważności produktu | |
Data końca, strefa nieistotna |
| Czas zdarzenia w logu | |
Warto zawsze przechowywać z przesunięciem |
GO TO FULL VERSION