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. 

To download the source code for this article, you can visit our GitHub repository.

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“.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

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:

FormatOutputType
yyyy-MM-dd2022-01-13Calendar Date
yyyyMMdd20220113Calendar Date
yyyy-MM2022-01Calendar Date
–MM-dd–01-13Calendar Date
–MMdd–0113Calendar Date
THH:mm:ss.fffT16:25:35.125Time
THH:mm:ssT16:25:35Time
THH:mmT16:25Time
THHT16Time
THHmmssfffT162535125Time
yyyyMMddTHHmmssK20220113T162535+06:00Date and Time
yyyyMMddTHHmm20220113T1625Date and Time
yyyy-MM-ddTHH:mmZ2022-01-13T16:25ZDate and Time
yyyyMMddTHHmmsszzz20220113T162535+06:00Date and Time
yyyyMMddTHHmmsszz20220113T162535+06Date 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.

Zdoes 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-WwwD

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 “yyyyDDD” 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.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!