Example of ambiguous DateTimeOffset - c#

We have DateTimeOffsets in Database/Model. To display these values in Web, we convert the DateTimeOffsets into the current user's timezone.
According MSDN, DateTimeOffset can be ambiguous in a specific TimeZone:
TimeZoneInfo.IsAmbiguousTime Method (DateTimeOffset)
This doesn't make sense to me at all. Can someone please give me an example DateTimeOffset which is ambiguous?
We're in TimeZone "W. Europe Standard Time".

Does what the documentation says not make it clear?
Typically, ambiguous times result when the clock is set to return to standard time from daylight saving time
I.e. if at 2am you come off of DST and reset the clock to 1am, then if someone starts talking about 1.30am, you don't know if that's 30 minutes from now or happened 30 minutes in the past.
There are a set of values (typically an hour long) which map to two different set of moments in UTC time.

I think the confusion comes from the way that "ambiguous" is defined here.
To be clear, a DateTimeOffset is never ambiguous unto itself. It always represents a specific moment in absolute, instantaneous time. Given a date, time, and offset, I can tell you both the local wall-time, and the precise UTC time (by applying the offset).
However, the wall-time portion of the value can be ambiguous within a specific time zone. That is, the date and time only when you ignore the offset. That's what TimeZoneInfo.IsAmbiguousTime is telling you. That if it weren't for the offset, the value would be ambiguous. The wall-time may be one that a person in that time zone might find confusing.
Consider that there are two overloads of this method, one that takes a DateTime and one that takes a DateTimeOffset.
The DateTime one makes perfect sense when .Kind is DateTimeKind.Unspecified.
DateTime dt = new DateTime(2016, 10, 30, 2, 0, 0);
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
bool ambiguous = tz.IsAmbiguousTime(dt); // true
It makes a little less sense with the other kinds, because it does conversions to the given time zone first - but still it does the same thing:
DateTime dt = new DateTime(2016, 10, 30, 1, 0, 0, DateTimeKind.Utc);
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
bool ambiguous = tz.IsAmbiguousTime(dt); // true
The DateTimeOffset overload is essentially doing the same thing as the previous example. Whatever the offset is, it gets applied to the date and time, then ambiguity is checked on the resulting date and time alone - just like in the first example.
DateTimeOffset dto = new DateTimeOffset(2016, 10, 30, 2, 0, 0, TimeSpan.FromHours(1));
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
bool ambiguous = tz.IsAmbiguousTime(dto); // true
Even with an offset that is meaningless to that time zone, it still gets applied before comparing.
DateTimeOffset dto = new DateTimeOffset(2016, 10, 29, 19, 0, 0, TimeSpan.FromHours(-5));
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
bool ambiguous = tz.IsAmbiguousTime(dto); // true
It boils down to the implementation of the overload, which is essentially:
// Make sure the dto is adjusted to the tz. This could be a no-op if it already is.
DateTimeOffset adjusted = TimeZoneInfo.ConvertTime(dto, tz);
// Then just get the wall time, stripping away the offset.
// The resulting datetime has unspecified kind.
DateTime dt = adjusted.DateTime;
// Finally, call the datetime version of the function
bool ambiguous = tz.IsAmbiguousTime(dt);
You can see this in the .net reference source here. They condense it to two lines, and preface it with a shortcut for better perf when DST is not applicable, but that's what it does.

