Skip to content

TimeZoneInfo.AdjustmentRule information is not correct on Unix #20626

@eerhardt

Description

@eerhardt

On Unix, we are trying to populate the "DaylightTransitionStart" and "DaylightTransitionEnd" values for the AdjustmentRules. However, this design is flawed and leads to incorrect data being reported by the API.

See https://github.com/dotnet/corefx/issues/2465#issuecomment-127645308 and the whole issue for discussion on how the Windows-centric TimeZoneInfo.AdjustmentRule API was modified to fit the information that is available on Unix.

We are trying to hydrate the DaylightTransition Start and End data here https://github.com/dotnet/coreclr/blob/c2b5d5a707e3bb037df847e17b4e55e19fd5bfa8/src/mscorlib/src/System/TimeZoneInfo.Unix.cs#L124-L158. However, this data is incorrect on Unix. Take the Africa/Casablanca time zone from 1940-1945:

From our tests

            // Africa/Casablanca had DST from
            //     1940-02-25T00:00:00.0000000Z {+01:00:00 DST=True}
            //     1945-11-17T23:00:00.0000000Z { 00:00:00 DST=False}

Printing the AdjustmentRules from around that time:

Baseoffset: 00:00:00
DateStart       DateEnd         DaylightDelta   DaylightTransitionStart DaylightTransitionEnd
11/17/1945      06/10/1950      00:00:00        M11.w1.d17 11:00 PM     M6.w1.d10 11:59 PM
02/25/1940      11/17/1945      01:00:00        M2.w1.d25 1:00 AM       M11.w1.d17 11:59 PM
11/18/1939      02/24/1940      00:00:00        M11.w1.d18 11:00 PM     M2.w1.d24 11:59 PM
09/12/1939      11/18/1939      01:00:00        M9.w1.d12 1:00 AM       M11.w1.d18 11:59 PM
10/26/1913      09/11/1939      00:00:00        M10.w1.d26 12:30 AM     M9.w1.d11 11:59 PM

Specifically, look at this row:

DateStart       DateEnd         DaylightDelta   DaylightTransitionStart DaylightTransitionEnd
02/25/1940      11/17/1945      01:00:00        M2.w1.d25 1:00 AM       M11.w1.d17 11:59 PM

What this says is that for times in this time zone between 02/25/1940 and 11/17/1945, Daylight Savings Time starts on the 25th day of February and ends on the 17th day of November of every year. So from Nov 17 to Feb 25 every year, the time zone should not be in daylight savings time, which is incorrect because the time zone was in DST all year round those 5 years.

I don't think we should be populating these Daylight TransitionTime values at all when giving the AdjustmentRules to public consumers. Anyone who wants to consume this information is going to be broken trying to use it. This is just going to lead to incorrect information being displayed to their users.

Code to print the above table

    class Program
    {
        static void Main(string[] args)
        {
            TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Africa/Casablanca");
            System.Console.WriteLine($"Baseoffset: {tz.BaseUtcOffset}");

            StringBuilder builder = new StringBuilder();
            builder.Append("DateStart");
            builder.Append("\t");
            builder.Append("DateEnd");
            builder.Append("\t");
            builder.Append("\t");
            builder.Append("DaylightDelta");
            builder.Append("\t");
            builder.Append("DaylightTransitionStart");
            builder.Append("\t");
            builder.Append("DaylightTransitionEnd");
            System.Console.WriteLine(builder);

            var rules = tz.GetAdjustmentRules();
            DateTime startDate = new DateTime(1945, 12, 31);
            //DateTime startDate = DateTime.Now;
            foreach (var rule in rules.Where(r => r.DateStart < startDate).Reverse().Take(5))
            {
                builder = new StringBuilder();
                builder.Append(rule.DateStart.ToString("MM/dd/yyyy"));
                builder.Append("\t");
                builder.Append(rule.DateEnd.ToString("MM/dd/yyyy"));
                builder.Append("\t");
                builder.Append(rule.DaylightDelta);
                builder.Append("\t");
                PrintTransition(rule.DaylightTransitionStart, builder);
                builder.Append("\t");
                PrintTransition(rule.DaylightTransitionEnd, builder);
                System.Console.WriteLine(builder);
            }
        }

        private static void PrintTransition(TimeZoneInfo.TransitionTime transition, StringBuilder builder)
        {
            builder.Append($"M{transition.Month}.w{transition.Week}.d{transition.Day} {transition.TimeOfDay.ToShortTimeString()}");
        }
    }

/cc @tarekgh @mj1856

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions