2

I have the below code to add time to a DateTime instance:

        DateTime d1 = new DateTime();
        d1 = d1.withZone(DateTimeZone.forID("Europe/London"));
        ArrayList<String> timeList = new ArrayList<String>();

        for(int x = 1; x <= 10; x++) {
        //Adds six hours to the DateTime instance.
                d1 = d1.plusHours(6);
                d1 = d1.plusMinutes(0);
                d1 = d1.plusSeconds(0);
                timeList.add(d1.toString());
        }

This will create a set of 10 times to add to an arraylist. However say there was a daylight saving change when the 6 hours were added. How can I generate the correct time when the extra hour is added/removed due to the timezone change? At the moment it does not remove/add the extra hour using this method.

For example I would expect the below times to be generated if I was to begin running the code on the 24th Oct 2015 at 10:00am. Note that the timezone changes at 02:00am on 25/10/2015.

24/10/2015 10:00:00 BST
24/10/2015 16:00:00 BST
24/10/2015 22:00:00 BST
25/10/2015 05:00:00 GMT
25/10/2015 11:00:00 GMT
25/10/2015 17:00:00 GMT
25/10/2015 23:00:00 GMT
26/10/2015 05:00:00 GMT
26/10/2015 11:00:00 GMT
26/10/2015 17:00:00 GMT
MisterIbbs
  • 247
  • 1
  • 7
  • 20
  • 1
    What do you mean by "accommodate"? It would be really helpful if you would show a short but complete program demonstrating the problem - including actual output and expected output. – Jon Skeet May 22 '15 at 12:24
  • Added more information to the post. – MisterIbbs May 22 '15 at 12:34
  • 1
    You haven't added a short but complete example though, which is what I'd really like to have seen... – Jon Skeet May 22 '15 at 12:35
  • 1
    I assume this is with Joda Time, btw? You don't actually say anywhere... – Jon Skeet May 22 '15 at 12:36
  • 4
    Why would you expect it to go from 22:00:00 to 05:00:00? I would expect it to go the *other* way, to 03:00:00, which is what I actually observe. Going from 22:00:00 BST to 05:00:00 GMT is an elapsed time of *8* hours. – Jon Skeet May 22 '15 at 12:39

2 Answers2

2

Normally I would have left the right answer to Jon Skeet (his last comment is more or less an answer in my opinion), but now there are two other inacceptable answers which miss the critical point.

Your "problem" can be narrowed to these lines and (wrong) expectations:

24/10/2015 22:00:00 BST

25/10/2015 05:00:00 GMT

You correctly writes that the daylight saving switch jumping back to winter time happens in UK on 2015-10-25 at 2:00 AM. This means that the hour labelled "01" happens twice (overlap situation) because the clock is set back and repeats this hour. Therefore the nominal count of hours as clock positions must be increased by ONE hour to get the real physical duration in hours. Mathematically:

nominal duration + one hour = real duration (= 6 real hours)
=> nominal duration = (6 - 1) hours = 5 virtual hours

Keep in mind that timestamps like "24/10/2015 22:00:00 BST" (in ISO-offset notation: "2015-10-24T22:00:00+01") stand for global physical instants so the time delta between such instants express a physical duration. The addition of a duration of six hours to the original instant contains an extra hour however so you have to remove one hour from the real hours to get the nominal duration (measured in clock positions - see the second part of given equation above). Therefore in instant notation:

[2015-10-24T22:00+01] + 6 physical hours = 
  [2015-10-25T04:00+01] = [2015-10-25T03:00+00] = [2015-10-25T03:00Z]

And in nominal local timestamp notation (just watching the clock positions):

[2015-10-24T22:00] + 5 virtual hours (clock positions) = [2015-10-25T03:00]

So repeating a clock position reduces the nominal duration and does not increase it.

And that is what Joda-Time correctly does:

DateTime d1 = new DateTime(2015, 10, 24, 10, 0, 0, 0, DateTimeZone.forID("Europe/London"));

for (int x = 1; x <= 10; x++) {
    d1 = d1.plusHours(6);
    System.out.println("> " + d1.toString());
}

> 2015-10-24T16:00:00.000+01:00
> 2015-10-24T22:00:00.000+01:00
> 2015-10-25T03:00:00.000Z
> 2015-10-25T09:00:00.000Z
> 2015-10-25T15:00:00.000Z
> 2015-10-25T21:00:00.000Z
> 2015-10-26T03:00:00.000Z
> 2015-10-26T09:00:00.000Z
> 2015-10-26T15:00:00.000Z
> 2015-10-26T21:00:00.000Z
Community
  • 1
  • 1
Meno Hochschild
  • 42,708
  • 7
  • 104
  • 126
  • Oh, god *facepalm* I just assumed OP was right and didn't look at the date. – Lennart Regebro May 24 '15 at 11:21
  • Correction… Regarding the statement: “the hour labelled "02" happens twice”, I believe it is actually the `01` hour that repeats. From what I have read, in the US, Canada, and the EU, at 1:59 or the stroke of 2 AM, the clocks are set back to `01:00` to repeat that 1 AM hour. – Basil Bourque Apr 04 '17 at 04:37
  • @BasilBourque Have corrected the typo. I was misguided because in central Europe, the clocks jump back one local hour later at 3 am (in Germany: the clock 2 is repeating). – Meno Hochschild Apr 04 '17 at 07:48
1

Don’t worry, use java.time

  • Apparently you are using the Joda-Time library. Use java.time instead.
  • The java.time classes automatically handle Daylight Saving Time (DST) cutovers.
  • No need for you to do anything except keep your JVM up-to-date with tzdata time zone database changes that may affect your time zones of interest. See Oracle’s provided tool for tzdata updates.

