8285844: TimeZone.getTimeZone(ZoneOffset) does not work for all ZoneOffsets and returns GMT unexpected
Reviewed-by: uschindler, scolebourne, joehw
This commit is contained in:
parent
dbd3737085
commit
b884db8f7c
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1996, 2022, 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
|
||||
@ -40,6 +40,7 @@ package java.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
import jdk.internal.util.StaticProperty;
|
||||
import sun.security.action.GetPropertyAction;
|
||||
@ -74,6 +75,7 @@ import sun.util.locale.provider.TimeZoneNameUtility;
|
||||
*
|
||||
* <blockquote><pre>
|
||||
* <a id="CustomID"><i>CustomID:</i></a>
|
||||
* {@code GMT} <i>Sign</i> <i>Hours</i> {@code :} <i>Minutes</i> {@code :} <i>Seconds</i>
|
||||
* {@code GMT} <i>Sign</i> <i>Hours</i> {@code :} <i>Minutes</i>
|
||||
* {@code GMT} <i>Sign</i> <i>Hours</i> <i>Minutes</i>
|
||||
* {@code GMT} <i>Sign</i> <i>Hours</i>
|
||||
@ -84,11 +86,13 @@ import sun.util.locale.provider.TimeZoneNameUtility;
|
||||
* <i>Digit</i> <i>Digit</i>
|
||||
* <i>Minutes:</i>
|
||||
* <i>Digit</i> <i>Digit</i>
|
||||
* <i>Seconds:</i>
|
||||
* <i>Digit</i> <i>Digit</i>
|
||||
* <i>Digit:</i> one of
|
||||
* {@code 0 1 2 3 4 5 6 7 8 9}
|
||||
* </pre></blockquote>
|
||||
*
|
||||
* <i>Hours</i> must be between 0 to 23 and <i>Minutes</i> must be
|
||||
* <i>Hours</i> must be between 0 to 23 and <i>Minutes</i>/<i>Seconds</i> must be
|
||||
* between 00 to 59. For example, "GMT+10" and "GMT+0010" mean ten
|
||||
* hours and ten minutes ahead of GMT, respectively.
|
||||
* <p>
|
||||
@ -102,17 +106,20 @@ import sun.util.locale.provider.TimeZoneNameUtility;
|
||||
* zone ID is normalized in the following syntax:
|
||||
* <blockquote><pre>
|
||||
* <a id="NormalizedCustomID"><i>NormalizedCustomID:</i></a>
|
||||
* {@code GMT} <i>Sign</i> <i>TwoDigitHours</i> {@code :} <i>Minutes</i>
|
||||
* {@code GMT} <i>Sign</i> <i>TwoDigitHours</i> {@code :} <i>Minutes</i> [<i>ColonSeconds</i>]
|
||||
* <i>Sign:</i> one of
|
||||
* {@code + -}
|
||||
* <i>TwoDigitHours:</i>
|
||||
* <i>Digit</i> <i>Digit</i>
|
||||
* <i>Minutes:</i>
|
||||
* <i>Digit</i> <i>Digit</i>
|
||||
* <i>ColonSeconds:</i>
|
||||
* {@code :} <i>Digit</i> <i>Digit</i>
|
||||
* <i>Digit:</i> one of
|
||||
* {@code 0 1 2 3 4 5 6 7 8 9}
|
||||
* </pre></blockquote>
|
||||
* For example, TimeZone.getTimeZone("GMT-8").getID() returns "GMT-08:00".
|
||||
* <i>ColonSeconds</i> part only appears if the seconds value is non-zero.
|
||||
*
|
||||
* <h2>Three-letter time zone IDs</h2>
|
||||
*
|
||||
@ -529,11 +536,11 @@ public abstract class TimeZone implements Serializable, Cloneable {
|
||||
*/
|
||||
public static TimeZone getTimeZone(ZoneId zoneId) {
|
||||
String tzid = zoneId.getId(); // throws an NPE if null
|
||||
char c = tzid.charAt(0);
|
||||
if (c == '+' || c == '-') {
|
||||
tzid = "GMT" + tzid;
|
||||
} else if (c == 'Z' && tzid.length() == 1) {
|
||||
tzid = "UTC";
|
||||
if (zoneId instanceof ZoneOffset zo) {
|
||||
var totalMillis = zo.getTotalSeconds() * 1_000;
|
||||
return new ZoneInfo(totalMillis == 0 ? "UTC" : GMT_ID + tzid, totalMillis);
|
||||
} else if (tzid.startsWith("UT") && !tzid.equals("UTC")) {
|
||||
tzid = tzid.replaceFirst("(UTC|UT)(.*)", "GMT$2");
|
||||
}
|
||||
return getTimeZone(tzid, true);
|
||||
}
|
||||
@ -823,19 +830,24 @@ public abstract class TimeZone implements Serializable, Cloneable {
|
||||
}
|
||||
|
||||
int hours = 0;
|
||||
int minutes = 0;
|
||||
int num = 0;
|
||||
int countDelim = 0;
|
||||
int len = 0;
|
||||
while (index < length) {
|
||||
c = id.charAt(index++);
|
||||
if (c == ':') {
|
||||
if (countDelim > 0) {
|
||||
if (countDelim > 1) {
|
||||
return null;
|
||||
}
|
||||
if (len > 2) {
|
||||
if (len == 0 || len > 2) {
|
||||
return null;
|
||||
}
|
||||
hours = num;
|
||||
if (countDelim == 0) {
|
||||
hours = num;
|
||||
} else if (countDelim == 1){
|
||||
minutes = num;
|
||||
}
|
||||
countDelim++;
|
||||
num = 0;
|
||||
len = 0;
|
||||
@ -853,20 +865,31 @@ public abstract class TimeZone implements Serializable, Cloneable {
|
||||
if (countDelim == 0) {
|
||||
if (len <= 2) {
|
||||
hours = num;
|
||||
minutes = 0;
|
||||
num = 0;
|
||||
} else if (len <= 4) {
|
||||
hours = num / 100;
|
||||
minutes = num % 100;
|
||||
num = 0;
|
||||
} else {
|
||||
hours = num / 100;
|
||||
num %= 100;
|
||||
return null;
|
||||
}
|
||||
} else if (countDelim == 1){
|
||||
if (len == 2) {
|
||||
minutes = num;
|
||||
num = 0;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
if (len != 2) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (hours > 23 || num > 59) {
|
||||
if (hours > 23 || minutes > 59 || num > 59) {
|
||||
return null;
|
||||
}
|
||||
int gmtOffset = (hours * 60 + num) * 60 * 1000;
|
||||
int gmtOffset = (hours * 3_600 + minutes * 60 + num) * 1_000;
|
||||
|
||||
if (gmtOffset == 0) {
|
||||
zi = ZoneInfoFile.getZoneInfo(GMT_ID);
|
||||
|
@ -178,15 +178,16 @@ public final class ZoneInfoFile {
|
||||
|
||||
public static String toCustomID(int gmtOffset) {
|
||||
char sign;
|
||||
int offset = gmtOffset / 60000;
|
||||
int offset = gmtOffset / 1_000;
|
||||
if (offset >= 0) {
|
||||
sign = '+';
|
||||
} else {
|
||||
sign = '-';
|
||||
offset = -offset;
|
||||
}
|
||||
int hh = offset / 60;
|
||||
int mm = offset % 60;
|
||||
int hh = offset / 3_600;
|
||||
int mm = (offset % 3_600) / 60;
|
||||
int ss = offset % 60;
|
||||
|
||||
char[] buf = new char[] { 'G', 'M', 'T', sign, '0', '0', ':', '0', '0' };
|
||||
if (hh >= 10) {
|
||||
@ -197,7 +198,13 @@ public final class ZoneInfoFile {
|
||||
buf[7] += (char)(mm / 10);
|
||||
buf[8] += (char)(mm % 10);
|
||||
}
|
||||
return new String(buf);
|
||||
var id = new String(buf);
|
||||
if (ss != 0) {
|
||||
buf[7] = (char)('0' + ss / 10);
|
||||
buf[8] = (char)('0' + ss % 10);
|
||||
id += new String(buf, 6, 3);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2022, 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
|
||||
@ -25,10 +25,11 @@
|
||||
* @test
|
||||
* @bug 4028006 4044013 4096694 4107276 4107570 4112869 4130885 7039469 7126465 7158483
|
||||
* 8008577 8077685 8098547 8133321 8138716 8148446 8151876 8159684 8166875 8181157
|
||||
* 8228469 8274407
|
||||
* 8228469 8274407 8285844
|
||||
* @modules java.base/sun.util.resources
|
||||
* @library /java/text/testlib
|
||||
* @summary test TimeZone
|
||||
* @run main TimeZoneTest -verbose
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
@ -220,12 +221,14 @@ public class TimeZoneTest extends IntlTest
|
||||
}
|
||||
}
|
||||
|
||||
static final String formatMinutes(int min) {
|
||||
static final String formatSeconds(int sec) {
|
||||
char sign = '+';
|
||||
if (min < 0) { sign = '-'; min = -min; }
|
||||
int h = min/60;
|
||||
min = min%60;
|
||||
return "" + sign + h + ":" + ((min<10) ? "0" : "") + min;
|
||||
if (sec < 0) { sign = '-'; sec = -sec; }
|
||||
int h = sec / 3_600;
|
||||
int m = sec % 3_600 / 60;
|
||||
sec = sec % 60;
|
||||
return "" + sign + h + ":" + ((m<10) ? "0" : "") + m +
|
||||
(sec > 0 ? ":" + ((sec < 10) ? "0" : "") + sec : "");
|
||||
}
|
||||
/**
|
||||
* As part of the VM fix (see CCC approved RFE 4028006, bug
|
||||
@ -240,21 +243,28 @@ public class TimeZoneTest extends IntlTest
|
||||
*/
|
||||
public void TestCustomParse() throws Exception {
|
||||
Object[] DATA = {
|
||||
// ID Expected offset in minutes
|
||||
"GMT", null,
|
||||
"GMT+0", new Integer(0),
|
||||
"GMT+1", new Integer(60),
|
||||
"GMT-0030", new Integer(-30),
|
||||
"GMT+15:99", null,
|
||||
"GMT+", null,
|
||||
"GMT-", null,
|
||||
"GMT+0:", null,
|
||||
"GMT-:", null,
|
||||
"GMT+0010", new Integer(10), // Interpret this as 00:10
|
||||
"GMT-10", new Integer(-10*60),
|
||||
"GMT+30", null,
|
||||
"GMT-3:30", new Integer(-(3*60+30)),
|
||||
"GMT-230", new Integer(-(2*60+30)),
|
||||
// ID Expected offset in seconds
|
||||
"GMT", null,
|
||||
"GMT+0", 0,
|
||||
"GMT+1", 60 * 60,
|
||||
"GMT-0030", -30 * 60,
|
||||
"GMT+15:99", null,
|
||||
"GMT+", null,
|
||||
"GMT-", null,
|
||||
"GMT+0:", null,
|
||||
"GMT-:", null,
|
||||
"GMT+0010", 10 * 60, // Interpret this as 00:10
|
||||
"GMT-10", -10 * 60 * 60,
|
||||
"GMT+30", null,
|
||||
"GMT-3:30", -(3 * 60 + 30) * 60,
|
||||
"GMT-230", -(2 * 60 + 30) * 60,
|
||||
"GMT+00:00:01", 1,
|
||||
"GMT-00:00:01", -1,
|
||||
"GMT+00000", null,
|
||||
"GMT+00:00:01:", null,
|
||||
"GMT+00:00:012", null,
|
||||
"GMT+00:00:0", null,
|
||||
"GMT+00:00:", null,
|
||||
};
|
||||
for (int i=0; i<DATA.length; i+=2) {
|
||||
String id = (String)DATA[i];
|
||||
@ -266,13 +276,13 @@ public class TimeZoneTest extends IntlTest
|
||||
// returns GMT -- a dubious practice, but required for
|
||||
// backward compatibility.
|
||||
if (exp != null) {
|
||||
throw new Exception("Expected offset of " + formatMinutes(exp.intValue()) +
|
||||
throw new Exception("Expected offset of " + formatSeconds(exp.intValue()) +
|
||||
" for " + id + ", got parse failure");
|
||||
}
|
||||
}
|
||||
else {
|
||||
int ioffset = zone.getRawOffset()/60000;
|
||||
String offset = formatMinutes(ioffset);
|
||||
int ioffset = zone.getRawOffset() / 1_000;
|
||||
String offset = formatSeconds(ioffset);
|
||||
logln(id + " -> " + zone.getID() + " GMT" + offset);
|
||||
if (exp == null) {
|
||||
throw new Exception("Expected parse failure for " + id +
|
||||
@ -280,7 +290,7 @@ public class TimeZoneTest extends IntlTest
|
||||
", id " + zone.getID());
|
||||
}
|
||||
else if (ioffset != exp.intValue()) {
|
||||
throw new Exception("Expected offset of " + formatMinutes(exp.intValue()) +
|
||||
throw new Exception("Expected offset of " + formatSeconds(exp.intValue()) +
|
||||
", id Custom, for " + id +
|
||||
", got offset of " + offset +
|
||||
", id " + zone.getID());
|
||||
|
70
test/jdk/java/util/TimeZone/ZoneIdRoundTripTest.java
Normal file
70
test/jdk/java/util/TimeZone/ZoneIdRoundTripTest.java
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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.
|
||||
*/
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8285844
|
||||
* @summary Checks round-trips between TimeZone and ZoneId are consistent
|
||||
* @run testng ZoneIdRoundTripTest
|
||||
*/
|
||||
@Test
|
||||
public class ZoneIdRoundTripTest {
|
||||
|
||||
@DataProvider
|
||||
private Object[][] testZoneIds() {
|
||||
return new Object[][] {
|
||||
{ZoneId.of("Z"), 0},
|
||||
{ZoneId.of("UT"), 0},
|
||||
{ZoneId.of("UTC"), 0},
|
||||
{ZoneId.of("GMT"), 0},
|
||||
{ZoneId.of("+00:01"), 60_000},
|
||||
{ZoneId.of("-00:01"), -60_000},
|
||||
{ZoneId.of("+00:00:01"), 1_000},
|
||||
{ZoneId.of("-00:00:01"), -1_000},
|
||||
{ZoneId.of("UT+00:00:01"), 1_000},
|
||||
{ZoneId.of("UT-00:00:01"), -1_000},
|
||||
{ZoneId.of("UTC+00:00:01"), 1_000},
|
||||
{ZoneId.of("UTC-00:00:01"), -1_000},
|
||||
{ZoneId.of("GMT+00:00:01"), 1_000},
|
||||
{ZoneId.of("GMT-00:00:01"), -1_000},
|
||||
{ZoneOffset.of("+00:00:01"), 1_000},
|
||||
{ZoneOffset.of("-00:00:01"), -1_000},
|
||||
};
|
||||
}
|
||||
|
||||
@Test(dataProvider="testZoneIds")
|
||||
public void test_ZoneIdRoundTrip(ZoneId zid, int offset) {
|
||||
var tz = TimeZone.getTimeZone(zid);
|
||||
assertEquals(tz.getRawOffset(), offset);
|
||||
assertEquals(tz.toZoneId().normalized(), zid.normalized());
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user