Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Noda –DateTime Extensions for .NET

4.25/5 (4 votes)
30 Apr 2010CPOL5 min read 37.9K   286  
Noda –DateTime extensions for .NET

Introduction

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.

Background

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.

C#
DateTime today = DateTime.Today; // Kind is DateTimeKind.Local

If you create a DateTime object using its default constructor, then the DateTimeKind is unspecified meaning that it’s unaware of any time zone:

C#
DateTime dateTime = new DateTime(2006, 1, 1); // DateTimeKind.Unspecified

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:

C#
NodaDateTime.CurrentTimeZone = 
	TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time");
            
var dateTime = new NodaDateTime(2006, 3, 21, 18, 0, 0);
Console.Out.WriteLine(dateTime); // 21-03-2006 18:00:00

NodaDateTime.CurrentTimeZone = 
	TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");

Console.Out.WriteLine(dateTime); // 21-03-2006 09:00:00

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:

C#
DateTime utc = dateTime.UtcDateTime;

The 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.

C#
var utc = NodaDateTime.FromUtc(new DateTime(2006, 3, 21, 17, 0, 0));

// UTC -8:00
//
var pacificTime = new NodaDateTime(2006, 3, 21, 9, 0, 0, "Pacific Standard Time");

// UTC +1:00
//
var copenhagenTime = new NodaDateTime(2006, 3, 21, 18, 0, 0, "Romance Standard Time");

Assert.AreEqual(utc, pacificTime.ToUtc());
Assert.AreEqual(utc, pacificTime.To(TimeZoneInfo.Utc));
Assert.AreEqual(utc, pacificTime);
Assert.AreEqual(utc, copenhagenTime);
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:

C#
Assert.AreEqual(utc, new DateTime(2006, 3, 21, 17, 0, 0, DateTimeKind.Utc));

Both, NodaDateTime and 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:

C#
var utc = NodaDateTime.FromUtc(new DateTime(2006, 3, 21, 0, 0, 0));

// UTC -8:00
//
var pacificTime = new NodaDateTime(utc, "Pacific Standard Time");

Assert.AreEqual(pacificTime.Year, 2006);
Assert.AreEqual(pacificTime.Month, 3);
Assert.AreEqual(pacificTime.Day, 20);
Assert.AreEqual(pacificTime.Hour, 16);
Assert.AreEqual(pacificTime.Minute, 0);
Assert.AreEqual(pacificTime.Second, 0);

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.

History

  • V1.0 Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)