Before you start wondering what Noda might stand for: Noda stands for nothing, except that it tries to express its conceptional closeness to the popular Java Framework called Joda.
One of the major disadvantages of the .NET
DateTime implementation is that it is only half-aware of the time zone: if you create a simple and standard
DateTime object, the object is of
DateTimeKind.Local which means, it has been created in the local time zone defined on the machine.
DateTime today = DateTime.Today;
If you create a
DateTime object using its default constructor, then the
DateTimeKind is unspecified meaning that it’s unaware of any time zone:
DateTime dateTime = new DateTime(2006, 1, 1);
In both cases, you cannot just simply pass the
DateTime objects around (even not storing it in a database) without losing the important information of the time zone it has been created in.
Consider the following case: User A, situated in New York, creates a
DateTime and stores it in the database. User B, in Copenhagen, somehow queries the record and instantiates a new
DateTime object locally on his machine. User B would certainly expect to see the object relative to his time zone and not the original time of the zone User A has created the object.
The obvious solution would be to let the time zone be part of the
DateTime object and then convert it whenever the local time zone is different. This would mean to extend the
DateTime object with the time zone information but would also mean that if you store the object in a database or an XML, you would need to store both,
DateTime and time zone to be able to recreate the object.
Another solution introduced in .NET 3.5 with the
DateTimeOffset is to express the differences relative in time as an offset to UTC. This also is a breaking change to the regular
DateTime object since you additionally need to store the offset. There is a corresponding SQL-Server type for such purpose called
datetimeoffset and changing your implementation would force you to change database types too. Therefore it didn’t seem to be an ideal candidate for a
DateTime replacement, also because it has some other problems with daylight savings.
If you query the BCL blog regarding
DateTime, you’ll find a lot of information about the history of the
DateTime object. The BCL acknowledge that they might have chosen the wrong implementation in version 1.0 of the framework but they are now caught to keep the signature and behavior to retain compatibility.
Using the Code
Our implementation tries to address the issue in a specific way but we think, it might be suitable for the most common scenarios:
If your business application has to deal with different time zones, you might (otherwise we think you should) have stored those
DateTime values always in a reference time zone. Business logic operations are either done in a context of a specific time zone or relative to the reference time zone. Displaying and parsing
DateTime values is also done in a context of a specific time zone.
We have introduced a
DateTime replacement called
NodaDateTime which has the same signature as the
DateTime object but internally, it stores its value as UTC together with a time zone. The time zone defaults to the time zone defined as
NodaDateTime.CurrentTimeZone which internally defaults to
TimeZoneInfo.Local as the time zone defined on the local machine:
TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time");
var dateTime = new NodaDateTime(2006, 3, 21, 18, 0, 0);
TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
The benefit now is that all operations like adding hours, minutes, etc. are always done relative to the internal UTC value and storing the value can be done via the UTC value:
DateTime utc = dateTime.UtcDateTime;
CurrentTimeZone is made thread
static to be used in web applications to set the time zone context at the beginning of the request so that all further operations are done relative to the context. When using it in a WPF or Windows Forms application, you can let the user select the
CurrentTimeZone he or she prefers to work in and all input/output will then be done with the time zone provided.
There are some issues around this approach which should be mentioned: one question is: when are two instance of
NodaDateTime equal to each other? Normally, one might expect that two instances of the same object are equal when their members are equal. In case of
NodaDateTime, it would mean that two dates are only equal if they are created within the same time zone. However, this is not what you would expect. Two instances should be equal when they relate to the same moment in time.
var utc = NodaDateTime.FromUtc(new DateTime(2006, 3, 21, 17, 0, 0));
var pacificTime = new NodaDateTime(2006, 3, 21, 9, 0, 0, "Pacific Standard Time");
var copenhagenTime = new NodaDateTime(2006, 3, 21, 18, 0, 0, "Romance Standard Time");
Assert.AreEqual(utc, pacificTime.To("Romance Standard Time"));
This goes even further: A
NodaDateTime object can also be equal to a regular
DateTime object as long as the
DateTime object makes clear in which time zone it was created:
Assert.AreEqual(utc, new DateTime(2006, 3, 21, 17, 0, 0, DateTimeKind.Utc));
DateTime are defining the same absolute moment and time and hence, they are equal.
Another issue is, what should the properties such as Year, Month, etc. return? The Uct or the local value? The
NodaDateTime always returns the properties within the time zone the object has been created in:
var utc = NodaDateTime.FromUtc(new DateTime(2006, 3, 21, 0, 0, 0));
var pacificTime = new NodaDateTime(utc, "Pacific Standard Time");
Points of Interest
There are multiple attempts (found f.i. here: TimeZoneAwareDateTime.aspx) which are trying to address the issues around the limitations of
DateTime. As for the referenced example, we see some disadvantages:
- Separate objects for CET/UTC and local date time
We don’t see any need for having specific objects for UTC or CET, etc. while having a primitive object that holds time zone information. The information that a
DateTime object is UCT is merrily a property of the
DateTime rather than a separate type. Those objects don’t differ in terms of definition of a
DateTime object as being a reference to a specific moment in time
- Coded time zone information
Time zone information is subject to changes over time. Hardcoding them in a framework (as also done in the Joda framework) will result in the need for releasing new versions whenever the time zone information is updated.
Other aspects of the framework are to provide support for primitive types such as Days, Months, etc. as well as features as the Interval. The framework is definitely missing documentation and lacks test coverage, but we decided to show it anyway to gather feedback.
So, any feedback is appreciated.