Convert DST rule to DateTime in C# - c#

I have DST rules like this one:
"2,-1,1,3600000"
the 2 is the zero based month
The -1 is the week number containing the day, -1 means the last week in the month which contains the day
The 1 is the day of the week, 1 - Sunday through to 7 - Saturday
The 3600000 is the mS from midnight on the appointed day that the change to/from DST will take place and is expressed in local time including DST, so the end of DST switch time is in DST.
what's the proper way to transform it in a C# DateTime?
So far I've done this:
public static DateTime ConvertDstRule(int year, string rule, bool isEndRule)
{
const int DaysInWeek = 7;
var ruleName = isEndRule ? "endRule" : "startRule";
var startStrings = rule.Split(',');
var month = Convert.ToInt32(startStrings[0]);
if ((month < 0) || (month > 11))
{
throw new ArgumentOutOfRangeException(ruleName, "The month value must be between 0 and 11");
}
var week = Convert.ToInt32(startStrings[1]);
if ((week < -1) || (week > 5))
{
throw new ArgumentOutOfRangeException(ruleName, "The week value must be between -1 and 5");
}
if ((Convert.ToInt32(startStrings[2]) < 1) || (Convert.ToInt32(startStrings[2]) > 7))
{
throw new ArgumentOutOfRangeException(ruleName, "The day value must be between 1 and 7");
}
var day = (DayOfWeek)(Convert.ToInt32(startStrings[2]) - 1); // DayOfWeek is zero based so shift by one.
var timeOffset = Convert.ToInt64(startStrings[3]);
if ((timeOffset / 1000 / 60) > 86400)
{
throw new ArgumentOutOfRangeException(ruleName, "The time offset is limited to one day");
}
// Find the start of the relevant year.
var startTime = new DateTime(year, 1, 1);
// Add on the month to get to the start of the selected month.
startTime = startTime.AddMonths(month);
// If the week is negative then go to the first occurance of the day in
// the next month, adding a negative week number will jump back into
// the previous month.
if (week < 0)
{
startTime = startTime.AddMonths(1);
}
else
{
week = week - 1;
}
// Jump to the first occurence of the day to switch in that month.
var monthStartsOn = startTime.DayOfWeek;
var daysToSwitchDay = (int)day - (int)monthStartsOn;
// This is likely to be negative as most zones switch on a Sunday
if (daysToSwitchDay < 0)
{
daysToSwitchDay = DaysInWeek + daysToSwitchDay; // daysToSwitchDay is negative so add it.
}
startTime = startTime.AddDays(daysToSwitchDay); // Now on the correct day.
startTime = startTime.AddDays(week * 7); // Week counts from 1.
startTime = startTime.AddMilliseconds(timeOffset);
if (isEndRule)
{
startTime = startTime.AddHours(-1); // Take off the DST hour to convert it to UTC.
}
return startTime;
}
Does it takes into account half-hour DST changes like in India? Can you spot any bug in this code?

