In preparation of a talk I gave in Karlsruhe in May 2014 and will give in Nürnberg in July 2014, I decided to start a serie of articles to ramp up the topics I'll cover during those conferences. Here's the first topic this talk covers: NodaTime. You'll (ultimately) find the other parts here: 2-JsonFX & Json.NET, 3-RestSharp and 4-TinyIoC.
Noda Time is an idiomatic port of Joda Time, a date and time handling library, to the .NET platform. It was intially written by Jon Skeets (that you probably know as "that-guy-that-knows-(almost)-everything-about-C#" on StackOverflow ;)
NodaTime can be found at http://nodatime.org and is available on Nuget with this exact same name.
What is wrong with the DateTime anyway?
The main problem of the DateTime API of the BCL (Base Class Library) is that this class is the representation of different concepts with one (or two) single type(s).
Obviously Date and Time are mixed in a
DateTime object and even if you can extract or manipulate one or the other separately, it still represents both types combined. Similarly, the
TimeSpan type wraps up the concepts of offset, periods or duration all in one.
If you - for instance - only want a Date, you still have to instantiate a
DateTime object and its time will be defaulted to "noon". If you then compare it with another
DateTime object, this time will be used as well in the comparison. If you make sure to compare what's comparable, e.g. only
DateTime objects which times are defaulted to "noon", you'll be fine. But it also makes it awfully easy to forget about that time and shoot yourself in the foot.
So what about that NodaTime?
To quote the user doc of NodaTime "Noda Time aims to change that with a library which is powerful and easy to use correctly"!
Here's a partially quoted / partially shortened & reformulated version of Jonathan Allen's introduction to NodaTime on InfoQ regarding the main objects:
Instant: represents an instant on the timeline since the Unix epoch. There are 10,000 ticks in a millisecond. This class is similar to a
DateTimeOffset with the offset being zero.
var now = SystemClock.Instance.Now; Console.WriteLine(now.Ticks); // 14001007108796083
Partial: represents part of a local date/time, but isn't complete enough to represent a specific instant in time. For anyone who has had problems mixing date-only and date-time values, the LocalDate type will be appreciated.
var myBirthday = new LocalDate(1983, 04, 19); var noon = new LocalTime(12,0,0);
Duration: roughly the
A DateTimeZone contains most of the information needed to represent a timezone. It is rarely used alone, rather it is combined with a specific CalendarSystem object into a type called Chronology.
ZonedDateTime: This combines a local date/time with a specific time zone and calendar system. This makes it less error prone than just using a UTC offset.
var now = SystemClock.Instance.Now; var dtzp = DateTimeZoneProviders.Tzdb; var berlinTz = dtzp["Europe/Berlin"]; var berlinNow = new ZonedDateTime(now, berlinTz); //2014-05-08T22:42:08 Europe/Berlin (+02)
Interval: represents a specific time interval. Unlike
TimeSpan, this retains the starting and ending instants. You would use this to express concepts such as “on January 1st, 2011 from 8 am to 12 pm”.
// 2014-05-21T08:00:00Z – 2014-05-23T17:00:00Z var tz = DateTimeZoneProviders.Tzdb.GetSystemDefault(); var localBeginDateTime = LocalDateTime.FromDateTime(new DateTime(2014, 05, 21, 8, 0, 0)); ZonedDateTime zonedBeginDateTime = localBeginDateTime.InZoneStrictly(tz); // Same for endTime... var karlsruheEntwicklerTageInterval = new Interval(zonedBeginDateTime.ToInstant(), zonedEndDateTime.ToInstant());
Period: “A period in Noda-Time represents a period of time defined in terms of fields, for example, 3 years 5 months 2 days and 7 hours. This differs from a duration in that it is inexact in terms of milliseconds. A period can only be resolved to an exact number of milliseconds by specifying the instant (including chronology and time zone) it is relative to.”
var fourthieth = new LocalDate(2023, 4, 19); var today = berlinNow.LocalDateTime.Date; //P3257D Period period = Period.Between(today, fourthieth, PeriodUnits.Days); //P8Y11M Period between = Period.Between(today, fourthieth, PeriodUnits.YearMonthDay);
NodaTime supports Mono, but with some limitations. The following article of the documentation is very transparent about it. There are also some general limitations which are described in this other article.
Date and Time manipulations are horribly complex, thus I personally welcome any framework that will help me structure my thoughts. The BCL API is not helping there so even if NodaTime tends to make things a tad more complicated, I still prefer using it over the BCL.
If you need some motivation about the complexity of the task, or if you just want to laugh a bit, I would recommend the splendid video The problem with Time&TimeZones from Computerphile. A fun look into it for sure!
References and further readings
In order to prepare this entry, the following articles were particularly useful:
- What's wrong with DateTime anyway? by Jon Skeets on NodaTime's Blog
- NodaTime's UserGuide
- Working with Date and Time in RavenDB
- Noda Time on MSDN
- Noda Time: An Advanced Date/Time Library for .NET
- Getting started with NodaTime on Stackoverflow
- Getting a Handle On Time with Noda Time
- NodaTime: What date time is it?