🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

Formatowanie daty w C# — DateTime i CultureInfo

Formatowanie dat w C# to temat, który pozornie wygląda prosto — ale w produkcji potrafi zaskoczyć. Wystarczy pomylić MM z mm, polegać na kulturze maszyny serwera albo zapomnieć o strefach czasowych — i masz buga, który pojawia się tylko w nocy albo tylko na maszynach z innym locale.

W tym wpisie omawiam wszystko, czego potrzebujesz: specyfikatory standardowe i niestandardowe, CultureInfo, DateTimeOffset, DateOnly, parsowanie dat oraz wzorce, które stosuje się w realnych projektach .NET.

📌 Przykłady bazują na dacie: DateTime dt = new DateTime(2026, 6, 7, 14, 30, 5); — 7 czerwca 2026, godzina 14:30:05.

1. Standardowe specyfikatory formatu daty

Jeden znak = predefiniowany format. Wynik zależy od kultury bieżącego wątku (Thread.CurrentThread.CurrentCulture).

DateTime dt = new DateTime(2026, 6, 7, 14, 30, 5);

Console.WriteLine(dt.ToString("d"));   // 07.06.2026          (krótka data, pl-PL)
Console.WriteLine(dt.ToString("D"));   // niedziela, 7 czerwca 2026
Console.WriteLine(dt.ToString("t"));   // 14:30                (krótki czas)
Console.WriteLine(dt.ToString("T"));   // 14:30:05             (pełny czas)
Console.WriteLine(dt.ToString("f"));   // niedziela, 7 czerwca 2026 14:30
Console.WriteLine(dt.ToString("F"));   // niedziela, 7 czerwca 2026 14:30:05
Console.WriteLine(dt.ToString("g"));   // 07.06.2026 14:30
Console.WriteLine(dt.ToString("G"));   // 07.06.2026 14:30:05
Console.WriteLine(dt.ToString("M"));   // 7 czerwca            (miesiąc i dzień)
Console.WriteLine(dt.ToString("Y"));   // czerwiec 2026         (miesiąc i rok)
Console.WriteLine(dt.ToString("R"));   // Sun, 07 Jun 2026 14:30:05 GMT  (RFC 1123)
Console.WriteLine(dt.ToString("s"));   // 2026-06-07T14:30:05   (sortowalne, invariant)
Console.WriteLine(dt.ToString("u"));   // 2026-06-07 14:30:05Z  (universal sortowalne)
Console.WriteLine(dt.ToString("o"));   // 2026-06-07T14:30:05.0000000   (round-trip ISO 8601)

Tabela — najczęściej używane specyfikatory

SpecyfikatorWynik (pl-PL)Kiedy używać
"d"07.06.2026Wyświetlanie daty w UI
"D"niedziela, 7 czerwca 2026Nagłówki, komunikaty dla użytkownika
"T"14:30:05Logi, UI z pełnym czasem
"s"2026-06-07T14:30:05Sortowalne, niezależne od kultury
"o"2026-06-07T14:30:05.0000000Serializacja, round-trip, JSON API
"R"Sun, 07 Jun 2026 14:30:05 GMTNagłówki HTTP (If-Modified-Since)

2. Niestandardowe specyfikatory — budujesz format sam

Gdy żaden standardowy format Ci nie odpowiada, składasz go z liter-cegiełek:

DateTime dt = new DateTime(2026, 6, 7, 14, 30, 5);

Console.WriteLine(dt.ToString("yyyy-MM-dd"));              // 2026-06-07
Console.WriteLine(dt.ToString("dd.MM.yyyy"));              // 07.06.2026
Console.WriteLine(dt.ToString("dd.MM.yyyy HH:mm"));        // 07.06.2026 14:30
Console.WriteLine(dt.ToString("dd.MM.yyyy HH:mm:ss"));     // 07.06.2026 14:30:05
Console.WriteLine(dt.ToString("dddd, d MMMM yyyy"));       // niedziela, 7 czerwca 2026
Console.WriteLine(dt.ToString("HH:mm:ss"));                // 14:30:05
Console.WriteLine(dt.ToString("HH:mm"));                   // 14:30
Console.WriteLine(dt.ToString("d MMM yyyy"));              // 7 cze 2026
Console.WriteLine(dt.ToString("yyyyMMdd_HHmmss"));         // 20260607_143005   (nazwy plików)