A few things:
The type of input you are describing is called a "Transition Rule". Or outside the context of time zones, it is a particular type of "Recurrence Rule". It simply describes a pattern for determining when a particular point in time occurs for a given year, based on month, week, and weekday.
A transition rule does not tell you everything you need to know to calculate values for time zones. In particular, it doesn't tell you what the offset from UTC is before or after the transition, or the direction that the offset is being adjusted. Also, you would need a set of these, as there is usually two of these per year, but there could also be any number of them. For example, in 2014, Russia had only one transition, and Egypt had four. Also consider that these rules have changed over time, so a single rule, or even a single pair of rules, will not tell you how to convert values for all points in time. For a given time zone, you need a set-of-sets-of-rules, which is what we mean when we say "a time zone".
In the .NET Framework, the TimeZoneInfo class is used for working with time zones. It contains subclasses, TimeZoneInfo.AdjustmentRule and TimeZoneInfo.TransitionTime. In particular, there's an internal function in TimeZoneInfo called TransitionTimeToDateTime which does exactly what you are asking. It takes TransitionTime and a year, and gives you the DateTime that the transition occurs within the year. You can find this in the .NET Reference Source here.
However, if you really think you want to try to implement time zone rules from your own data sources, you should be thinking about several things:
Do you really think you can do better than the hundreds of people that have come before you? Maybe so, but don't go into this with a "this is simple" attitude. I suggest you watch this short video, and do a LOT of research before attempting this.
Are you thinking about how you will keep things maintained? Time zone rules change often. Annually, there can be about a dozen changes worldwide. Are you planning to monitor news feeds, discussion forums, government press releases, and other sources? Are you prepared to act when a government doesn't give that much warning that it's DST rules are going to change?
How much impact will it have on your system when things aren't accurate? It could range from not much (ex: a blog or forum) to critical (ex: airline schedules, communications, medical, financial, etc.).
Additionally:
The suggestion of using Noda Time from the question's comments is a good one, but unfortunately Noda Time doesn't have any specific API for interpreting just a single transition rule. Instead, it's prefered that you use existing TZDB zones, such as "America/New_York". Alternatively, you can use the TimeZoneInfo class in the .NET Framework, with IDs like "Eastern Standard Time".
Internally, Noda Time handles transition rules through the ZoneYearOffset class. You can see how a TransitionRule maps to a ZoneYearOffset in this code.
In your code sample, the comment "Take off the DST hour to convert it to UTC." is highly misleading. The output of your function is in terms of local time. UTC has nothing to do with it. Nowhere else in the code you showed here are you tracking offsets from UTC, either with or without DST. I think you meant "... convert it to standard time", but I'm not sure why you would want to do that. This particular function shouldn't try to account for that.
You also asked: "Does it takes into account half-hour DST changes like in India?" The question is invalid, because India doesn't have DST. Its standard time zone offset has a half-hour in it (UTC+05:30), but there's no DST. The only place in the world that currently uses a half-hour DST bias is Lord Howe Island, which is represented by the "Australia/Lord_Howe" time zone in the tzdb, and currently has no Windows equivalent. Everywhere else in the world (except a few research stations in Antarctica), if DST is used the transition bias is one hour.

Related

Code example of comparing times in different time zone in C#

