# HG changeset patch # User okutsu # Date 1459242281 -32400 # Node ID ca66d3e0dfca5860b7f84fb8374766ec562eeee8 # Parent 517aee24e7b171751db3cb55d4e8552a47191926 8152077: (cal) Calendar.roll does not always roll the hours during daylight savings Reviewed-by: peytoia diff -r 517aee24e7b1 -r ca66d3e0dfca jdk/src/java.base/share/classes/java/util/GregorianCalendar.java --- a/jdk/src/java.base/share/classes/java/util/GregorianCalendar.java Tue Mar 29 10:47:03 2016 +0800 +++ b/jdk/src/java.base/share/classes/java/util/GregorianCalendar.java Tue Mar 29 18:04:41 2016 +0900 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1189,37 +1189,33 @@ case HOUR: case HOUR_OF_DAY: { - int unit = max + 1; // 12 or 24 hours - int h = internalGet(field); - int nh = (h + amount) % unit; - if (nh < 0) { - nh += unit; + int rolledValue = getRolledValue(internalGet(field), amount, min, max); + int hourOfDay = rolledValue; + if (field == HOUR && internalGet(AM_PM) == PM) { + hourOfDay += 12; } - time += ONE_HOUR * (nh - h); - // The day might have changed, which could happen if - // the daylight saving time transition brings it to - // the next day, although it's very unlikely. But we - // have to make sure not to change the larger fields. + // Create the current date/time value to perform wall-clock-based + // roll. CalendarDate d = calsys.getCalendarDate(time, getZone()); - if (internalGet(DAY_OF_MONTH) != d.getDayOfMonth()) { - d.setDate(internalGet(YEAR), - internalGet(MONTH) + 1, - internalGet(DAY_OF_MONTH)); - if (field == HOUR) { - assert (internalGet(AM_PM) == PM); - d.addHours(+12); // restore PM + d.setHours(hourOfDay); + time = calsys.getTime(d); + + // If we stay on the same wall-clock time, try the next or previous hour. + if (internalGet(HOUR_OF_DAY) == d.getHours()) { + hourOfDay = getRolledValue(rolledValue, amount > 0 ? +1 : -1, min, max); + if (field == HOUR && internalGet(AM_PM) == PM) { + hourOfDay += 12; } + d.setHours(hourOfDay); time = calsys.getTime(d); } - int hourOfDay = d.getHours(); - internalSet(field, hourOfDay % unit); - if (field == HOUR) { - internalSet(HOUR_OF_DAY, hourOfDay); - } else { - internalSet(AM_PM, hourOfDay / 12); - internalSet(HOUR, hourOfDay % 12); - } + // Get the new hourOfDay value which might have changed due to a DST transition. + hourOfDay = d.getHours(); + // Update the hour related fields + internalSet(HOUR_OF_DAY, hourOfDay); + internalSet(AM_PM, hourOfDay / 12); + internalSet(HOUR, hourOfDay % 12); // Time zone offset and/or daylight saving might have changed. int zoneOffset = d.getZoneOffset(); diff -r 517aee24e7b1 -r ca66d3e0dfca jdk/test/java/util/Calendar/Bug8152077.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/java/util/Calendar/Bug8152077.java Tue Mar 29 18:04:41 2016 +0900 @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8152077 + * @summary Make sure that roll() with HOUR/HOUR_OF_DAY works around standard/daylight + * time transitions + */ + +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; +import static java.util.Calendar.*; + +public class Bug8152077 { + private static final TimeZone LA = TimeZone.getTimeZone("America/Los_Angeles"); + private static final TimeZone BR = TimeZone.getTimeZone("America/Sao_Paulo"); + + private static final int[] ALLDAY_HOURS = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 + }; + private static final int[] AM_HOURS = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }; + private static final int[] PM_HOURS = { // in 24-hour clock + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 + }; + + private static int errors; + + public static void main(String[] args) { + TimeZone initialTz = TimeZone.getDefault(); + try { + testRoll(LA, new int[] { 2016, MARCH, 13 }, + new int[] { 0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 }); + testRoll(LA, new int[] { 2016, MARCH, 13 }, + new int[] { 0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11 }); + testRoll(LA, new int[] { 2016, MARCH, 13 }, PM_HOURS); + + testRoll(LA, new int[] { 2016, NOVEMBER, 6 }, ALLDAY_HOURS); + testRoll(LA, new int[] { 2016, NOVEMBER, 6 }, AM_HOURS); + testRoll(LA, new int[] { 2016, NOVEMBER, 6 }, PM_HOURS); + + testRoll(BR, new int[] { 2016, FEBRUARY, 21 }, ALLDAY_HOURS); + testRoll(BR, new int[] { 2016, FEBRUARY, 21 }, AM_HOURS); + testRoll(BR, new int[] { 2016, FEBRUARY, 21 }, PM_HOURS); + + testRoll(BR, new int[] { 2016, OCTOBER, 15 }, ALLDAY_HOURS); + testRoll(BR, new int[] { 2016, OCTOBER, 15 }, PM_HOURS); + testRoll(BR, new int[] { 2016, OCTOBER, 16 }, + new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 }); + testRoll(BR, new int[] { 2016, OCTOBER, 16 }, + new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }); + testRoll(BR, new int[] { 2016, OCTOBER, 16 }, PM_HOURS); + } finally { + TimeZone.setDefault(initialTz); + } + if (errors > 0) { + throw new RuntimeException("Test failed"); + } + } + + private static void testRoll(TimeZone tz, int[] params, int[] sequence) { + TimeZone.setDefault(tz); + for (int i = 0; i < sequence.length; i++) { + testRoll(+1, params, sequence, i); + testRoll(-1, params, sequence, i); + } + } + + // amount must be 1 or -1. + private static void testRoll(int amount, int[] params, int[] sequence, int startIndex) { + int year = params[0]; + int month = params[1]; + int dayOfMonth = params[2]; + int hourOfDay = sequence[startIndex]; + Calendar cal = new GregorianCalendar(year, month, dayOfMonth, + hourOfDay, 0, 0); + int ampm = cal.get(AM_PM); + + int length = sequence.length; + int count = length * 2; + int field = (length > 12) ? HOUR_OF_DAY : HOUR; + + System.out.printf("roll(%s, %2d) starting from %s%n", + (field == HOUR) ? "HOUR" : "HOUR_OF_DAY", amount, cal.getTime()); + for (int i = 0; i < count; i++) { + int index = (amount > 0) ? (startIndex + i + 1) % length + : Math.floorMod(startIndex - i - 1, length); + int expected = sequence[index]; + if (field == HOUR) { + expected %= 12; + } + cal.roll(field, amount); + int value = cal.get(field); + if (value != expected) { + System.out.println("Unexpected field value: got=" + value + + ", expected=" + expected); + errors++; + } + if (cal.get(DAY_OF_MONTH) != dayOfMonth) { + System.out.println("DAY_OF_MONTH changed: " + dayOfMonth + + " to " + cal.get(DAY_OF_MONTH)); + } + if (field == HOUR && cal.get(AM_PM) != ampm) { + System.out.println("AM_PM changed: " + ampm + " to " + cal.get(AM_PM)); + errors++; + } + } + } +}