I have DST rules like this one:
"2,-1,1,3600000"
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.