In this article, we are going to learn how to convert DateTime to ISO 8601 string in C#. There are various formats of ISO 8601 compliant date and time strings. We are going to build a console application and explore all these different formats.
Let’s start.
Standard DateTime Format Strings
The standard DateTime
format specifier is the most convenient way to get an ISO 8601 output. While there are many standard format specifiers, only three of them can give us ISO 8601 compliant output: “s“, “o“, “u“.
To begin, let’s update our Program
class and declare two DateTime
objects, one local and another as a UTC standard type:
var localTime = new DateTime(2022, 1, 13, 16, 25, 35, 125, DateTimeKind.Local); var utcTime = new DateTime(2022, 1, 13, 16, 25, 35, 125, DateTimeKind.Utc);
Now, let’s explore the different specifiers.
Sortable Format Specifier (“s”)
The sortable format specifier (“s“) is useful to get an ISO 8601 compliant sortable string:
Console.WriteLine($"Local: {localTime.ToString("s")}"); // 2022-01-13T16:25:35 Console.WriteLine($"UTC: {utcTime.ToString("s")}"); // 2022-01-13T16:25:35
This is the most common way of getting ISO 8601 compliant DateTime
strings.
Round-trip Format Specifier (“o”)
The round-trip format specifier (“o“) is useful to get an ISO 8601 string which includes time-zone information:
Console.WriteLine($"Local: {localTime.ToString("o")}"); // 2022-01-13T16:25:35.1250000+06:00 Console.WriteLine($"UTC: {utcTime.ToString("o")}"); // 2022-01-13T16:25:35.1250000Z
We can see that the output of localTime
includes the time-zone information at the end (+6:00) which is interpreted as UTC+06:00
. In contrast, utcTime
prints just ‘Z‘ at the end, which complies with the ISO 8601 standard for UTC+00:00
DateTime. In addition, the output contains the fraction of seconds part up to 7 digits. This is why round-trip format is recommended when accuracy is an important concern.
Universal Format Specifier (“u”)
While working with DateTime
, it’s a common scenario to get the output directly in UTC value as well as in ISO 8601 compliant format. We can use the universal format specifier (“u“) for such cases.
Unlike the previous two cases, we need slight customization here, so let’s create an extension method:
public static string ToUniversalIso8601(this DateTime dateTime) { return dateTime.ToUniversalTime().ToString("u").Replace(" ", "T"); }
In this method, we convert the date to UTC value and then format it using ToString("u")
. Furthermore, we replace whitespace with “T“. This is because ISO 8601 does not allow whitespace as the date-time separator but expects “T” instead. So, we have our extension method ready for standard ISO 8601 compliant UTC output:
Console.WriteLine($"Local: {localTime.ToUniversalIso8601()}"); // 2022-01-13T10:25:35Z Console.WriteLine($"UTC: {utcTime.ToUniversalIso8601()}"); // 2022-01-13T16:25:35Z
As expected, the resulting strings are all in UTC standard.
Custom DateTime Format Strings
The standard C# format strings can’t cover all the output patterns that ISO 8601 supports. But, we have custom format strings that can cover the majority of the cases.
Similarly to the standard formats, we can simply call the ToString
method with the custom format:
var output = localTime.ToString("yyyy-MM-ddTHH:mm:ssK");
Not all custom formats are ISO 8601 compliant. According to ISO 8601 rules, we can use various combinations of date and time formats as long as the format honors the chronological order of date and time components. There are certain other restrictions, too. For example, we can use “yyyy-MM” but not “yyyyMM” which is prohibited because of ambiguity.
ISO 8601 Compliant Custom DateTime Formats
So, what are the supported formats then? Let’s have a look at this table of some compliant formats along with their effect on localTime
:
Format | Output | Type |
---|---|---|
yyyy-MM-dd | 2022-01-13 | Calendar Date |
yyyyMMdd | 20220113 | Calendar Date |
yyyy-MM | 2022-01 | Calendar Date |
–MM-dd | –01-13 | Calendar Date |
–MMdd | –0113 | Calendar Date |
THH:mm:ss.fff | T16:25:35.125 | Time |
THH:mm:ss | T16:25:35 | Time |
THH:mm | T16:25 | Time |
THH | T16 | Time |
THHmmssfff | T162535125 | Time |
yyyyMMddTHHmmssK | 20220113T162535+06:00 | Date and Time |
yyyyMMddTHHmm | 20220113T1625 | Date and Time |
yyyy-MM-ddTHH:mmZ | 2022-01-13T16:25Z | Date and Time |
yyyyMMddTHHmmsszzz | 20220113T162535+06:00 | Date and Time |
yyyyMMddTHHmmsszz | 20220113T162535+06 | Date and Time |
Custom Formats with Time Zone
We’ve used several custom formats that include special characters ‘Z‘, ‘z‘, and ‘K‘. It’s worth knowing a bit more about them as they have an essential role in producing ISO 8601 compliant output with time-zone information.
‘Z‘ does not have any special interpretation as a C# formatting literal, but does have special meaning from ISO 8601 perspective. Any date string with ‘Z‘ appended at the end implies that it represents a UTC DateTime
.
In contrast, ‘z’ and ‘K’ are purely C# formatting literals that add time zone information based on the DateTimeKind
value associated with the DateTime
object. While ‘K‘ provides full time-zone phrase, ‘z‘ offers flexibility to print in a short phrase:
var format = "yyyy-MM-ddTHH:mm:ssK"; var unspecified = new DateTime(2022, 1, 13, 16, 25, 30, DateTimeKind.Unspecified); var utc = new DateTime(2022, 1, 13, 16, 25, 30, DateTimeKind.Utc); var local = new DateTime(2022, 1, 13, 16, 25, 30, DateTimeKind.Local); Console.WriteLine(unspecified.ToString(format)); // 2022-01-13T16:25:30 Console.WriteLine(utc.ToString(format)); // 2022-01-13T16:25:30Z Console.WriteLine(local.ToString(format)); // 2022-01-13T16:25:30+06:00 Console.WriteLine(local.ToString("yyyy-MM-ddTHH:mm:sszz")); // 2022-01-13T16:25:30+06
We can see that the time zones have been applied to the resulting strings as expected.
Week Dates
ISO 8601 week date patterns are something that we can’t produce directly using format strings. So, we are going to build extension methods that can give us the desired outputs.
Week dates can be presented in two forms:
- “yyyy-Www“
- “yyyy-Www–D“
Here, [ww] stands for weak of the year in number, and [D] stands for the day of the week in number. We can also omit the separators (‘-‘). For example, our localTime
date can be presented as “2022-W03” or “2022W03” in short form and “2022-W03-4” or “2022W034” in extended form.
Let’s create our extension methods for both short and extended versions:
public static string ToShortIso8601WeekDateString(this DateTime dateTime, bool useSeparator = true) { var separator = useSeparator ? "-" : string.Empty; var culture = CultureInfo.InvariantCulture; var format = culture.DateTimeFormat; var weekOfYear = culture.Calendar.GetWeekOfYear(dateTime, format.CalendarWeekRule, format.FirstDayOfWeek); var weekPlaceHolder = $"{weekOfYear}".PadLeft(2, '0'); return $"{dateTime.Year}{separator}W{weekPlaceHolder}"; } public static string ToExtendedIso8601WeekDateString(this DateTime dateTime, bool useSeparator = true) { var separator = useSeparator ? "-" : string.Empty; return $"{dateTime.ToShortIso8601WeekDateString(useSeparator)}{separator}{(int)dateTime.DayOfWeek}"; }
In the first method, we use invariant culture because ISO 8601 does not allow culture-specific outputs. We get two necessary pieces of information from this culture
instance: DateTimeFormat
and Calendar
. Subsequently, we retrieve the week number using the calendar’s GetWeekOfYear
method. Finally, we render this week number with necessary padding because the [ww] placeholder needs to be filled in full:
// Week dates - short form Console.WriteLine(localTime.ToShortIso8601WeekDateString()); // 2022-W03 Console.WriteLine(localTime.ToShortIso8601WeekDateString(useSeparator: false)); // 2022W03 // Week dates - extended form Console.WriteLine(localTime.ToExtendedIso8601WeekDateString()); // 2022-W03-4 Console.WriteLine(localTime.ToExtendedIso8601WeekDateString(useSeparator: false)); // 2022W034
Nice! We have the output in desired week date formats.
If you’re interested in more useful stuff about week dates, check out: Check if DateTime is Weekend or Weekday.
Ordinal Dates
Similarly to the week dates, we will need custom solutions for ISO 8601 ordinal date patterns. Ordinal dates can be represented as “yyyy–DDD” or “yyyyDDD” where [DDD] indicates the day of the year.
Let’s create an extension method named ToIso8601OrdinalDateString
to manage the ordinal dates:
public static string ToIso8601OrdinalDateString(this DateTime dateTime, bool useSeparator = true) { var separator = useSeparator ? "-" : string.Empty; var dayPlaceHolder = $"{dateTime.DayOfYear}".PadLeft(3, '0'); return $"{dateTime.Year}{separator}{dayPlaceHolder}"; }
In this method, we retrieve the day of the year and render it with appropriate padding to comply with the [DDD] pattern:
Console.WriteLine(localTime.ToIso8601OrdinalDateString()); // 2022-013 Console.WriteLine(localTime.ToIso8601OrdinalDateString(useSeparator: false)); // 2022013
So, we now have the desired output in ordinal date format.
Conclusion
In this article, we have learned a few different ways to convert DateTime to ISO 8601 string in C#. We have also built some useful extension methods that can convert DateTime to complex ISO 8601 patterns including week dates and ordinal dates.