Article: Date/time compendium

Home Page


Consultancy

  • Service Vouchers
  • Escrow Service

Shop



Programming
  • Articles
  • Tools
  • Links

Search

 

Contact

 

PHPinfo


$_SERVER







Useful information about dates and times.

category 'KB', language C#, created 26-Dec-2009, version V1.2 (25-Aug-2010), by Luc Pattyn


License: The author hereby grants you a worldwide, non-exclusive license to use and redistribute the files and the source code in the article in any way you see fit, provided you keep the copyright notice in place; when code modifications are applied, the notice must reflect that. The author retains copyright to the article, you may not republish or otherwise make available the article, in whole or in part, without the prior written consent of the author.

Disclaimer: This work is provided as is, without any express or implied warranties or conditions or guarantees. You, the user, assume all risk in its use. In no event will the author be liable to you on any legal theory for any special, incidental, consequential, punitive or exemplary damages arising out of this license or the use of the work or otherwise.


This article contains practical information about dates and times.

The .NET DateTime type

The .NET FrameWork has a DateTime type, which is a struct, hence a value type. It holds both date and time information, however one can keep the time at zero (=midnight) when only interested in dates, or the date constant (say at today) when only interested in time.

One of the popular constructors would be public DateTime(int year, int month, int day, int hour, int minute, int second) and the most popular property is DateTime.Now which returns the current date and time in the highest available resolution, i.e. up to the tick (here a tick is a tenth of a microsecond).

For dates, the most useful constructor is new DateTime(int year, int month, int day) which constructs a date provided the numbers are OK: year in [1,9999], month in [1, 12], day in [1, number of days in that month].

Some specials values are made available as static members of the DateTime class:

  • DateTime.Today; // today's date
  • DateTime.MinValue; // the smallest valid date and time, i.e. midnight, January 1, 0001
  • DateTime.MaxValue; // the largest valid date and time, i.e. the last tick of December 31, 9999.

Other dates can be derived by adding a positive or negative number of days, months or years; the methods are AddDays(int days), AddMonths(int months), AddYears(int years). The latter two will keep the day number constant, unless the resulting day would be invalid, in which case the last valid day of the resulting month is returned (e.g. one month after March 31 is April 30; however one month before April 30 is March 30).

DateTime formatting

There are several methods for formatting a DateTime, i.e. converting it to a string:

  • the methods ToLongDateString(), ToShortDateString(), ToLongTimeString(), ToShortTimeString() are culture-sensitive; they reflect the pattern defined by the current culture's DateTimeFormatInfo object, which can be modified through the "Regional Settings" control panel. These methods are fine when you want the user to choose the format and probably have all dates to look the same across all applications. This is no good for storing dates in files that will be exchanged across the world.
  • the more flexible ToString() method has several overloads; it basically has three modes of operation:
    1. ToString(): equivalent to ToString("G") which combines the short date format and the long time format.
    2. ToString(string): the formatting string contains:
      • a single letter for a Standard Date and Time Format String; some of them are culture-sensitive such as "F", the full format (may look like "Monday, June 15, 2009 1:45:30 PM"); a few are culture-insensitive such as "s", the sortable format (example: "2009-06-15T13:45:30");
      • or a string of letters and symbols for a Custom Date and Time Format String; a lot of it is culture-sensitive through the names of days and months, and the characters used as delimiters for date and time parts. One can add literal characters enclosed in single quotes (example: a slash gets replaced by the regional date separator, whereas a quoted slash results in an actual slash).
    3. ToString(IFormatProvider): this most flexible and complex overload accepts a CultureInfo, a DateTimeFormatInfo, or a custom object that implements IFormatProvider. We refer to the MSDN documentation for the details.
  • String.Format() also accepts the standard and custom format strings, as in this example:
    DateTime dt=new DateTime(2009, 12, 26, 01, 23, 45);
    string s=String.Format("Time = {0:s} = {0:HH:mm:ss}", dt); // generates "Time = 2009-12-21T01:23:45 = 01:23:45"
    

Persistent storage (including databases)

As for most data types, there are several ways to store a date, a time, a DateTime in a file or database:

  • as a string, e.g. "December 21, 2009"
  • as a number, e.g. the number of ticks since a reference point
  • using a binary representation

Most often it is a bad idea to store a date or time as a string: it isn't the most compact way of storing the information, and it probably is culture-sensitive, so data created on one machine may not be readable by an app on another machine, assuming both have different settings.

When you keep dates or datetimes in a database (such as SQL Server, or MySQL), by all means use the appropriate database types, and don't use strings; the specialized types will avoid all culture sensitivity and the query language is bound to provide a set of functions to operate on datetimes, so that is the way to go. And whenever available (most databases, but not MS Access) use SQL parameters, not date literals, within your SQL queries, so you never need to format a date at all to get at your data. Here is an example:

    DateTime dt = dateTimePicker1.Value.Date;
    SqlCommand cmd = new SqlCommand("INSERT INTO myTable (DateColumn) VALUES (@DT)");
    cmd.Parameters.AddWithValue("@DT", dt); 

When using OLEDB (the "JET Engine") for MS Access, SqlParameter is not available, and literal dates need to be inserted into the SQL query strings themselves. Here a pound sign is used as a delimiter, and the format is pretty forgiving; when an ambiguous date is given, it is taken as mm/dd/yy. Since ambiguity may depend on the actual value (1/2/3 is ambiguous, 27/12/99 isn't), I strongly recoomend to always use the format yyyy-MM-dd which never is ambiguous. Here are some examples:

    string preferredQuery = "INSERT INTO myTable (DateColumn) VALUES (#2010-08-25#)";
    string sufficientQuery = "INSERT INTO myTable (DateColumn) VALUES (#25/8/2010#)";
    string confusingQuery = "INSERT INTO myTable (DateColumn) VALUES (#1/2/3#)";

When the only possible way to store date information is through a string (e.g. because it is a simple data file; or the database has no datetime type, see Ingres), at least try and use one of a few formats that are culture-insensitive:

  • DateTime.ToString("s"): the sortable format (example: "2009-12-26T01:23:45"), which conforms to ISO-8601 and which I strongly recommend;
  • DateTime.ToString("r"): the RFC1123 pattern (example: "Sat, 26 Dec 2009 01:23:45 GMT");
  • DateTime.ToString("u"): the "universal sortable" format (example: "2009-12-26 01:23:45Z");
  • DateTime.ToString(CultureInfo InvariantCulture): the "invariant" format (example: "12/26/2009 01:23:45").

DateTime parsing

Parsing is the inverse of formatting, so the DateTime parsing methods try and turn a date/time string into a DateTime instance. There are basically four parse methods:

  • DateTime.Parse() tries to understand a datetime string; it recognizes several formats of date, time, and datetime, including the ISO-8601 format; unspecified time parts are set to zero, unspecified date parts are replaced by the parts of the current date.
  • DateTime.ParseExact() needs a format string, and parses the datetime string according to that exact format.
  • DateTime.TryParse() and DateTime.TryParseExact() are alternatives that, in case of a parse problem, return false, rather than throwing a FormatException.

As an example, this is what you could do when the exact format is known:

	string str="Wed Feb 24 04:56:30 2010";
	log("str="+str);
	DateTime dt1=DateTime.ParseExact(str, "ddd MMM dd HH:mm:ss yyyy", System.Globalization.CultureInfo.InvariantCulture);
	log("dt1="+dt1.ToString());

Some typical operations

Getting current date or date/time

These are the relevant properties:

DateTime.Now           // yields a DateTime holding the current date and time, with 1-tick resolution
DateTime.Today         // yields a DateTime holding the current date
myDateTime.Date        // yields a DateTime holding the date part
myDateTime.TimeOfDay   // yields a TimeSpan holding the time part (time elapsed since midnight)

First day of a month

Getting the first day of a month is straightforward; here are two ways to get it:

public static DateTime FirstDay(DateTime date) {
	return new DateTime(date.Year, date.Month, 1);
}
public static DateTime FirstDay(DateTime date) {
	return date.AddDays(1-date.Day);
}

Last day of a month

Getting the last day of a month is slightly more complex as the number of days in a month varies; here are two ways to get it:

public static DateTime LastDay(DateTime date) {
	date=date.AddMonths(1);                       // this gets us somewhere in the next month
	date=new DateTime(date.Year, date.Month, 1);  // first day of next month
	return date.AddDays(-1);                      // last day of current month
}
public static DateTime LastDay(DateTime date) {
	return new DateTime(date.Year, date.Month, DateTime.DaysInMonth(date.Year, date.Month));
}

The first week in a year

ISO 8601 defines the first week of a year as the week that contains the first Thursday of that year. Here is a simple method to get that first Thursday:

DateTime FirstThursday(int year) {
	DateTime dt=new DateTime(year, 1, 1);
	return dt.AddDays((11-(int)dt.DayOfWeek)%7);
}

The other days of the same week obviously can be found by adding a small number of days (-3 for Monday, -2 for Tuesday, ..., +3 for Sunday).

Number of sundays, mondays, ... in a month or a year

How many Sundays are there in a specific month? or Mondays? or... I came up with a magic formula that can calculate that for any given day without resorting to loops or conditional statements; it uses the DayOfWeek enum which ranges from 0=Sunday to 6=Saturday.

public static int CountDays(int year, int month, DayOfWeek dayOfWeek) {
	return (DateTime.DaysInMonth(year, month) + (int)new DateTime(year, month, 7-(int)dayOfWeek).DayOfWeek) / 7;
}

Basically it divides the number of days by 7, which is fine if the previous month ended on a day of interest, which implies the seventh is also such date; when it is not, we have to tweak the month length. Keep in mind that integer division in most languages rounds down (this would be most relevant when trying the formula in VB.NET).

A very similar formula would yield the number of Sundays, Mondays, ... in a year:

public static int CountDays(int year, DayOfWeek dayOfWeek) {
	return (365-28+DateTime.DaysInMonth(year, 2) + (int)new DateTime(year, 1, 7-(int)dayOfWeek).DayOfWeek) / 7;
}

Popular mistakes

Some mistakes seem to occur quite frequently when using the DateTime type; the most popular ones are listed here:

  1. DateTime instances are immutable; so the following snippet is incorrect:
    DateTime date=DateTime.Today;
    date.AddDays(1);   // this is wrong, the result is discarded
    Console.WriteLine("tomorrow is "+date.ToShortDateString ());  // shows today, not tomorrow!
    
    The right way to do that would be:
    DateTime date=DateTime.Today;
    date=date.AddDays(1);
    Console.WriteLine("tomorrow is "+date.ToShortDateString ());
    
  2. DateTime.Now and DateTime.Today are live properties, if you call them repeatedly at the turn of a second, minute, hour, day, month or year, they will be re-evaluated and may return inconsistent property values, so don't write:
    int hour=DateTime.Now.Hour;
    int minute=DateTime.Now.Minute;
    // when it just turned 3 PM (from 14:59 to 15:00), this could result in hour=14, minute=0 (i.e. wrong by one hour)
    
    instead just call DateTime.Now only once:
    DateTime now=DateTime.Now;
    int hour=now.Hour;
    int minute=now.Minute;
    // this yields values that belong together
    
  3. DateTime instances store their information as a number of ticks, so the resolution (not the accuracy!) is one tenth of a microsecond; be careful when comparing DateTimes, even when the results of a simple ToString() look identical, the instances themselves may be different (very similar to the floating-point situation, where a decimal representation seldom is bit-perfect).
  4. The internal number of ticks is stored in a long, i.e. 8 bytes; those 8 bytes cannot be operated upon atomically when running on a 32-bit architecture; when several threads need to access the same DateTime instance, a lock is required.
  5. When using a string to specify a DateTime formatting, some symbols have a special meaning; example:
    DateTime dt=new DateTime(2009, 12, 21);
    dt=dateTime.ToString("dd-MM-yyyy");      // will result in 21-Dec-2009 
    dt=dateTime.ToString("dd/MM/yyyy");      // result depends on regional settings, slash being a special character
    dt=dateTime.ToString("dd'/'MM'/'yyyy");  // will result in 21/Dec/2009 
    

The .NET TimeSpan type

The .NET FrameWork also holds a TimeSpan type (also a struct). It gets used to express the difference between two DateTime instances, so you can have:

timeSpan=dateTime1-dateTime2;
timeSpan=dateTime1.Subtract(dateTime2);
dateTime2=dateTime1+timeSpan;
dateTime2=dateTime1.Add(timeSpan);

Note: a TimeSpan instance has time properties (such as Hours) similar to a DateTime instance; its date properties are limited to Days, there is no Months or Years property as it is impossible to turn days into months or years without having a reference date, as months and years have a variable number of days in them.

History

  • Version 1.0 (26-Dec-2009): Original version
  • Version 1.1 (08-Mar-2010): Added "The first week in a year"
  • Version 1.2 (25-Aug-2010): Added more about date literals (mostly for MS Access)


Perceler

Copyright © 2012, Luc Pattyn

Last Modified 04-May-2025