Вопрос

I have this test code below.

Lines /// 1 /// and /// 2 /// are alternatives.

If I use line /// 2 /// the output looks buggy, seems it does not account for the fact that US Boston and UK London do not differ by 5 hours during the whole year. If I use line /// 1 /// it looks OK, seems it accounts for that fact. Why so? Where is the conceptual difference? Why is shifting both dates by 1 day like this (I mean as in /// 2 ///) incorrect?

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;

public class TimeZoneExample02 {

    // private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

    public static void main(String[] args) {

        Calendar bostonTime = Calendar.getInstance(TimeZone.getTimeZone("America/New_York"));

        Calendar londonTime = new GregorianCalendar(TimeZone.getTimeZone("Europe/London"));
        londonTime.setTimeInMillis(bostonTime.getTimeInMillis());

        bostonTime.getTime();
        londonTime.getTime();

        for (int i=0; i>=-500; i--){
            bostonTime.add(Calendar.DATE, -1);
            // londonTime.setTimeInMillis(bostonTime.getTimeInMillis()); /// 1 ///
            londonTime.add(Calendar.DATE, -1); /// 2 ///

            bostonTime.getTime();
            londonTime.getTime();

            System.out.printf("Boston time: %s", getString(bostonTime));
            System.out.print(" /// ");
            System.out.printf("London time: %s\n", getString(londonTime));
        }
    }


    private static String getString(Calendar c){
        StringBuilder sb = new StringBuilder();
        sb.append(c.get(Calendar.YEAR));
        sb.append("-");
        sb.append(String.format("%02d", c.get(Calendar.MONTH) + 1));
        sb.append("-");
        sb.append(String.format("%02d", c.get(Calendar.DAY_OF_MONTH)));
        sb.append(" ");
        sb.append(String.format("%02d", c.get(Calendar.HOUR_OF_DAY)));
        sb.append(":");
        sb.append(String.format("%02d", c.get(Calendar.MINUTE)));
        sb.append(":");
        sb.append(String.format("%02d", c.get(Calendar.SECOND)));
        sb.append(".");
        return sb.toString();
    }

}

OUTPUT 1:

Boston time: 2013-10-30 18:51:12. /// London time: 2013-10-30 22:51:12.
Boston time: 2013-10-29 18:51:12. /// London time: 2013-10-29 22:51:12.
Boston time: 2013-10-28 18:51:12. /// London time: 2013-10-28 22:51:12.
Boston time: 2013-10-27 18:51:12. /// London time: 2013-10-27 22:51:12.
Boston time: 2013-10-26 18:51:12. /// London time: 2013-10-26 23:51:12.
Boston time: 2013-10-25 18:51:12. /// London time: 2013-10-25 23:51:12.
Boston time: 2013-10-24 18:51:12. /// London time: 2013-10-24 23:51:12.

OUTPUT 2:

Boston time: 2013-10-30 18:50:53. /// London time: 2013-10-30 23:50:53.
Boston time: 2013-10-29 18:50:53. /// London time: 2013-10-29 23:50:53.
Boston time: 2013-10-28 18:50:53. /// London time: 2013-10-28 23:50:53.
Boston time: 2013-10-27 18:50:53. /// London time: 2013-10-27 23:50:53.
Boston time: 2013-10-26 18:50:53. /// London time: 2013-10-26 23:50:53.
Boston time: 2013-10-25 18:50:53. /// London time: 2013-10-25 23:50:53.
Boston time: 2013-10-24 18:50:53. /// London time: 2013-10-24 23:50:53.

Это было полезно?

Решение

London DST

London time changed with Daylight Saving Time (DST) ending on 2013-10-27 when 2 AM became 1 AM (again).

Features, Not Bugs

The documentation for java.util.Calendar in its discussion at top explains that unlike set(), add() forces an immediate recomputation of the calendar's milliseconds and all fields.

Furthermore, the documentation for java.util.GregorianCalendar on the add method notes that smaller units of time are not adjusted. The doc specifically notes that HOUR is a smaller field that DAY_OF_MONTH, so it is not adjusted. Meaning that you started with an hour of 23 so you'll end up with an hour of 23, with milliseconds-since-epoch recomputed as needed.

The behaviors you see for the set and add methods are both correct. Features, not bugs.

Indirect Answer

The bundled java.util.Date, java.util.Calendar, and java.text.SimpleDateFormat are notoriously troublesome, confusing, and tricky. They have flaws in both design and implementation.

I understand your interest in "to play with Java's built-in capabilities". While that interest is generally laudable, in this specific corner of Java, it is a waste of time. Even Sun and Oracle have given up on these classes. Java 8 brings a whole new java.time.* package, defined by JSR 310, inspired by Joda-Time, and supplanting the old bundled classes.

If you cannot yet move to Java 8, use Joda-Time. Joda-Time works in multiple versions of Java, and continues to work in Java 8 as it is actively maintained.

Example Code

Some example code using Joda-Time 2.3 to get you going.

Some Notes…

A Joda-Time DateTime actually knows its own time zone. In contrast, a java.util.Date has no time zone yet its toString method applies the JVM's default time zone which creates no end of confusion.

Note in this example that dateTime_Boston and dateTime_London have the same number of milliseconds-since-epoch.

Joda-Time by default uses standard ISO 8601 format for string output, like this 2014-02-13T10:32:28.131+05:30.

The + or - at the end marks a time zone offset from UTC/GMT. Do not read this as an operand in a formula. Read this as a label saying, for example, "India has a time zone offset of +05:30, so the date-time displayed is five and a half hours ahead of UTC/GMT".

  • PLUS SIGN ("+") means ahead of UTC/GMT.
  • HYPHEN-MINUS ("-") means behind UTC/GMT.

The Z at the end is pronounced "Zulu" and is shorthand for +00:00. That means UTC/GMT time zone, that is, no time zone offset.

// Specify a time zone rather than rely on default.
DateTimeZone timeZone_Boston = DateTimeZone.forID( "America/New_York" );
DateTimeZone timeZone_London = DateTimeZone.forID( "Europe/London" );

DateTime dateTime_Boston = new DateTime( 2013, 10, 27, 22, 51, 12, timeZone_Boston );
DateTime dateTime_London = dateTime_Boston.toDateTime( timeZone_London ); 
DateTime earlier_London = dateTime_London.minusDays( 2 ); // Use '2' to get us before DST change.
DateTime earlier_UtcGmt = earlier_London.toDateTime( DateTimeZone.UTC );

Dump to console…

System.out.println( "dateTime_Boston " + dateTime_Boston );
System.out.println( "dateTime_London " + dateTime_London );
System.out.println( "earlier_London " + earlier_London );
System.out.println( "earlier_UtcGmt " + earlier_UtcGmt );

When run…

dateTime_Boston 2013-10-27T22:51:12.000-04:00
dateTime_London 2013-10-28T02:51:12.000Z
earlier_London 2013-10-26T02:51:12.000+01:00
earlier_UtcGmt 2013-10-26T01:51:12.000Z
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top