Create DateTime in specific time zone then convert to utc - c#

I need to create a DateTime with a set date and time which will be in a specific time zone(West Asia Standard Time, W. Europe Standard Time etc).
DST must be preserved, so offset is out because for half of the year for a given time zone it will be for example +2h and for the other half +3h.
Then I want to convert the date to UTC.
I tried to do it in such a way that I could add this timezone offset later. However, firstly I do not like this solution and I am afraid that I will lose one hour when the time changes twice a year, and secondly I get an error:
"The UTC Offset for Utc DateTime instances must be 0."
var testTime = new DateTime(testDate.Year, testDate.Month, testDate.Day, 4, 0, 0, DateTimeKind.Utc);
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("West Asia Standard Time");
var timezoneTime = TimeZoneInfo.ConvertTimeFromUtc(runTime, timeZoneInfo);
var offset = timeZoneInfo.GetUtcOffset(timezoneTime);
I would like to get this kind of code
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("West Asia Standard Time");
var testTime = new DateTime(testDate.Year, testDate.Month, testDate.Day, 4, 0, 0, timeZoneInfo);
var utcTime = testTime.ToUniversalTime();
So, to sum up, I want to have method, where I pass year, month, day, hour, minute and timeZone and in return I will get DateTime that is in UTC
In javascript there are libraries, where the given time zone is given as a parameter but I have to do it on the server side.

You'd basically need TimeZoneInfo.ConvertTimeToUtc method.
Just make sure the Kind property of the passed DateTime is Unspecified, otherwise the method has special expectations for the sourceTimeZone argument and will throw exception.
e.g.
var testTime = new DateTime(testDate.Year, testDate.Month, testDate.Day, 4, 0, 0);
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("West Asia Standard Time");
var utcTime = TimeZoneInfo.ConvertTimeToUtc(testTime, timeZoneInfo);;

Related

How to check if an hour exists in a specific Time Zone in .NET

During a Daylight Saving Time transition, the clock is moved forward, and so a specific hour will not exist in that specific day for that specific time zone.
Is there an easy way in .NET to find out if an hour exists or not for a time zone?
The only way I found was by trying to convert an hour to UTC, and check for an exception:
public bool IsValidTime(DateTime date, TimeZoneInfo tzi)
{
try
{
date = DateTime.SpecifyKind(date, DateTimeKind.Unspecified);
TimeZoneInfo.ConvertTimeToUtc(date, tzi);
return true;
}
catch
{
return false;
}
}
And so running something like this will return false:
var date = new DateTime(2020, 3, 8);
var tzi = TimeZoneInfo.FindSystemTimeZoneById("Cuba Standard Time");
var isValid = IsValidTime(date, tzi);
Is there any built in way of doing this, that is less messy?
You can use IsInvalidTime method of TimeZoneInfo.
From Microsoft : https://learn.microsoft.com/en-us/dotnet/api/system.timezoneinfo.isinvalidtime?view=netframework-4.6.2
Example: In the Pacific Time zone, daylight saving time begins at 2:00 A.M. on April 2, 2006. The following code passes the time at one-minute intervals from 1:59 A.M. on April 2, 2006, to 3:01 A.M. on April 2, 2006, to the IsInvalidTime method of a TimeZoneInfo object that represents the Pacific Time zone. The console output indicates that all times from 2:00 A.M. on April 2, 2006, to 2:59 A.M. on April 2, 2006, are invalid.
// Specify DateTimeKind in Date constructor
DateTime baseTime = new DateTime(2007, 3, 11, 1, 59, 0, DateTimeKind.Unspecified);
DateTime newTime;
// Get Pacific Standard Time zone
TimeZoneInfo pstZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
// List possible invalid times for a 63-minute interval, from 1:59 AM to 3:01 AM
for (int ctr = 0; ctr < 63; ctr++)
{
// Because of assignment, newTime.Kind is also DateTimeKind.Unspecified
newTime = baseTime.AddMinutes(ctr);
Console.WriteLine("{0} is invalid: {1}", newTime, pstZone.IsInvalidTime(newTime));
}

