implementing DST checkbox - c#

I am implementing DST checkbox, when checkbox is on clock is adjusted if required and if checkbox is off then DST is not considered,
Suppose When DST begins, clocks are advanced by one hour
so here we have two times - original time and adjusted time
how do I retrieve both these times?
at New York (-04) On Sun, Mar 8 at 2:00 am DST starts so clocks are adjusted +1 hour.
so there is original time and adjusted time
at original time Sun, Mar 9 at 8:00 am the clock displays adjusted time Sun, Mar 9 at 9:00 am
being at a place out of USA, given input as current universal time I want to retrieve original time and DST adjusted time at new york.
Input/Output - Update 03/05
Is this correct way of achieving above
string fromZoneId = "Asia/Kolkata";
string toZoneId = "America/New_York";
var fromDateTime = DateTime.Parse("March 9, 2020");//Input kolkata time
LocalDateTime fromLocal = LocalDateTime.FromDateTime(fromDateTime);
DateTimeZone fromZone = DateTimeZoneProviders.Tzdb[fromZoneId];
ZonedDateTime fromZoned = fromLocal.InZoneLeniently(fromZone);
DateTimeZone toZone = DateTimeZoneProviders.Tzdb[toZoneId];
ZonedDateTime toZoned = fromZoned.WithZone(toZone);
LocalDateTime toLocal = toZoned.LocalDateTime;
var interval = toZoned.GetZoneInterval();
var savings = interval.Savings;
var originalTime = toLocal.ToDateTimeUnspecified().AddSeconds(-savings.Seconds);
var dstAdjustedTime = toLocal.ToDateTimeUnspecified();
Console.WriteLine("Actual:"+ originalTime);//output-dst off
Console.WriteLine("Adjusted:"+ dstAdjustedTime);//output-dst on

What I would change in your example is staying in Noda to do all the transformations before moving to BLC. That way you can take advantage of the WithOffset() method and use the StandardOffset instead of doing the arithmetics yourself within BLC
So, instead of doing
DateTimeZone toZone = DateTimeZoneProviders.Tzdb[toZoneId];
ZonedDateTime toZoned = fromZoned.WithZone(toZone);
LocalDateTime toLocal = toZoned.LocalDateTime;
var interval = toZoned.GetZoneInterval();
var savings = interval.Savings;
var originalTime = toLocal.ToDateTimeUnspecified().AddSeconds(-savings.Seconds);
var dstAdjustedTime = toLocal.ToDateTimeUnspecified();
I would do
DateTimeZone toZone = DateTimeZoneProviders.Tzdb[toZoneId];
ZonedDateTime toZoned = fromZoned.WithZone(toZone);
LocalDateTime toLocal = toZoned.LocalDateTime;
var interval = toZoned.GetZoneInterval();
var standardOffset = interval.StandardOffset;
var originalTime = toZoned.ToOffsetDateTime().WithOffset(standardOffset).ToDateTimeOffset();
var dstAdjustedTime = toLocal.ToDateTimeUnspecified();
Then you can check how to better display the original time because now it is a DateTimeOffset and not a DateTime. But I would even try to use the NodaTime serialization methods instead of going to DateTime or DateTimeOffset

I am implementing DST checkbox, when checkbox is on clock is adjusted if required and if checkbox is off then DST is not considered ...
You immediately have a problem. You should not implement such a checkbox. The requirement is flawed. Every implementation that does that is broken. It's simply not how time zones work.
For example, say I pick "America/New_York" then I disable DST. What does that even mean? All of "America/New_York" by definition uses DST presently. You could apply it to an earlier date that did not (say, 1944) - or you could pick a different time zone that is currently at UTC-5 without DST, such as "America/Jamaica". But you can't arbitrarily lie about whether DST is in effect or not.
Put another way - a person living in a particular area does not have a choice on whether DST applies or not for themselves personally. It is up to the government, and those established rules are reflected in the time zone data for the given identifier.

Related

Create DateTime in specific time zone then convert to utc

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);;

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);
}

Which Noda Time type should be used to calculate years/months/days between a specific date and today?

I'm building an application that needs to calculate things like 'how many weeks/months/years are there between two dates' and all my users are from a single time zone.
I wonder, is it better to use LocalDateTime for this (because I use only one time zone) or ZonedDateTime (to account for possible DST and offset changes)?
Also, am I right when doing
LocalDateTime.FromDateTime(DateTime.UtcNow)
For the first scenario, use the LocalDate type, along with the Period.Between method.
For the second scenario, you could construct a LocalDateTime like that, but then the resulting value would reflect the "UTC day" - which is an artificial construct.
Whenever you need "now":
Start from an IClock implementation, such as SystemClock.Instance.
Get an Instant by calling .Now on the clock.
Decide what time zone you want the "now" to reflect. Use .InZone on the instant to project to a ZonedDateTime in that zone.
From there, split off whatever component you need to satisfy the scenario you're using. For example, you could call .Date to get a LocalDate instance to use with the first scenario you asked about.
In general, try to avoid calling DateTime.Now or DateTime.UtcNow. Sure, there are places you could use that for short-cutting, but the API won't necessarily guide you to the right conclusion.
To summarize both scenarios:
// assuming you start with a DateTime, perhaps from a db.
DateTime dt = new DateTime(2016,1,1);
LocalDate ldt = LocalDateTime.FromDateTime(dt).Date;
DateTimeZone tz = DateTimeZoneProviders.Tzdb["America/New_York"];
ZonedDateTime now = SystemClock.Instance.Now.InZone(tz);
LocalDate today = now.Date;
Period period = Period.Between(today, ldt, PeriodUnits.YearMonthDay);
int years = (int) period.Years;
int months = (int) period.Months;
int days = (int) period.Days;
Also, note that in Noda Time 2.0 (currently in alpha), it gets a little bit simpler:
DateTime dt = new DateTime(2016, 1, 1);
LocalDate ldt = LocalDate.FromDateTime(dt); // 2.0
DateTimeZone tz = DateTimeZoneProviders.Tzdb["America/New_York"];
LocalDate today = SystemClock.Instance.InZone(tz).GetCurrentDate(); // 2.0
Period period = Period.Between(today, ldt, PeriodUnits.YearMonthDay);
int years = (int)period.Years;
int months = (int)period.Months;
int days = (int)period.Days;

