Is there a flaw in Windows 7 Timezone rules [duplicate] - c#

This question already has answers here:
Why doesn't C# detect that 1970/1/1 was under BST?
(3 answers)
Closed 4 years ago.
British Summer time adjusts clocks forward in March and back in October every year. During 1968 to 1971 the UK trialed BST as a permanent option, such that the clocks where put forward 1 hour in March 1968 and not reverted back until October 1971.
I am creating dates in Javascript, serializing them as JSON and posting to a WebApi.
Currently using windows 7 as a development environment, Windows is NOT recognizing that period as BST. For example 01/01/1970 should be Daylight Saving time, however
new System.DateTime(1970, 01, 01, 00, 00, 00).IsDaylightSavingTime();
returns false.
also...
System.TimeZone.CurrentTimeZone.GetDaylightChanges(1970)
{System.Globalization.DaylightTime}
Delta: {01:00:00}
End: {25/10/1970 02:00:00}
Start: {29/03/1970 01:00:00}
1970 should have a rule covering the entire year as the whole year was BST.
Is there a patch to correct the flaw in Windows?

Update
There is a bug after all, or rather GMT Standard Time doesn't contain UK-specific rules. Between 1968 and 1970 the offset for the UK changed to +1:00 and there was no DST.
The real problem is that the offset is wrong for the UK for that period :
var date= new DateTime(1970,1,1,0,0,0,DateTimeKind.Local);
var tzi = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
var offset=tzi.GetUtcOffset(date);
offset is 00:00:00. Oooops!
For 1970-08-01 the offset is 01:00:00 and IsDaylightSavingTime() returns True.
PS:
SQL Server's AT TIME ZONE uses the Windows time zone names. This could be an even bigger source of problems with historical data.
Original
There's no bug. When the entire year has a single offset it doesn't make sense to talk about Daylight Saving Time.
The IANA timezone database shows that 1970-01-01 did not use DST. The offset was +1:00. Using NodaTime :
var london = DateTimeZoneProviders.Tzdb["Europe/London"];
// Time zone conversions
var localDate = new LocalDateTime(1970, 1, 1, 0, 0, 00);
var before = london.AtStrictly(localDate);
Console.WriteLine($"{before} {before.IsDaylightSavingTime()} {before.Offset}");
This returns :
1970-01-01T00:00:00 Europe/London (+01) DST:False +01
For 1971-11-01 the result is :
1971-11-01T00:00:00 Europe/London (+00) DST:False +00
At that point the offset changed from +1:00 to +00:00 and the DST rule was reintroduced.
The results are more interesting for summer dates.
1971-07-30 returns :
1971-07-30T00:00:00 Europe/London (+01) DST:False +01
Which is correct - there were no DST rules in effect on that date. The offset was fixed at +1.
1972-07-30 returns :
1972-07-30T00:00:00 Europe/London (+01) DST:True +01
The offset is the same, because the DST rules were in effect on that date.

Windows does not have a separate time zone for the United Kingdom. The time zone used by Windows in the United Kingdom ("GMT Standard Time") is shared with Ireland and Portugal.
That's why historic deviations that only applied to the United Kingdom are not reflected.
Borders of countries have changed a lot even in the last 200 years and until quite recently (only a few decades ago) very small regions in Europe had their own definition of time zones and they changed frequently. Windows cannot reflect that complex information. If you need that information you need to use a dedicated database.

As has been stated in the comments, the code you presented is using the current timezone on the computer you are running that code. This might be the UK but it's not a good idea to assume. The below code takes into account the comments above:
var tzi = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
var dt = new DateTime(1970, 1, 2, 0, 0, 0, DateTimeKind.Utc);
var isDlt = tzi.IsDaylightSavingTime(dt);
This also returns false though so the bug as you state does indeed exist. I very much doubt there is a patch but if you were so inclined you could quite easily write an extension method that uses the IANA database to determine if a given date is during daylight saving.
You might also want to look at the documentation for TimeZoneInfo.GetAdjustmentRules - https://msdn.microsoft.com/en-us/library/system.timezoneinfo.getadjustmentrules(v=vs.110).aspx