java.time

Let's look at the results of adding six hours using the java.time classes.

Define the date and the time-of-day portions.

LocalDate ld = LocalDate.of ( 2015, Month.OCTOBER, 24 );  // 24th Oct 2015 at 10:00am per the Question.
LocalTime lt = LocalTime.of ( 10, 0 );

Define the time zone, a ZoneId object, for Europe/London.

ZoneId z = ZoneId.of ( "Europe/London" );

Combine to create a ZonedDateTime object.

ZonedDateTime zdtStart = ZonedDateTime.of ( ld, lt, z );

Extract an Instant from the ZonedDateTime. The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).

Instant instantStart = zdtStart.toInstant ( );

Define our span of time, six hours, as a Duration. The java.time classes can perform date-time math by adding a Duration object.

A Duration is unattached to the timeline, and actually stores a number of seconds and a number of nanoseconds. So there are no smarts about “six hours” and the clock and DST etc. within this class. When we ask for a Duration of six hours, that class immediately calculates ( 6 hours * 60 minutes per hour * 60 seconds per minute ) = 21,600 seconds in total.

Duration sixHours = Duration.ofHours ( 6 );  // 21,600 seconds = ( 6 hours * 60 minutes per hour * 60 seconds per minute ).

Loop ten times. First loop by adding the Duration to ZonedDateTime, and convert the result to an Instant.

// Increment the `ZonedDateTime`.
ZonedDateTime zdt = zdtStart;
for ( int i = 1 ; i <= 10 ; i++ ) {
    System.out.println ( ">zdt.toString() " + zdt + " | zdt.toInstant().toString(): " + zdt.toInstant ( ) + "\n");
    // Set up next loop.
    zdt = zdt.plus ( sixHours );
}

When run. Note the jump in the time-of-day in London time. This is the Daylight Saving Time (DST) cutover, the “Fall-back” time in the autumn when England switches back to standard time going from an offset-from-UTC of +01:00 to Zulu offset of +00:00, where at 2 AM the clock jumps back to repeat the 1 AM hour. So where we would otherwise expect 22:00 plus six hours to result in 4 AM, we instead see 3 AM. You can see in the Instant value that six hours did indeed elapse. The trick was that Londoners wound-back their clocks an hour around then.

See the history of DST cutovers for Europe/London.

zdt.toString() 2015-10-24T10:00+01:00[Europe/London] | zdt.toInstant().toString(): 2015-10-24T09:00:00Z

zdt.toString() 2015-10-24T16:00+01:00[Europe/London] | zdt.toInstant().toString(): 2015-10-24T15:00:00Z

zdt.toString() 2015-10-24T22:00+01:00[Europe/London] | zdt.toInstant().toString(): 2015-10-24T21:00:00Z

zdt.toString() 2015-10-25T03:00Z[Europe/London] | zdt.toInstant().toString(): 2015-10-25T03:00:00Z

zdt.toString() 2015-10-25T09:00Z[Europe/London] | zdt.toInstant().toString(): 2015-10-25T09:00:00Z

zdt.toString() 2015-10-25T15:00Z[Europe/London] | zdt.toInstant().toString(): 2015-10-25T15:00:00Z

zdt.toString() 2015-10-25T21:00Z[Europe/London] | zdt.toInstant().toString(): 2015-10-25T21:00:00Z

zdt.toString() 2015-10-26T03:00Z[Europe/London] | zdt.toInstant().toString(): 2015-10-26T03:00:00Z

zdt.toString() 2015-10-26T09:00Z[Europe/London] | zdt.toInstant().toString(): 2015-10-26T09:00:00Z

zdt.toString() 2015-10-26T15:00Z[Europe/London] | zdt.toInstant().toString(): 2015-10-26T15:00:00Z

For fun we swap, adding the six hours successively to Instant and convert the result to London time.

// Increment the `Instant`.
Instant instant = instantStart;
for ( int i = 1 ; i <= 10 ; i++ ) {
    System.out.println ( ">instant.toString() " + instant + " | instant.atZone(z).toString(): " + instant.atZone ( z ) + "\n");
    // Set up next loop.
    instant = instant.plus ( sixHours );
}

When run, we see the same values output.

instant.toString() 2015-10-24T09:00:00Z | instant.atZone(z).toString(): 2015-10-24T10:00+01:00[Europe/London]

instant.toString() 2015-10-24T15:00:00Z | instant.atZone(z).toString(): 2015-10-24T16:00+01:00[Europe/London]

instant.toString() 2015-10-24T21:00:00Z | instant.atZone(z).toString(): 2015-10-24T22:00+01:00[Europe/London]

instant.toString() 2015-10-25T03:00:00Z | instant.atZone(z).toString(): 2015-10-25T03:00Z[Europe/London]

instant.toString() 2015-10-25T09:00:00Z | instant.atZone(z).toString(): 2015-10-25T09:00Z[Europe/London]

instant.toString() 2015-10-25T15:00:00Z | instant.atZone(z).toString(): 2015-10-25T15:00Z[Europe/London]

instant.toString() 2015-10-25T21:00:00Z | instant.atZone(z).toString(): 2015-10-25T21:00Z[Europe/London]

instant.toString() 2015-10-26T03:00:00Z | instant.atZone(z).toString(): 2015-10-26T03:00Z[Europe/London]

instant.toString() 2015-10-26T09:00:00Z | instant.atZone(z).toString(): 2015-10-26T09:00Z[Europe/London]

instant.toString() 2015-10-26T15:00:00Z | instant.atZone(z).toString(): 2015-10-26T15:00Z[Europe/London]

See this code run live at IdeOne.com.


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Community
  • 1
  • 1
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154