Noda Time

Richard Pickett

A breakdown of the "Noda Time' date and time library for .NET platforms.

hero background

In the ever-expanding realm of globalised software development, effective date and time management is a key cornerstone of success. Yet, many developers consider the temporal domain to be a tricky beast, which is exacerbated by the need to consider times on a global scale. Supporting the many time zones and their daylight savings rules around the world is already quite a colossal task, which is made all the more difficult given these rules are determined by governments worldwide and can change on a whim. Unfortunately, and perhaps perplexingly, the built-in .NET types for date and time management are just not up to this challenge. Luckily, NodaTime has emerged as a stalwart alternative and offers developers a powerful toolkit to conquer the temporal landscape. 

What is NodaTime? 

NodaTime is an open-source date and time library that was created by Jon Skeet to address the limitations and complexities of the built-in DateTime structure in .NET. Its internals were originally based on Joda-Time for Java, from where it sought its inspiration. Thankfully though, it was designed with an idiomatic .NET API, which makes it feel very natural and easy to use. NodaTime is an open-source date and time library that was created by Jon Skeet to address the limitations and complexities of the built-in DateTime structure in .NET. Its internals were originally based on Joda-Time for Java, from where it sought its inspiration. Thankfully though, it was designed with an idiomatic .NET API, which makes it feel very natural and easy to use.

What is wrong with DateTime? 

The built-in .NET type DateTime has a number of shortcomings: 

  • Local vs global time: The only way to tell if a DateTime is referring to local time or UTC time is based on the DateTime.Kind property, which is made more confusing by the Unspecified option. It is even possible to compare a local time with a UTC time, which really doesn’t make a lot of sense. 
  • Time zones: It lacks explicit support for time zones, which can be challenging at the best of times, but is particularly noticeable when trying to support daylight savings time transitions. 
  • Calendar systems: While .NET can display dates using other calendar systems, the DateTime type itself only supports the Gregorian calendar. For anyone developing an application with specific cultural requirements, this could be quite the constraint. 
  • Date-only scenarios: There are many scenarios that only require a date, but are still represented using DateTime. This can add complexity and cumbersome code because the time part is still there, even if ignored, and converting to text will always want to include the time. While .NET has more recently introduced a DateOnly type, it still requires developers to opt in because making breaking changes to the DateTime type at this stage would be catastrophic. 
  • Unit testing: The lack of an interface or factory for obtaining the current time makes unit testing difficult, often requiring the creation of manual wrappers or abstractions to mock DateTime instances, just to enable it to be tested. 

How is NodaTime better? 

NodaTime offers several advantages over the built-in DateTime type: 

  • Clear types: There are clear and specific types to cater to all the representations required. For example, there is a type for a date (LocalDate), another for a time (LocalTime), and yet another for a combined date and time (the ). The API is very clear as you move and convert between types. 
  • Local vs global time: There is clear separation between APIs for working in local time vs APIs for working in global time, which helps to avoid a lot of common pitfalls when dealing with time. In fact, these are completely separate types that cannot be directly compared. They must instead be explicitly converted to the same type to make the conversion possible. 
  • Time zones: Time zone handling appears to have been well-thought-out in NodaTime and relies on the IANA time zone database for a detailed list of time zones and their past and future time transitions, particularly due to daylight savings. Again, there are clear and explicit methods for converting times to and from different zones so nothing is left to chance. 
  • Calendar systems: In addition to the default ISO/Gregorian calendar, NodaTime supports a plethora of other calendars, including Julian, Coptic, Islamic, Persian, Um Al Qura, Hebrew, and Badíʻ calendars. Unlike DateTime, this is not just when displaying a formatted date, but internal calculations and operations also take place using a different calendar system. 
  • Unit testing: NodaTime simplifies unit testing by providing interfaces for dependency injection as well as test doubles. This enables developers to easily mock time-dependent tests, improving testability and code quality.  

Core types 

NodaTime’s core types can be divided into a few different groups. These are better depicted using the following infographic. 