Related

Getting the Arizona Standard Time in .net

I have an application in which time zones are treated as string, by using the system name so we can make an actual System.TimeZoneInfo object by doing:
var tz = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
Such values are persisted to DB, now we are facing an issue where one such object is requested to be on Arizona Time which is not a standard timezone. From what I have investigated the Arizona Time changes Time Zones due to the fact that it doesn't observes "Day Light Savings".
I am looking for a way to set one value in DB so that there is no need to change it according to day light savings changes.
Is there a way to do this?
Even if I have to change a bit the code to get the TimeZoneInfo object. What really matters to me is a way to determine the actual timezone corresponding to Arizona Time
About Arizona time zones
From timeanddate.com:
There is a common misconception that Arizona is on Pacific Daylight
Time (PDT) during the summer and on Mountain Standard Time (MST)
during the winter. Because MST and PDT have the same UTC offset of
minus 7 hours (UTC-7), Arizona has the same local time as neighboring
states California and Nevada during the summer season. Although the
time is the same, Arizona uses standard time (MST) all year.
“Daylight” time zones, such as MDT, are mostly used for areas that
switch to DST every year
IANA (tz database) time zone database contains two time zones for Arizona:
America/Phoenix (Mountain Standard Time - Arizona, except Navajo), which does not observe daylight saving changes (DST), and
America/Shiprock, which observes DST.
Arizona time zones in .NET
Depending on your users' exact location in Arizona, you should use either America/Phoenix or America/Shiprock time zone, so you will need two values in the database. However, if you try to get time zones with TimeZoneInfo.FindSystemTimeZoneById using tz database names, you will get System.TimeZoneNotFoundException.
In order to get Arizona time zone that does not observe DST (America/Phoenix), you can use:
TimeZoneInfo.FindSystemTimeZoneById("US Mountain Standard Time")
In order to get Arizona time zone that does observe DST (America/Shiprock), you can use:
TimeZoneInfo.FindSystemTimeZoneById("Mountain Standard Time")
So, you would have both ids in your database, US Mountain Standard Time and Mountain Standard Time, or alternatively some other strings that you would later map to these .NET time zone ids.
Check out NodaTime, it can help you a lot when it comes to dealing with date, time and time zones.
And finally, here is a sample program (with NodaTime) that demonstrates the difference between .NET US Mountain Standard Time (America/Phoenix, Arizona without DST) and Mountain Standard Time (America/Shiprock, Arizona with DST).
using System;
using NodaTime;
using NodaTime.TimeZones;
namespace TimeZoneExample
{
class Program
{
static void Main(string[] args)
{
// Arizona without daylight saving time (TZ: America/Phoenix)
var mstWithoutDstTz = TimeZoneInfo.FindSystemTimeZoneById("US Mountain Standard Time");
// Arizona with daylight saving time (TZ: America/Shiprock)
var mstWithDstTz = TimeZoneInfo.FindSystemTimeZoneById("Mountain Standard Time");
// NodaTime BclDateTimeZone for Arizona without daylight saving time
var mstWithoutDstNodaTz = BclDateTimeZone.FromTimeZoneInfo(mstWithoutDstTz);
// NodaTime BclDateTimeZone for Arizona with daylight saving time
var mstWithDstNodaTz = BclDateTimeZone.FromTimeZoneInfo(mstWithDstTz);
// January 1, 2017, 15:00, local winter date
var localWinterDate = new LocalDateTime(2017, 01, 01, 15, 00);
// NodaTime ZonedDateTime for Arizona without daylight saving time: January 1, 2017, 15:00
var winterTimeWithoutDst = mstWithoutDstNodaTz.AtStrictly(localWinterDate);
// NodaTime ZonedDateTime for Arizona with daylight saving time: January 1, 2017, 15:00
var winterTimeWithDst = mstWithDstNodaTz.AtStrictly(localWinterDate);
// Both time zones have the same time during winter
Console.WriteLine($"Winter w/o DST: {winterTimeWithoutDst}"); // 2017-01-01T15:00:00 US Mountain Standard Time (-07)
Console.WriteLine($"Winter w/ DST: {winterTimeWithDst}"); // 2017-01-01T15:00:00 Mountain Standard Time (-07)
// add 180 days to get June 30, 2017
var sixMonthsToSummer = Duration.FromTimeSpan(new TimeSpan(180, 0, 0, 0));
// During summer, e.g. on June 30, Arizona without daylight saving time is 1 hour behind.
Console.WriteLine($"Summer w/o DST: {winterTimeWithoutDst + sixMonthsToSummer}"); // 2017-06-30T15:00:00 US Mountain Standard Time (-07)
Console.WriteLine($"Summer w/ DST: {winterTimeWithDst + sixMonthsToSummer}"); // 2017-06-30T16:00:00 Mountain Standard Time (-06)
}
}
}
If I understand your problem correctly, you want to create a custom time zone representing "Arizona Time" which has a constant offset from UTC regardless the date of the year.
If so, you should be able to use the static method
TimeZoneInfo.CreateCustomTimeZone
Just set the TimeSpan to the number of hours from UTC that you need it to be (-7 hours from what I can tell).
https://msdn.microsoft.com/en-us/library/bb309898(v=vs.110).aspx
EDIT: You might also have some success by simply using the named timezone
"US Mountain Standard Time"
which should represent the same.

