Joe Phillips
Joe Phillips

Reputation: 51200

How can I print DateTime in UTC format?

I'm simply trying to print a DateTime in its UTC equivalent time format. What am I doing wrong?

var utcEpoch = DateTime.Parse("1970-01-01", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); //This specifies the time I provided is in UTC
Console.WriteLine(utcEpoch.ToString("yyyy-MM-dd HH:mm:ss zzz")); //This properly shows my UTC offset of -6, so it's not wrong
Console.WriteLine(utcEpoch.ToString("u")); //This just flat out seems wrong because it doesn't specify a timezone or offset in its output

> 1969-12-31 18:00:00 -06:00
> 1969-12-31 18:00:00Z

I expected to see 1970-01-01 00:00:00Z for the last one.

Upvotes: 3

Views: 7656

Answers (2)

Mikael Eliasson
Mikael Eliasson

Reputation: 5227

I think you are missunderstanding what the API is doing.

First thing to note is that both DateTimeStyles.AssumeUniversal and DateTimeStyles.AssumeLocalwill still return a DateTime where Kind = Local

> DateTime.Parse("1970-01-01 00:00:00", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).Kind
=> Local
> DateTime.Parse("1970-01-01 00:00:00", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal).Kind
=> Local

So no matter what we will get a local date. That means that the API most likely is there to make it possible to get a local time from a UTC date. Let's try if that's correct.

I'm in Sweden so we are UTC + 1 during standard time. So if I use DateTimeStyles.AssumeUniversal and put in todays date I should get a local date being 01:00 today.

Running this in C# Interactive:

> DateTime.Parse("2018-03-03", .CultureInfo.InvariantCulture, .DateTimeStyles.AssumeUniversal)
=> [2018-03-03 01:00:00]

Meaning C# assumed that the string I inputed was in UTC and I wanted it in local so it "fixed" it for me.

Doing the same with AssumeLocal.

DateTime.Parse("2018-03-03", .CultureInfo.InvariantCulture, .DateTimeStyles.AssumeLocal)
=> [2018-03-03 00:00:00]

As expected we now treated the input as a local string and got the same value.

To get the date as UTC you can specify the kind

DateTime.SpecifyKind(DateTime.Parse("2018-03-03", CultureInfo.InvariantCulture), DateTimeKind.Utc).ToString("o")
=> "2018-03-03T00:00:00.0000000Z"

Upvotes: 3

Soner Gönül
Soner Gönül

Reputation: 98858

From The Universal Sortable ("u") Format Specifier :

Although the result string should express a time as Coordinated Universal Time (UTC), no conversion of the original DateTime value is performed during the formatting operation. Therefore, you must convert a DateTime value to UTC by calling the DateTime.ToUniversalTime method before formatting it. In contrast, DateTimeOffset values perform this conversion automatically; there is no need to call the DateTimeOffset.ToUniversalTime method before the formatting operation.

Your utcEpoch.Kind is not UTC, it is Local. DateTime's are triciker than you might think. You are expecting that it will return UTC as Kind property but it is not. It returns Local.

This situation has been discussed on Phil Haack blog post as well and Matt Johnson has a quite nice comment about this;

AssumeLocal and AssumeUniversal are both related to how the input string is interpreted. By themselves, neither will change the output kind.

The default output kind is Local. To get it to be Utc, you can use the AdjustToUniversal style.

The DateTimeStyles enum is flags-based, so you can combine these in some ways that make sense. To achieve what you originally set out to do (parse the input as UTC and output it as UTC), then you would use:

DateTime utcDate = DateTime.Parse("10/01/2006 19:30", culture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);

As others pointed pointed out, a separate call to ToUniversalTime() would also work, but this is technically more correct.

You can see it on referance source as well;

case 'u':   // Universal time in sortable format.
if (offset != NullOffset)
{
    // Convert to UTC invariants mean this will be in range
    dateTime = dateTime - offset;
}
else if (dateTime.Kind == DateTimeKind.Local)
{
    InvalidFormatForLocal(format, dateTime);
}

Upvotes: 4

Related Questions