Well the sample is (the last October's Sunday 2:00-3:00)
DateTimeOffset example = new DateTimeOffset(2015, 10, 25, 2, 30, 0,
new TimeSpan(0, 2, 0, 0));
TimeZoneInfo tst = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
if (tst.IsAmbiguousTime(example))
Console.Write("Ambiguous time");
Opposite to Ambiguous time is Invalid time (the last March's Sunday 2:00-3:00):
TimeZoneInfo tst = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
if (tst.IsInvalidTime(new DateTime(2016, 03, 27, 2, 30, 0)))
Console.Write("Invalid time");

Related

Get Spain time in C#

I need to get Spanish time in an Azure Function. I have used this code in C#:
DateTime date = DateTime.UtcNow;
It give me an incorrect datetime and I don't know how I can the correct datetime.
For example, if I use the code above at 20:30 in Spanish time, I will get 18:30 in utc.
Can anyone help me?
You need to use DateTimeOffset type instead of DateTime
// First get the TimeZone by ID. Spain Timezone is "Romance Standard Time"
var tz = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time");
// This would return +1 in winter and +2 in summer when daylight saving is active
var offset = tz.GetUtcOffset(DateTime.UtcNow);
// Get Time based on the current offset (+1 in winter +2 in summer)
var now = DateTimeOffset.UtcNow.ToOffset(offset);
Update for Azure since apparently TimezoneInfo is not present in the machine registry
// Timezones are usually stored in windows registry. Since apparently it is not stored on Azure, we can recreate the TimeZoneInfo object manually
// This is the Spanish Timezone based on current Ppanish law.
var tz = TimeZoneInfo.CreateCustomTimeZone(
id: "Romance Standard Time",
baseUtcOffset: TimeSpan.FromHours(1),
displayName: "(UTC+01:00) Brussels, Copenhagen, Madrid, Paris",
standardDisplayName: "Romance Standard Time",
daylightDisplayName: "Romance Daylight Time",
adjustmentRules: new[] {
TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(
dateStart: DateTime.MinValue,
dateEnd: DateTime.MaxValue,
daylightDelta: TimeSpan.FromHours(1),
daylightTransitionStart: TimeZoneInfo.TransitionTime.CreateFloatingDateRule(
timeOfDay: new DateTime(1, 1, 1, 2, 0, 0, DateTimeKind.Unspecified), // Starts at 2AM
month: 3, // Daylight starts on March
week: 5, // Last week of the month
dayOfWeek: DayOfWeek.Sunday), // Starts on Sunday
daylightTransitionEnd: TimeZoneInfo.TransitionTime.CreateFloatingDateRule(
timeOfDay: new DateTime(1, 1, 1, 3, 0, 0, DateTimeKind.Unspecified), // Ends at 3AM
month: 10, // Daylight ends in October
week: 5, // Last week of October
dayOfWeek: DayOfWeek.Sunday) // Ends On Sunday
)
});
// This would return +1 in winter and +2 in summer when daylight saving is active
var offset = tz.GetUtcOffset(DateTime.UtcNow);
// Get Time based on the current offset (+1 in winter +2 in summer)
var now = DateTimeOffset.UtcNow.ToOffset(offset);
An important note though, .NET class library takes in consideration only the current laws regarding daylight saving and timezone. If you want to take into consideration historical changes use Noda Time Library
You will need to convert the UTC time running in Azure Functions into the time zone you require, similar to this:
TimeZoneInfo.ConvertTimeFromUtc(
DateTime.UtcNow,
TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time");
UTC is Universal Time
you can use
DateTime.Now()
for show local time

Convert current local time to any other timezone

I want to test some time-zone related code by comparing local time to UTC. However this test relies on local time being different to UTC and I'm in the UK so 6 months of the year, local time is UTC according to DateTime comparison tests (due to summer time).
I could hard-code my test to convert UTCNow to a certain timezone like EST but on the off-chance my code was used in an American system, now I have the same issue.
So is there a way I can easily convert DateTime.UtcNow to a timezone that's definitely different to my local timezone, without hard-coding it or making assumptions what timezone I'm in?
Ok, as I mentoined in comments, if you want to get timezones which differs from yours, you can do it in that way:
var zone = TimeZoneInfo.GetSystemTimeZones()
.Where(x=>x.BaseUtcOffset != TimeZoneInfo.Local.BaseUtcOffset)
.First();
To convert UTC DateTime to another timezone, you have to use TimeZoneInfo.ConvertTimeFromUtc, sample:
var datetime = // datetime in UTC, for example, DateTime.UtcNow
var zone = // target zone
return TimeZoneInfo.ConvertTimeFromUtc(datetime, zone);
You can check samples in my pet project here
Time zones in .net are a very confusing matter. DateTime only supports local and UTC timezones really, it has no concept of different zones as it only gets the current offset from the machine and applies it to a tick count value which is in UTC.
So at a first there's no way to change to another timezone, but you can simulate something. If you want per example simulate a timezone GMT+2, you must first retrieve the current zone offset and add the difference of this offset and the desired offset to the local date, something like this:
TimeSpan targetOffset = new TimeSpan(2, 0, 0) - TimeZoneInfo.Local.BaseUtcOffset; //target to GMT+2
DateTime targetNow = DateTime.Now + targetOffset;
In this way the Date will have the values like if it were on that timezone, but for calculations using the datetime object and not just the year/month/day/hour/minute/second all of them will be wrong as the object will be marked as local and thus will have the wrong values.
To solve this you have two options, reverse your logic (convert non-local time to local time) or just work with UTC dates.
The first approach is very easy:
DateTime nonLocal = new DateTime(2016, 10, 21, 13, 33, 0); //We suppose we want 2016-10-21 13:33:00 at GMT+2
DateTime local = nonLocal + (TimeZoneInfo.Local.BaseUtcOffset - new TimeSpan(2, 0, 0));
This will yield to correct DateTime operations, but, here we go again with problems, if you use the year/month/day/hour/minute they will be in your local zone, not in the supposed zone and code using these properties will fail.
Finally, the best approach is the second one, you just forget about timezones and whenever you get a DateTime you convert it to UTC, all will work flawlesly and when you need to represent the data just convert it to local and all is done, no need to worry about DateTime arithmetic or properties differing just because they're on another timezone.
public static class DateTimeExtensions
{
public static DateTime As(this DateTime source, string timeZoneName)
{
DateTime utcTime = DateTime.SpecifyKind(source, DateTimeKind.Unspecified);
TimeZoneInfo newTimeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneName);
return TimeZoneInfo.ConvertTimeFromUtc(utcTime, newTimeZone);
}
}
Usage:
DateTime date1 = DateTime.UtcNow;
DateTime date2 = date1.As("Eastern Standard Time");

ConvertTimeToUtc is always off by one hour

I am facing issue while converting datetime to UTC.
User enters date in mydate in the webform, which we need to convert in UTC and store it in DB.
User also selects timezone from the list which is stored in selectedTimeZone variable. So we have to convert mydate from selectedTimeZone to UTC.
TimeZoneInfo.ConvertTimeToUtc(mydate, selectedTimeZone);
Example#1 :
If mydate = 05/02/2016 09:00 AM and selectedTimeZone = EST (-5:00) then
TimeZoneInfo.ConvertTimeToUtc(mydate, selectedTimeZone) returns
05/02/2016 13:00
which is off by one hour
Example#2
If mydate = 05/02/2016 09:00 AM and selectedTimeZone = IST (indian
standard time) (+5:30) then TimeZoneInfo.ConvertTimeToUtc(mydate,
selectedTimeZone) returns 05/02/2016 03:30
which is correct
There are multiple examples like this.
What is the issue?
Edit:
I don't need to convert user input to DateTime as .net does it, we are getting mydate in mvc action method parameter.
I tried it by setting local timezone of a machine to UTC, London, IST..... but it makes no difference to the output.
It seems adjustment rules for given timezone play their role here. Take the following code:
var mydate = new DateTime(2016, 05, 02, 9, 0, 0);
var selectedTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
var dstRule = selectedTimeZone.GetAdjustmentRules().FirstOrDefault(c => c.DateStart < mydate && c.DateEnd > mydate);
There is just one adjustment rule for EST timezone which is active at given time (it is active starting at 2007 year), and this rule works from second Sunday of March to first Sunday of November every year, adjusting time by one hour during that period. That is why you observe that behaviour. If you try to convert date outside of period for this rule (say, in February) - you will get what you would expect.
So to clarify: EST timezone has base offset of UTC-5, but because of daylight savings becomes UTC-4 during summer, and because your time is "summer" - .NET actually correctly converts that to UTC, it's not "off by one hour".

.Net DateTime with local time and DST

I'm afraid I don't really understand how .Net's DateTime class handles local timestamps (I live in Germany, so my locale is de_DE). Perhaps someone can enlighten me a bit ;-)
The DateTime constructor can be called with year, month etc. parameters. Additionally a DateTimeKind value of Local, Utc, or Unspecified (=default) can be provided.
Example:
DateTime a = new DateTime(2015, 03, 29, 02, 30, 00, DateTimeKind.Local);
DateTime b = new DateTime(2015, 03, 29, 02, 30, 00, DateTimeKind.Utc);
DateTime c = new DateTime(2015, 03, 29, 02, 30, 00, DateTimeKind.Unspecified);
DateTime d = new DateTime(2015, 03, 29, 02, 30, 00);
Per definition, values c and d are identical. But if I compare all against each other, all four are identical. Inspecting the objects in VS's debugger shows that the Ticks value (and InternalTicks as well) is the same for all. However, internal dateData values are different, but are obviously ignored by the comparison operator.
As you might have noticed, I constructed a value for March 29th this year, 02:30 AM. This moment in time does not exist in our timezone as it is skipped by switching to Daylight Saving Time. So I had expected to get an exception for constructing object a, but this did not happen.
Furthermore, DateTime has a method ToUniversalTime() that converts a value that is interpreted as local time to the equivalent UTC value. For testing, I ran a loop as follows:
DateTime dt = new DateTime(2015, 03, 29, 01, 58, 00, DateTimeKind.Local);
DateTime dtEnd = new DateTime(2015, 03, 29, 03, 03, 00, DateTimeKind.Local);
while (dt < dtEnd)
{
Log(" Localtime " + dt + " converted to UTC is " + dt.ToUniversalTime());
dt = dt.AddMinutes(1);
}
The result is:
Localtime 29.03.2015 01:58:00 converted to UTC is 29.03.2015 00:58:00
Localtime 29.03.2015 01:59:00 converted to UTC is 29.03.2015 00:59:00
Localtime 29.03.2015 02:00:00 converted to UTC is 29.03.2015 01:00:00
Localtime 29.03.2015 02:01:00 converted to UTC is 29.03.2015 01:01:00
Localtime 29.03.2015 02:02:00 converted to UTC is 29.03.2015 01:02:00
...
Localtime 29.03.2015 02:58:00 converted to UTC is 29.03.2015 01:58:00
Localtime 29.03.2015 02:59:00 converted to UTC is 29.03.2015 01:59:00
Localtime 29.03.2015 03:00:00 converted to UTC is 29.03.2015 01:00:00
Localtime 29.03.2015 03:01:00 converted to UTC is 29.03.2015 01:01:00
Localtime 29.03.2015 03:02:00 converted to UTC is 29.03.2015 01:02:00
So, .Net has no problem converting non-existing timestamps from local time to UTC. Also, adding a minute to an existing local timestamp is not local-aware and gives a non-existing timestamp.
As a result, adding 64 single minutes yields, after conversion, a UTC timestamp that is only 4 minutes larger than before.
In other words, converting between local time and UTC should be a bijection, giving a one-to-one correspondence between legal timestamp values.
To cut a long story short: How do I handle this correctly the intended way (according to .Net)? What is the sense of having DateTimeKind if it is not taken into account correctly? I don't even dare to ask how leap seconds (at 23:59:60) are handled ;-)
Mike's answer is good. Yes, DateTimeOffset is almost always prefered over DateTime (but not for all scenarios), and Noda Time is vastly superior in many regards. However, I can add some more details to address your questions and observations.
First, MSDN has this to say:
UTC time is suitable for calculations, comparisons, and storing dates and time in files. Local time is appropriate for display in user interfaces of desktop applications. Time zone-aware applications (such as many Web applications) also need to work with a number of other time zones.
...
Conversion operations between time zones (such as between UTC and local time, or between one time zone and another) take daylight saving time into account, but arithmetic and comparison operations do not.
From this we can conclude that the test you provided is not valid because it performs calculations using a local time. It is useful only in that it highlights how the API allows you to break its own documented guidelines. In general, since the time from 02:00 to just before 03:00 does not exist in the local time zone on that date, it's not likely to be encountered in the real world unless it was obtained mathematically, such as by a daily recurrence pattern that didn't take DST into account.
BTW, the part of Noda Time that addresses this is the ZoneLocalMappingResolver, which is used when converting a LocalDateTime to a ZonedDateTime via the localDateTime.InZone method. There are some reasonable defaults such as InZoneStrictly, or InZoneLeniently, but it's not just silently shifted like you illustrated with DateTime.
With regard to your assertion:
In other words, converting between local time and UTC should be a bijection, giving a one-to-one correspondence between legal timestamp values.
Actually, it's not a bijection. (By the definition of bijection on Wikipedia, it does not satisfy criteria 3 or 4.) Only conversion in the UTC-to-local direction is a function. Conversion in the local-to-UTC direction has a discontinuity in during the spring-forward DST transition, and has ambiguity during the fall-back DST transition. You may wish to review the graphs in the DST tag wiki.
To answer your specific questions:
How do I handle this correctly the intended way (according to .Net)?
DateTime dt = new DateTime(2015, 03, 29, 01, 58, 00, DateTimeKind.Local);
DateTime dtEnd = new DateTime(2015, 03, 29, 03, 03, 00, DateTimeKind.Local);
// I'm putting this here in case you want to work with a different time zone
TimeZoneInfo tz = TimeZoneInfo.Local; // you would change this variable here
// Create DateTimeOffset wrappers so the offset doesn't get lost
DateTimeOffset dto = new DateTimeOffset(dt, tz.GetUtcOffset(dt));
DateTimeOffset dtoEnd = new DateTimeOffset(dtEnd, tz.GetUtcOffset(dtEnd));
// Or, if you're only going to work with the local time zone, you can use
// this constructor, which assumes TimeZoneInfo.Local
//DateTimeOffset dto = new DateTimeOffset(dt);
//DateTimeOffset dtoEnd = new DateTimeOffset(dtEnd);
while (dto < dtoEnd)
{
Log(" Localtime " + dto + " converted to UTC is " + dto.ToUniversalTime());
// Math with DateTimeOffset is safe in instantaneous time,
// but it might not leave you at the desired offset by local time.
dto = dto.AddMinutes(1);
// The offset might have changed in the local zone.
// Adjust it by either of the following (with identical effect).
dto = TimeZoneInfo.ConvertTime(dto, tz);
//dto = dto.ToOffset(tz.GetUtcOffset(dto));
}
What is the sense of having DateTimeKind if it is not taken into account correctly?
Originally, DateTime didn't have a kind. It behaved as if the kind was unspecified. DateTimeKind was added in .NET 2.0.
The main use case it covers is to prevent double conversion. For example:
DateTime result = DateTime.UtcNow.ToUniversalTime();
or
DateTime result = DateTime.Now.ToLocalTime();
Before .NET 2.0, these would both result in bad data, because the ToUniversalTime and ToLocalTime methods had to make the assumption that the input value was not converted. It would blindly apply the time zone offset, even when the value was already in the desired time zone.
There are a few other edge cases, but this is the main one. Also, there is a hidden fourth kind, which is used such that the following will still hold up with ambiguous values during the fall-back transition.
DateTime now = DateTime.Now;
Assert.True(now.ToUniversalTime().ToLocalTime() == now);
Jon Skeet has a good blog post about this, and you can also now see it discussed in the comments in the .NET Reference sources or in the new coreclr sources.
I don't even dare to ask how leap seconds (at 23:59:60) are handled ;-)
Leap seconds are actually not supported by .NET at all, including the current version of Noda Time. They're also not supported by any of the Win32 APIs, nor will you ever observe a leap second on the Windows clock.
In Windows, leap seconds are applied via NTP synchronization. The clock ticks by as if the leap second didn't occur, and during its next clock sync, the time is adjusted and it is absorbed. Here's what the next leap second will look like:
Real World Windows
-------------------- --------------------
2015-06-30T23:59:58Z 2015-06-30T23:59:58Z
2015-06-30T23:59:59Z 2015-06-30T23:59:59Z
2015-06-30T23:59:60Z 2015-07-01T00:00:00Z <-- one sec behind
2015-07-01T00:00:00Z 2015-07-01T00:00:01Z
2015-07-01T00:00:01Z 2015-07-01T00:00:02Z
2015-07-01T00:00:02Z 2015-07-01T00:00:02Z <-- NTP sync
2015-07-01T00:00:03Z 2015-07-01T00:00:03Z
I'm showing the sync at 2 seconds past midnight, but it could really be much later. Clock sync happens all the time, not just at leap seconds. The computer's local clock is not an ultra-precise instrument - it will drift, and periodically has to be corrected. You cannot assume the current time is always monotonically increasing - it could skip forward, or jump backward.
Also, the chart above isn't exactly accurate. I showed a hard shift in seconds, but in reality the OS will often introduce minor corrections by spreading out the effect of the change in several sub-second increments over a longer period of several seconds (a few milliseconds at a time).
At an API level, none of the APIs will support more than 59 in a seconds field. If they were to support it at all, it would probably just be during parsing.
DateTime.Parse("2015-06-30T23:59:60Z")
This will throw an exception. If it were to work, it would have to munge the extra leap second and either return the previous second (2015-06-30T23:59:59Z), or the next second (2015-07-01T00:00:00Z).
Yes, the DateTime type in .NET is major mess, as you can observe because it doesn't support the concepts of time-zones, multiple calendars and many other useful concepts such as intervals etc.
A little better is the DateTimeOffset type which adds time-zone offset information. The DateTimeOffset will allow you to more accurately represent the times you show in your question and comparisons will take the timezone offset into account. But this type is not perfect either. It still does not support true time zone information, only the offset. So it is not possible to perform complex DST calculations or to support advanced calendars.
For a much more thorough solution you can use NodaTime

How to get UTC time based on a DateTime and a TimeZone Offset?

I have a datetime object, and I know the UTC offset (double). How can I get UTC time from these two pieces of information?
All of the examples I've seen require a timezone. Well, I don't know what timezone is, and it shouldn't really matter. If i have an offset of -7, it could either be PDT, or it could be MST - it's really irrelevant as either would produce the same UTC. It seems really stupid that I have to convert the offset that I have to a timezone just so the "ToUniversalTime" can pull the offset back out.
Honestly, I'm about to resort to just using something like this:
DateTime dateTime = new DateTime(2014, 8, 6, 12, 0, 0);
Double timeZone = -7.0;
string utc = String.Format("{0}-{1}-{2}T{3}:{4}:{5}{6}:{7}", startDate.Year, startDate.Month, startDate.Day, startDate.Hour, startDate.Minute, startDate.Second, (int) Math.Floor(timeZone), (timeZone % 1) * 60);
can someone please tell me why this is a bad idea?
(someone will probably close this as a duplicate, but I looked at a dozen other questions and none of them were quite the same - they all used the TimeZoneInfo object).
Just use DateTimeOffset:
TimeSpan utcOffset = TimeSpan.FromHours(timeZone);
DateTimeOffset result = new DateTimeOffset(dateTime, utcOffset);
DateTime utc = result.UtcDateTime;
or
string utc = result.ToString("yyyy-MM-dd'T'HH:mm:ssK", CultureInfo.InvariantCulture);
It's not clear why you want it as a string in the end though...
(You might also want to consider using my Noda Time project, particularly as you're likely to see time zone IDs which are TZDB time zones...)

Categories

Resources