Menu

C# DateTime Format: Standard and Custom Format Strings, Culture, ISO 8601

How to format a DateTime as a string in C#: the standard format strings (d, D, o, s, u, t), custom patterns such as yyyy-MM-dd HH:mm:ss, a full specifier table, how culture changes the output, ISO 8601 round-trip with "o", and the mm vs MM and hh vs HH mistakes.

This page includes runnable editors - edit, run, and see output instantly.

DateTime.ToString(format) turns a date into text. The format is either a standard format string (a single letter such as d or o, whose exact pattern comes from the culture) or a custom pattern built from specifiers such as yyyy, MM and HH. The same strings work in interpolation, $"{date:yyyy-MM-dd}", and in string.Format.

Output:

2026-10-09
2026-10-09 18:30:07
09/10/2026
Oct 9, 2026
Friday, October 9
6:30 PM
18:30:07.045
Shipped 2026-10-09 at 18:30

The examples on this page pass CultureInfo.InvariantCulture so the output is the same on every machine; without it, ToString uses the current culture, which can change month names, separators and even the calendar.

Custom format specifiers

A custom format is a pattern in which certain letters stand for parts of the date. They are case-sensitive: M is month and m is minute, H is the 24-hour hour and h the 12-hour hour.

SpecifierMeaningExample for 2026-10-09 18:30:07.045
yyyyYear, 4 digits2026
yyYear, 2 digits26
MMMMFull month nameOctober
MMMAbbreviated month nameOct
MMMonth, 2 digits10
MMonth, 1 or 2 digits10
ddddFull day nameFriday
dddAbbreviated day nameFri
ddDay of month, 2 digits09
dDay of month, 1 or 2 digits (in a pattern)9
HHHour, 00 to 2318
HHour, 0 to 2318
hhHour, 01 to 1206
hHour, 1 to 126
mmMinute, 2 digits30
ssSecond, 2 digits07
fffMilliseconds045
fffffffTicks (seven fractional digits)0450000
ttAM/PM designatorPM
zzzOffset from UTC (DateTimeOffset or local time)+02:00
KZone information: Z, an offset, or nothingZ (for a UTC value)
/The culture's date separator/ or . or -
:The culture's time separator:
'text' or \cLiteral text or character'at'

Any other character is copied as it is. That includes letters that are not specifiers, which is how "YYYY-DD" quietly prints YYYY-DD: there is no uppercase Y or D specifier in .NET.

A single letter on its own is read as a standard format, not a custom one: ToString("d") is the short date pattern, not the day number. To get just the day, write ToString("%d") or use the Day property.

Output:

10/09/2026
9
20261009_183007
Week of Oct 9
18h30
2026-10-09T18:30:07
YYYY-DD

Standard format strings

Standard formats are single letters whose actual pattern is defined by the culture. They are the right choice for text shown to users, because each culture gets its own conventions for free.

Output:

d  10/09/2026
D  Friday, 09 October 2026
f  Friday, 09 October 2026 18:30
g  10/09/2026 18:30
G  10/09/2026 18:30:07
M  October 09
Y  2026 October
t  18:30
T  18:30:07
o  2026-10-09T18:30:07.0000000Z
s  2026-10-09T18:30:07
u  2026-10-09 18:30:07Z
r  Fri, 09 Oct 2026 18:30:07 GMT
en-US d: 10/9/2026, D: Friday, October 9, 2026
CodeNameCulture-dependent?
d / DShort / long dateYes
t / TShort / long timeYes
f / FLong date with short / long timeYes
g / GShort date with short / long timeYes
MMonth and dayYes
YYear and monthYes
oRound-trip ISO 8601, 7 fractional digits, zoneNo
sSortable ISO 8601, no fractions, no zoneNo
uUniversal sortable, ends in ZNo
rRFC 1123, for HTTP headersNo

o, s, u and r produce the same text in every culture, which makes them safe for logs, file names and data exchange. Note that on a DateTime, u and r do not convert the value to UTC; they print it as is and label it as UTC, so call ToUniversalTime() first (or use DateTime.UtcNow) if the value is local. (On a DateTimeOffset they do convert.)

Culture changes the output

The standard formats, the month and day names, and the / and : separators all come from the culture. Pass one as the second argument to ToString:

Output:

en-US  10/9/2026
en-GB  09/10/2026
de-DE  09.10.2026
fr-FR  09/10/2026
ja-JP  2026/10/09
Freitag, 9. Oktober 2026
vendredi 9 octobre 2026
09.10.2026
09/10/2026

