83

[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);
Daniel Crenna
  • 3,326
  • 1
  • 24
  • 34
  • Perhaps you could supply us with an actual code sample of what you are trying to do, so we don't need to spend time trying to guess it? – Fredrik Mörk May 07 '09 at 06:27
  • I just updated with a sample. I'm confused because I know 'Z' isn't supported according to MSDN, but the exception asks me to use it. And I what I want to be able to do is convert with this custom string, even if it's UTC, and get "+0000" in the result, like I would get "-04:00" if I used 'zzz' with a local time. – Daniel Crenna May 07 '09 at 06:34
  • I see you problem now; thanks for the code update. Will dive into it for a little while and post something if I find it. – Fredrik Mörk May 07 '09 at 06:41

8 Answers8

88

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.

Andy White
  • 86,444
  • 48
  • 176
  • 211
  • 2
    Using 'K' ironically outputs 'Z' in my string rather than "+0000". – Daniel Crenna May 07 '09 at 06:27
  • 2
    @Dimebrain, A literal "Z" in the string is directly equivalent to "+0000" as far as ISO8601 is concerned. If you need to have "+0000" rather than "Z" then I suspect you'll need to do a replace on the formatted string. – LukeH May 07 '09 at 09:08
  • 3
    @Dimebrain, I think referring to "Z" as a format specifier in the exception message is a mistake, "K" is almost certainly the specifier that you need. – LukeH May 07 '09 at 09:11
  • 3
    'Z' is typically pronounced 'Zulu' AKA: UTC time, you'll see it used in aviation a lot. – StrangeWill Oct 13 '11 at 16:51
  • Zulu originated as a military term for UTC. Aviation took it from them. Alpha is the equivalent for "Whatever the local time is". – Dr Rob Lang Apr 15 '14 at 09:19
  • 2
    @RobLang, for whatever it's worth... alpha is actually another predefined timezone, as is bravo, charlie, et cetera. See for example: [link](http://www.timeanddate.com/time/zones/c). – Herman Cordes Aug 11 '15 at 13:09
  • "Z" is the zone designator for the zero UTC offset. "09:30 UTC" is therefore represented as "09:30Z" or "T0930Z". "14:45:15 UTC" would be "14:45:15Z" or "T144515Z". The Z suffix in the ISO 8601 time representation is sometimes referred to as "Zulu time" or "Zulu meridian" because the same letter is used to designate the Zulu time zone. However the ACP 121 standard that defines the list of military time zones makes no mention of UTC and derives the "Zulu time" from the Greenwich Mean Time which was formerly used as the international civil time standard. https://en.wikipedia.org/wiki/ISO_8601 – Thiago Santos Feb 15 '23 at 02:44
12

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

Marco Staffoli
  • 2,475
  • 2
  • 27
  • 29
8

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

So I ended up with:

dateTimeOffset.UtcDateTime.ToString("o")
Kugel
  • 19,354
  • 16
  • 71
  • 103
5

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 );
Paul Alexander
  • 31,970
  • 14
  • 96
  • 151
  • I'm good with that normally but I don't control the source of the DateTime, it's coming to me in an XML REST call, and I have to deserialize it on my side, but also be able to revert back to the exact format used, otherwise I'd stick with best practice and use 'o'. This is a non-standard format, and my code works with local times but I can't make this work without a hack, i.e. format.Replace(" zzzzz ", "+0000"); – Daniel Crenna May 07 '09 at 06:44
4

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");
Fredrik Mörk
  • 155,851
  • 29
  • 291
  • 343
  • Yes, I know there is a 'Z' format string. But this question is about why the exception suggests a 'Z' format specifier, which is a different thing entirely. – Daniel Crenna May 07 '09 at 06:15
  • I think you may mix them up; the format string for UTC is not 'Z', it's 'u' (according to the info in the link I posted). So if you want a DateTime expression formatted as the standard UTC format (as the format string in my sample above) you can use .ToString("u") – Fredrik Mörk May 07 '09 at 06:22
  • I'm using a *custom* string with specifiers not a format string. – Daniel Crenna May 07 '09 at 06:23
3
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.

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

qqtf
  • 632
  • 6
  • 17
balexandre
  • 73,608
  • 45
  • 233
  • 342
  • Not my question; I know 'zzz' format specifiers work with local offsets. I'm asking about replacing 'zzz' with 'ZZZ' like the exception suggests, if I'm dealing with UTC times. If you changed that dt to UTC with dt.ToUniversalTime(), then tried to format it with your string, it would raise the exception I'm talking about. – Daniel Crenna May 07 '09 at 06:20
  • I'm not sure why you didn't get the exception, did you debug step through? If you don't you won't catch it. But yes, it's true 'Z' isn't supported, but if it's not then the Exception shouldn't tell me to use it. – Daniel Crenna May 07 '09 at 06:35
2

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

0

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'");
slayernoah
  • 4,382
  • 11
  • 42
  • 73