Why UtcDateTime function is not adding the offset to the UTC date? - c#

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.

Related

Create new DateTime in Country via TimeZoneInfo for office hours

Question
Q: How can I create a DateTime for e.g. 09:00 in Europe/Vienna
Catch: Most solutions that I've found already take a DateTime object and convert it but I want to CREATE it IN A SPECIFIC timezone knowing the timezone.
So it is not DateTimeKind.Utc and it is not DateTimeKind.Local it would be in DateTime in timezone.
Problem
P: TimeZoneInfo is not a parameter of DateTime. Why not? Could there be a simple extension?
Basis data:
1. string From = "09:00" //local time because summertime/wintertime
2. string Till = "17:00" //local time because summertime/wintertime
3. string TimeZoneResolved = "Europe/Vienna"
Implicitly I have:
TimeZoneInfo timeZoneInfo = TZConvert.GetTimeZoneInfo(TimeZoneResolved);
TimeSpan workHoursStart = TimeSpan.Parse(From);
TimeSpan workHoursEnd = TimeSpan.Parse(Till);
What I want to achieve:
//reconstruct today 9am in that country of timeZoneInfo
var now = DateTime.UtcNow;
var startTime = new DateTime(now.Year, now.Month, now.Day, workHoursStart.Hour, workHoursStart.Minute, 0, 0, timeZoneInfo);
-> invalid because TimeZoneInfo parameter is invalid. Expects DateTimeKind
Because constructor overload might be tricky maybe like that
var now = DateTime.UtcNow;
var officeHoursStart = new DateTime().BasedOnTz(timeZoneInfo, now.Year, now.Month, now.Day, workHoursStart.Hour, workHoursStart.Minute, 0, 0);
var officeHoursStartUtc = officeHoursStart.ToUtc();
To restate the problem - you have as input a time and a time zone, and you need as output the point in time that corresponds to that time on the current day in the given time zone.
Since the output is a point in time, you should prefer using DateTimeOffset. (If you must use a DateTime, you can take the .DateTime property from that.)
Since the input is a time of day, you should prefer using TimeOnly, available in .NET 6 and higher. (If you're using older .NET, then use a TimeSpan.)
Here's the general approach:
using System;
using System.Globalization;
// Get the time zone as a TimeZoneInfo object, either directly or via TimeZoneConverter.
TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById("Eurpope/Vienna");
// Parse the time string. Prefer using TimeOnly (.NET 6+).
string timeString = "17:00";
TimeOnly time = TimeOnly.ParseExact(timeString, "HH:mm", CultureInfo.InvariantCulture);
// Or use TimeSpan on older .NET
// TimeSpan timeSpan = TimeSpan.ParseExact(timeString, "hh\\:mm", CultureInfo.InvariantCulture);
// Get the time on "today" with respect to the given time zone
DateTimeOffset timeTodayInTimeZone = time.OnTodayInTimeZone(timeZone);
You'll need some extension methods. Pick one of these, depending on which input you're using.
public static DateTimeOffset OnTodayInTimeZone(this TimeOnly time, TimeZoneInfo tz) =>
DateOnly.FromDateTime(TimeZoneInfo.ConvertTime(DateTime.UtcNow, tz))
.ToDateTime(time)
.ToDateTimeOffset(tz);
public static DateTimeOffset OnTodayInTimeZone(this TimeSpan time, TimeZoneInfo tz) =>
TimeZoneInfo.ConvertTime(DateTime.UtcNow, tz).Date
.Add(time)
.ToDateTimeOffset(tz);
And finally, you need this extension method that has the bulk of the logic.
(I've used this one on several other answers now.)
public static DateTimeOffset ToDateTimeOffset(this DateTime dt, TimeZoneInfo tz)
{
if (dt.Kind != DateTimeKind.Unspecified)
{
// Handle UTC or Local kinds (regular and hidden 4th kind)
DateTimeOffset dto = new DateTimeOffset(dt.ToUniversalTime(), TimeSpan.Zero);
return TimeZoneInfo.ConvertTime(dto, tz);
}
if (tz.IsAmbiguousTime(dt))
{
// Prefer the daylight offset, because it comes first sequentially (1:30 ET becomes 1:30 EDT)
TimeSpan[] offsets = tz.GetAmbiguousTimeOffsets(dt);
TimeSpan offset = offsets[0] > offsets[1] ? offsets[0] : offsets[1];
return new DateTimeOffset(dt, offset);
}
if (tz.IsInvalidTime(dt))
{
// Advance by the gap, and return with the daylight offset (2:30 ET becomes 3:30 EDT)
TimeSpan[] offsets = { tz.GetUtcOffset(dt.AddDays(-1)), tz.GetUtcOffset(dt.AddDays(1)) };
TimeSpan gap = offsets[1] - offsets[0];
return new DateTimeOffset(dt.Add(gap), offsets[1]);
}
// Simple case
return new DateTimeOffset(dt, tz.GetUtcOffset(dt));
}

Preserve DateTimeOffset in Date String after UTC to TimeZoneInfo Date Conversion

Given the following UTC date string: "2020-10-10T12:00:00"
When converting to string, why do I loose the datetime offset, despite specifying it in the format?
string parseString = "2020-10-10T12:00:00";
if (DateTime.TryParseExact(parseString, "s", null, DateTimeStyles.None, out DateTime parsedDate))
{
TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific/Honolulu");
DateTime atUtc = DateTime.SpecifyKind(parsedDate, DateTimeKind.Utc);
DateTimeOffset dateAtTimeZone = TimeZoneInfo.ConvertTime(atUtc, timeZoneInfo);
Console.WriteLine(dateAtTimeZone.ToString("O"));
Console.WriteLine(dateAtTimeZone.ToString("yyyy-MM-ddTHH:mm:ss.fffffffK"));
}
Why do I get back the following: 2020-10-10T02:00:00.0000000+13:00
Instead of: 2020-10-10T02:00:00.0000000-10:00
There are two TimeZoneInfo.ConvertTime that takes 2 parameters - one that takes a DateTime and returns a DateTime, and one that takes an DateTimeOffset and returns a DateTimeOffset. Right now, you are calling the one that takes a DateTime:
DateTime atUtc = DateTime.SpecifyKind(parsedDate, DateTimeKind.Utc);
DateTimeOffset dateAtTimeZone = TimeZoneInfo.ConvertTime(atUtc, timeZoneInfo); // atUtc is a DateTime here!
The fact that you are assigning the result to DateTimeOffset doesn't matter, because the return value type does not participate in overload resolution.
The overload that takes a DateTime just changes the date and time parts of the DateTime passed in, since timezone/offset information is not a part of DateTimes. When you convert it to DateTimeOffset, your local offset is added to it during the conversion, and that is what gets printed.
What you should be calling is the overload that takes an DateTimeOffset. Because it takes a DateTimeOffset, it is able to change the offset part of the DateTimeOffset as well as the date and time parts, to the desired values. You need to declare atUtc as DateTimeOffset too:
DateTimeOffset atUtc = DateTime.SpecifyKind(parsedDate, DateTimeKind.Utc);
DateTimeOffset dateAtTimeZone = TimeZoneInfo.ConvertTime(atUtc, timeZoneInfo);
Or better, use DateTimeOffset.TryParseExact in the first place!

C# DateTime - converting a DateTimeOffset to another TimeZone

When converting a DateTimeOffset to another TimeZone, the OffSet is incorrect.
I've read many articles and experimented for too many hours, but can't see what I'm missing here:
// It's June in the UK and we're in British Summer Time, which is 1 hour ahead of UTC (GMT)
var UKoffsetUtc = new TimeSpan(1, 0, 0);
// It's 4pm - declare local time as a DateTimeOffset
var UKdateTimeOffset = new DateTimeOffset(2020, 6, 17, 16, 0, 0, UKoffsetUtc);
// Convert to UTC as a date
var utc = DateTime.SpecifyKind(UKdateTimeOffset.UtcDateTime, DateTimeKind.Utc);
// Get Aus TimeZoneInfo
var AUSTimeZone = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");
// Check the Aus offset from UTC
var AUSOffset = AUSTimeZone.GetUtcOffset(utc);
Console.WriteLine(AUSOffset); // Output is 10 as expected
// Declare Aus Time as DateTimeOffset
var AUSDateTimeOffset = TimeZoneInfo.ConvertTimeFromUtc(utc, AUSTimeZone);
// The Aus Offset from UTC is not correct
Console.WriteLine(AUSDateTimeOffset.ToString("dd MM yyyy HH:mm zzz"));
The output is 18 06 2020 01:00 +01:00
Aus are 10 hours ahead of UTC (9 hours ahead of GMT) so the date and time are correct, but not the offset.
How can I get the correct offset in AUSDateTimeOffset?
You can create new offset and use it -
// Create new offset for UTC
var AUSOffset = new DateTimeOffset(utc, TimeSpan.Zero);
// Declare Aus Time as DateTimeOffset
var AUSDateTimeOffset = UKdateTimeOffset.ToOffset(AUSTimeZone.GetUtcOffset(AUSOffset));
Console.WriteLine(AUSDateTimeOffset.ToString("dd MM yyyy HH:mm zzz"));
Or:
Use ConvertTimeBySystemTimeZoneId as suggested by Jimi in the comment!
var finalDate = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(UKdateTimeOffset, "AUS Eastern Standard Time");
Console.WriteLine(finalDate.ToString("dd MM yyyy HH:mm zzz"));
The error is in this part:
var AUSDateTimeOffset = TimeZoneInfo.ConvertTimeFromUtc(utc, AUSTimeZone);
In that code, utc is a DateTime, and thus the resulting AUSDateTimeOffset is actually a DateTime. Its Kind will be DateTimeKind.Unspecified.
The conversion will have been done correctly, and thus you see the correct date and time in the result. However, the offset is wrong because it is not part of a DateTime. The documentation about the zzz specifier says:
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.
Thus, the +01:00 is coming from your local time zone, not from the target time zone.
There are a few ways you could fix this:
You could make AUSDateTimeOffset a DateTimeOffset with the correct offset:
DateTime AUSDateTime = TimeZoneInfo.ConvertTimeFromUtc(utc, AUSTimeZone);
TimeSpan AUSOffset = AUSTimeZone.GetUtcOffset(utc);
DateTimeOffset AUSDateTimeOffset = new DateTimeOffset(AUSDateTime, AUSOffset);
You could use a UTC-based DateTimeOffset instead of a UTC-based DateTime:
DateTimeOffset utc = UKdateTimeOffset.ToUniversalTime();
DateTimeOffset AUSDateTimeOffset = TimeZoneInfo.ConvertTime(utc, AUSTimeZone);
You could just convert the orignal DateTimeOffset, as there's no need to convert to UTC first:
DateTimeOffset AUSDateTimeOffset = TimeZoneInfo.ConvertTime(UKdateTimeOffset, AUSTimeZone);
As Jimi pointed out in comments, you can even convert without constructing a TimeZoneInfo object at all:
DateTimeOffset AUSDateTimeOffset = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(UKdateTimeOffset, "AUS Eastern Standard Time");
Any of the above will give the same, correct response.
It's easy to get around:
var fromTime = DateTimeOffset.Parse("2020-06-17T16:00:00+01:00");
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");//TZConvert.GetTimeZoneInfo("Australia/Sydney");//for Mac
var toTime = fromTime.ToOffset(timeZoneInfo.GetUtcOffset(fromTime.UtcDateTime));
Console.Write(toTime.ToString("yyyy-MM-ddTHH:mm:sszzz")); //2020-06-18T01:00:00+10:00

Convert date from UTC to local timezone during daylight saving [duplicate]

I'm trying to use DateTimeOffset to convey a specific moment in time across any time zone. I can't figure out how to use TimeZoneInfo to deal with daylight saving time.
var dt = DateTime.UtcNow;
Console.WriteLine(dt.ToLocalTime());
var tz = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
var utcOffset = new DateTimeOffset(dt, TimeSpan.Zero);
Console.WriteLine(utcOffset.ToOffset(tz.BaseUtcOffset));
This prints out:
6/2/2010 4:37:19 PM
6/2/2010 3:37:19 PM -06:00
I am in the central time zone, and and we are currently in daylight saving time.
I am trying to get the second line to read:
6/2/2010 4:37:19 PM -05:00
BaseUtcOffset apparently doesn't change based on DST.
How can I get the the right time with the proper offset value?
You can also use TimeZoneInfo.ConvertTimeFromUtc, which will allow for daylight saving time:
DateTime utc = DateTime.UtcNow;
TimeZoneInfo zone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
DateTime localDateTime = TimeZoneInfo.ConvertTimeFromUtc(utc, zone);
You need to get the UtcOffset from the TimeZoneInfo, then pass that to the ToOffset() method:
var dt = DateTime.UtcNow;
Console.WriteLine(dt.ToLocalTime());
var tz = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
var utcOffset = new DateTimeOffset(dt, TimeSpan.Zero);
Console.WriteLine(utcOffset.ToOffset(tz.GetUtcOffset(utcOffset)));
Or better, if you don't want to hard code the time zone identifier:
TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById(TimeZoneInfo.Local.Id);
DateTime localDateTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tzi);
I'm a beginner both at .NET and stackoverflow, so I could be wrong, but here goes:
Using TimeZoneInfo.ConvertTimeFromUtc will allow for daylight saving time, and convert to the correct time according to the time zone + a possible DST offset. However - the offset itself in the resulting object will show the offset for standard time, and not take daylight saving time into account. So if you want to do a ToString on the object, you will end up with the correct time (in hours and minutes), but the wrong offset during daylight saving time, which may lead to the wrong moment in time later in the code.
If you instead use the GetUtcOffset to get the offset for a specific time, and then do a ToOffset on the DateTimeOffset object, both the hours/minutes and the offset itself will be correctly converted, and you can safely do a ToString.
string ExpectedDateTimePattern = "yyyy'-'MM'-'dd'T'HH':'mm':'ss''zzz";
string timeZoneId = "FLE Standard Time";
string dateTimestr = "2017-10-09T09:00:00+02:00";
DateTimeOffset dto = DateTimeOffset.Parse(dateTimeStr);
TimeZoneInfo zone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
TimeSpan offset = zone.GetUtcOffset(dto);
dto = dto.ToOffset(offset);
string localTime = dto.ToString(ExpectedDateTimePattern);
localTime will return "2017-10-09T10:00:00+03:00".
datetimeoffset timezoneinfo getutcoffset
This will Adjust automatically... and Return time as per your timezone.
public static string SetLastModified (
TimeZoneInfo csttzi = TimeZoneInfo.FindSystemTimeZoneById(TimeZone.CurrentTimeZone.StandardName);
DateTime cstTime = TimeZoneInfo.ConvertTime(DateTime.UtcNow, csttzi);
return String.Format("DaylightSavingTime: {0}", cstTime.IsDaylightSavingTime().ToString());
}

Converting UTC DateTime to EST DateTime with correct offset

My app gets a DateTime object in UTC and needs to output as a string it in EST format. I've tried the following code, but when I get the output the offset still shows as +00:00 instead of -05:00
static void Main(string[] args)
{
var currentDate = DateTime.Now.ToUniversalTime();
var convertedTime = ConvertUtcToEasternStandard(currentDate);
Console.WriteLine(currentDate.ToString("yyyy-MM-ddTHH:mm:sszzz"));
Console.WriteLine(convertedTime.ToString("yyyy-MM-ddTHH:mm:sszzz"));
}
private static DateTime ConvertUtcToEasternStandard(DateTime dateTime)
{
var easternZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
return TimeZoneInfo.ConvertTimeFromUtc(dateTime, easternZone);
}
This outputs:
2016-11-18T06:56:14+00:00
2016-11-18T01:56:14+00:00
So the time is being shifted correctly, but the offset stays at +00:00 when I want it to be -05:00. Any idea how I can get a DateTime object with the correct offset when output with the above format string?
I read this in the API not long ago, basically, with DateTime values, it's very much impossible to get the zzz format offset to be something useful.
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 does not 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.
With DateTimeOffset values, this format specifier represents the DateTimeOffset value's offset from UTC in hours and minutes.
The offset is always displayed with a leading sign. A plus sign (+) indicates hours ahead of UTC, and a minus sign (-) indicates hours behind UTC. A single-digit offset is formatted with a leading zero.
For example, I'm in Eastern Standard Time, and this is my result:
2016-11-18T07:9:38-05:00
2016-11-18T02:9:38-05:00
Obviously UTC time shouldn't have an offset of -05:00.
Modify your code just a bit and we have a solution:
void Main()
{
var currentDate = DateTimeOffset.Now.ToUniversalTime();
var convertedTime = ConvertUtcToEasternStandard(currentDate);
var format = "yyyy-MM-ddTHH:m:sszzz";
Console.WriteLine(currentDate.ToString(format));
Console.WriteLine(convertedTime.ToString(format));
}
// Define other methods and classes here
private static DateTimeOffset ConvertUtcToEasternStandard(DateTimeOffset dateTime)
{
var easternZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
return TimeZoneInfo.ConvertTime(dateTime, easternZone);
}
Result:
2016-11-18T07:17:46+00:00
2016-11-18T02:17:46-05:00
That's more like it.
Note: replaced previous code, ignorance got the best of me and I failed to realize that it wasn't working right. TimeZoneInfo.ConvertTime takes a DateTimeOffset which does what we want.
And if we add another case for Pacific Standard Time:
private static DateTimeOffset ConvertUtcToPacificStandard(DateTimeOffset dateTime)
{
var pacificZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
return TimeZoneInfo.ConvertTime(dateTime, pacificZone);
}
We get the correct result:
2016-11-17T23:21:21-08:00

Categories

Resources