What I want to accomplish is to convert a DateTime (parsed from a string assumed to be in EST/EDT) to UTC. I am using NodaTime because I need to use Olson timezones.
Converting an invalid (skipped) DateTime to UTC using NodaTime's ZoneLocalMappingResolver is not converting minute and seconds part of the input because I have configured the CustomResolver to return the start of interval after the gap. NodaTime does not seem to have an equivalent of TimeZoneInfo.IsInvalidTime.
How do I use NodaTime to convert skipped datetime values to UTC and match the result of GetUtc() method in the Utils class below? (Utils.GetUtc method uses System.TimeZoneInfo not NodaTime)
This is the test case :
[TestMethod]
public void Test_Invalid_Date()
{
var ts = new DateTime(2013, 3, 10, 2, 15, 45);
// Convert to UTC using System.TimeZoneInfo
var utc = Utils.GetUtc(ts).ToString(Utils.Format);
// Convert to UTC using NodaTime (Tzdb/Olson dataabase)
var utcNodaTime = Utils.GetUtcTz(ts).ToString(Utils.Format);
Assert.AreEqual(utc, utcNodaTime);
}
This is what I am getting :
Assert.AreEqual failed. Expected:<2013-03-10 07:15:45.000000>. Actual:<2013-03-10 07:00:00.000000>.
Here is the Utils class (also on github):
using System;
using NodaTime;
using NodaTime.TimeZones;
/// <summary>
/// Functions to Convert To and From UTC
/// </summary>
public class Utils
{
/// <summary>
/// The date format for display/compare
/// </summary>
public const string Format = "yyyy-MM-dd HH:mm:ss.ffffff";
/// <summary>
/// The eastern U.S. time zone
/// </summary>
private static readonly NodaTime.DateTimeZone BclEast = NodaTime.DateTimeZoneProviders.Bcl.GetZoneOrNull("Eastern Standard Time");
private static readonly TimeZoneInfo EasternTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
private static readonly NodaTime.DateTimeZone TzEast = NodaTime.DateTimeZoneProviders.Tzdb.GetZoneOrNull("America/New_York");
private static readonly ZoneLocalMappingResolver CustomResolver = Resolvers.CreateMappingResolver(Resolvers.ReturnLater, Resolvers.ReturnStartOfIntervalAfter);
public static DateTime GetUtc(DateTime ts)
{
return TimeZoneInfo.ConvertTimeToUtc(EasternTimeZone.IsInvalidTime(ts) ? ts.AddHours(1.0) : ts, EasternTimeZone);
}
public static DateTime GetUtcTz(DateTime ts)
{
var local = LocalDateTime.FromDateTime(ts);
var zdt = TzEast.ResolveLocal(local, CustomResolver);
return zdt.ToDateTimeUtc();
}
public static DateTime GetUtcBcl(DateTime ts)
{
var local = LocalDateTime.FromDateTime(ts);
var zdt = BclEast.ResolveLocal(local, CustomResolver);
return zdt.ToDateTimeUtc();
}
}
NodaTime does not seem to have an equivalent of TimeZoneInfo.IsInvalidTime.
Well, rather than just asking that one question - and then having to ask follow-on ones - you use DateTimeZone.MapLocal
. That gives you everything you could know about a local to UTC mapping: whether it's unambiguous, ambiguous, or invalid.
Alternatively, use ResolveLocal
but with your own custom SkippedTimeResolver
delegate.
For example, making this change makes your code work for me:
private static readonly ZoneLocalMappingResolver CustomResolver =
Resolvers.CreateMappingResolver(Resolvers.ReturnLater, AddGap);
// SkippedTimeResolver which adds the length of the gap to the
// local date and time.
private static ZonedDateTime AddGap(LocalDateTime localDateTime,
DateTimeZone zone,
ZoneInterval intervalBefore,
ZoneInterval intervalAfter)
{
long afterMillis = intervalAfter.WallOffset.Milliseconds;
long beforeMillis = intervalBefore.WallOffset.Milliseconds;
Period gap = Period.FromMilliseconds(afterMillis - beforeMillis);
return zone.AtStrictly(localDateTime + gap);
}
(There are other equivalent ways of doing it, of course.)
I'd personally suggest trying to avoid converting to and from DateTime
unless you really have to - I would do as much as possible in Noda Time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With