Getting local hour with NodaTime - c#

New to NodaTime and I chose to use it instead of the BCL libraries because it removes a lot of ambiguity when dealing with dates. However, I can't seem to get it to do what I want. I have a date and time specified by year, month, day, hours, and minutes. I also have two timezones for which I need to display the "clock time". For example, if my input is December 15, 2015 at 3:30 PM and my two timezones are central standard and eastern standard (one hour apart), I expect my output to be
12/15/2015 3:30 PM Central
12/15/2015 4:30 PM Eastern
But I can only seem to get the central (local to me, if that matters) timezone. Here's my code:
var localDateTime = new LocalDateTime(
year: 2015,
month: 12,
day: 15,
hour: 15,
minute: 30,
second: 0
);
var centralTimeZone = DateTimeZoneProviders.Bcl.GetZoneOrNull("Central Standard Time");
var easternTimeZone = DateTimeZoneProviders.Bcl.GetZoneOrNull("Eastern Standard Time");
var centralTime = centralTimeZone.AtLeniently(localDateTime);
var easternTime = easternTimeZone.AtLeniently(localDateTime);
It seems that centralTime and easternTime are both ZonedDateTime objects whose times are 2015-12-10T15:30 with the correct offset i.e. centralTime is -6 and easternTime is -5.)
I just can't figure out how to get the output I want.

It sounds like your initial date/time is actually in Central time - so once you've performed the initial conversion, you can just say "Hey, I want the same instant in time, but in a different time zone" - which isn't the same as "I want a different instant in time for the same local time". You want:
var centralTime = centralTimeZone.AtLeniently(localDateTime);
var easternTime = centralTime.InZone(easternTimeZone);

Related

How to check for local time 02:30am without running into Winter/Summer time problems