Setting datetime to a specific date by specific way

I have a variable with date time which I have to set on a specific date by these rukles and scenarios:
The API that I connect to has a daily limit and once that limit is reached I have to wait until NEXT DAY until 9:10 AM CEST <= This is very important
So I've been simply doing this:
var localTime = TimeZoneInfo.ConvertTime(DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time"));
var tomorrowAt0910 = localTime.AddDays(1).Date + new TimeSpan(9, 10, 0);
And I have realized that this code has a bug, because I can have following scenarios:
Let's say my application would expire on 30th of July at 15:00 PM in which case this logic up there would be VALID
BUT
We have a next following scenario which is more likely to happen:
Application expires on 31st of July 5:00 AM, in which case this logic is faulty because RENEWAL DATE will be set to 1st of August 9:10AM WHICH IS BAD
If the application expires in this second case, I should set the date to same day and few hours difference (from 5AM to 9AM)
How could I do this?
It sounds like what you really want is to say:
Find the current time in Central Europe
Find 9:10am on the same date
If 9:10am is after the current time, add a day
So something like:
// No need to do this more than once
private static readonly TimeZoneInfo centralEuropeZone =
TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time")
private static DateTime GetUtcResetTime()
{
// Using UtcNow to make it clear that the system time zone is irrelevant
var centralEuropeNow = TimeZoneInfo.ConvertTime(DateTime.UtcNow, centralEuropeZone);
var centralEuropeResetTime = centralEuropeNow.Date + new TimeSpan(9, 10, 0);
if (centralEuropeResetTime <= centralEuropeNow)
{
centralEuropeResetTime = centralEuropeResetTime.AddDays(1);
}
return TimeZoneInfo.ConvertTimeToUtc(centralEuropeResetTime, centralEuropeZone);
}
I've made that return a UTC DateTime so that no other code needs to worry about which zone it's in.
Check if the expire date is less that the current date, if so add one day.
DateTime expireDate = new DateTime(2018, 7, 30, 22, 0, 0); //for testing
DateTime tomorrowAt0910 = DateTime.Now.Date.AddHours(9).AddMinutes(10);
if (expireDate.Date < DateTime.Now.Date)
{
tomorrowAt0910.AddDays(1);
}

Build DateTimeOffset from unspecified DateTime and Timezone name

I have a DateTime dt, which has a date and time of some local instant and a String tz specifying the timezone name for that datetime. How do I get a DateTimeOffset struct fully representing the DateTime?
I can get the timezone info with TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById(csf.TimezoneName);
but I'm now unsure how to get the DateTimeOffset I want from these two elements
You can convert a local time in a particular zone to a DateTimeOffset like this:
DateTime dt = new DateTime(2013, 1, 1, 0, 0, 0);
TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTimeOffset dto = new DateTimeOffset(dt, tzi.GetUtcOffset(dt));
Just be aware that if the input time is ambiguous or invalid due to daylight saving time, it will use the zone's standard offset.

Are adding weeks, months or years to a date/time independent from time zone?

My software displays date/time using local time and then send it to server in UTC. On the server-side I want to add months, years, weeks, days etc to this date/time. However, the question is, if I use such methods with UTC date/time and then convert it back to local time, would the result be always the same, as if I use this methods with local time directly?
This is an example in C#:
// #1
var utc = DateTime.Now.ToUtcTime();
utc = utc.AddWeeks(2); // or AddDays, AddYears, AddMonths...
var localtime = utc.ToLocalTime();
// #2
var localtime = DateTime.Now;
localtime = localtime.AddWeeks(2); // or AddDays, AddYears, AddMonths...
Would the results in #1 and #2 always be the same? Or timezone can influence the result?
The answer may surprise you but it is NO. You cannot add days, weeks, months, or years to a UTC timestamp, convert it to a local time zone, and expect to have the same result as if you had added directly to the local time.
The reason is that not all local days have 24 hours. Depending on the time zone, the rules for that zone, and whether DST is transitioning in the period in question, some "days" may have 23, 23.5, 24, 24.5 or 25 hours. (If you are trying to be precise, then instead use the term "standard days" to indicate you mean exactly 24 hours.)
As an example, first set your computer to one of the USA time zones that changes for DST, such as Pacific Time or Eastern Time. Then run these examples:
This one covers the 2013 "spring-forward" transition:
DateTime local1 = new DateTime(2013, 3, 10, 0, 0, 0, DateTimeKind.Local);
DateTime local2 = local1.AddDays(1);
DateTime utc1 = local1.ToUniversalTime();
DateTime utc2 = utc1.AddDays(1);
DateTime local3 = utc2.ToLocalTime();
Debug.WriteLine(local2); // 3/11/2013 12:00:00 AM
Debug.WriteLine(local3); // 3/11/2013 1:00:00 AM
And this one covers the 2013 "fall-back" transition:
DateTime local1 = new DateTime(2013, 11, 3, 0, 0, 0, DateTimeKind.Local);
DateTime local2 = local1.AddDays(1);
DateTime utc1 = local1.ToUniversalTime();
DateTime utc2 = utc1.AddDays(1);
DateTime local3 = utc2.ToLocalTime();
Debug.WriteLine(local2); // 11/4/2013 12:00:00 AM
Debug.WriteLine(local3); // 11/3/2013 11:00:00 PM
As you can see in both examples - the result was an hour off, one direction or the other.
A couple of other points:
There is no AddWeeks method. Multiply by 7 and add days instead.
There is no ToUtcTime method. I think you were looking for ToUniversalTime.
Don't call DateTime.Now.ToUniversalTime(). That is redundant since inside .Now it has to take the UTC time and convert to local time anyway. Instead, use DateTime.UtcNow.
If this code is running on a server, you shouldn't be calling .Now or .ToLocalTime or ever working with DateTime that has a Local kind. If you do, then you are introducing the time zone of the server - not of the user. If your users are not in the same time zone, or if you ever deploy your application somewhere else, you will have problems.
If you want to avoid these kind of problems, then look into NodaTime. It's API will prevent you from making common mistakes.
Here's what you should be doing instead:
// on the client
DateTime local = new DateTime(2013, 3, 10, 0, 0, 0, DateTimeKind.Local);
DateTime utc = local.ToUniversalTime();
string zoneId = TimeZoneInfo.Local.Id;
// send both utc time and zone to the server
// ...
// on the server
TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById(zoneId);
DateTime theirTime = TimeZoneInfo.ConvertTimeFromUtc(utc, tzi);
DateTime newDate = theirTime.AddDays(1);
Debug.WriteLine(newDate); // 3/11/2013 12:00:00 AM
And just for good measure, here is how it would look if you used Noda Time instead:
// on the client
LocalDateTime local = new LocalDateTime(2013, 3, 10, 0, 0, 0);
DateTimeZone zone = DateTimeZoneProviders.Tzdb.GetSystemDefault();
ZonedDateTime zdt = local.InZoneStrictly(zone);
// send zdt to server
// ...
// on the server
LocalDateTime newDate = zdt.LocalDateTime.PlusDays(1);
Debug.WriteLine(newDate); // 3/11/2013 12:00:00 AM

Converting time between Timezones

I'm using TimeZoneInfo.ConvertTime method to convert time from one to another.
While converting the Date Time 1/1/2006 2.00 AM from Perth to Sri Jeyawardenepura its converted to 1/31/2005 11.30pm
While converting the same time back (1/31/2005 11.30pm) from Sri Jeyawardenepura to Perth its converted to 1/1/2006 3.00 AM.
Why there is one hour difference in the Time Zone conversion?
Wow, this is a Double Whammy! I just stumbled across this post and wasn't going to post anything at all since it's so old and the OP didn't show any code. But then curiosity got the best of me so I checked it out.
Using just the .NET BCL:
string tzid1 = "W. Australia Standard Time"; // Perth
TimeZoneInfo tz1 = TimeZoneInfo.FindSystemTimeZoneById(tzid1);
string tzid2 = "Sri Lanka Standard Time"; // Sri Jeyawardenepura
TimeZoneInfo tz2 = TimeZoneInfo.FindSystemTimeZoneById(tzid2);
DateTime dt1 = new DateTime(2006, 1, 1, 2, 0, 0);
Debug.WriteLine(dt1); // 1/1/2006 2:00:00 AM
DateTime dt2 = TimeZoneInfo.ConvertTime(dt1, tz1, tz2);
Debug.WriteLine(dt2); // 12/31/2005 11:30:00 PM
DateTime dt3 = TimeZoneInfo.ConvertTime(dt2, tz2, tz1);
Debug.WriteLine(dt3); // 1/1/2006 3:00:00 AM
Sure enough, there is the discrepancy that the OP described. At first I thought this must be due to some kind of DST issue, so I checked for Sri Lanka and Perth. While both had a transition in 2006, neither were anywhere close to it for this date. Still, I thought I should check using DateTimeOffset to avoid any ambiguity issues:
string tzid1 = "W. Australia Standard Time"; // Perth
TimeZoneInfo tz1 = TimeZoneInfo.FindSystemTimeZoneById(tzid1);
string tzid2 = "Sri Lanka Standard Time"; // Sri Jeyawardenepura
TimeZoneInfo tz2 = TimeZoneInfo.FindSystemTimeZoneById(tzid2);
DateTime dt = new DateTime(2006, 1, 1, 2, 0, 0);
DateTimeOffset dto1 = new DateTimeOffset(dt, tz1.GetUtcOffset(dt));
Debug.WriteLine(dto1); // 1/1/2006 2:00:00 AM +08:00
DateTimeOffset dto2 = TimeZoneInfo.ConvertTime(dto1, tz2);
Debug.WriteLine(dto2); // 12/31/2005 11:30:00 PM +05:30
DateTimeOffset dto3 = TimeZoneInfo.ConvertTime(dto2, tz1);
Debug.WriteLine(dto3); // 1/1/2006 3:00:00 AM +09:00
And it's still off. You can see that it thinks the target time should be at +09:00, but Perth didn't switch to that until December 3rd 2006. In January it was clearly still +08:00.
So then I thought... Noda Time to the rescue!
First let's check using the same Windows .NET BCL time zones.
string tzid1 = "W. Australia Standard Time"; // Perth
DateTimeZone tz1 = DateTimeZoneProviders.Bcl[tzid1];
string tzid2 = "Sri Lanka Standard Time"; // Sri Jeyawardenepura
DateTimeZone tz2 = DateTimeZoneProviders.Bcl[tzid2];
LocalDateTime ldt1 = new LocalDateTime(2006, 1, 1, 2, 0, 0);
ZonedDateTime zdt1 = ldt1.InZoneStrictly(tz1);
Debug.WriteLine(zdt1.ToDateTimeOffset()); // 1/1/2006 2:00:00 AM +08:00
ZonedDateTime zdt2 = zdt1.WithZone(tz2);
Debug.WriteLine(zdt2.ToDateTimeOffset()); // 12/31/2005 11:30:00 PM +05:30
ZonedDateTime zdt3 = zdt1.WithZone(tz1);
Debug.WriteLine(zdt3.ToDateTimeOffset()); // 1/1/2006 2:00:00 AM +08:00
Hey, that seems like it fixed it, right? If so, that would mean that the problem isn't with the Windows time zone data, because Noda Time's BCL provider uses the exact same data. So there must be something actually defective in TimeZoneInfo.ConvertTime. There's Whammy #1.
So just to check that it's all good and well, let's try the same thing with IANA TZDB data. It's known to be more accurate after all:
string tzid1 = "Australia/Perth";
DateTimeZone tz1 = DateTimeZoneProviders.Tzdb[tzid1];
string tzid2 = "Asia/Colombo"; // Sri Jeyawardenepura
DateTimeZone tz2 = DateTimeZoneProviders.Tzdb[tzid2];
LocalDateTime ldt1 = new LocalDateTime(2006, 1, 1, 2, 0, 0);
ZonedDateTime zdt1 = ldt1.InZoneStrictly(tz1);
Debug.WriteLine(zdt1.ToDateTimeOffset()); // 1/1/2006 2:00:00 AM +08:00
ZonedDateTime zdt2 = zdt1.WithZone(tz2);
Debug.WriteLine(zdt2.ToDateTimeOffset()); // 1/1/2006 12:00:00 AM +06:00
ZonedDateTime zdt3 = zdt1.WithZone(tz1);
Debug.WriteLine(zdt3.ToDateTimeOffset()); // 1/1/2006 2:00:00 AM +08:00
And there, my friends, is Whammy #2. Notice that the middle time is using a +06:00 offset? I thought this was in error, but when I checked once more here it turns out that the TZDB data is correct. Sri Lanka was at +06:00 at that time. It didn't switch to +05:30 until April.
So to recap the Whammys:
The Windows TimeZoneInfo.ConvertTime function appears to be flawed.
The Windows Time Zone data for the "Sri Lanka Standard Time" zone is incorrect.
All the better to just use Noda Time and TZDB always!
UPDATE
Thanks to Jon Skeet for helping identify that the first problem is with the way that the "W. Australia Standard Time" zone is being interpreted by the TimeZoneInfo class.
I dug much deeper into the .NET Framework reference source code, and I believe this is happening in the private static method TimeZoneInfo.GetIsDaylightSavingsFromUtc. I believe that they are not taking into account that DST doesn't always start and stop in the same calendar year.
In this case, they are applying the 2006 adjustment rule with the 2005 year, and getting an endTime of 1/2/2005 before the startTime of 12/4/2005. They do attempt to reconcile that this should be in 2006 (by incorrectly adding a year), but they don't consider that data is in reversed order.
This problem will probably show up for any time zones that start their DST in the winter (such as Australia), and it will show up in one form or another any time the transition rule changes - which it did in 2006.
I've raised an issue on Microsoft Connect here.
The "second whammy" I mentioned is just because the historical data for Sri Lanka doesn't exist in the Windows time zone registry keys.
Just to add a little more information to Matt's answer, it seems that the BCL is very confused about its own data for Perth. It seems to think there were two transitions around the end of 2005 - one at 4pm UTC, and one eight hours later.
Demo:
using System;
class Test
{
static void Main()
{
var id = "W. Australia Standard Time"; // Perth
var zone = TimeZoneInfo.FindSystemTimeZoneById(id);
var utc1 = new DateTime(2005, 12, 31, 15, 59, 0, DateTimeKind.Utc);
var utc2 = new DateTime(2005, 12, 31, 16, 00, 0, DateTimeKind.Utc);
var utc3 = new DateTime(2005, 12, 31, 23, 59, 0, DateTimeKind.Utc);
var utc4 = new DateTime(2006, 1, 1, 0, 0, 0, DateTimeKind.Utc);
Console.WriteLine(zone.GetUtcOffset(utc1));
Console.WriteLine(zone.GetUtcOffset(utc2));
Console.WriteLine(zone.GetUtcOffset(utc3));
Console.WriteLine(zone.GetUtcOffset(utc4));
}
}
Results:
08:00:00 // 3:59pm UTC
09:00:00 // 4:00pm UTC
09:00:00 // 11:59pm UTC
08:00:00 // 12:00am UTC the next day
This is highly bizarre, and may be related to the Libyan time zone breakage - although that doesn't have two transitions, just one misplaced one.
You would have to post the specific code to be certain. There could be an issue e.g. with daylight time being applied by one conversion but not the other.
There are may nuances of timezone management. Suggest you review this Jon Skeet blog for a great overview.
It is in fact so tricky to correctly use the .NET time classes that Jon has undertaken a port of Joda-Time to .NET, called Noda Time.
It's worth seriously considering for any project that supports multiple time zones.
Have you considered Day light savings when converting times ?
Refer following link and you will get your answer. The time displayed is absolutely correct
http://www.timeanddate.com/worldclock/timezone.html?n=196&syear=2000

Categories