Can anyone explain why the comparisonTime variable is calculated using the logic like below, especially using the lines below:
local.GetAdjustmentRules()[local.GetAdjustmentRules().Length - 1].DaylightDelta;
and
TimeSpan comparisonTime = time + (offset - tz.BaseUtcOffset).Negate() + (delta - storeDelta).Negate();
The TimeSpan structure
using System;
public struct StoreInfo
{
public String store;
public TimeZoneInfo tz;
public TimeSpan open;
public TimeSpan close;
public bool IsOpenNow()
{
return IsOpenAt(DateTime.Now.TimeOfDay);
}
public bool IsOpenAt(TimeSpan time)
{
TimeZoneInfo local = TimeZoneInfo.Local;
TimeSpan offset = TimeZoneInfo.Local.BaseUtcOffset;
// Is the store in the same time zone?
if (tz.Equals(local)) {
return time >= open & time <= close;
}
else {
TimeSpan delta = TimeSpan.Zero;
TimeSpan storeDelta = TimeSpan.Zero;
// Is it daylight saving time in either time zone?
if (local.IsDaylightSavingTime(DateTime.Now.Date + time))
delta = local.GetAdjustmentRules()[local.GetAdjustmentRules().Length - 1].DaylightDelta;
if (tz.IsDaylightSavingTime(TimeZoneInfo.ConvertTime(DateTime.Now.Date + time, local, tz)))
storeDelta = tz.GetAdjustmentRules()[tz.GetAdjustmentRules().Length - 1].DaylightDelta;
TimeSpan comparisonTime = time + (offset - tz.BaseUtcOffset).Negate() + (delta - storeDelta).Negate();
return comparisonTime >= open & comparisonTime <= close;
}
}
}
TimeZoneInfo.GetAdjustmentRules returns an array of AdjustmentRule objects, each of which identifies time adjustments made for daylight savings time. This includes the start and end date as well as the amount of time the clocks are adjusted and when these rules go into effect (Rules change, for example the US used to have an April-October cycle but now its March-November). The rules are generally ordered by oldest to newest.
The code in question is incorrectly (see below) finding the last rule in the list (local.GetAdjustmentRules()[local.GetAdjustmentRules().Length - 1]) and figuring out how much the time is changed by that rule (DaylightDelta--in the US this is typically one hour, but parts of the world may adjust by other values such as 30 minutes)
The second bit of code you inquire about attempts to normalize the passed in time if either/both zone is currently respecting daylight savings. It does this by adding the differences in the two DaylightDelta (the amout the clocks get moved ahead/behind) and the two BaseUtcOffset (the zone's non-DST difference from UTC). It then adds that to the original time and uses that for comparisons instead.
The code is incorrect for several reasons (and this is perhaps not exhaustive):
It assumes the adjustment rules are ordered. The MSDN documentation says that they are "generally ordered" but makes no guarantees
It assumes that the last adjustment rule is the current one. As noted above, they aren't ordered. But they might also contain future rules that are not yet effective
It does not account for times that are near to midnight and might roll over to the next day. This holds true for both branches of the code. While it's possibly unlikely for a store to be open passed midnight it is a definite possibility (I don't know your business rules). If the store is open from 8am to 2am, the tests will fail for 1am when it should succeed
Similarly, for a store where the hours pass midnight, the daylight savings calculation could prove even more inaccurate if the times fall at the DST boundaries
TimeZoneInfo.Local can be manipulated by the system settings. In fact depending on whether "Automatically adjust for daylight savings" is checked in control panel the local time zone info could always return false for IsDaylightSavings and a zero length array for local.GetAdjustmentRules. This means that the same code run on two computers could return different results even if they are sitting next to each other!
It assumes that the system is up to date. Depending on where in the world you are, daylight savings rules may change often. In cases like these, it is common to have an Operating System that doesn't have all of the latest information.
Dealing with time zones and daylight savings is tough. The rules are changing all the time and the tools in the framework are quite clunky and difficult to work with (assuming you can understand them). What's worse is that they aren't always up to date!
I'm not going to attempt to provide you with fixed code. I tend to leave anything related to time and DST calculations to the experts. I'd highly suggest looking into the NodaTime library for calculations such as these. It's entirely devoted to the subject matter and the timezone rules are regularly updated. Their Why do we exist page calls out some of the things I've mentioned above as does this blog post written by one of the library's authors, Jon Skeet.

How to Convert DateTime.Now in 0 to 1 decimal format in c#

I would like to convert the current time to a decimal representing a fraction of the day. For example, if the day starts at 0, then 12:00 PM should be 0.5.
I need to send that value to an API, and it needs to be in that format. i.e.
"LAST_PRINT_TIME":0.22020833"
Depending on the precision requirements of your result, this may help you:
DateTime now = DateTime.Now;
double dayFraction = (now.Hour + now.Minute / 60d) / 24d;
now.Minute / 60d calculates the fraction of the current hour (so if the time is XX:15 PM this will give 0.25). This is then added to the current hour. This value is then divided by 24 to obtain the final result.
For example, 3:45 PM would go as follows:
(15 + 45 / 60) / 24) => (15 + 0.75) / 24 => 15.75 / 24 => 0.65625
So 3:45 PM, which is 15.75 hours into the day, would be 0.65625 (or 65.625%) of the day.
Or, as #madreflection mentioned in a comment, you could use .ToOADate() as well. In this case, you could do something like:
DateTime now = DateTime.Now;
double dayFraction = now.ToOADate() - now.Date.ToOADate();
This is one of those problems that seems deceptively simple, but the solution is actually much more complex than you would think.
The complexities arise from the nature of local time, whose rules are defined by time zones. Many time zones have transitions that occur either regularly (such as for daylight saving time), or irregularly (such as for changes in standard time).
As such, one needs to consider:
Could the day be shorter or longer than 24 hours?
For example, in most of the US the start of DST is at 2:00 AM, and on that day there are 23 hours in the day because the hour from 2:00 to 2:59 is skipped. At the end of DST, also at 2:00 AM in the US, the hour from 1:00 through 1:59 is repeated, creating 25 hours in that day.
Could the day start or stop at a time other than midnight?
For example, in most of Chile in 2019, the start of DST made the date 2019-09-08 start at 01:00 instead of 00:00.
Learn more in Falsehoods programmers believe about time.
Consider using the following approach to overcome these real-world considerations.
First, define some helper functions to do most of the work. They are not specific to a particular point in time or a particular time zone.
static double GetFractionOfDay(DateTimeOffset dto, TimeZoneInfo tz)
{
// Get the start of the day, and the start of the next day
DateTimeOffset startOfDay = GetStartOfDay(dto, tz);
DateTimeOffset startOfNextDay = GetStartOfDay(startOfDay.AddDays(1), tz);
// Calculate the length of the day. It might not be 24 hours!
TimeSpan lengthOfDay = startOfNextDay - startOfDay;
// Now calculate the position within the day, and the fraction to return
TimeSpan durationSinceStartOfDay = dto - startOfDay;
return durationSinceStartOfDay / lengthOfDay;
}
static DateTimeOffset GetStartOfDay(DateTimeOffset dto, TimeZoneInfo tz)
{
// Make sure we're in the correct time zone
dto = TimeZoneInfo.ConvertTime(dto, tz);
// Start by assuming a local midnight exists
DateTime dt = dto.Date;
// Handle days without a local midnight (these do exist in several time zones)
if (tz.IsInvalidTime(dt))
{
// Advance by the transition gap. This is usually 1 hour, but best not to hard-code that.
TimeSpan[] offsets = { tz.GetUtcOffset(dt.AddDays(-1)), tz.GetUtcOffset(dt.AddDays(1)) };
TimeSpan gap = offsets[1] - offsets[0];
return new DateTimeOffset(dt.Add(gap), offsets[1]);
}
// Handle days with more than one midnight (it's possible, even if unlikely)
if (tz.IsAmbiguousTime(dt))
{
// There's more than one. Prefer the first one, since we want the beginning of the day.
TimeSpan[] offsets = tz.GetAmbiguousTimeOffsets(dt);
TimeSpan offset = offsets[0] > offsets[1] ? offsets[0] : offsets[1];
return new DateTimeOffset(dt, offset);
}
// Clean case, just one local midnight and it does exist
return new DateTimeOffset(dt, tz.GetUtcOffset(dt));
}
With those defined, you can now get an answer to your question with regard to "now" in the local time zone.
double dayFraction = GetFractionOfDay(DateTimeOffset.Now, TimeZoneInfo.Local);
However - Though this is the correct answer of "what fraction of the day is it", keep in mind it may be more important to align with what the receiving API expects, even if not exactly correct. In other words, if 12:00 should always be 0.5, even when it's not exactly at the midpoint of the day, then use elmer007's approach.

Noda Time Countdown

