vksp
vksp

Reputation: 71

How to get UTC Offset during DST time in .NET

I am trying to get all offsets seen in a timezone, in an interval. Below is the function that I used to accomplish this, I know you could use TimeZoneInfo.BaseUtcOffset to get the UTC offset for a timezone during standard time, but there is no similar method to get one during daylight saving time unless you pass a particular DST point of time to the GetUTCOffset() method.

static void GetOffsets(DateTime startTime, DateTime endTime, TimeZoneInfo tz)
{
    var result = new HashSet<int>();
    var adjRules = tz.GetAdjustmentRules();
    result.Add(tz.BaseUtcOffset);

    foreach (var adjustmentRule in adjRules)
    {
        if ((startTime >= adjustmentRule.DateStart && startTime <= adjustmentRule.DateEnd) || (endTime >= adjustmentRule.DateStart && endTime <= adjustmentRule.DateEnd) ||
             (stTime <= adjustmentRule.DateStart && endTime >= adjustmentRule.DateEnd))
        {
            if(adjustmentRule.DaylightDelta != TimeSpan.Zero)
            {
                if (!result.Contains(tz.BaseUtcOffset + adjustmentRule.DaylightDelta))
                      result.Add((tz.BaseUtcOffset + adjustmentRule.DaylightDelta));
            }
         }
     }

     foreach (var res in result)
     {
         Console.WriteLine(res);
     }
}

Please let me know if there is a better way to do this.

Upvotes: 7

Views: 3842

Answers (3)

vksp
vksp

Reputation: 71

I did comeup with this method to get the offSets needed to be considered for a UTC interval within a year interval for a specified timezoneinfo. I pass this collection to database to filter datetime fields saved in UTC for interval filter inputs like ("1am - 2am"), I did test this for all Timezones in my system and it worked fine. Although this is not a answer to my original question as I am still using Adjustment rules to get the offSets, I tried to make it usable this time.

class Program
{
    static void Main(string[] args)
    {
        foreach (var tz in TimeZoneInfo.GetSystemTimeZones())
        {
            var result = GetUTCOffsetsByUTCIntervals(1900, 2012, tz);
            Console.WriteLine(tz.DisplayName);
            foreach (var tuple in result)
            {
                Console.WriteLine(tuple.Item1 + "     " + tuple.Item2 + "   " + tuple.Item3);
            }
            Console.WriteLine("------------------------------------------------------------");
        }
        Console.Read();
    }

    public static List<Tuple<TimeSpan, DateTime, DateTime>> GetUTCOffsetsByUTCIntervals(int stYear, int endYear, TimeZoneInfo tz)
    {
        var cal = CultureInfo.CurrentCulture.Calendar;
        var offsetsByUTCIntervals = new List<Tuple<TimeSpan, DateTime, DateTime>>();
        var adjRules = tz.GetAdjustmentRules();
        for (var year = stYear; year <= endYear && year < DateTime.MaxValue.Year && year >= DateTime.MinValue.Year; year++)
        {
            var adjRule =
                adjRules.FirstOrDefault(
                    rule =>
                    rule.DateStart.Year == year || rule.DateEnd.Year == year ||
                    (rule.DateStart.Year < year && rule.DateEnd.Year > year));

            var yrStTime = new DateTime(year, 1, 1);
            var yrEndTime = yrStTime.AddYears(1).AddTicks(-1);

            if (adjRule != null)
            {
                var tStDate = GetTransitionDate(adjRule.DaylightTransitionStart, year);
                var tEnddate = GetTransitionDate(adjRule.DaylightTransitionEnd, year);

                var stTsp = adjRule.DaylightTransitionStart.TimeOfDay.TimeOfDay;

                var endTsp = adjRule.DaylightTransitionEnd.TimeOfDay.TimeOfDay;

                if (yrStTime.Date == tStDate && yrStTime.TimeOfDay == stTsp)
                    yrStTime = yrStTime.Add(adjRule.DaylightDelta);

                if (yrEndTime.Date == tEnddate && yrEndTime.TimeOfDay == endTsp)
                    yrEndTime = yrEndTime.Subtract(adjRule.DaylightDelta);

              if (tStDate.Month > tEnddate.Month)
                {
                    offsetsByUTCIntervals.Add(new Tuple<TimeSpan, DateTime, DateTime>(tz.BaseUtcOffset + adjRule.DaylightDelta, ConvertTimeToUtc(yrStTime, tz), ConvertTimeToUtc(tEnddate.AddTicks(endTsp.Ticks - 1), tz)));
                    offsetsByUTCIntervals.Add(new Tuple<TimeSpan, DateTime, DateTime>(tz.BaseUtcOffset, ConvertTimeToUtc(tEnddate.Add(endTsp.Subtract(adjRule.DaylightDelta)), tz), ConvertTimeToUtc(tStDate.AddTicks(stTsp.Ticks - 1), tz)));
                    offsetsByUTCIntervals.Add(new Tuple<TimeSpan, DateTime, DateTime>(tz.BaseUtcOffset + adjRule.DaylightDelta, ConvertTimeToUtc(tStDate.Add(stTsp.Add(adjRule.DaylightDelta)), tz), ConvertTimeToUtc(yrEndTime, tz))); 
                }
                else
                {
                    offsetsByUTCIntervals.Add(new Tuple<TimeSpan, DateTime, DateTime>(tz.BaseUtcOffset, ConvertTimeToUtc(yrStTime, tz), ConvertTimeToUtc(tStDate.AddTicks(stTsp.Ticks - 1), tz)));
                    offsetsByUTCIntervals.Add(new Tuple<TimeSpan, DateTime, DateTime>(tz.BaseUtcOffset + adjRule.DaylightDelta, ConvertTimeToUtc(tStDate.Add(stTsp.Add(adjRule.DaylightDelta)), tz), ConvertTimeToUtc(tEnddate.AddTicks(endTsp.Ticks - 1), tz)));
                    offsetsByUTCIntervals.Add(new Tuple<TimeSpan, DateTime, DateTime>(tz.BaseUtcOffset, ConvertTimeToUtc(tEnddate.Add(endTsp.Subtract(adjRule.DaylightDelta)), tz), ConvertTimeToUtc(yrEndTime, tz))); 
                }
            }
            else
            {
                offsetsByUTCIntervals.Add(new Tuple<TimeSpan, DateTime, DateTime>(tz.BaseUtcOffset, ConvertTimeToUtc(yrStTime, tz), ConvertTimeToUtc(yrEndTime, tz)));
            }
        }
        return offsetsByUTCIntervals;
    }