Getting invalid result when using Noda Time c#

Assume Instant variable contains "4/8/2014 11:09:24 AM" when i pass this value to this method it gives me an output "4/8/2014 12:09:24 PM" i checked in some time zone calcluator plus in oracle i should be getting "4/8/2014 04:09:24 PM" or "4/8/2014 16:09:24 PM" depending on hour format. why is it not converting into proper time format?
public static string ConvertDateTimeToUserTimeZone()
{
DateTime dt = DateTime.Parse("4/8/2014 11:09:24 AM");
Instant now = Instant.FromDateTimeUtc(DateTime.SpecifyKind(dt,DateTimeKind.Utc));
DateTimeZone dtZone = DateTimeZoneProviders.Tzdb["Europe/London"];
ZonedDateTime zdt = now.InZone(dtZone);
return zdt.ToDateTimeOffset().ToString("G");
}
Reading through your comments, it would appear that you'd like to do the following:
Parse the input string
Assign it to the the US Eastern time zone ("America/New_York")
Convert it to the time zone for the UK ("Europe/London")
Format it as a string in the general format
I will show using Noda Time for all of these steps, rather than mixing in non-Noda mechanisms.
var generalPattern = LocalDateTimePattern.CreateWithCurrentCulture("G");
string inputString = "4/8/2014 11:09:24 AM";
string sourceTimeZone = "America/New_York";
string targetTimeZone = "Europe/London";
LocalDateTime ldt1 = generalPattern.Parse(inputString).Value;
DateTimeZone tz1 = DateTimeZoneProviders.Tzdb[sourceTimeZone];
ZonedDateTime zdt1 = ldt1.InZoneLeniently(tz1);
DateTimeZone tz2 = DateTimeZoneProviders.Tzdb[targetTimeZone];
ZonedDateTime zdt2 = zdt1.WithZone(tz2);
LocalDateTime ldt2 = zdt2.LocalDateTime;
string output = generalPattern.Format(ldt2);
Notice that I used CreateWithCurrentCulture when setting up the pattern. This assumes you mean to use whatever the current culture is for the machine the code is running on. If that's not the case, then you should set a specific culture instead. This is important when you realize that users in the US will use M/D/Y formatting, while users in the UK will use D/M/Y formatting. This applies to both dates regardless of time zone. (In other words, 4/8/2014 could be April 8th, or August 4th).
Also notice that I used InZoneLeniently when applying the source time zone. This has lenient behavior when it comes to ambiguous and invalid input values due to DST transitions. If you want different behavior, then you might instead use InZoneStrictly, or use InZone and provide your own algorithm.
And finally, it should be noted that I inferred from your comments that you were sourcing these from the US Eastern time zone, which would be either EDT or EST depending on what time of year it is. If you actually meant that the values were always EDT, even when EST is the norm, then you would do the following:
DateTimeZone tz1 = DateTimeZone.ForOffset(Offset.FromHours(-4));
The value you got back from the method is in fact correct.
Let's break it down using LINQPad:
void Main()
{
var t = new DateTime(2014, 8, 4, 11, 09, 24, DateTimeKind.Utc);
var i = Instant.FromDateTimeUtc(t);
var s = ConvertDateTimeToUserTimeZone(i);
s.Dump("User time zone value");
}
public static string ConvertDateTimeToUserTimeZone(Instant now)
{
DateTimeZone dtZone = DateTimeZoneProviders.Tzdb["Europe/London"];
dtZone.GetUtcOffset(now).Dump("TZ at instant");
ZonedDateTime zdt = now.InZone(dtZone);
return zdt.ToDateTimeOffset().ToString("G");
}
Output:
TZ at instant
+01
User time zone value
04.08.2014 12:09:24
The time zone at the point of that instant is thus UTC+1, which means that the value you gave the code gives you the correct value back, 12:09.
To get 16:09, you would need UTC+5, and Europe/London has never been that value.
So 12:09:24 is correct, your assumptions that it should be 16:09:24 is wrong.
You need to go back to the code and/or sites you used to get the "correct" value, the source of your confusion is there, not in this code.

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

Categories