The Instant type serves as a universal reference point, representing a precise moment in time across the globe, independent of any time zone or calendar system. This is akin to capturing the current moment, which happens at exactly the same point in time for everyone in the universe. That makes it a perfect type for storing timestamps and can also refer to an exact moment in the future. 

NodaTime excels in providing clarity and explicitness about what a particular date and/or time represents. For example, the LocalDateTime, LocalDate, and LocalTime variants is assumed to be local to its user so there is no option for a time zone or offset. Conversely, times can be accompanied by either a time zone or just a fixed offset. The OffsetDateTime type, along with its date and time only variants, allows for fixed offset specification, which is the closest to the built-in DateTimeOffset. 

Alternatively, the ZonedDateTime type pairs a specific time zone with a date and time, offering precision localisation and accounting for daylight savings transitions. Unlike the other types, there are no date and time only variants here because there are several instances where a single date or single time could have two different offsets in the same time zone due to daylight savings transitions. 

Time zones and daylight savings 

The easiest way to use time zones with NodaTime is to use the IANA Time Zone database (also known as the tz database) as the time zone data source. This database comes integrated with NodaTime, so it’s good to keep it up to date, but there are also ways to download the data separately and load that into NodaTime. Time zones are identified by area and location, for example Australia/Brisbane or America/Los_Angeles, which is also consistent across many other systems. 

Converting from Instant or ZonedDateTime to a LocalDateTime is always unambiguous because they refer to a specific point in time, whereas converting from a LocalDateTime to a ZonedDateTime may be ambiguous in some scenarios. For instance, a single local time may map to two different points in time—such as at the end of daylight savings, where 2am shifts back to 1am, resulting in two occurrences of 1:30am. On the other hand, a single local time may not map to any point in time—when turning the clocks forward at the start of daylight savings, 1am skips to 2am, so 1:30am didn’t occur at all. 

These ambiguities are easily managed using NodaTime’s dedicated conversion methods, that allow either a lenient or strict conversion. Lenient conversions prioritise the earlier option for ambiguous times or automatically shift forward for skipped times, while strict conversions will throw an exception in both scenarios. 

Unit testing  

NodaTime provides a dedicated testing package, which includes test doubles that are significantly more powerful than simply mocking the relevant interfaces. For example, FakeClock implements IClock but rather than just returning the current time, it can return any time you set it to, and the time returned can even automatically advance each time it is used. There is also a fake time zone source, and you can create fake time zones, including the ability to simulate daylight savings transitions. By decoupling unit tests from real time zones, this prevents your tests being at the mercy of a government who could change the time zone at any time. 

How do I get it? 

Installation is as simple as installing the NodaTime NuGet package, and registering the clock and time zone provider in your dependency injection container: 

services.AddSingleton<IClock>(SystemClock.Instance); 
services.AddSingleton(DateTimeZoneProviders.Tzdb); 

There are also serialisation packages available with the NodaTime.Serialization.* prefix and current support is available for Json.Net, System.Text.Json, and Protocol Buffers. Configuring is very easy due to the bundled extension methods, for example configuring for JSON is as easy as: 

jsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); 

There are a number of packages available to add support for NodaTime types to databases. One example for Entity Framework Core and SQL Server is the SimplerSoftware.EntityFrameworkCore.SqlServer.NodaTime package, which not only supports the type conversions, but also provides some database functions so common date and time calculations can be performed server-side. 

Conclusion 

Time is such a critical component of the globalised software development world we live in. With the complexities of time zones and daylight savings ever-present, relying solely on the built-in .NET date and time types can lead to a myriad of confusion and pitfalls. However, with NodaTime in your toolbelt, these challenges become a walk in the park. If you’re not already leveraging NodaTime in your projects, the time to do so is now. There’s simply no reason not to. NodaTime not only covers all the basic scenarios of DateTime, but does so with unparalleled clarity, elegance, precision, and flexibility not often seen in the software world. Embrace NodaTime and discover how a stitch in time management saves nine headaches down the line!