    public static DateTime ConvertTimeToUtc(DateTime date, TimeZoneInfo timeZone)
    {
        if (date == null || timeZone == null)
        {
            return date;
        }
        DateTime convertedDate = TimeZoneInfo.ConvertTimeToUtc(date, timeZone);
        return convertedDate;
    }

    //copy from msdn http://msdn.microsoft.com/en-us/library/system.timezoneinfo.transitiontime.isfixeddaterule.aspx
    private static DateTime GetTransitionDate(TimeZoneInfo.TransitionTime transition, int year)
    {
        if (transition.IsFixedDateRule)
            return new DateTime(year, transition.Month, transition.Day);

        int transitionDay;
        var cal = CultureInfo.CurrentCulture.Calendar;
        var startOfWeek = transition.Week * 7 - 6;
        var firstDayOfWeek = (int)cal.GetDayOfWeek(new DateTime(year, transition.Month, 1));
        var changeDayOfWeek = (int)transition.DayOfWeek;

        if (firstDayOfWeek <= changeDayOfWeek)
            transitionDay = startOfWeek + (changeDayOfWeek - firstDayOfWeek);
        else
            transitionDay = startOfWeek + (7 - firstDayOfWeek + changeDayOfWeek);

        if (transitionDay > cal.GetDaysInMonth(year, transition.Month))
            transitionDay -= 7;
        return new DateTime(year, transition.Month, transitionDay);
    }

   /* static void GetOffsets(DateTime startTime, DateTime endTime, TimeZoneInfo tz)
    {
        var result = new HashSet<string>();
        var adjRules = tz.GetAdjustmentRules();
        result.Add(tz.BaseUtcOffset.ToString());

        foreach (var adjustmentRule in adjRules)
        {
            if ((startTime >= adjustmentRule.DateStart && startTime <= adjustmentRule.DateEnd)
                    || (endTime >= adjustmentRule.DateStart && endTime <= adjustmentRule.DateEnd)
                 || (startTime <= adjustmentRule.DateStart && endTime >= adjustmentRule.DateEnd))
            {
                if(adjustmentRule.DaylightDelta != TimeSpan.Zero)
                {
                    if (!result.Contains((tz.BaseUtcOffset + adjustmentRule.DaylightDelta).ToString()))
                      result.Add((tz.BaseUtcOffset + adjustmentRule.DaylightDelta).ToString());
                }
            }
        }
        Console.Write(tz.DisplayName + "   ");
        foreach (var res in result)
        {
            Console.Write(res);
        }
    }*/
}

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1501163

I am trying to get all offsets seen in a timezone, in an interval.

I would strongly advise you to avoid trying to use TimeZoneInfo directly. The adjustment rules can be surprisingly awkward for some zones in some years, as I've found to my expense.

While I'm biased, I'd suggest using Noda Time, which can wrap a TimeZoneInfo and do the hard work for you, via BclDateTimeZone.FromTimeZoneInfo. Your question isn't entirely clear in terms of requirements, but if you can say a bit more about what you're trying to do, I can write the appropriate Noda Time code for you.

Your initial description could be implemented with something like this:

public IEnumerable<ZoneInterval> GetZoneIntervalsInInterval
    (this DateTimeZone zone, Interval interval)
{
    Instant current = interval.Start;
    while (current < interval.End && current != Instant.MaxValue)
    {
        ZoneInterval zi = zone.GetZoneInterval(current);
        yield return zi;
        current = zi.End;
    }
}

Then:

var zone = BclDateTimeZone.FromTimeZoneInfo(tzInfo);
var offsets = zone.GetZoneIntervalsInInterval(interval)
                  .Select(zi => zi.WallOffset)
                  .Distinct();

That's assuming that you mean the same thing by "offset" as I do (namely the difference between UTC and local time).

Upvotes: 6

cfeduke
cfeduke

Reputation: 23236

If you're doing anything complex with dates you should take a look at Noda Time (which is a port of the Joda Time Java library). It has a whole namespace for dealing with thorny time zone issues.

Upvotes: -1

Related Questions