Tabela cegiełek niestandardowych

CegiełkaZnaczeniePrzykład
yyyyRok 4-cyfrowy2026
yyRok 2-cyfrowy26
MMMiesiąc (01–12)06
MMiesiąc bez zera wiodącego6
MMMMPełna nazwa miesiącaczerwiec
MMMSkrócona nazwa miesiącacze
ddDzień (01–31)07
dDzień bez zera wiodącego7
ddddPełna nazwa dnianiedziela
HHGodzina 24h (00–23)14
hhGodzina 12h (01–12)02
mmMinuta (00–59)30
ssSekunda (00–59)05
fffMilisekundy (3 cyfry)000
ttAM/PMPM
zzzOffset strefy czasowej+02:00

⚠️ Pułapka: MM (miesiąc) vs mm (minuta)

To najczęstszy błąd przy ręcznym budowaniu formatów. Wielkość liter ma znaczenie.

DateTime dt = new DateTime(2026, 6, 7, 14, 30, 5);

// ✅ Poprawnie — MM to miesiąc
Console.WriteLine(dt.ToString("dd.MM.yyyy"));   // 07.06.2026

// ❌ Błąd — mm to MINUTA, nie miesiąc!
Console.WriteLine(dt.ToString("dd.mm.yyyy"));   // 07.30.2026 ← buga w dacie

// Inne mylące pary:
// HH = godzina 24h    vs    hh = godzina 12h
// dddd = nazwa dnia   vs    d  = numer dnia
// MMMM = pełna nazwa  vs    MM = numer miesiąca

💡 Zapamiętaj: miesiąc to WIELKIE M, minuta to małe m. Godzina 24h to WIELKIE H, godzina 12h to małe h.

3. Formatowanie z CultureInfo — nie ufaj maszynie

Wynik ToString("D") bez podania kultury zależy od ustawień maszyny, na której działa kod. Na serwerze produkcyjnym może być inaczej niż na Twoim lokalnym środowisku.

using System.Globalization;

DateTime dt = new DateTime(2026, 6, 7, 14, 30, 5);

// Konkretna kultura — wynik przewidywalny niezależnie od maszyny
Console.WriteLine(dt.ToString("D", new CultureInfo("pl-PL")));
// niedziela, 7 czerwca 2026

Console.WriteLine(dt.ToString("D", new CultureInfo("en-US")));
// Sunday, June 7, 2026

Console.WriteLine(dt.ToString("D", new CultureInfo("de-DE")));
// Sonntag, 7. Juni 2026

// Invariant — ASCII, niezależne od kultury (do API, plików, logów)
Console.WriteLine(dt.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
// 2026-06-07

Kiedy używać InvariantCulture?

  • Zawsze przy zapisywaniu daty do bazy danych, pliku CSV, logu lub odpowiedzi API.
  • Zawsze gdy data jest odczytywana i interpretowana maszynowo (nie wyświetlana użytkownikowi).
  • Nie przy wyświetlaniu daty w UI — tam chcesz kulturę użytkownika.

4. Formatowanie dat w interpolacji stringów

Możesz podać specyfikator formatu bezpośrednio w interpolacji — wynik jest taki sam jak ToString(format):

DateTime dt = new DateTime(2026, 6, 7, 14, 30, 5);

string display  = $"Data: {dt:dd.MM.yyyy}";          // Data: 07.06.2026
string iso      = $"ISO: {dt:yyyy-MM-dd}";            // ISO: 2026-06-07
string full     = $"Zamówienie z {dt:D}";             // Zamówienie z niedziela, 7 czerwca 2026
string logEntry = $"[{dt:yyyy-MM-dd HH:mm:ss}] INFO"; // [2026-06-07 14:30:05] INFO

⚠️ Interpolacja używa kultury bieżącego wątku — tak samo jak ToString(format) bez CultureInfo. Dla API/pliku użyj FormattableString.Invariant($"{dt:yyyy-MM-dd}") lub dt.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture).

5. Parsowanie dat — ParseExact i TryParseExact

