For instantaneous DateTime tracking, I am using a DateTimeOffset
datatype. The following function adds the user corresponding TimeZone ID offset to the UTC DateTime property of DateTimeOffset
According to the documentation, UtcDateTime
will perform both a time zone conversion and a type conversion on a DateTimeOffset
. The following code does not though. Why is the conversion not taking place?
Function to add TimeSpan offset,
public static DateTimeOffset GetUtcDateTime (DateTime sourceDateTime, string timeZoneId) {
TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById (timeZoneId);
TimeSpan offset = timeZone.GetUtcOffset (sourceDateTime);
DateTimeOffset utcTime = new DateTimeOffset (sourceDateTime, offset);
return utcTime;
}
and here where I am trying to convert,
DateTimeOffset utcDate = (DateTime.UtcNow);
DateTime fromUtc = utcDate.DateTime;
DateTimeOffset UtcDate = StaticHandlers.GetUtcDateTime (fromUtc, "America/Los_Angeles");
Console.WriteLine ("UTC now is {0} and UTC Date LA is {1} and UtcDateTime LA is {2}", utcDate, UtcDate, utcDate.UtcDateTime);
the output is,
UTC now is 5/8/18 6:43:37 AM +00:00 and and UTC Date LA is 5/8/18
6:43:37 AM -07:00 UtcDateTime LA is 5/8/18 6:43:37 AM
update,
I want to preserve both UTC and the user offset for tracking purposes. DST matters in this context. The example below shows what I am talking about.
DateTime currentDateTime = DateTime.Now;
DateTime beforeDST_LA = new DateTime (2018, 3, 11, 0, 0, 0);
DateTime afterDST_LA = new DateTime (2018, 3, 12, 0, 0, 0);
TimeSpan offsetCurrent = tzi.GetUtcOffset (currentDateTime);
TimeSpan offsetBeforeDST = tzi.GetUtcOffset (beforeDST_LA);
TimeSpan offsetAfterDST = tzi.GetUtcOffset (afterDST_LA);
Console.WriteLine ("Current offset is {0} before DST is {1} and After DST is {2}", offsetCurrent, offsetBeforeDST, offsetAfterDST);
Current offset is -07:00:00 before DST is -08:00:00 and After DST is
-07:00:00
First, I would not call your function GetUtcDateTime
, because that's not what it does. It is trying to get a DateTimeOffset
for a specific time zone for a specific time, so call it something like GetDateTimeOffset
.
The main concept you're missing in your code is that DateTime
has .Kind
property, which sets a DateTimeKind
value. The kind is taken into consideration by several places in your code:
GetUtcOffset
will convert Utc
or Local
kinds to the zone provided before determining the offset.
new DateTimeOffset
(the constructor) will error if the kind and the offset conflict, if you provide an offset.
When you assign a DateTime
to a DateTimeOffset
, the implicit conversion is evaluating the kind.
When you call .DateTime
from the DateTimeOffset
, the kind will always be Unspecified
- regardless of the offset.
If you take all of this into account, you'll realize you need to check the kind yourself before calling GetUtcOffset
. If it's not Unspecified
then you'll need to convert it to the specified time zone before getting the offset.
public static DateTimeOffset GetDateTimeOffset(DateTime sourceDateTime, string timeZoneId)
{
TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
// here, is where you need to convert
if (sourceDateTime.Kind != DateTimeKind.Unspecified)
sourceDateTime = TimeZoneInfo.ConvertTime(sourceDateTime, timeZone);
TimeSpan offset = timeZone.GetUtcOffset(sourceDateTime);
return new DateTimeOffset(sourceDateTime, offset);
}
Now that this is handled, turn to the next set of problems, which is where you call it.
DateTimeOffset utcDate = (DateTime.UtcNow);
DateTime fromUtc = utcDate.DateTime;
In line 1, the implicit cast from DateTime
to DateTimeOffset
sets the offset to 00:00
- because DateTime.UtcNow
has .Kind == DateTimeKind.Utc
.
In line 2, the call to the .DateTime
property sets fromUtc.Kind == DateTimeKind.Unspecified
. Essentially, you've stripped away the kind.
So instead of this, just pass DateTime.UtcNow
directly into the function. The kind will persist, and it will all work - now that the Kind
is recognized and the conversion is happening inside the function.
All that said, if your original values are all DateTimeOffset
(example, DateTimeOffset.UtcNow
) then you don't need that function at all. Just call TimeZoneInfo.ConvertTime
with the DateTimeOffset
directly.