TimeZoneInfo BaseUtcOffset always zero on Windows Embedded 7

I'm trying to get the difference between an application running on Windows Embedded 7 and UTC time. To do that I have the following piece of code:
TimeZoneInfo utcTimeZone = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
DateTime localTime = DateTime.Now;
DateTime utcTime = TimeZoneInfo.ConvertTime(localTime, TimeZoneInfo.Local, utcTimeZone);
TimeSpan utcOffset = localTime - utcTime;
This runs fine on my own development PC, running Windows 7. However, when I install my application on a device running Windows Embedded 7, no matter what timezone I set it to, when I run my application,
The value for TimeZoneInfo.Local.BaseUtcOffset is always 00:00.
The BaseUtcOffset value in the object returned by TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time") is also 00:00 (though this is to be expected).
The ConvertTime() function above always returns the current time less one hour. (Kind of not surprised since the TimeZoneInfo.Local.SupportsDaylightSavingsTime value is always false.)
Should I be using another way that TimeZoneInfo.Local to get the offset between UTC and the current time zone? I need to include Daylight Savings in this.
A few things:
The time zone with the ID "GMT Standard Time" is not UTC - it's UK Time. Its display name matches "Dublin, Edinburgh, Lisbon, London". It uses UTC+0 in the winter, and UTC+1 in the summer for daylight saving time.
The UTC time zone ID is simply "UTC" - though you'll rarely need that.
If TimeZoneInfo.Local.BaseUtcOffset is zero, then that means the computer's time zone setting is one that has UTC+0 as its standard offset. There are four of those currently defined in Windows. This property does not reflect daylight saving time.
Recognize that offsets will change depending on what time of the year that you are running the code. A time zone is not the same as a time zone offset.
Since you said you got zero in your above code, I'd guess that your local time zone is either the previously mentioned UK Time, or Casablanca, Morocco. This is because you are subtracting a UTC+1 local time with the time from another time zone that is also UTC+1 presently. 1 - 1 = 0
The correct way to do this does not involve subtraction at all. Simply use the GetUtcOffset method:
TimeSpan offset = TimeZoneInfo.Local.GetUtcOffset(DateTime.Now);
Again, note that this returns the current offset. Running it at different times of the year, or by passing a different value instead of DateTime.Now could return a different result.

Incorrect time using TimeZoneInfo.ConvertTime()

