DateTime.ParseExact to NodaTime ZonedDateTime issue

I'm having an issue converting a parsed string into a correct NodaTime ZonedDateTime. The below method takes a string (allegedly in UTC) pushed by the brokers server, and returns a ZonedDateTime with the DateTimeZone being set to UTC. The domain model will only accept ZonedDateTime objects so this is happening at the system boundary.

For example, a string being passed as the argument being exactly '2017060623:20:10' would then return a ZonedDateTime like 2017-06-06T13-:20:10 (curiously a ten hour difference, my local time zone here in Sydney is +10 UTC).

public static ZonedDateTime GetZonedDateTimeUtcFromMarketDataString(string dateTime)
{
    var dotNetDateTime = DateTime.ParseExact(
        dateTime,
        "yyyyMMddHH:mm:ss,                
        CultureInfo.InvariantCulture)
        .ToUniversalTime();

    return new ZonedDateTime(Instant.FromDateTimeUtc(dotNetDateTime), DateTimeZone.Utc);
}

So I changed the method to the below, which works, but I'm sure there's a better way. I'm certain my error is that I'm somehow involving my local time zone, rather than keeping everything in UTC.

public static ZonedDateTime GetZonedDateTimeUtcFromMarketDataString(string dateTime)
{
    var parsedDateTime = DateTime.ParseExact(
        dateTime,
        "yyyyMMddHH:mm:ss",
        CultureInfo.InvariantCulture);

    var dotNetDateTime = new DateTime(
        parsedDateTime.Year,
        parsedDateTime.Month,
        parsedDateTime.Day,
        parsedDateTime.Hour,
        parsedDateTime.Minute,
        parsedDateTime.Second,
        DateTimeKind.Utc);

    return new ZonedDateTime(Instant.FromDateTimeUtc(dotNetDateTime), DateTimeZone.Utc);
}
Jon Skeet
people
quotationmark

I would suggest not using DateTime at all. You're using Noda Time - so go all in :)

To parse text as a ZonedDateTime, you use a ZonedDateTimePattern. Here's an example:

using System;
using NodaTime;
using NodaTime.Text;

class Program
{
    static void Main(string[] args)
    {
        string text = "2017060623:20:10";
        Console.WriteLine(GetZonedDateTimeUtcFromMarketDataString(text));

    }

    static readonly ZonedDateTimePattern ParsePattern =
        ZonedDateTimePattern.CreateWithInvariantCulture(
           "yyyyMMddHH:mm:ss",
           DateTimeZoneProviders.Tzdb); // Won't actually be used...

    static ZonedDateTime GetZonedDateTimeUtcFromMarketDataString(string dateTime)
        => ParsePattern.Parse(dateTime).Value;
}

The default template value already uses UTC as a time zone, so you don't need to worry about that.

Alternatively, you could use a LocalDateTimePattern given that the text you're using doesn't specify a time zone - parse to a LocalDateTime and then call LocalDateTime.InUtc().

All of this raises another question though, which is why you're parsing the value to a ZonedDateTime at all. If you've always got a UTC value, do you really need it to be a ZonedDateTime rather than an Instant? You don't know the time zone the value was originally observed in, so it feels more like a point in time to me.

people

See more on this question at Stackoverflow