Once again lets dive into the Little Wonders of .NET, those small things in the .NET languages and BCL classes that make development easier by increasing readability, maintainability, or performance.
Today I’m going to focus a bit on the System.DateTime. This nice little value type (struct) has been in the BCL since the beginning, and while being broadly used to represent an instance of a date and time, there are many great properties, methods, and operators in this class that perhaps folks are less familiar with but can give you a lot of power.
A quick note on default construction
One of the first things people notice when moving from Java to C# is that the default constructor for DateTime does not give the current time, but instead gives the minimum time.
1: // what is the default date and time?
2: var someTime = new DateTime();
4: // The date and time are: 1/1/0001 12:00:00 AM
5: Console.WriteLine("The date and time are: " + someTime);
Why is this? Why not the current date and time? The key here is to realize that DateTime is a struct. The choice of struct was chosen for performance reasons to make the DateTime instances very light, but one of the many consequences of a struct is that you can’t specify behavior in the default constructor. The default constructor of a struct always initializes all fields to their default (zero for numeric types, null for reference types, etc).
For DateTime this means that its one instance field (dateData) is initialized to zero. So what is dateData? Well, it’s a long whose top two bits specify the kind of DateTime (Unspecfied, Utc, Local) and the remainder of the bits (62) specify the number of ticks from January 1st, 0001 (AD/CE) at midnight.
So, since by default all fields in a struct are their default values, this means that constructing a DateTime using the default constructor gives you an instance where dateData is zero. This means that the number of ticks (the bottom 62 bits) is zero and the kind (the top 2 bits) is also zero. This means an offset of zero ticks since the minimum DateTime and an Unspecified kind.
Now is now, but where?
So, if you do want to get the current local date and time, the easiest way is to call the static property DateTime.Now.
1: // currentTime will have the current local Date and Time
2: var currentTime = DateTime.Now;
This is very useful for grabbing the local date and time, but if we are operating across multiple time zones, we may want to use UTC time instead to give us location independence. In that case, you should instead call:
1: // currentTime will have the current UTC Date and Time
2: var someTime = DateTime.UtcNow;
The DateTime.UtcNow gets the current date and time, but instead of using the local time zone, it creates it as UTC time instead. Which one you use is important, because in either case, it will set the upper two bits in dateData to represent the kind of DateTime. We can query these upper two bits easily by using the Kind property:
1: // Output: Local
4: // Output: Utc
Constructing with DateTimeKind
So, using Now versus UtcNow will set the kind bits of dateData appropriately, but what if you want to hand-construct a date and time (say to represent certain holidays or key date/time events)? Well, many of the constructors are overloaded in such a way to provide an optional DateTimeKind parameter:
1: // specifies 11/18/2010 5:30 PM - Unspecified kind
2: var someTime = new DateTime(2010, 11, 18, 17, 30, 0);
4: // specifies 11/18/2010 5:30 PM - Local Time
5: var localTime = new DateTime(2010, 11, 18, 17, 30, 0, DateTimeKind.Local);
7: // specifies 11/18/2010 5:30 PM - Coordinated Universal Time (UTC)
8: var utcTime = new DateTime(2010, 11, 18, 17, 30, 0, DateTimeKind.Utc);
Note, however, that the constructor that takes only a year, month, and day does not have a DateTimeKind parameter. So, if you want to express a particular date-only instance of DateTime in terms of UTC or Local, it is better to be explicit:
1: // currentTime will have the given date at midnight but unspecified kind
2: var unspecifiedTime = new DateTime(2010, 11, 18);
4: // currentTime will have the given date at midnight local time
5: var localTime = new DateTime(2010, 11, 18, 0, 0, 0, DateTimeKind.Local);
7: // currentTime will have the given date at midnight utc time
8: var utcTime = new DateTime(2010, 11, 18, 0, 0, 0, DateTimeKind.Utc);
When you want date or time, but not both
Many times when we’re developing we really only care about the date component or time component. There are some helpful properties that can help you easily get to these.
There’s no need to take a date time and hand-construct a new DateTime instance:
1: var now = DateTime.Now;
3: // don't need to do this, use Today static property
4: var today = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0, DateTimeKind.Local);
6: // 11/18/2010 5:30:00 PM
7: var someTime = new DateTime(2010, 11, 18, 17, 30, 0, DateTimeKind.Local);
9: // don't need to do this, use Date instance property
10: var justDate = new DateTime(someTime.Year, someTime.Month, someTime.Day, 0, 0, 0, DateTimeKind.Local);
12: // don't need to do this, use TimeOfDay instance property
13: var justTime = new TimeSpan(someTime.Hour, someTime.Minute, someTime.Second);
First, there is the static property Today that gives you a DateTime for the current day at midnight local time. Or, if you have an instance of DateTime and only want the date or time component, you can call the Date and TimeOfDay properties respectively.
The Date property returns a new instance containing the original instance date at midnight (zero hours, minutes, and seconds), and the TimeOfDay property returns a TimeSpan containing the original instance’s time (or, you can think of it as an offset since midnight of the original instance).
1: // gets the current day based on local time (midnight).
2: var today = DateTime.Today;
4: // 11/18/2010 at 5:30 PM local time
5: var someTime = new DateTime(2010, 11, 18, 17, 30, 0, DateTimeKind.Local);
7: // 11/18/2010 12:00:00 AM
8: var justDate = someTime.Date;
10: // 17:30:00
11: var justTime = someTime.TimeOfDay;
Operations on DateTime
Most of us already know you can compare instance of DateTime with the traditional comparison operators (<, <=, >, >=, ==, !=) or with the CompareTo() method. But you can also do addition and subtraction on DateTime instances.
Now, keep in mind that DateTime is immutable (can’t change) which means that every time you operate on a DateTime it creates a new instance. But since it is a one-field struct the cost involved is extremely small and this shouldn’t be a factor.
Obviously, the concept of adding two DateTime instances together is meaningless. How do you add 11/19/2010 07:30:00 and 12/31/2010 03:50:00? The concept just doesn’t make sense, but you can add a TimeSpan to a date and time. For instance, 11/19/2010 07:30:00 plus 2:00:00 would yield 11/19/2010 09:30:00.
Addition of TimeSpan instances to DateTime are supported with the Add() method and it’s counterpart the operator +. The TimeSpan instances can be positive or negative as desired (adding a negative TimeSpan is the same as subtracting the positive TimeSpan and vice versa).
If you don’t need the full TimeSpan and just want to add (or subtract) individual components, you can use the Add…() convenience methods for days, hours, years, etc.
1: // get today at midnight
2: var today = DateTime.Today;
4: // 5 hours, 30 minutes, 10 seconds
5: var interval = new TimeSpan(5, 30, 10);
7: // newTime is today at 5:30:10 am
8: var newTime = today + interval;
10: // tomorrowNewTime is tomorrow at 5:30:10 am
11: var tomorrowNewTime = newTime.AddDays(1);
13: // yesterdayNewTime is yesterday at 5:30:10 am
14: var yesterdayNewTime = newTime.AddDays(-1);
16: // seeYouNextDecard is 10 years from today at midnight
17: var seeYouNextDecade = today.AddYears(10);
Now, unlike Add(), you can meaningfully Subtract() two DateTime instances and get the interval in between the two. So 11/19/2010 09:30:00 – 11/19/2010 07:30:00 would be a TimeSpan instance representing two hours. You can, of course, also subtract a TimeSpan from a DateTime.
Notice, however, the Add…() component adds do not have Subtract…() counterparts. In this case if you need to subtract days, hours, months, etc just add the negative value.
1: // New year's day
2: var newYears2010 = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Local);
4: // thanksgiving day
5: var thanksgiving2010 = new DateTime(2010, 11, 25, 0, 0, 0, DateTimeKind.Local);
7: // could also say thanksgiving2010.Subtract(newYears2010)
8: var timeFromNewYearsToThanksgiving = thanksgiving2010 - newYears2010;
10: // 328 days
The DateTime struct has a lot of goodness in it. Even though it is very lightweight and performant, it is also extremely powerful in the range of operations that it supports.