I'm getting an unexpected DateTime when converting from:
(UTC) Dublin, Edinburgh, Lisbon, London to (UTC-08:00) Baja California
Below is the code I have been using. I'm expecting serverDateTime to be 29/03/2016 00:00:01 (-8 hours), but instead I get 28/03/2016 23:00:01 - which is a 9 hour difference.
private static void Main(string[] args)
{
ReadOnlyCollection<TimeZoneInfo> timeZones = TimeZoneInfo.GetSystemTimeZones();
TimeZoneInfo localTimeZone = timeZones.FirstOrDefault(tz => tz.DisplayName.Contains("London"));
TimeZoneInfo serverTimeZone = timeZones.FirstOrDefault(tz => tz.DisplayName.Contains("California"));
DateTime clientDateTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 29, 8, 0, 1);
DateTime serverDateTime = TimeZoneInfo.ConvertTime(clientDateTime, localTimeZone, serverTimeZone);
}
My local machine is in the UK, which is currently UTC+1, and Baja - California is currently UTC-7, so I'm expecting to get an 8 hour difference, instead of 9. What am I doing wrong?
Thanks in advance.
A few things:
"(UTC-08:00) Baja California" refers to Baja California, Mexico - not California, USA.
This zone entry follows Mexican DST rules, which doesn't start DST until April 3rd this year. Thus this entry is on UTC-8 still, explaining the time difference you observed.
This particular entry is actually wrong in Windows, as it turns out that the entire state of Baja California follows USA rules for daylight saving time, not Mexican rules.
There is an extensive multi-thread discussion of this in the tz mailing list archives in October and November 2015. This resulted in the corresponding IANA zone America/Santa_Isabel being deprecated with release 2016a. It now links to America/Tijuana instead, which already had been following USA DST rules.
Microsoft has not yet made a similar change to its data, but it is likely in a future update to the Windows time zone data. (I have already communicated this to the appropriate personnel.) In the meantime, use "(UTC-08:00) Pacific Time (US & Canada)" - even if you're talking about Baja California, Mexico.
Don't try to look up a time zone by its DisplayName. Those values will be different depending on operating system language. Instead, use TimeZoneInfo.FindSystemTimeZoneById. The Id properties are not localized. Additionally, Microsoft considers the Id values to be stable identifiers, and thus they will not change with future updates. The DisplayName values have been modified in the past, and could indeed change in the future.
Use the ID "GMT Standard Time" for "(UTC) Dublin, Edinburgh, Lisbon, London"
Use the ID "Pacific Standard Time" for "(UTC-08:00) Pacific Time (US & Canada)"
The ID "Pacific Standard Time (Mexico)" is for "(UTC-08:00) Baja California" - but for the reasons I described, don't use this entry.
Really, you shouldn't be concerned with the time zone on the server at all. The server should only be concerned with UTC. (note that London is not UTC, since it switches for BST in the summer)
I'm not sure what you're intention was with this line:
DateTime clientDateTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 29, 8, 0, 1);
Pulling the month and year from DateTime.Now is going to use the server's time zone. Then you combine with fixed day of 29 and fixed time at 8:00:01. This will fail in February of a non-leap year (when there are only 28 days in the month), and also could give bad dates when the server's date is not in the same month and year as the client's (such as near the transition from one month to another).

.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

.net adds one hour to summer dst [duplicate]

