3
votes

I have my local time in GMT +01:00 as the time of writing. I experience something, to me unexpected, when doing a ToString in the following way. Here we go:

Demonstrating the local system settings with +01:00 time zone (all these run green):

var myLocalDate = new DateTime(2020, 11, 25, 08, 00, 00, DateTimeKind.Local);
Assert.AreEqual("2020-11-25T08:00:00+01:00", myLocalDate.ToString(@"yyyy-MM-dd\THH:mm:sszzz"));
Assert.AreEqual(DateTimeKind.Local, myLocalDate.Kind);
Assert.AreEqual(myLocalDate, myLocalDate.ToLocalTime());

And now I create the same time, in utc, by manually subtracting an hour and specifying the "utc" as kind. But when I call ToString, the time zone is written as +01:00 which I would expect to be +00:00:

var myUtcDate = new DateTime(2020, 11, 25, 07, 00, 00, DateTimeKind.Utc);
// THIS Breaks:
Assert.AreEqual("2020-11-25T07:00:00+00:00", myUtcDate.ToString(@"yyyy-MM-dd\THH:mm:sszzz"));

Error message:

Message: Assert.AreEqual failed. Expected:<2020-11-25T07:00:00+00:00>. Actual:<2020-11-25T07:00:00+01:00>.

Do I miss something about datetimes and formats here, or is this maybe a known bug?

I run .Net Framework 4.8

This post is about the same issue, I see: How to solve DateTimeInvalidLocalFormat error: "A UTC DateTime is being converted to text in a format that is only correct for local times."?

UPDATE:

Running the following program yields different results in dotnet framework and dotnet core (as mentioned by evk):

Console.WriteLine(new DateTime(2025, 11, 25, 07, 00, 00, DateTimeKind.Utc).ToString(@"yyyy-MM-dd\THH:mm:sszzz"));

dotnet core prints:

2020-11-25T07:00:00+00:00

dotnet framework prints:

2020-11-25T07:00:00+01:00

Furthermore, when running dotnet framework in debug mode, the following debug assistant message shows up, but is ignored internally in the DateTime.ToString():

Managed Debugging Assistant 'DateTimeInvalidLocalFormat' : '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. '

1
Whenever I asked myself "Is this a bug in the Framework or Compiler?" the answer was always, in 100% of the cases: "No".Uwe Keim
@Seabizkit: I don't know about the OP, but I'm finding your comment very, very hard to understand. You might want to rewrite it more clearly.Jon Skeet
@UweKeim: I'd go with 99.9% personally. There definitely are bugs in both the compiler and the framework, but I always treat my code with more suspicion first.Jon Skeet
@Seabizkit: I understand date/time formats very well - I just don't understand your comment. It's very imprecise - "move the [...]" - where to? Why? "inspect the value your comparison is wrong" - what do you mean by "wrong"? "how valid is that format MM/dd/yyyy hh:mm:ss.fff" - that isn't the format the OP is using - there's no dot missing. The OP isn't trying to get subsecond values. zzz is valid as a format specifier, as documented in the very page you linked to. I don't know what you were trying to say in the comment, but I believe it was unclear, incorrect or both.Jon Skeet
one race Seabiscuit was not destined to win I fearMatt Evans

1 Answers

5
votes

No, it's behaving as documented. From the documentation of the zzz format specifier (emphasis mine):

With DateTime values, the "zzz" custom format specifier represents the signed offset of the local operating system's time zone from UTC, measured in hours and minutes. It doesn't reflect the value of an instance's DateTime.Kind property. For this reason, the "zzz" format specifier is not recommended for use with DateTime values.

Arguably that's unfortunate, but it's not a bug.

Note that .NET Core (and .NET 5.0) apparently doesn't behave as documented. While you could argue it's "fixed" in .NET Core, I'd suggest that behaving in an undocumented way is a bug in itself, and one that could make code migration harder than expected.

I suggest you follow the recommendation in the docs, and avoid using zzz with DateTime values. I'd also suggest using my Noda Time library instead, where there isn't the ambiguity in terms of a value "maybe being local, or maybe being UTC", but that's a slightly different matter. (I wouldn't expect you to run into this problem using Noda Time, and your other date/time code would hopefully be clearer.)