I have a program where world-wide users shall be able to configure that a task always runs at X:YZ o'clock. In their local time, all year round.
I thought about storing the timestamp in UTC offset but that brings the problem that half a year there is an hour difference between the configured time and the calculated time based on UTC. (example when I configure it to be 10:00 in Germany on Nov 25th, it'll be 09:00 UTC. But in May it suddenly will be 11:00 in Germany because now the UTC offset is two hours.
However, storing just the time in the database brings the problem that some hours simply don't exist on certain days of the year. When the clock is moved forward, the time jumps from 1:59 directly to 3:00. Any time between 2:00 and 2:59 on that particular day is invalid.
But how do I figure out that a certain time on a certain day in a certain timezone is invalid?
Edit
I tested TimeZoneInfo.IsInvalid() but it doesn't appear to work:
On Sunday, 29th March 2020 there was no hour 02:30am. And yet IsInvalid() reports false:
var tzi = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
var isInvalid = tzi.IsInvalidTime(new DateTime(2020, 3, 29, 2, 30, 0, DateTimeKind.Local));
Edit 2
Ah ok, one needs to mark the timestamp as unspecified for IsInvalidTime() to work properly:
var isInvalid = tzi.IsInvalidTime(new DateTime(2020, 3, 29, 2, 30, 0, DateTimeKind.Unspecified));
TimeZoneInfo.IsInvalid() does work if the DateTime object is created properly. It must not have the DateTimeKind.Local attribute but rather DateTimeKind.Unspecified
var tzi = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
var isInvalid = tzi.IsInvalidTime(new DateTime(2020, 3, 29, 2, 30, 0, DateTimeKind.Unspecified));

Noda Time Instant to CET

I'm using the Noda Time libarary (v 2.0.3) for date time handling in a .net core project. However; I'm having some issues converting an instant to a CET date-time.
I'm fairly new to Noda Time, possibly using it wrong (is not CET tz-db entry referring to CET at all)?
Example code
// get current system instant
var systemInstant = SystemClock.Instance.GetCurrentInstant();
// get oslo zoneddatetime from instant
var osloDateTime = systemInstant.InZone(DateTimeZoneProviders.Tzdb["Europe/Oslo"]);
// get CET(?) zoneddatetime from instant
var cetDateTime = systemInstant.InZone(DateTimeZoneProviders.Tzdb["CET"]);
// output
Debug.WriteLine(osloDateTime.ToString());
Debug.WriteLine(cetDateTime.ToString());
The output from the example code above gives me:
2017-06-16T22:28:16 Europe/Oslo (+02)
2017-06-16T22:28:16 CET (+02)
Acutally I was expecting the CET zoned time to be 21:28:16 (UTC+1) and not 22:28:16 (UTC+2). UTC+1 is also what the Time and date website displaying.
The Zone line of the europe file in the IANA time zone database contains this single line for the CET zone ID:
Zone CET 1:00 C-Eur CE%sT
Then the end recurrence of the C-Eur rule is this pair of lines:
Rule C-Eur 1981 max - Mar lastSun 2:00s 1:00 S
Rule C-Eur 1996 max - Oct lastSun 2:00s 0 -
So it goes into UTC+2 at 2am on the last Sunday of March each year, and back to UTC+1 at 2am on the last Sunday of October each year.
Note that the "abbreviation" in the CET time zone will vary between "CET" and "CEST" - and that may be what's misleading you. But Noda Time is following the definition of the ID "CET" as per the IANA database.
This is just another reason to avoid using the abbreviations, and instead to use full zone IDs such as Europe/Oslo which are unambiguous. I would suggest avoid trying to use the concept of "a CET date-time" entirely.
If you look at the result of DateTimeZoneProviders.Tzdb["CET"], it has a minimum offset of +1 and a maximum offset of +2, so I think it is refering to the actual central European timezone (including the Central European Summer Time [CEST]).
As the date you selected falls into the summer time range, it is UTC+2.
If you try the following you get UTC+1 for CET:
Instant.FromDateTimeUtc(new DateTime(2017, 01, 01, 12, 0, 0, DateTimeKind.Utc)).InZone(DateTimeZoneProviders.Tzdb["CET"]

Different time output for ToUniversalTime

I cannot understand or find any information that could explain why there are two different time component output (12p.m and 11 a.m) for the following. Can somebody please explain.
DateTime d1 = new DateTime(2015, 05, 15).ToUniversalTime();
DateTime d2 = new DateTime(2015, 02, 02).ToUniversalTime();
Console.WriteLine(d1.ToString()); //OUTPUTS - 1/05/2015 12:00:00 p.m.
Console.WriteLine(d2.ToString()); //OUTPUTS - 1/02/2015 11:00:00 a.m.
The ToUniveralTime method converts from the local time zone where the code is running, to UTC.
Since time zones can change their offsets from UTC at different times of the year, the value can easily be different between two different dates - especially since one date is in the winter, and the other is in the summer, due to daylight saving time.
See also, the DST tag wiki, and "time zone != offset" in the timezone tag wiki.

.ToUniversalTime() Is Incorrect?

DateTime dt = new DateTime(1972, 4, 24, 0, 0, 0);
Response.Write("dt: " + dt.ToString("M/d/yyyy h:mm:ss tt") + "<br />");
Response.Write("dt.Kind: " + dt.Kind.ToString() + "<br />");
Response.Write("dt.ToUniversalTime(): " + dt.ToUniversalTime().ToString("M/d/yyyy h:mm:ss tt") + "<br />");
displays
dt: 4/24/1972 12:00:00 AM
dt.Kind: Unspecified
dt.ToUniversalTime(): 4/24/1972 7:00:00 AM
which is incorrect. April 24, 1972 at 12 PM Pacific is actually April 24, 1972 at 8 AM UTC.
I have confirmed the correct UTC conversion with iOS's internal UTC date conversation and www.timeanddate.com and the UTC time should be 8 AM. Am I doing something wrong?
The server is running in "Pacific Time" timezone so ToUniversalTime should be converting from Pacific timezone to UTC since Unspecified is treated like Local.
If you're on a machine that's not on Pacific Standard Time, you can see this behavior using the following code:
DateTime dt = new DateTime(1972, 4, 24, 0, 0, 0);
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
Console.WriteLine (TimeZoneInfo.ConvertTimeToUtc(dt, tz));
// 4/24/1972 7:00:00 AM
If you look at navy.mil's history of daylight saving time, you'll notice the following paragraph:
The Uniform Time Act of 1966 provided standardization in the dates of
beginning and end of daylight time in the U.S. but allowed for local
exemptions from its observance. The act provided that daylight time
begin on the last Sunday in April and end on the last Sunday in
October, with the changeover to occur at 2 a.m. local time.
And then a little later on:
In 1986, a law was passed that shifted the starting date of daylight
time to the first Sunday in April, beginning in 1987
So the DST switchover wasn't the first Sunday in April until 1987, but for some reason .NET is acting as if it was.
Timeanddate.com's history of DST seems to agree, and lists April 30, 1972 (the last Sunday in April) as the date clocks were turned forward one hour (to UTC-7).
Microsoft's DST adjustment rules for times before 1987 appear to be wrong (and I'm not the only one who thinks so).
Here's what TimeZoneInfo lists as the rules for PST:
Basically, Microsoft has ignored the historical rules and chosen to use rules put into effect in 1987 for dates that happened before those rules even existed.
Essentially your date (in 1972) is being handled incorrectly by Microsoft's TimeZoneInfo adjustment rules.
If you're looking for a library that handles these types of time zone rules much better, check out NodaTime, which handles this particular case correctly:
var pacific = DateTimeZoneProviders.Tzdb["America/Los_Angeles"];
LocalDateTime localDateTime = new LocalDateTime(1972, 4, 24, 0, 0);
ZonedDateTime zonedDateTime = pacific.AtStrictly(localDateTime);
DateTime utcDateTime = zonedDateTime.ToDateTimeUtc();
Console.WriteLine(utcDateTime);
// 4/24/1972 8:00:00 AM

How do you deal with 'Ambiguous Time' in .net?

I need to convert a 'DateTime' value from different timezones to UTC and vice-versa. I use TimeZoneInfo to do this. But, the issue is when the 'Day Light Saving' time change happens.
For example, this year, next time the time change happens at 2AM[CDT] on November 3. So on Nov 3, 1AM[CDT] is converted to 6AM and as time change happens the next hour we get 1AM[now its CST] again and it is also converted to 6AM. I tried the code on this page, but it didn't say anything how to handle this issue. So how to deal with this issue???
Edit:
I tried NodaTime and when I do the conversion like
DateTimeZoneProviders.Tzdb["America/Chicago"].AtStrictly(<localDateTime>)
it throws AmbiguousTimeException. Thats good and I can do this using TimeZoneInfo too. But how do I know which localTime value I need to pick?
Edit 2:
here is the link for the chat discussion with Matt.
If all you have is a local time, and that time is ambiguous, then you cannot convert it to an exact UTC instant. That is why we say "ambiguous".
For example, in the US Central time zone, which has the IANA zone name America/Chicago and the Windows zone id Central Standard Time - covering both "Central Standard Time" and "Central Daylight Time". If all I know is that it is November 3rd, 2013 at 1:00 AM, then then this time is ambiguous, and there is absolutely no way to know whether this was the first instance of 1:00 AM that was in Central Daylight Time (UTC-5), or Central Standard Time (UTC-6).
Different platforms do different things when asked to convert an ambiguous time to UTC. Some go with the first instance, which is usually the Daylight time. Some go with the Standard time, which is usually the second instance. Some throw an exception, and some (like NodaTime) give you a choice of what you want to happen.
Let's start with TimeZoneInfo first.
// Despite the name, this zone covers both CST and CDT.
var tz = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
var dt = new DateTime(2013, 11, 3, 1, 0, 0);
var utc = TimeZoneInfo.ConvertTimeToUtc(dt, tz);
Debug.WriteLine(utc); // 11/3/2013 7:00:00 AM
As you can see, .net chose to use the "standard" time, which is UTC-6. (Adding 6 hours to 1AM gets you to 7AM). It didn't give you any warning that the time was ambiguous. You could have checked yourself, like this:
if (tz.IsAmbiguousTime(dt))
{
throw new Exception("Ambiguous Time!");
}
But there isn't anything to enforce this. You must check it yourself.
The only way to not have ambiguity is to not use the DateTime type. Instead, you can use DateTimeOffset. Observe:
// Central Standard Time
var dto = new DateTimeOffset(2013, 11, 3, 1, 0, 0, TimeSpan.FromHours(-6));
var utc = dto.ToUniversalTime();
Debug.WriteLine(utc); // 11/3/2013 7:00:00 AM +00:00
// Central Daylight Time
var dto = new DateTimeOffset(2013, 11, 3, 1, 0, 0, TimeSpan.FromHours(-5));
var utc = dto.ToUniversalTime();
Debug.WriteLine(utc); // 11/3/2013 6:00:00 AM +00:00
Now, compare this to NodaTime:
var tz = DateTimeZoneProviders.Tzdb["America/Chicago"];
var ldt = new LocalDateTime(2013, 11, 3, 1, 0, 0);
// will throw an exception, only because the value is ambiguous.
var zdt = tz.AtStrictly(ldt);
// will pick the standard time, like TimeZoneInfo did
var zdt = tz.AtLeniently(ldt);
// manually specify the offset for CST
var zdt = new ZonedDateTime(ldt, tz, Offset.FromHours(-6));
// manually specify the offset for CDT
var zdt = new ZonedDateTime(ldt, tz, Offset.FromHours(-5));
// with any of the above, once you have a ZonedDateTime
// you can get an instant which represents UTC
var instant = zdt.ToInstant();
As you can see, there are lots of options. All are valid, it just depends on what you want to do.
If you want to completely avoid ambiguity, then always keep a DateTimeOffset, or when using NodaTime use a ZonedDateTime or OffsetDateTime. If you use DateTime or LocalDateTime, there is no avoiding ambiguity.

Categories