I have written an application to show the remaining time until the next big industry trade-show.
(it's about two years in the future at the time of writing)
I started off using the standard DateTime class but quickly ran into issues dealing with the varying number of days in each month, 2016 is a Leap Year and contains a Leap Day, Daylight Savings Time, etc.
Thankfully I discovered NodaTime. (Thanks #JonSkeet)
Not so thankfully, the way I am used to working with DateTime doesn't apply and I'm having a really hard time figuring out how to get the time remaining. (There aren't many examples floating around)
For example, the following code doesn't work because you can't subtract an instant from a LocalDateTime:
void example()
{
DateTime DT = Convert.ToDateTime("09/12/2016 10:00AM");
LocalDateTime NodaLocalDateTime = new LocalDateTime(
DT.Year, DT.Month, DT.Day, DT.Hour, DT.Minute, 0);
Period P = NodaLocalDateTime - SystemClock.Instance.Now;
}
So the question becomes:
How do you get the remaining time from now until some date?
To determine a "calendrical" amount of time between two events, you want Period as you've already discovered. However, that only deals with local dates and times.
To determine a "calendar-neutral" amount of time between two events, you can use Instant and Duration - but then you can't display the number of months left.
Both of these approaches have drawbacks, but basically they're fundamental to the way that time works. If you use the local time approach, then you'll find that the amount of time will jump back or forward an hour as you go over a DST transition. If you use the instant approach, you're restricted to days/months/hours/minutes etc - not months.
One option for between the two would be to use LocalDateTime and Period, but anchor both the event and the current time in UTC. That way there'll never be a discontinuity, as UTC is an unchanging base line, effectively. This also means that you'll always display the same amount of "time left" regardless of where in the world you look at the counter (or host the code, depending on exactly what you were planning to do).
If you want more details about why you can't get a Period between two ZonedDateTime values, I could think of examples which are fundamentally problematic. The bottom line is that calendrical arithmetic and time zones don't play nicely together though...
Just to give some actual code, I would have something like:
public sealed class EventCountdown
{
private readonly LocalDateTime eventTimeUtc;
private readonly IClock clock;
// It's probably most convenient to express the event time with the time zone
// in which it occurs. You could easily change this though.
public EventCountdown(ZonedDateTime zonedEventTime, IClock clock)
{
this.eventTimeUtc = zonedEventTime.WithZone(DateTimeZone.Utc).LocalDateTime;
this.clock = clock;
}
public Period GetPeriodRemaining()
{
return Period.Between(clock.Now.InUtc().LocalDateTime, eventTimeUtc);
}
}
Note that in Noda Time 2.0 the IClock.Now property is being changed to a GetCurrentInstant method... but in that case you'd probably use a ZonedClock in UTC and call GetCurrentLocalDateTime on it.
The solution I ended up using is Period.Between(), which seems do the trick.
using NodaTime;
DateTime EventDT;
LocalDateTime LocalizedEventDT;
Period TimeLeft;
public EventCountdown()
{
// Start with a date and time
EventDT = Convert.ToDateTime("09/12/2016 10:00AM");
// Localize it
LocalizedEventDT = new LocalDateTime(
EventDT.Year, EventDT.Month,
EventDT.Day, EventDT.Hour,
EventDT.Minute, 0);
}
// find out how much time is between now and the future date
public Period GetPeriodRemaining()
{
DateTime dt_Now = DateTime.Now;
return Period.Between(new LocalDateTime(
dt_Now.Year, dt_Now.Month, dt_Now.Day, dt_Now.Hour,
dt_Now.Minute, dt_Now.Second), LocalizedEventDT);
}
If anyone has a solution that does this while taking time zones (local vs where the event is taking place) into account that would be awesome. (I tried doing this using a ZonedDateTime in a similar fashion but ran into a brick wall).
Also, it's not clear to me if this method is taking the various days of the month, daylight savings time, leap year, etc. into account. Anyone know?
If it is not, obviously I'd welcome any solutions that do so gracefully.

Iterating through a TimeSpan and running an action every month (or arbitrary unit of time)

I'm writing a search program that includes a date range- DateFrom and DateTo, both of which are DateTimes. Searching January - April for any search criteria will return the results of January + February + March + April.
I would like to add functionality whereby a user can choose to search each month within a - range, so searching January - April will return each individual month's results. However I'm having trouble finding an intelligent way to implement this for any unit of time larger than days.
So far I'm getting a TimeSpan using:
TimeSpan ts = query.DateTo - query.DateFrom;
In a perfect world I'd just be able to do something like foreach (month m in TimeSpan){dostuff}, however TimeSpan stores dates as integers and does not include any units larger than days. Additionally, I thought maybe I could just use n = DateFrom.month - DateTo.month to get the difference in months and run a function in a for loop starting with DateFrom and lasting n months, but this won't work between years.
The last case is definitely fixable but includes a number of tedious special cases. Is there a cleaner / more elegant way of accomplishing this sort of iteration that I'm missing?
Thanks.
So for the basic pattern we can use a fairly simple for loop:
public static IEnumerable<DateTime> Months(DateTime start, DateTime end)
{
for (DateTime date = start; date < end; date = date.AddMonths(1))
yield return date;
}
Now in this case we have a start date that is inclusive and an end date that is exclusive. If we want to make the end date inclusive, as you have described, we can add:
end = end.AddMonths(1);
to the start of the method.
Next you have a few other considerations. Are the datetime objects passed in going to always be the first of the month? If not, how do you want to support it? If the start date is Feb 10th do you want the first yielded date to be Feb 1st (the start of the start date's month), March 1st (the first "first day of the month" on or after the start date), or Feb 10th (meaning that each date in the timespan would be the 10th day of that month)?
Those same questions also apply to the end date; do you want the last "first day of the month" before the end date, the first day of the next month, etc.
Also, what should happen if the start date is after the end date? Should it yield the dates "backwards", should it just pretend the start date is the end date and the end date is the start date? Should it keep adding days until you've overflowed DateTime and come back around to that date?
Pretty much all of these issues aren't too hard to deal with, the hard part is just knowing what you want to do in each case.
You could do something like:
var months = ((query.DateTo.Year - query.DateFrom.Year) * 12) + query.DateTo.Month - query.DateFrom.Month
for(int i=0;i<months;i++){
//do stuff as below
var currentDate=query.DateFrom.AddMonths(i);
}

What does "Source time in transition period." exception mean from the TZ4NET .NET timezone library

I am using TZ4Net library from
http://www.babiej.demon.nl/Tz4Net/main.htm
in order to use accurate timezones in ASP.NET
I recently started getting "Source time in transition period."
from this part of the code
static DateTime Convert(DateTime srcTime, string srcName, string dstName)
{
if (OlsonTimeZone.LookupName(srcName) == null)
{
throw new ArgumentException("Unknown source timezone name.");
}
if (OlsonTimeZone.LookupName(dstName) == null)
{
throw new ArgumentException("Unknown destintation timezone name.");
}
OlsonTimeZone srcZone = OlsonTimeZone.GetInstance(srcName);
TimeCheckResult srcCheckRes = srcZone.CheckLocalTime(srcTime);
switch (srcCheckRes)
{
case TimeCheckResult.Valid :
{
OlsonTimeZone dstZone = OlsonTimeZone.GetInstance(dstName);
DateTime dstTime = dstZone.ToLocalTime(srcZone.ToUniversalTime(srcTime));
return dstTime;
}
case TimeCheckResult.InSpringForwardGap :
case TimeCheckResult.InFallBackRange :
{
throw new ArgumentException("Source time in transition period."); // THIS PART HERE
}
default :
{
throw new ArgumentException("Source time out of range.");
}
}
}
What is TimeCheckResult.InFallBackRange and what should I do to handle this type of error?
The terms "Spring Forward" and "Fall Back" refer to changes for daylight saving time. You can read more in the DST tag wiki.
During the "Spring Forward" transition, there is a gap values that doesn't exist in the local time. For example, in the United States, most time zones skip from 1:59:59 to 3:00:00 on the second Sunday in March. So a time of 2:00:00 would be invalid because it's in the gap.
During the "Fall Back" transition, there is a range of values that exist twice in the local time. For example, in the United States, most time zones go from 1:59:59 back to 1:00:00 on the first Sunday in November. So a time of 1:00:00 exists twice and is therefore ambiguous as to which of the two moments it could refer to.
Here's what you should you do when you are trying to convert from a local time that falls in a transition period:
For a time that falls in a gap created by the "Spring Forward" transition, that's not really a valid time at all.
You should probably present an error message to your user so they can enter a valid time.
Alternatively, you might want to advance the time by the saving amount (usually 1 hour) if you want to assume that they just forgot to adjust for DST. This is often used for events that recur daily.
For a time that is ambiguous because it falls in the "Fall Back" transition, you need to determine which of the two possibilities you actually want to use.
In many cases, you should prompt your user with the two choices so they can decide. Ask the question, "Did you mean 1:00 EDT (-0400) or 1:00 EST (-0500)?"
Sometimes you will want to pick for them. You might pick the first occurrence or the second occurrence depending on your requirements.
TZ4Net is a great library, and the author (Zbigniew Babiej) has been kind enough to maintain it over the years. But it was originally written around the time of .NET 2.0, so it doesn't handle DateTimeOffset values. It also doesn't consider the .Kind property of any DateTime values used. Though DateTimeKind was introduced in .Net 2.0, it doesn't appear this was ever incorporated into TZ4Net. So you must be very careful that you feed its functions with correct values.
If you'd like to continue using Olson time zones, you could continue with TZ4Net, but I'd also like to recommend you try Noda Time. It is a community-developed open source project (rather than single author), and its lead developer is Jon Skeet. You will have the same concerns regarding DST transitions, but the API of Noda Time will force you to deal with these concerns up front, rather then having to find out after your application is deployed.
If you'd just like to keep what you have today, you could modify your above function as follows to handle the Fall Back transition:
To assume the first (daylight) instance:
case TimeCheckResult.InFallBackRange:
{
OlsonTimeZone dstZone = OlsonTimeZone.GetInstance(dstName);
DateTime dstTime = dstZone.ToLocalTime(srcZone.ToUniversalTime(srcTime.AddHours(-1)).AddHours(1));
return dstTime;
}
To assume the second (standard) instance:
case TimeCheckResult.InFallBackRange:
{
OlsonTimeZone dstZone = OlsonTimeZone.GetInstance(dstName);
DateTime dstTime = dstZone.ToLocalTime(srcZone.ToUniversalTime(srcTime.AddHours(1)).AddHours(-1));
return dstTime;
}
For the Spring Forward transition:
You should probably keep this:
case TimeCheckResult.InSpringForwardGap:
throw new ArgumentException("Source time in transition period.");
But if you want to make the assumption that the user just forgot to advance their clocks, you can do this to advance it in the conversion:
case TimeCheckResult.InSpringForwardGap:
{
OlsonTimeZone dstZone = OlsonTimeZone.GetInstance(dstName);
DateTime dstTime = dstZone.ToLocalTime(srcZone.ToUniversalTime(srcTime.AddHours(1)));
return dstTime;
}
case TimeCheckResult.InSpringForwardGap :
case TimeCheckResult.InFallBackRange :
Means that clock is moving forward or backward in the timeperiod you are trying to calculate. It is probably not defined how to handle this over diffrent time periods. It happens only twice a year ;). Handling it really depends on why your are calculating the timezone differences.
Response from the author of TZ4NET
Hi Matthew,
This means that 11/3/2013 1:26:00 is an ambiguous time in the source timezone. For example in America/New_York on 11/3/2013 at 02:00 the time will be set back to 01:00. This means that time 01:26 will occur twice at that day hence during conversion it is not clear if you refer to first occurrence or to second one as they will correspond to different UTC times.
I think the best is to detect this and ask user to elaborate on what exactly time he/she refers to. If this is not interactive process, the easiest way is to detect it and add 1 hour to source time and then subtract 1 hour from destination time.
I hope this answers your question,
Regards,
ZB

Categories