Culture data is not frozen. .NET on Linux and macOS reads it from the ICU library installed on the system, and since .NET 5 so does .NET on Windows 10 and later (.NET Framework uses Windows' own NLS data), so a pattern can change slightly between versions and platforms: recent ICU releases, for example, put a narrow no-break space rather than a plain space before "PM" in the US short time format, and some give Spanish short dates without the leading zero. That is one more reason never to parse text that was formatted for display.

Two rules follow. For text a person reads, format with their culture (on a server, the one from the request, not the server's) and prefer standard formats, so a German reader sees 09.10.2026 and an American reader 10/9/2026. For text a program reads, use CultureInfo.InvariantCulture and an unambiguous pattern such as yyyy-MM-dd or o. A date written with the current culture and parsed on another machine is a classic source of swapped days and months.

ISO 8601 and round-tripping with "o"

The round-trip format "o" writes every tick plus the zone information, so parsing the text gives back exactly the same DateTime, including its Kind:

Output:

2026-10-09T18:30:07.0450000Z
2026-10-09T18:30:07.0450000
2026-10-09T20:30:07.0000000+02:00
True
Utc
2026-10-09T18:30:07Z
2026-10-09 20:30 +02:00

The Z suffix marks UTC; a DateTime with Kind Unspecified gets no suffix; a local time or a DateTimeOffset gets its offset (+02:00). Parse with DateTimeStyles.RoundtripKind so the Kind survives. JSON serializers, databases and JavaScript's Date all read this format.

Formatting TimeSpan

TimeSpan has its own, smaller set of custom specifiers: d (days), hh, mm, ss, and fff. Unlike DateTime, every literal character, including : and ., must be escaped with a backslash or quoted:

Output:

1.03:25:09.1200000
1.03:25:09.1200000
03:25:09
1.03:25
25:09.120
27:25

hh in a TimeSpan format is the hours component (0 to 23), so a 27-hour duration prints as 03. For totals above a day, compute from TotalHours as in the last line. elapsed.ToString("hh:mm") without the backslash throws a FormatException.

Common mistakes

  • mm for month. "yyyy-mm-dd" prints minutes in the middle: 2026-30-09. Month is MM.
  • hh without tt. "hh:mm" is a 12-hour clock with no AM or PM, so 06:30 is ambiguous. Use HH for 24-hour time.
  • YYYY and DD. Not specifiers in .NET; they are printed literally. Use yyyy and dd.
  • / in a pattern for data. It is the culture's date separator. Quote it or use the invariant culture.
  • Default ToString() in logs or files. The format depends on the machine. Pass a format and a culture.
  • Local time labelled as UTC. "u", "r" and a hand-written 'Z' do not convert; convert first.
  • Parsing with a different pattern than you wrote with. Keep format strings for data in one constant and use it with both ToString and ParseExact.

Frequently Asked Questions

How do I format a DateTime as yyyy-MM-dd in C#?

date.ToString("yyyy-MM-dd") gives 2026-10-09. In an interpolated string write $"{date:yyyy-MM-dd}". Add " HH:mm:ss" for the time in 24-hour form. Pass CultureInfo.InvariantCulture as a second argument when the text is for a file or another program, so the calendar, month names and separators never depend on the machine.

What is the difference between mm and MM in C# date formats?

MM is the month (01 to 12) and mm is the minute (00 to 59). The format is case-sensitive, so yyyy-mm-dd prints the minutes where the month should be, a bug that is easy to miss when you glance at a single value. Likewise HH is the 24-hour hour and hh the 12-hour hour.

How do I format a DateTime in ISO 8601 in C#?

Use the round-trip format "o": DateTime.UtcNow.ToString("o") gives 2026-10-09T18:30:07.0450000Z, with seven fractional digits and a Z for UTC or an offset for local times. "s" gives the shorter sortable form without fractions or zone, and "yyyy-MM-ddTHH:mm:ssZ" is a common custom variant.

How do I show AM and PM in a C# date format?

Use hh or h for the 12-hour clock and tt for the AM/PM designator: date.ToString("h:mm tt", CultureInfo.GetCultureInfo("en-US")) gives 6:30 PM. The designator comes from the culture, and some cultures have none, so pass a culture explicitly when you need AM and PM.

Why does my date format show dots instead of slashes?

In a format string, / is not a literal slash but the culture's date separator, and : is the culture's time separator. On a German machine dd/MM/yyyy prints 09.10.2026. Quote the character to force it, dd'/'MM'/'yyyy, or format with CultureInfo.InvariantCulture.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED