Where's the DateTime 'Z' format specifier?

C#DatetimeFormatUtc

C# Problem Overview


[Update: Format specifiers are not the same thing as format strings; a format specifier is a piece of a custom format string, where a format string is 'stock' and doesn't provide customization. My problem is with specifiers not formats]

I've been trying to perform roundtrip DateTime conversions with a format string that uses 'zzz' format specifier, which I know is bound to local time. So, if I attempt to round trip with a UTC date time it throws a DateTimeInvalidLocalFormat exception, which it should, with this text:

A UTC DateTime is being converted to text in a format that is only correct for local times. This can happen when calling DateTime.ToString using the 'z' format specifier, which will include a local time zone offset in the output. In that case, either use the 'Z' format specifier, which designates a UTC time, or use the 'o' format string, which is the recommended way to persist a DateTime in text. This can also occur when passing a DateTime to be serialized by XmlConvert or DataSet. If using XmlConvert.ToString, pass in XmlDateTimeSerializationMode.RoundtripKind to serialize correctly. If using DataSet, set the DateTimeMode on the DataColumn object to DataSetDateTime.Utc.

Based on this suggestion, all I need to do to get my code to work is to replace 'zzz' with 'ZZZ' so I can stand in a UTC format. The problem is, 'Z' isn't found anywhere in the documentation and any 'Z' format combination I try, i.e. 'Z', 'ZZ', 'ZZZ', always just converts the DateTime instance with those Z's treated like literals.

Did someone forget to implement 'Z' without telling the exception message author, or am I missing how to swap out a valid local time offset with "+0000" without hacking?

Code Example:

// This is the format with 'zzzzz' representing local time offset
const string format = "ddd MMM dd HH:mm:ss zzzzz yyyy";

// create a UTC time
const string expected = "Fri Dec 19 17:24:18 +0000 2008";
var time = new DateTime(2008, 12, 19, 17, 24, 18, 0, DateTimeKind.Utc);

// If you're using a debugger this will rightfully throw an exception
// with .NET 3.5 SP1 because 'z' is for local time only; however, the exception
// asks me to use the 'Z' specifier for UTC times, but it doesn't exist, so it
// just spits out 'Z' as a literal.
var actual = time.ToString(format, CultureInfo.InvariantCulture);

Assert.AreEqual(expected, actual);

C# Solutions


Solution 1 - C#

Maybe the "K" format specifier would be of some use. This is the only one that seems to mention the use of capital "Z".

"Z" is kind of a unique case for DateTimes. The literal "Z" is actually part of the ISO 8601 datetime standard for UTC times. When "Z" (Zulu) is tacked on the end of a time, it indicates that that time is UTC, so really the literal Z is part of the time. This probably creates a few problems for the date format library in .NET, since it's actually a literal, rather than a format specifier.

Solution 2 - C#

When you use DateTime you are able to store a date and a time inside a variable.

The date can be a local time or a UTC time, it depend on you.

For example, I'm in Italy (+2 UTC)

var dt1 = new DateTime(2011, 6, 27, 12, 0, 0); // store 2011-06-27 12:00:00
var dt2 = dt1.ToUniversalTime()  // store 2011-06-27 10:00:00

So, what happen when I print dt1 and dt2 including the timezone?

dt1.ToString("MM/dd/yyyy hh:mm:ss z") 
// Compiler alert...
// Output: 06/27/2011 12:00:00 +2

dt2.ToString("MM/dd/yyyy hh:mm:ss z") 
// Compiler alert...
// Output: 06/27/2011 10:00:00 +2

dt1 and dt2 contain only a date and a time information. dt1 and dt2 don't contain the timezone offset.

So where the "+2" come from if it's not contained in the dt1 and dt2 variable?

It come from your machine clock setting.

The compiler is telling you that when you use the 'zzz' format you are writing a string that combine "DATE + TIME" (that are store in dt1 and dt2) + "TIMEZONE OFFSET" (that is not contained in dt1 and dt2 because they are DateTyme type) and it will use the offset of the server machine that it's executing the code.

The compiler tell you "Warning: the output of your code is dependent on the machine clock offset"

If i run this code on a server that is positioned in London (+1 UTC) the result will be completly different: instead of "+2" it will write "+1"

...
dt1.ToString("MM/dd/yyyy hh:mm:ss z") 
// Output: 06/27/2011 12:00:00 +1

dt2.ToString("MM/dd/yyyy hh:mm:ss z") 
// Output: 06/27/2011 10:00:00 +1

The right solution is to use DateTimeOffset data type in place of DateTime. It's available in sql Server starting from the 2008 version and in the .Net framework starting from the 3.5 version

Solution 3 - C#

I was dealing with DateTimeOffset and unfortunately the "o" prints out "+0000" not "Z".

So I ended up with:

dateTimeOffset.UtcDateTime.ToString("o")

Solution 4 - C#

Round tripping dates through strings has always been a pain...but the docs to indicate that the 'o' specifier is the one to use for round tripping which captures the UTC state. When parsed the result will usually have Kind == Utc if the original was UTC. I've found that the best thing to do is always normalize dates to either UTC or local prior to serializing then instruct the parser on which normalization you've chosen.

DateTime now = DateTime.Now;
DateTime utcNow = now.ToUniversalTime();

string nowStr = now.ToString( "o" );
string utcNowStr = utcNow.ToString( "o" );

now = DateTime.Parse( nowStr );
utcNow = DateTime.Parse( nowStr, null, DateTimeStyles.AdjustToUniversal );

Debug.Assert( now == utcNow );

Solution 5 - C#

This page on MSDN lists standard DateTime format strings, uncluding strings using the 'Z'.

Update: you will need to make sure that the rest of the date string follows the correct pattern as well (you have not supplied an example of what you send it, so it's hard to say whether you did or not). For the UTC format to work it should look like this:

// yyyy'-'MM'-'dd HH':'mm':'ss'Z'
DateTime utcTime = DateTime.Parse("2009-05-07 08:17:25Z");

Solution 6 - C#

Label1.Text = dt.ToString("dd MMM yyyy | hh:mm | ff | zzz | zz | z");

will output:

07 Mai 2009 | 08:16 | 13 | +02:00 | +02 | +2

I'm in Denmark, my Offset from GMT is +2 hours, witch is correct.

if you need to get the CLIENT Offset, I recommend that you check a little trick that I did. The Page is in a Server in UK where GMT is +00:00 and, as you can see you will get your local GMT Offset.


Regarding you comment, I did:

DateTime dt1 = DateTime.Now;
DateTime dt2 = dt1.ToUniversalTime();

Label1.Text = dt1.ToString("dd MMM yyyy | hh:mm | ff | zzz | zz | z");
Label2.Text = dt2.ToString("dd MMM yyyy | hh:mm | FF | ZZZ | ZZ | Z");

and I get this:

07 Mai 2009 | 08:24 | 14 | +02:00 | +02 | +2
07 Mai 2009 | 06:24 | 14 | ZZZ | ZZ | Z 

I get no Exception, just ... it does nothing with capital Z :(

I'm sorry, but am I missing something?


Reading carefully the MSDN on Custom Date and Time Format Strings

there is no support for uppercase 'Z'.

Solution 7 - C#

This will get what you need if you need to force +00:00 instead of Z to indicate UTC:

string timeStamp = DateTime.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:ss.ffffff'+00:00'");

Solution 8 - C#

Looks like the +00.00 happens when the DateTimeKind is not UTC. Specifying it makes "o" work correctly:

DateTime.SpecifyKind(new DateTime(2011, 6, 27, 12, 0, 0), DateTimeKind.Utc).ToString("o");

That will return the Z time

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDaniel CrennaView Question on Stackoverflow
Solution 1 - C#Andy WhiteView Answer on Stackoverflow
Solution 2 - C#Marco StaffoliView Answer on Stackoverflow
Solution 3 - C#KugelView Answer on Stackoverflow
Solution 4 - C#Paul AlexanderView Answer on Stackoverflow
Solution 5 - C#Fredrik MörkView Answer on Stackoverflow
Solution 6 - C#balexandreView Answer on Stackoverflow
Solution 7 - C#slayernoahView Answer on Stackoverflow
Solution 8 - C#Christos TatitzikidisView Answer on Stackoverflow