Reputation: 51200
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
Reputation: 5227
I think you are missunderstanding what the API is doing.
First thing to note is that both DateTimeStyles.AssumeUniversal
and DateTimeStyles.AssumeLocal
will 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
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
andAssumeUniversal
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 beUtc
, you can use theAdjustToUniversal
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 asUTC
and output it asUTC
), 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