25

What is the idiomatic way to get the system's time as a LocalDateTime in Noda Time? The most direct method I could think of would be

var dt = DateTime.Now
LocalDateTime systemTime = new LocalDateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute);

but given that Noda Time's entire purpose is to replace DateTime with something that has nicer semantics, I assumed there was a preferred method than to use DateTime in the above manner. The best I could come up with using Noda's facilities is

var zone = NodaTime.TimeZones.BclDateTimeZone.ForSystemDefault();
LocalDateTime systemTime = SystemClock.Instance.Now.InZone(zone).LocalDateTime;

but this seems quite verbose.

Matt Kline
  • 10,149
  • 7
  • 50
  • 87

1 Answers1

54

Your second example is exactly how you would do this. It is intentionally verbose. See Noda Time's design philosophy.

Think of it in three parts:

  1. Get the current moment in time:

    // Instant now = SystemClock.Instance.Now;               // NodaTime 1.x
    Instant now = SystemClock.Instance.GetCurrentInstant();  // NodaTime 2.x
    
  2. Get the system's time zone. (This is the preferred syntax)

    DateTimeZone tz = DateTimeZoneProviders.Bcl.GetSystemDefault();
    
  3. Apply the time zone to the instant:

    ZonedDateTime zdt = now.InZone(tz);
    

Your last step of getting a LocalDateTime is trivial, but recognize that when you do it, you are stripping away any time zone information. "Local" in NodaTime does not mean "local to the computer where the code is running". (In other words, it's not like DateTimeKind.Local)

Additional things to think about:

  • You may prefer to abstract the clock using the IClock interface:

    IClock clock = SystemClock.Instance;
    // Instant now = clock.Now;               // NodaTime 1.x
    Instant now = clock.GetCurrentInstant();  // NodaTime 2.x
    

    Then you could pass the clock in as a method parameter, or inject it with your favorite DI/IoC framework. (Autofac, Ninject, StructureMap, whatever...). The advantage is then you could use a NodaTime.Testing.FakeClock during your unit tests. See Unit Testing with Noda Time for more details.

  • You might also prefer to pass in the DateTimeZone so you can run your code anywhere without being tied to the system time zone. This is important for server applications, but less so for desktop/mobile apps.

  • If you have other work that's using IANA time zones, or if you're using the .NET Standard build of Noda Time, then change step 2 to use the Tzdb provider:

    DateTimeZone tz = DateTimeZoneProviders.Tzdb.GetSystemDefault();
    

    The TZDB zones are much more accurate than the BCL zones. Noda Time will map your system's Windows (BCL) time zone to an IANA (TZDB) time zone in this initial call.

If you have curiosity into why DateTime.Now is so compact while Noda Time is so verbose, try decompiling DateTime.Now or looking at the MS reference sources. You'll see that it's doing essentially the same steps under the hood.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • Thanks for such a deep and informative answer! Re: the additional points, I do plan on injecting an IClock instead. Also, I do want to use the system time here. The code is for an application technicians will use in the field to set the time on a microcontroller that is "too dumb" to have a concept of time zone. I'm using Noda Time, however, because the same library will also be used on our server to communicate with the devices wirelessly once they are set up. – Matt Kline Jan 09 '14 at 21:37
  • I did something similar not to long ago. In my case, it made much more sense to associate each device with a particular named time zone, rather than using the time zone of the machine they were talking to. YMMV. – Matt Johnson-Pint Jan 09 '14 at 21:41
  • Also, you may need to think about how to handle DST changes on your device. That was another area I had to focus on with that project. – Matt Johnson-Pint Jan 09 '14 at 21:43
  • The device is associated with a particular time zone on the server, and time zone will be taken into account there, but the device itself is too dumb to know about it (hence the use of LocalDateTime). The device handles DST somewhat simplistically - it holds a date and time for when DST starts and matching pair for when it ends. Unfortunately, the device or its firmware are beyond my control. – Matt Kline Jan 09 '14 at 21:50
  • @MattJohnson might worth to update the example since the Now property has been deprecated and replaced by GetCurrentInstant(), https://codeblog.jonskeet.uk/2016/03/ – Natalie Perret Nov 09 '17 at 15:47
  • `DateTimeZoneProviders.Bcl` is not available, am I missing something or has this changed? – Markus Hütter Mar 23 '18 at 01:05
  • 2
    @MarkusHütter - Per [these docs](https://nodatime.org/2.2.x/api/NodaTime.DateTimeZoneProviders.html#NodaTime_DateTimeZoneProviders_Bcl), the `Bcl` provider is not available if you're using the .NET Standard build of Noda Time. Use the `Tzdb` provider instead. The results will be similar. – Matt Johnson-Pint Mar 23 '18 at 01:15