Search code examples
c#utcnodatime

Get and format UTC Time corresponding to NodaTime ZonedDateTime value?


I have ZonedDateTime value. I need to obtain corresponding UTC time and to format it as ISO8601 string (without Time Zone). What's the right "NodaTime way" of doing it?

I understand that I can use ZonedDateTime.ToDateTimeUtc() method to get .Net DateTime Kind of Utc. Should I do it and then just use ToString()?

Like

var myresult = resZonedDateTime.ToDateTimeUtc().ToString("s")  

Should I really use "s" there?


Solution

  • There are a few different ways, depending on exactly what you're looking for.

    First, you need to decide whether you want a value like 2014-10-30T16:46:49 or one like 2014-10-30T16:46:49Z. Both are allowed by ISO8601, but the trailing Z at the end is specifically used when the value is UTC. If you send the string elsewhere, the receiving party will not have to guess what the basis for the value is.

    • If you want the Z, then convert your ZonedDateTime to an Instant using the .ToInstant() method.

      Instant instant = zdt.ToInstant();
      
    • If you don't want the Z, but still want the value to reflect UTC, then adjust your ZonedDateTime to UTC, then strip it down to the LocalDateTime using it's .LocalDateTime property.

      LocalDateTime ldt = zdt.WithZone(DateTimeZone.Utc).LocalDateTime;
      

    Next, you need to decide whether you want to format the string inline with the BCL-based API, or whether you want to use the pattern-based API. Noda Time supports both, which you can read about in the user guide.

    • The BCL-based API allows you to use methods you're already familiar with, such as .ToString() and .ToString("some format", someCulture) For example:

      string s = instant.ToString();
      

      or

      string s = ldt.ToString("s", CultureInfo.InvariantCulture);
      
    • The pattern-based API separates the work of parsing the format string from doing the actual formatting, as two steps:

       var pattern = InstantPattern.ExtendedIsoPattern;
       var s = pattern.Format(instant);
      

      It is much more efficient if you are going to be formatting many items. I typically use it when I'm working with LINQ. For example:

      var pattern = InstantPattern.GeneralPattern;
      var strings = instants.Select(pattern.Format);
      

      or

      var pattern = LocalDateTimePattern.GeneralIsoPattern;
      var strings = ldts.Select(pattern.Format);
      

    And finally, you need to think about the precision you want. In the pattern API, the "general" formats are precise to whole seconds. The "extended" formats include fractional seconds up to 7 decimal places. You could also create your own pattern using the Create... static methods on the various pattern classes.

    If you are just using ToString, then keep in mind that the default format may or may not be what you are looking for. Again, you can pass a format string. For Instant values, "g" is precise to whole seconds, but you would use a custom string of "yyyy'-'MM'-'dd'T'HH':'mm':'ss;FFFFFFF'Z'" if you need precision. For LocalDateTime values, "s" is precise to whole seconds, and "o" is precise to 7 decimals.

    Again, all of this is in the "Text" chapter of the user guide, which I would encourage you to read.