JDK-4221082 : Across the European Union summer-time should end at 1 a.m. GMT.
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util:i18n
  • Affected Version: 1.1.7
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: generic
  • CPU: generic
  • Submitted: 1999-03-17
  • Updated: 1999-07-06
  • Resolved: 1999-07-06
Related Reports
Duplicate :  
Description

Name: mf23781			Date: 03/17/99


Across the European Union summer-time should end at 1 a.m. GMT.

See http://europa.eu.int/eur-lex/en/lif/en_397L0044.html for the
Community legislation in force.

The current code seems to make summer-time end 1 hour earlier than that.

The code in TimeZone.java and SimpleTimeZone.java seems to indicate that
the European directive 97/44/EC is being followed, but things don't
quite seem to work.

test case summer.java:

------------------------------------------------------------------------------------
import java.util.*;
import java.text.*;
public class summer {
    public static void main(String[] args) {
        int errors = 0;

        TimeZone tGMT = TimeZone.getTimeZone("GMT" /*not "Europe/London"*/);
        TimeZone tParis = TimeZone.getTimeZone("ECT" /*"Europe/Paris"*/);
        TimeZone tIstanbul = TimeZone.getTimeZone("EET" /*"Europe/Istanbul"*/);
        Calendar cGMT = Calendar.getInstance(tGMT, Locale.UK);
        Calendar cParis = Calendar.getInstance(tParis, Locale.UK);
        Calendar cAthens = Calendar.getInstance(tIstanbul, Locale.UK);
        String ss = "dd MMMM yyyy HH:mm:ss.SSS z";
        SimpleDateFormat sdf = new SimpleDateFormat(ss);
        SimpleDateFormat sdfGMT = new SimpleDateFormat(ss);
        sdfGMT.setCalendar(cGMT);
        SimpleDateFormat sdfParis = new SimpleDateFormat(ss);
        sdfParis.setCalendar(cParis);
        SimpleDateFormat sdfAthens = new SimpleDateFormat(ss);
        sdfAthens.setCalendar(cAthens);

        Date dt;
        String s1, s2;

        cGMT.clear();
        cGMT.set(1999, 3 - 1, 28, 0, 59, 59);
        dt =cGMT.getTime();
        s1 = "Time in GMT is "+sdfGMT.format(dt);
        s2 = "Time in GMT is 28 March 1999 00:59:59.000 GMT+00:00";
        System.out.println(s1);
        if (!s1.equals(s2)) {
            System.out.println("Error: expected "+s2);
            ++errors;
        }

        s1 = "Time in Paris is "+sdfParis.format(dt);
        s2 = "Time in Paris is 28 March 1999 01:59:59.000 GMT+01:00";
        System.out.println(s1);
        if (!s1.equals(s2)) {
            System.out.println("Error: expected "+s2);
            ++errors;
        }

        s1 = "Time in Athens is "+sdfAthens.format(dt);
        s2 = "Time in Athens is 28 March 1999 02:59:59.000 GMT+02:00";
        System.out.println(s1);
        if (!s1.equals(s2)) {
            System.out.println("Error: expected "+s2);
            ++errors;
        }

        System.out.println();

        cGMT.clear();
        cGMT.set(1999, 3 - 1, 28, 1, 00, 00);
        dt =cGMT.getTime();
        s1 = "Time in GMT is "+sdfGMT.format(dt);
        s2 = "Time in GMT is 28 March 1999 01:00:00.000 GMT+00:00";
        System.out.println(s1);
        if (!s1.equals(s2)) {
            System.out.println("Error: expected "+s2);
            ++errors;
        }

        s1 = "Time in Paris is "+sdfParis.format(dt);
        s2 = "Time in Paris is 28 March 1999 03:00:00.000 GMT+02:00";
        System.out.println(s1);
        if (!s1.equals(s2)) {
            System.out.println("Error: expected "+s2);
            ++errors;
        }

        s1 = "Time in Athens is "+sdfAthens.format(dt);
        s2 = "Time in Athens is 28 March 1999 04:00:00.000 GMT+03:00";
        System.out.println(s1);
        if (!s1.equals(s2)) {
            System.out.println("Error: expected "+s2);
            ++errors;
        }

        System.out.println();

        cGMT.clear();
        cGMT.set(1999, 10 - 1, 31, 0, 59, 59);
        dt =cGMT.getTime();
        s1 = "Time in GMT is "+sdfGMT.format(dt);
        s2 = "Time in GMT is 31 October 1999 00:59:59.000 GMT+00:00";
        System.out.println(s1);
        if (!s1.equals(s2)) {
            System.out.println("Error: expected "+s2);
            ++errors;
        }

        s1 = "Time in Paris is "+sdfParis.format(dt);
        s2 = "Time in Paris is 31 October 1999 02:59:59.000 GMT+02:00";
        System.out.println(s1);
        if (!s1.equals(s2)) {
            System.out.println("Error: expected "+s2);
            ++errors;
        }

        s1 = "Time in Athens is "+sdfAthens.format(dt);
        s2 = "Time in Athens is 31 October 1999 03:59:59.000 GMT+03:00";
        System.out.println(s1);
        if (!s1.equals(s2)) {
            System.out.println("Error: expected "+s2);
            ++errors;
        }

        System.out.println();

        cGMT.clear();
        cGMT.set(1999, 10 - 1, 31, 1, 00, 00);
        dt =cGMT.getTime();
        s1 = "Time in GMT is "+sdfGMT.format(dt);
        s2 = "Time in GMT is 31 October 1999 01:00:00.000 GMT+00:00";
        System.out.println(s1);
        if (!s1.equals(s2)) {
            System.out.println("Error: expected "+s2);
            ++errors;
        }

        s1 = "Time in Paris is "+sdfParis.format(dt);
        s2 = "Time in Paris is 31 October 1999 02:00:00.000 GMT+01:00";
        System.out.println(s1);
        if (!s1.equals(s2)) {
            System.out.println("Error: expected "+s2);
            ++errors;
        }

        s1 = "Time in Athens is "+sdfAthens.format(dt);
        s2 = "Time in Athens is 31 October 1999 03:00:00.000 GMT+02:00";
        System.out.println(s1);
        if (!s1.equals(s2)) {
            System.out.println("Error: expected "+s2);
            ++errors;
        }

        System.out.println();



        System.out.println("Testing for summer time change-over through the year - please wait");

        SimpleDateFormat sdfParis2 = new SimpleDateFormat("z");
        sdfParis2.setCalendar(cParis);

        cGMT.clear();
        cGMT.set(1999, 1 - 1, 1, 0, 0, 0);
        dt =cGMT.getTime();
        long l1 = dt.getTime();

        cGMT.clear();
        cGMT.set(2000, 1 - 1, 1, 0, 0, 0);
        dt =cGMT.getTime();
        long l2 = dt.getTime();

        Date dd = new Date(l1);

        System.out.println("Time in GMT is "+sdfGMT.format(dd));
        System.out.println("Time in Paris is "+sdfParis.format(dd));
        String ssref = sdfParis2.format(dd), sx = null;
        System.out.println(ssref);
        Date pd = null;
        for (long l3 = l1; l3 < l2; l3 += 10000) {
           dd = new Date(l3);
           sx = sdfParis2.format(dd);
           if (!sx.equals(ssref)) {
               System.out.println();
               System.out.println("Time in GMT is "+sdfGMT.format(pd));
               System.out.println("Time in Paris is "+sdfParis.format(pd));
               System.out.println("Time in GMT is "+sdfGMT.format(dd));
               System.out.println("Time in Paris is "+sdfParis.format(dd));
               ssref = sx;
           }
           pd = dd;
        }

        if (errors > 0) throw new RuntimeException("" + errors + " error(s) detected");
    }
}