Formatowanie to jedna strona medalu. Równie ważne jest parsowanie stringa z powrotem do DateTime. Unikaj DateTime.Parse() dla niezaufanych danych — rzuca wyjątek jeśli format nie pasuje. Zamiast tego:

using System.Globalization;

// TryParseExact — bezpieczna wersja (nie rzuca wyjątku)
string input = "07.06.2026";

bool success = DateTime.TryParseExact(
    input,
    "dd.MM.yyyy",
    CultureInfo.InvariantCulture,
    DateTimeStyles.None,
    out DateTime result);

if (success)
    Console.WriteLine(result);   // 07.06.2026 00:00:00

// Kilka obsługiwanych formatów naraz:
string[] formats = { "dd.MM.yyyy", "yyyy-MM-dd", "d/M/yyyy" };

DateTime.TryParseExact(
    "2026-06-07",
    formats,
    CultureInfo.InvariantCulture,
    DateTimeStyles.None,
    out DateTime result2);   // 07.06.2026 00:00:00

💡 Zasada: ParseExact gdy format jest znany i kontrolowany (wewnętrzny system). TryParseExact dla danych od użytkownika lub zewnętrznych systemów. DateTime.Parse tylko dla prototypów i skryptów.

6. DateTimeOffset — gdy strefa czasowa ma znaczenie

DateTime nie przechowuje informacji o strefie czasowej. Jeśli Twoja aplikacja działa globalnie lub obsługuje użytkowników z różnych stref — użyj DateTimeOffset.

DateTimeOffset now = DateTimeOffset.UtcNow;

Console.WriteLine(now.ToString("o"));
// 2026-06-07T12:30:05.1234567+00:00   (UTC, round-trip)

DateTimeOffset local = DateTimeOffset.Now;
Console.WriteLine(local.ToString("o"));
// 2026-06-07T14:30:05.1234567+02:00   (czas lokalny z offsetem)

// Konwersja do strefy czasowej użytkownika:
var warsawZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time");
DateTimeOffset warsawTime = TimeZoneInfo.ConvertTime(now, warsawZone);
Console.WriteLine(warsawTime.ToString("dd.MM.yyyy HH:mm zzz"));
// 07.06.2026 14:30 +02:00

7. DateOnly i TimeOnly — C# 10+ (.NET 6+)

W wielu przypadkach nie potrzebujesz pełnego DateTime — chcesz tylko datę albo tylko czas. Od .NET 6 masz do tego dedykowane typy:

// DateOnly — tylko data, bez godziny
DateOnly date = new DateOnly(2026, 6, 7);
Console.WriteLine(date.ToString("yyyy-MM-dd"));   // 2026-06-07
Console.WriteLine(date.ToString("d", new CultureInfo("pl-PL")));  // 07.06.2026
Console.WriteLine(date.AddDays(30));              // 07/07/2026

// TimeOnly — tylko czas, bez daty
TimeOnly time = new TimeOnly(14, 30, 5);
Console.WriteLine(time.ToString("HH:mm"));    // 14:30
Console.WriteLine(time.ToString("HH:mm:ss")); // 14:30:05
Console.WriteLine(time.AddHours(2));          // 16:30

// Konwersja z/do DateTime:
DateTime dt = new DateTime(2026, 6, 7, 14, 30, 5);
DateOnly dateOnly = DateOnly.FromDateTime(dt);   // 07.06.2026
TimeOnly timeOnly = TimeOnly.FromDateTime(dt);   // 14:30:05

Kiedy używać: data urodzenia, harmonogram wizyt (bez godziny), godziny otwarcia (bez daty), EF Core mapowanie kolumn date i time w SQL Server.

8. Wzorce produkcyjne — UTC w środku, lokalny na zewnątrz

Najczęściej stosowany wzorzec w produkcyjnych systemach .NET:

// ✅ PRZECHOWUJ w UTC — niezależne od strefy serwera i użytkownika
public class Order
{
    public DateTime CreatedAtUtc { get; init; } = DateTime.UtcNow;  // zawsze UTC
    // lub:
    public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.UtcNow;
}

// ✅ SERIALIZACJA do JSON/API — ISO 8601, InvariantCulture
public string ToIso(DateTime utcDate)
    => utcDate.ToString("o", CultureInfo.InvariantCulture);
// "2026-06-07T12:30:05.0000000"

// ✅ WYŚWIETLANIE użytkownikowi — kultura użytkownika
public string FormatForUser(DateTime utcDate, string userTimeZoneId, CultureInfo userCulture)
{
    var tz   = TimeZoneInfo.FindSystemTimeZoneById(userTimeZoneId);
    var local = TimeZoneInfo.ConvertTimeFromUtc(utcDate, tz);
    return local.ToString("D", userCulture);
}
// Dla Warszawy, pl-PL: "niedziela, 7 czerwca 2026"

// ✅ LOGI — sortowalne, invariant, z milisekundami
public string FormatForLog(DateTime utcDate)
    => utcDate.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture);
// "2026-06-07T14:30:05.123Z"

Najczęstsze błędy

❌ Błąd 1: mm zamiast MM w dacie

// ❌ Zwróci minutę zamiast miesiąca!
dt.ToString("dd.mm.yyyy");   // "07.30.2026" — buga

// ✅
dt.ToString("dd.MM.yyyy");   // "07.06.2026"

❌ Błąd 2: Poleganie na kulturze maszyny w API

// ❌ Wynik zależy od kultury serwera — nieprzewidywalny
var date = DateTime.UtcNow.ToString("D");

// ✅ Zawsze podaj kulturę dla API/pliku/bazy
var date = DateTime.UtcNow.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);

❌ Błąd 3: DateTime.Now zamiast UtcNow w serwisach

// ❌ Czas lokalny serwera — zależy od konfiguracji, zmienia się przy DST
public class OrderService
{
    public DateTime GetTimestamp() => DateTime.Now;

// ✅ UTC — niezmienny, porównywalny, niezależny od serwera
    public DateTime GetTimestamp() => DateTime.UtcNow;
}

❌ Błąd 4: DateTime.Parse dla niezaufanych danych

// ❌ Rzuci FormatException jeśli format nie pasuje
DateTime dt = DateTime.Parse(userInput);

// ✅ TryParseExact — nie rzuca, możesz obsłużyć błąd
if (!DateTime.TryParseExact(userInput, "dd.MM.yyyy",
        CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dt))
{
    return BadRequest("Nieprawidłowy format daty. Wymagany: dd.MM.yyyy");
}

Ściąga — szybki wybór

ScenariuszRozwiązaniePrzykład wyniku
Data dla użytkownika (pl-PL)dt.ToString("d", new CultureInfo("pl-PL"))07.06.2026
Pełna data słownadt.ToString("D", new CultureInfo("pl-PL"))niedziela, 7 czerwca 2026
ISO 8601 dla APIdt.ToString("o", CultureInfo.InvariantCulture)2026-06-07T14:30:05.0000000
Prosta data dla APIdt.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)2026-06-07
Log (sortowalny)dt.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture)2026-06-07T14:30:05.123Z
Nazwa plikudt.ToString("yyyyMMdd_HHmmss")20260607_143005
Czas w nagłówku HTTPdt.ToString("R")Sun, 07 Jun 2026 14:30:05 GMT
Parsowanie niezaufanego wejściaDateTime.TryParseExact(str, format, ...)

Podsumowanie

  • MM = miesiąc, mm = minuta — ta pułapka kosztuje devów godziny debugowania.
  • Przechowuj UTC (DateTime.UtcNow lub DateTimeOffset.UtcNow), wyświetlaj w lokalu użytkownika.
  • API i pliki — zawsze CultureInfo.InvariantCulture lub format "o"/"s".
  • UI — podawaj kulturę użytkownika jawnie, nie polegaj na kulturze maszyny.
  • ParsowanieTryParseExact dla niezaufanych danych wejściowych.
  • DateOnly/TimeOnly (C# 10+) gdy potrzebujesz tylko daty albo tylko czasu — typ mówi więcej niż komentarz.

Zobacz także — powiązane artykuły

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 Co dalej?

Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.

Dodaj komentarz

czytanie to początek

Zamień wiedzę w umiejętności

Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.

Pobieram roadmapę →