This question already has answers here:
Date Conversion issue from webservice
(2 answers)
Closed 8 years ago.
I'm getting a following string from a web service 2013-10-15T12:54:18+01:00. This date is in the summer DST and my .NET code (web service proxy, I presume) automatically adds one hour to it. The same is not the case if the returned value falls withing the winter DST. The time returned (12:54:18) is what I want to display, I don't want any sort of recalculation to be done.
I'm using TimeSpan DateTime.TimeOfDay to show the time.
What can I do to make it happen?
I am trying to put the pieces together from your question and the additional comments. So far this is my analysis:
On the wire you see two different date and time strings:
Without daylight savings (winter) the string is 2013-12-30T12:54:18
With daylight savings (summer) the string is 2013-10-15T12:54:18+01:00
This is a sign that the web service is using GMT Standard Time as the time zone.
You want to extract the GMT timestamp from the date and time string.
However, between you and the web service there is some unspecified web service proxy (I assume some kind of .NET framework?) and in your code you only have access to a DateTime and furthermore your code is executing in the Central Europe Standard Time time zone which basically is one hour ahead of GMT both during summer and winter if we disregard the short time where there is a transition to and from daylight savings.
I hope I am correct so far.
You can convert the incoming DateTime to GMT using this code:
// Framework code creates the DateTime.
var sourceDateTime = DateTime.Parse("2013-10-15T12:54:18+01:00");
// Application code can further process the DateTime.
var destinationTimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
var destinationDateTime = TimeZoneInfo.ConvertTime(sourceDateTime, destinationTimeZoneInfo);
This example gives the correct answer during daylight savings. The incoming date and time string contains an offset and is correctly parsed into a local time zone (CET). The TimeZoneInfo.ConvertTime assumes that the source DateTime is in the local time zone and the result is correct.
However, the code fails during winter where there is no daylight savings:
var sourceDateTime = DateTime.Parse("2013-12-30T12:54:18");
Notice that the date and time string no longer contains a time zone offset. The offset is +00:00 but for some reason it is missing from the string. This means that the source DateTime is assumed to be in the local time zone (CET) and not converted from the actual time zone (GMT). This is the source of your problem if my analysis is correct.
Another way to explain it:
| Server (GMT) | Framework (CET) | My code
-------+---------------+---------------------------+----------------------------
Summer | +01:00 suffix | GMT -> CET adds 1 hour | CET -> GMT subtracts 1 hour
-------+---------------+---------------------------+----------------------------
Winter | No suffix | Assumed to be CET | CET -> GMT subtracts 1 hour
There is no easy fix for this problem. If you could persuade the web service to provide the correct offset even when it is +00:00 my code above would work both summer and winter. Even better, only use UTC and only convert to a local time when the end-user gets involved. But I guess that you have no control over the web service?
One option would be to execute your code in the same time zone as the server (e.g. GMT). Then you should be able to use the timestamp directly without any conversion.
Another more ugly option is to determine if the web service is outside daylight savings and then adjust the time accordingly:
var destinationTimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
if (!destinationTimeZoneInfo.IsDaylightSavingTime(sourceDateTime)) {
var sourceDateTimeOffset = new DateTimeOffset(sourceDateTime, destinationTimeZoneInfo.BaseUtcOffset);
sourceDateTime = sourceDateTimeOffset.UtcDateTime;
}
var destinationDateTime = TimeZoneInfo.ConvertTime(sourceDateTime, destinationTimeZoneInfo);
I have tried to keep the code as general as possible but actually it is only trying to fix the situation where +00:00 is missing and in that case destinationTimeZoneInfo.BaseUtcOffset is precisely 0 so it might be slightly overkill to do it like this.
More importantly, I am not sure that this code provides the correct results during the time when there is a daylight savings transition. Even though I believe that GMT and CET transitions at the same date CET is still one hour ahead of GMT. You really have to create some unit tests to make sure you get the desired result.
A DateTime object does not contain offset in this kind of example. It only holds a kind which could be i.e.: local offset (offset of the runtime environment) or UTC. Do it like so, since DateTimeOffset is what you want:
DateTimeOffset test = DateTimeOffset.Parse("2013-10-15T12:54:18+01:00");
Console.WriteLine(test.DateTime.TimeOfDay); // UTC
Console.WriteLine(test.LocalDateTime.TimeOfDay); // Localized with the offset parsed
Will output
12:54:18
11:54:18
You might want to check this MSDN article which describes the transitions that your TimeOfDay is currently returning.
By examining that article, you can find the way on how to correct it. TimeZoneInfo is the class that you'll need.
Edit, the work that Jon Skeet provided might help as well, look at the Noda Time blogspot for more info!

Categories

Resources