------------------------------------------------------------------------------------

Output is:-


Time in GMT is 31 October 1999 00:59:59.000 GMT+00:00
Time in Paris is 31 October 1999 01:59:59.000 GMT+01:00
Error: expected Time in Paris is 31 October 1999 02:59:59.000 GMT+02:00
Time in Athens is 31 October 1999 02:59:59.000 GMT+02:00
Error: expected Time in Athens is 31 October 1999 03:59:59.000 GMT+03:00

Time in GMT is 31 October 1999 01:00:00.000 GMT+00:00
Time in Paris is 31 October 1999 02:00:00.000 GMT+01:00
Time in Athens is 31 October 1999 03:00:00.000 GMT+02:00




The code in SimpleTimeZone.java

        // Compare the date to the starting and ending rules.  For the ending
        // rule comparison, we add the dstSavings to the millis passed in to convert
        // them from standard to wall time.  +1 = date>rule, -1 = date<rule, 0 =
        // date==rule.
        int startCompare = compareToRule(month, day, dayOfWeek, millis,
                                         startMode, startMonth, startDayOfWeek,
                                         startDay, startTime);
        int endCompare = compareToRule(month, day, dayOfWeek, millis + dstSavings,
                                       endMode, endMonth, endDayOfWeek,
                                       endDay, endTime);


This compares wall-clock time (including summer time adjustment for the ending
time) against the endTime of the rule, so the endTime must be held in wall-clock
time.

In TimeZone.java:

       new SimpleTimeZone(1 * millisPerHour, "Europe/Paris" /*CE%sT*/,
               Calendar.MARCH, -1, Calendar.SUNDAY /*DOW_IN_DOM*/, 2 * millisPerHour,
               Calendar.OCTOBER, -1, Calendar.SUNDAY /*DOW_IN_DOM*/, 2 * millisPerHour, 1 * millisPerHour),
       // Rule EU      1981    max     -       Mar     lastSun 1:00u   1:00    S
       // Rule EU      1996    max     -       Oct     lastSun 1:00u   0       -


so the rule is stated, in the comment, in UTC (the 1:00u) but the offset for ending,
in the actual code, doesn't include the summer-time adjustment of 1 hour in addition
to the timezone offset.

This same mistake has been made for all rules specified as xx:00u

---------------------------------------------------------------------------------------------

Browsing this same code shows some summertime offsets coded as negative numbers. I doubt if
these are correct, because summertime start and end are normally early morning, but also it
is clear that the algorithm in compareToRule assumes a positive offset and could yield an
incorrect result with a negative offset.

The European Union legislation specifies that the 1am GMT change applies only to the EU itself
and not its overseas territories.


(Review ID: 55690)

======================================================================

Comments
EVALUATION The evaluation is in Description. Solaris level zoneinfo based timezone support is desired in Java. masayoshi.okutsu@Eng 1999-03-19 Fixed in Kestrel-beta as 4191164. masayoshi.okutsu@Eng 1999-07-06
19-03-1999