DateTime Parsing and Formatting with Time Zones [Anthony Moore]

Here is a new DateTim FAQ entry.

What is the relationship between DateTime parsing and formatting and the time zone?

Before reading below, see this entry for a more practical description of how to using formatting and parsing for the purposes of storing dates and times, which is where you are most likely to have interaction with the time zone.

 

As described here, most of the members of DateTime are time zone agnostic, while a small number take or return DateTime instances that are specifically local or UTC. For parsing and formatting methods the relationship is less clear. These include the following APIs

 

  • Parse
  • ParseExact
  • TryParse
  • TryParseExact
  • ToString

 

The short answer is that these methods will be time zone agnostic unless there is a time zone element in either the input string, the format string, or one of the DateTimeStyles flags.

 

Let’s look first at formatting. The default DateTime.ToString format has no time zone information, and most simple DateTime formats do not. A typical example is “dd MMM yyyy hh:mm tt” which would output “20 Dec 2004 12:30 PM”. There is nothing to identify the time zone in the output and no conversion is done.

 

Certain formatting elements do cause time zone information to be present in the output:

  • “z”, “zz” or “zzz”: outputs the current system local time zone offset e. g “+10:00”.
  • “Z”, “GMT”: outputs text “Z” or “GMT” which identify the DateTime as a UTC time with a time zone offset of zero.
  • “K”: (V2.0 only) output depends on the DateTimeKind value of the DateTime instance:
    • Outputs an empty string for Unspecified.
    • Outputs a local time zone offset for Local, e.g. “+10:00”
    • Outputs “Z” signifying a UTC instance for Utc.

There are a couple of important caveats when using any of these formats. Firstly, there are never any conversions of the time of the DateTime instance when these formats are used. Secondly, for the “z”, “Z” and “GMT” formats, the information does not come from the DateTime instance. For example, if you have a DateTime instance that you treat as a UTC time, but you format it with “zzz”, it will actually write out the local time zone offset despite the fact that this will not represent the intended time. More correct would be to convert the time to local first, or to output “+00:00”, but information to do this is not present in DateTime as shipped in V1.0 and V1.1. A similar error can occur if you use something like “Z” to represent a local time. If you don’t know what the instance is, it is much safer to include no time zone information. One of the mistakes we made in V1.0 was in XML Serialization, where we always use the “z” format which incorrectly represents any UTC DateTime instances or whole dates without times.

 

In V2.0 the DateTime can store some basic flags to identify times that are definitely Local or Utc. Unfortunately, for compatibility reasons, it is not possible to actually use this information to prevent mismatched times and formats in existing code paths, as it is not uncommon to take a dependency on this behavior. As a result, you can still have bugs when using the “z” or “Z” formats as described above. A new format element, “K”, has been added that outputs either a local time zone offset, a UTC marker (“Z”) or nothing, depending on what sort of DateTime you have. This is the recommended way to store a DateTime instance in text .

 

DateTime.ToString also supports a number of single-letter formats that are short-cuts for longer formats. Most of these formats, such as “d” and “F”, do not have any time zone information. Short hand formats that can output time zone information are:

  • “r”/”R”: This outputs “GMT” and so will be incorrect for anything but a UTC DateTime. This is the RFC1123 date e.g. “Sun, 31 Oct 1999 10:00:00 GMT”
  • “u”: This outputs “Z” and so will be incorrect for anything but a UTC DateTime. This is the ISO8601 format e.g. “1999-10-31T10:00:00Z”
  • “U”: This format is a little strange. It is basically the “F” format, which depends on regional options for the exact format, but it also converts from local time to UTC first. (With hindsight, “U” is an almost useless format that we would not recommend using at all, because culture-dependent formats are unsuitable for persistence and UTC dates are unsuitable for UI and this format combines the two.)
  • “o”/”O”: (V2.0 only). This is a format designed to preserve all the information in the DateTime, e.g “1999-10-31-T10:00:00.0000000-07:00” It also uses the “K” format, so it will output correct time zone information only if present.

 

 

The behavior of DateTime parsing with respect to time zone is affected by the DateTimeStyles flags passed in, and by the presence of time zone information in the input string.

 

In V1.0 and V1.1, the logic works like this:

 

    If the input contains time zone information

      If DateTime.Styles.AdjustToUniversal is passed in

      Convert the DateTime to UTC time

      Else

       Convert time DateTime to the local time zone

   Else

      Parse the DateTime without modification.

 

Here are some examples, assuming the local time zone is GMT + 10:

- “2005/01/10 4:00 AM” becomes “2005/01/10 4:00 AM”. It is not converted at all because there is no time zone information.

- “2005/01/10 4:00 AM Z” becomes “2005/01/10 2:00 PM”, because the “Z” indicates a UTC time and a conversion to local time is done by default.

- “2005/01/10 4:00:00+10:00” becomes “2005/01/10 4:00 AM”. There is effectively no conversion done because the time zone offset in the input matches the local time zone.

- “2005/01/10 4:00-07:00” becomes “2005/01/10 9:00 PM”. It is first adjusted 7 hours to 11:00 AM UTC, and then adjusted 10 more hours to the local time zone.

 

In V2.0 there are some additional DateTimeStyles flags and other behaviors. Firstly, there are two new flags, AssumeLocal and AssumeUtc. These make the local or UTC conversions occur even for inputs that don’t have a time zone. This is useful if you want DateTime to always return a Utc or Local DateTime and there is a reasonable default. Secondly, whenever there is a conversion to Local or UTC because of either time zone information in the input or because the AssumeLocal or AssumeUtc flag is used, the DateTime.Kind property will be set appropriately. Otherwise this will be set to Unspecified.

 

Finally, there is a new flag called RoundtripKind, which is designed to be used with the “o” format described above. This flag will roundtrip the data in the DateTime, including preserving the Kind. It does a local time zone conversion only if your DateTime was actually local.