views:

1928

answers:

5

[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);
A: 

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
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
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
I'm using a *custom* string with specifiers not a format string.
Daniel Crenna
A: 
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'.

balexandre
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
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
+3  A: 

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
Using 'K' ironically outputs 'Z' in my string rather than "+0000".
Daniel Crenna
@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
@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
A: 

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
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
A: 

I have the exact same problem, I need to send a UTC Datetime in the standard XML DateTime format. I get the same error message using this format:

dt.ToString("yyyy-MM-ddTHH:mm:ss.fffzzz")

Jens Hykkelbjerg