mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 06:45:07 +02:00
8177819: DateTimeFormatterBuilder zone parsing should recognise DST
8277049: ZonedDateTime parse in Fall DST transition fails to retain the correct zonename. Reviewed-by: joehw, scolebourne
This commit is contained in:
parent
9b3e672059
commit
a363b7b921
5 changed files with 121 additions and 130 deletions
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -81,6 +81,7 @@ import java.time.Period;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.time.chrono.ChronoLocalDateTime;
|
import java.time.chrono.ChronoLocalDateTime;
|
||||||
|
import java.time.chrono.ChronoZonedDateTime;
|
||||||
import java.time.chrono.Chronology;
|
import java.time.chrono.Chronology;
|
||||||
import java.time.chrono.IsoChronology;
|
import java.time.chrono.IsoChronology;
|
||||||
import java.time.format.DateTimeFormatterBuilder.CompositePrinterParser;
|
import java.time.format.DateTimeFormatterBuilder.CompositePrinterParser;
|
||||||
|
@ -373,15 +374,15 @@ import sun.util.locale.provider.TimeZoneNameUtility;
|
||||||
* letters throws {@code IllegalArgumentException}.
|
* letters throws {@code IllegalArgumentException}.
|
||||||
* <p>
|
* <p>
|
||||||
* <b>Zone names</b>: This outputs the display name of the time-zone ID. If the
|
* <b>Zone names</b>: This outputs the display name of the time-zone ID. If the
|
||||||
* pattern letter is 'z' the output is the daylight savings aware zone name.
|
* pattern letter is 'z' the output is the daylight saving aware zone name.
|
||||||
* If there is insufficient information to determine whether DST applies,
|
* If there is insufficient information to determine whether DST applies,
|
||||||
* the name ignoring daylight savings time will be used.
|
* the name ignoring daylight saving time will be used.
|
||||||
* If the count of letters is one, two or three, then the short name is output.
|
* If the count of letters is one, two or three, then the short name is output.
|
||||||
* If the count of letters is four, then the full name is output.
|
* If the count of letters is four, then the full name is output.
|
||||||
* Five or more letters throws {@code IllegalArgumentException}.
|
* Five or more letters throws {@code IllegalArgumentException}.
|
||||||
* <p>
|
* <p>
|
||||||
* If the pattern letter is 'v' the output provides the zone name ignoring
|
* If the pattern letter is 'v' the output provides the zone name ignoring
|
||||||
* daylight savings time. If the count of letters is one, then the short name is output.
|
* daylight saving time. If the count of letters is one, then the short name is output.
|
||||||
* If the count of letters is four, then the full name is output.
|
* If the count of letters is four, then the full name is output.
|
||||||
* Two, three and five or more letters throw {@code IllegalArgumentException}.
|
* Two, three and five or more letters throw {@code IllegalArgumentException}.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -502,7 +503,10 @@ import sun.util.locale.provider.TimeZoneNameUtility;
|
||||||
* {@code LocalDateTime} to form the instant, with any zone ignored.
|
* {@code LocalDateTime} to form the instant, with any zone ignored.
|
||||||
* If a {@code ZoneId} was parsed without an offset then the zone will be
|
* If a {@code ZoneId} was parsed without an offset then the zone will be
|
||||||
* combined with the {@code LocalDateTime} to form the instant using the rules
|
* combined with the {@code LocalDateTime} to form the instant using the rules
|
||||||
* of {@link ChronoLocalDateTime#atZone(ZoneId)}.
|
* of {@link ChronoLocalDateTime#atZone(ZoneId)}. If the {@code ZoneId} was
|
||||||
|
* parsed from a zone name that indicates whether daylight saving time is in
|
||||||
|
* operation or not, then that fact will be used to select the correct offset
|
||||||
|
* at the local time-line overlap.
|
||||||
* </ol>
|
* </ol>
|
||||||
*
|
*
|
||||||
* @implSpec
|
* @implSpec
|
||||||
|
|
|
@ -4324,9 +4324,10 @@ public final class DateTimeFormatterBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final int STD = 0;
|
static final int UNDEFINED = -1;
|
||||||
private static final int DST = 1;
|
static final int STD = 0;
|
||||||
private static final int GENERIC = 2;
|
static final int DST = 1;
|
||||||
|
static final int GENERIC = 2;
|
||||||
private static final Map<String, SoftReference<Map<Locale, String[]>>> cache =
|
private static final Map<String, SoftReference<Map<Locale, String[]>>> cache =
|
||||||
new ConcurrentHashMap<>();
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@ -4433,11 +4434,11 @@ public final class DateTimeFormatterBuilder {
|
||||||
nonRegionIds.add(zid);
|
nonRegionIds.add(zid);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tree.add(zid, zid); // don't convert zid -> metazone
|
tree.add(zid, zid, UNDEFINED); // don't convert zid -> metazone
|
||||||
zid = ZoneName.toZid(zid, locale);
|
zid = ZoneName.toZid(zid, locale);
|
||||||
int i = textStyle == TextStyle.FULL ? 1 : 2;
|
int i = textStyle == TextStyle.FULL ? 1 : 2;
|
||||||
for (; i < names.length; i += 2) {
|
for (; i < names.length; i += 2) {
|
||||||
tree.add(names[i], zid);
|
tree.add(names[i], zid, (i - 1) / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4450,7 +4451,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
int i = textStyle == TextStyle.FULL ? 1 : 2;
|
int i = textStyle == TextStyle.FULL ? 1 : 2;
|
||||||
for (; i < cidNames.length; i += 2) {
|
for (; i < cidNames.length; i += 2) {
|
||||||
if (cidNames[i] != null && !cidNames[i].isEmpty()) {
|
if (cidNames[i] != null && !cidNames[i].isEmpty()) {
|
||||||
t.add(cidNames[i], cid);
|
t.add(cidNames[i], cid, (i - 1) / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -4465,7 +4466,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
}
|
}
|
||||||
int i = textStyle == TextStyle.FULL ? 1 : 2;
|
int i = textStyle == TextStyle.FULL ? 1 : 2;
|
||||||
for (; i < names.length; i += 2) {
|
for (; i < names.length; i += 2) {
|
||||||
tree.add(names[i], zid);
|
tree.add(names[i], zid, (i - 1) / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4571,15 +4572,16 @@ public final class DateTimeFormatterBuilder {
|
||||||
// parse
|
// parse
|
||||||
PrefixTree tree = getTree(context);
|
PrefixTree tree = getTree(context);
|
||||||
ParsePosition ppos = new ParsePosition(position);
|
ParsePosition ppos = new ParsePosition(position);
|
||||||
String parsedZoneId = tree.match(text, ppos);
|
PrefixTree parsedZoneId = tree.match(text, ppos);
|
||||||
if (parsedZoneId == null) {
|
if (parsedZoneId.value == null) {
|
||||||
if (context.charEquals(nextChar, 'Z')) {
|
if (context.charEquals(nextChar, 'Z')) {
|
||||||
context.setParsed(ZoneOffset.UTC);
|
context.setParsed(ZoneOffset.UTC);
|
||||||
return position + 1;
|
return position + 1;
|
||||||
}
|
}
|
||||||
return ~position;
|
return ~position;
|
||||||
}
|
}
|
||||||
context.setParsed(ZoneId.of(parsedZoneId));
|
context.setParsed(ZoneId.of(parsedZoneId.value));
|
||||||
|
context.setParsedZoneNameType(parsedZoneId.type);
|
||||||
return ppos.getIndex();
|
return ppos.getIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4641,14 +4643,16 @@ public final class DateTimeFormatterBuilder {
|
||||||
static class PrefixTree {
|
static class PrefixTree {
|
||||||
protected String key;
|
protected String key;
|
||||||
protected String value;
|
protected String value;
|
||||||
|
protected int type;
|
||||||
protected char c0; // performance optimization to avoid the
|
protected char c0; // performance optimization to avoid the
|
||||||
// boundary check cost of key.charat(0)
|
// boundary check cost of key.charat(0)
|
||||||
protected PrefixTree child;
|
protected PrefixTree child;
|
||||||
protected PrefixTree sibling;
|
protected PrefixTree sibling;
|
||||||
|
|
||||||
private PrefixTree(String k, String v, PrefixTree child) {
|
private PrefixTree(String k, String v, int type, PrefixTree child) {
|
||||||
this.key = k;
|
this.key = k;
|
||||||
this.value = v;
|
this.value = v;
|
||||||
|
this.type = type;
|
||||||
this.child = child;
|
this.child = child;
|
||||||
if (k.isEmpty()) {
|
if (k.isEmpty()) {
|
||||||
c0 = 0xffff;
|
c0 = 0xffff;
|
||||||
|
@ -4664,13 +4668,10 @@ public final class DateTimeFormatterBuilder {
|
||||||
* @return the tree, not null
|
* @return the tree, not null
|
||||||
*/
|
*/
|
||||||
public static PrefixTree newTree(DateTimeParseContext context) {
|
public static PrefixTree newTree(DateTimeParseContext context) {
|
||||||
//if (!context.isStrict()) {
|
|
||||||
// return new LENIENT("", null, null);
|
|
||||||
//}
|
|
||||||
if (context.isCaseSensitive()) {
|
if (context.isCaseSensitive()) {
|
||||||
return new PrefixTree("", null, null);
|
return new PrefixTree("", null, ZoneTextPrinterParser.UNDEFINED, null);
|
||||||
}
|
}
|
||||||
return new CI("", null, null);
|
return new CI("", null, ZoneTextPrinterParser.UNDEFINED, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4683,7 +4684,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
public static PrefixTree newTree(Set<String> keys, DateTimeParseContext context) {
|
public static PrefixTree newTree(Set<String> keys, DateTimeParseContext context) {
|
||||||
PrefixTree tree = newTree(context);
|
PrefixTree tree = newTree(context);
|
||||||
for (String k : keys) {
|
for (String k : keys) {
|
||||||
tree.add0(k, k);
|
tree.add0(k, k, ZoneTextPrinterParser.UNDEFINED);
|
||||||
}
|
}
|
||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
|
@ -4692,7 +4693,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
* Clone a copy of this tree
|
* Clone a copy of this tree
|
||||||
*/
|
*/
|
||||||
public PrefixTree copyTree() {
|
public PrefixTree copyTree() {
|
||||||
PrefixTree copy = new PrefixTree(key, value, null);
|
PrefixTree copy = new PrefixTree(key, value, type, null);
|
||||||
if (child != null) {
|
if (child != null) {
|
||||||
copy.child = child.copyTree();
|
copy.child = child.copyTree();
|
||||||
}
|
}
|
||||||
|
@ -4710,11 +4711,11 @@ public final class DateTimeFormatterBuilder {
|
||||||
* @param v the value, not null
|
* @param v the value, not null
|
||||||
* @return true if the pair is added successfully
|
* @return true if the pair is added successfully
|
||||||
*/
|
*/
|
||||||
public boolean add(String k, String v) {
|
public boolean add(String k, String v, int t) {
|
||||||
return add0(k, v);
|
return add0(k, v, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean add0(String k, String v) {
|
private boolean add0(String k, String v, int t) {
|
||||||
k = toKey(k);
|
k = toKey(k);
|
||||||
int prefixLen = prefixLength(k);
|
int prefixLen = prefixLength(k);
|
||||||
if (prefixLen == key.length()) {
|
if (prefixLen == key.length()) {
|
||||||
|
@ -4723,12 +4724,12 @@ public final class DateTimeFormatterBuilder {
|
||||||
PrefixTree c = child;
|
PrefixTree c = child;
|
||||||
while (c != null) {
|
while (c != null) {
|
||||||
if (isEqual(c.c0, subKey.charAt(0))) {
|
if (isEqual(c.c0, subKey.charAt(0))) {
|
||||||
return c.add0(subKey, v);
|
return c.add0(subKey, v, t);
|
||||||
}
|
}
|
||||||
c = c.sibling;
|
c = c.sibling;
|
||||||
}
|
}
|
||||||
// add the node as the child of the current node
|
// add the node as the child of the current node
|
||||||
c = newNode(subKey, v, null);
|
c = newNode(subKey, v, t, null);
|
||||||
c.sibling = child;
|
c.sibling = child;
|
||||||
child = c;
|
child = c;
|
||||||
return true;
|
return true;
|
||||||
|
@ -4738,18 +4739,20 @@ public final class DateTimeFormatterBuilder {
|
||||||
// return false;
|
// return false;
|
||||||
//}
|
//}
|
||||||
value = v;
|
value = v;
|
||||||
|
type = t;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// split the existing node
|
// split the existing node
|
||||||
PrefixTree n1 = newNode(key.substring(prefixLen), value, child);
|
PrefixTree n1 = newNode(key.substring(prefixLen), value, type, child);
|
||||||
key = k.substring(0, prefixLen);
|
key = k.substring(0, prefixLen);
|
||||||
child = n1;
|
child = n1;
|
||||||
if (prefixLen < k.length()) {
|
if (prefixLen < k.length()) {
|
||||||
PrefixTree n2 = newNode(k.substring(prefixLen), v, null);
|
PrefixTree n2 = newNode(k.substring(prefixLen), v, t, null);
|
||||||
child.sibling = n2;
|
child.sibling = n2;
|
||||||
value = null;
|
value = null;
|
||||||
} else {
|
} else {
|
||||||
value = v;
|
value = v;
|
||||||
|
type = t;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -4760,9 +4763,9 @@ public final class DateTimeFormatterBuilder {
|
||||||
* @param text the input text to parse, not null
|
* @param text the input text to parse, not null
|
||||||
* @param off the offset position to start parsing at
|
* @param off the offset position to start parsing at
|
||||||
* @param end the end position to stop parsing
|
* @param end the end position to stop parsing
|
||||||
* @return the resulting string, or null if no match found.
|
* @return the resulting tree, or null if no match found.
|
||||||
*/
|
*/
|
||||||
public String match(CharSequence text, int off, int end) {
|
public PrefixTree match(CharSequence text, int off, int end) {
|
||||||
if (!prefixOf(text, off, end)){
|
if (!prefixOf(text, off, end)){
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -4770,16 +4773,16 @@ public final class DateTimeFormatterBuilder {
|
||||||
PrefixTree c = child;
|
PrefixTree c = child;
|
||||||
do {
|
do {
|
||||||
if (isEqual(c.c0, text.charAt(off))) {
|
if (isEqual(c.c0, text.charAt(off))) {
|
||||||
String found = c.match(text, off, end);
|
PrefixTree found = c.match(text, off, end);
|
||||||
if (found != null) {
|
if (found != null) {
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
return value;
|
return this;
|
||||||
}
|
}
|
||||||
c = c.sibling;
|
c = c.sibling;
|
||||||
} while (c != null);
|
} while (c != null);
|
||||||
}
|
}
|
||||||
return value;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4789,9 +4792,9 @@ public final class DateTimeFormatterBuilder {
|
||||||
* @param pos the position to start parsing at, from 0 to the text
|
* @param pos the position to start parsing at, from 0 to the text
|
||||||
* length. Upon return, position will be updated to the new parse
|
* length. Upon return, position will be updated to the new parse
|
||||||
* position, or unchanged, if no match found.
|
* position, or unchanged, if no match found.
|
||||||
* @return the resulting string, or null if no match found.
|
* @return the resulting tree, or null if no match found.
|
||||||
*/
|
*/
|
||||||
public String match(CharSequence text, ParsePosition pos) {
|
public PrefixTree match(CharSequence text, ParsePosition pos) {
|
||||||
int off = pos.getIndex();
|
int off = pos.getIndex();
|
||||||
int end = text.length();
|
int end = text.length();
|
||||||
if (!prefixOf(text, off, end)){
|
if (!prefixOf(text, off, end)){
|
||||||
|
@ -4803,7 +4806,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
do {
|
do {
|
||||||
if (isEqual(c.c0, text.charAt(off))) {
|
if (isEqual(c.c0, text.charAt(off))) {
|
||||||
pos.setIndex(off);
|
pos.setIndex(off);
|
||||||
String found = c.match(text, pos);
|
PrefixTree found = c.match(text, pos);
|
||||||
if (found != null) {
|
if (found != null) {
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
@ -4813,15 +4816,15 @@ public final class DateTimeFormatterBuilder {
|
||||||
} while (c != null);
|
} while (c != null);
|
||||||
}
|
}
|
||||||
pos.setIndex(off);
|
pos.setIndex(off);
|
||||||
return value;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String toKey(String k) {
|
protected String toKey(String k) {
|
||||||
return k;
|
return k;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PrefixTree newNode(String k, String v, PrefixTree child) {
|
protected PrefixTree newNode(String k, String v, int t, PrefixTree child) {
|
||||||
return new PrefixTree(k, v, child);
|
return new PrefixTree(k, v, t, child);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isEqual(char c1, char c2) {
|
protected boolean isEqual(char c1, char c2) {
|
||||||
|
@ -4861,13 +4864,13 @@ public final class DateTimeFormatterBuilder {
|
||||||
*/
|
*/
|
||||||
private static class CI extends PrefixTree {
|
private static class CI extends PrefixTree {
|
||||||
|
|
||||||
private CI(String k, String v, PrefixTree child) {
|
private CI(String k, String v, int t, PrefixTree child) {
|
||||||
super(k, v, child);
|
super(k, v, t, child);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CI newNode(String k, String v, PrefixTree child) {
|
protected CI newNode(String k, String v, int t, PrefixTree child) {
|
||||||
return new CI(k, v, child);
|
return new CI(k, v, t, child);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -4890,86 +4893,6 @@ public final class DateTimeFormatterBuilder {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Lenient prefix tree. Case insensitive and ignores characters
|
|
||||||
* like space, underscore and slash.
|
|
||||||
*/
|
|
||||||
private static class LENIENT extends CI {
|
|
||||||
|
|
||||||
private LENIENT(String k, String v, PrefixTree child) {
|
|
||||||
super(k, v, child);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected CI newNode(String k, String v, PrefixTree child) {
|
|
||||||
return new LENIENT(k, v, child);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isLenientChar(char c) {
|
|
||||||
return c == ' ' || c == '_' || c == '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String toKey(String k) {
|
|
||||||
for (int i = 0; i < k.length(); i++) {
|
|
||||||
if (isLenientChar(k.charAt(i))) {
|
|
||||||
StringBuilder sb = new StringBuilder(k.length());
|
|
||||||
sb.append(k, 0, i);
|
|
||||||
i++;
|
|
||||||
while (i < k.length()) {
|
|
||||||
if (!isLenientChar(k.charAt(i))) {
|
|
||||||
sb.append(k.charAt(i));
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return k;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String match(CharSequence text, ParsePosition pos) {
|
|
||||||
int off = pos.getIndex();
|
|
||||||
int end = text.length();
|
|
||||||
int len = key.length();
|
|
||||||
int koff = 0;
|
|
||||||
while (koff < len && off < end) {
|
|
||||||
if (isLenientChar(text.charAt(off))) {
|
|
||||||
off++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!isEqual(key.charAt(koff++), text.charAt(off++))) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (koff != len) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (child != null && off != end) {
|
|
||||||
int off0 = off;
|
|
||||||
while (off0 < end && isLenientChar(text.charAt(off0))) {
|
|
||||||
off0++;
|
|
||||||
}
|
|
||||||
if (off0 < end) {
|
|
||||||
PrefixTree c = child;
|
|
||||||
do {
|
|
||||||
if (isEqual(c.c0, text.charAt(off0))) {
|
|
||||||
pos.setIndex(off0);
|
|
||||||
String found = c.match(text, pos);
|
|
||||||
if (found != null) {
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
c = c.sibling;
|
|
||||||
} while (c != null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pos.setIndex(off);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
//-----------------------------------------------------------------------
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -417,6 +417,24 @@ final class DateTimeParseContext {
|
||||||
currentParsed().zone = zone;
|
currentParsed().zone = zone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the parsed zone name type.
|
||||||
|
* <p>
|
||||||
|
* This stores the zone name type that has been parsed.
|
||||||
|
* The parsed type should either be;
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link DateTimeFormatterBuilder.ZoneTextPrinterParser#UNDEFINED}</li>
|
||||||
|
* <li>{@link DateTimeFormatterBuilder.ZoneTextPrinterParser#STD}</li>
|
||||||
|
* <li>{@link DateTimeFormatterBuilder.ZoneTextPrinterParser#DST}</li>
|
||||||
|
* <li>{@link DateTimeFormatterBuilder.ZoneTextPrinterParser#GENERIC}</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param type the parsed zone name type
|
||||||
|
*/
|
||||||
|
void setParsedZoneNameType(int type) {
|
||||||
|
currentParsed().zoneNameType = type;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the parsed leap second.
|
* Stores the parsed leap second.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -87,6 +87,7 @@ import java.time.LocalTime;
|
||||||
import java.time.Period;
|
import java.time.Period;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
import java.time.chrono.ChronoLocalDate;
|
import java.time.chrono.ChronoLocalDate;
|
||||||
import java.time.chrono.ChronoLocalDateTime;
|
import java.time.chrono.ChronoLocalDateTime;
|
||||||
import java.time.chrono.ChronoZonedDateTime;
|
import java.time.chrono.ChronoZonedDateTime;
|
||||||
|
@ -132,6 +133,10 @@ final class Parsed implements TemporalAccessor {
|
||||||
* The parsed zone.
|
* The parsed zone.
|
||||||
*/
|
*/
|
||||||
ZoneId zone;
|
ZoneId zone;
|
||||||
|
/**
|
||||||
|
* The parsed zone name type.
|
||||||
|
*/
|
||||||
|
int zoneNameType = DateTimeFormatterBuilder.ZoneTextPrinterParser.UNDEFINED;
|
||||||
/**
|
/**
|
||||||
* The parsed chronology.
|
* The parsed chronology.
|
||||||
*/
|
*/
|
||||||
|
@ -175,6 +180,7 @@ final class Parsed implements TemporalAccessor {
|
||||||
Parsed cloned = new Parsed();
|
Parsed cloned = new Parsed();
|
||||||
cloned.fieldValues.putAll(this.fieldValues);
|
cloned.fieldValues.putAll(this.fieldValues);
|
||||||
cloned.zone = this.zone;
|
cloned.zone = this.zone;
|
||||||
|
cloned.zoneNameType = this.zoneNameType;
|
||||||
cloned.chrono = this.chrono;
|
cloned.chrono = this.chrono;
|
||||||
cloned.leapSecond = this.leapSecond;
|
cloned.leapSecond = this.leapSecond;
|
||||||
cloned.dayPeriod = this.dayPeriod;
|
cloned.dayPeriod = this.dayPeriod;
|
||||||
|
@ -652,8 +658,12 @@ final class Parsed implements TemporalAccessor {
|
||||||
fieldValues.put(INSTANT_SECONDS, instant);
|
fieldValues.put(INSTANT_SECONDS, instant);
|
||||||
} else {
|
} else {
|
||||||
if (zone != null) {
|
if (zone != null) {
|
||||||
long instant = date.atTime(time).atZone(zone).toEpochSecond();
|
var czdt = date.atTime(time).atZone(zone);
|
||||||
fieldValues.put(INSTANT_SECONDS, instant);
|
if (zoneNameType == DateTimeFormatterBuilder.ZoneTextPrinterParser.STD ||
|
||||||
|
zoneNameType == DateTimeFormatterBuilder.ZoneTextPrinterParser.GENERIC) {
|
||||||
|
czdt = czdt.withLaterOffsetAtOverlap();
|
||||||
|
}
|
||||||
|
fieldValues.put(INSTANT_SECONDS, czdt.toEpochSecond());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -718,6 +728,7 @@ final class Parsed implements TemporalAccessor {
|
||||||
buf.append(fieldValues).append(',').append(chrono);
|
buf.append(fieldValues).append(',').append(chrono);
|
||||||
if (zone != null) {
|
if (zone != null) {
|
||||||
buf.append(',').append(zone);
|
buf.append(',').append(zone);
|
||||||
|
buf.append(',').append(zoneNameType);
|
||||||
}
|
}
|
||||||
if (date != null || time != null) {
|
if (date != null || time != null) {
|
||||||
buf.append(" resolved to ");
|
buf.append(" resolved to ");
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -24,10 +24,12 @@
|
||||||
package test.java.time.format;
|
package test.java.time.format;
|
||||||
|
|
||||||
import static org.testng.Assert.assertEquals;
|
import static org.testng.Assert.assertEquals;
|
||||||
|
import static org.testng.Assert.fail;
|
||||||
|
|
||||||
import java.text.DateFormatSymbols;
|
import java.text.DateFormatSymbols;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
import java.time.format.DecimalStyle;
|
import java.time.format.DecimalStyle;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.DateTimeFormatterBuilder;
|
import java.time.format.DateTimeFormatterBuilder;
|
||||||
|
@ -49,7 +51,7 @@ import org.testng.annotations.Test;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @test
|
* @test
|
||||||
* @bug 8081022 8151876 8166875 8189784 8206980
|
* @bug 8081022 8151876 8166875 8177819 8189784 8206980 8277049
|
||||||
* @key randomness
|
* @key randomness
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -236,4 +238,37 @@ public class TestZoneTextPrinterParser extends AbstractTestPrinterParser {
|
||||||
.withDecimalStyle(DecimalStyle.of(locale));
|
.withDecimalStyle(DecimalStyle.of(locale));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DataProvider(name="roundTripAtOverlap")
|
||||||
|
Object[][] data_roundTripAtOverlap() {
|
||||||
|
return new Object[][] {
|
||||||
|
{"yyyy-MM-dd HH:mm:ss.SSS z", "2021-10-31 02:30:00.000 CET"},
|
||||||
|
{"yyyy-MM-dd HH:mm:ss.SSS z", "2021-10-31 02:30:00.000 CEST"},
|
||||||
|
{"yyyy-MM-dd HH:mm:ss.SSS z", "2021-11-07 01:30:00.000 EST"},
|
||||||
|
{"yyyy-MM-dd HH:mm:ss.SSS z", "2021-11-07 01:30:00.000 EDT"},
|
||||||
|
{"yyyy-MM-dd HH:mm:ss.SSS zzzz", "2021-10-31 02:30:00.000 Central European Standard Time"},
|
||||||
|
{"yyyy-MM-dd HH:mm:ss.SSS zzzz", "2021-10-31 02:30:00.000 Central European Summer Time"},
|
||||||
|
{"yyyy-MM-dd HH:mm:ss.SSS zzzz", "2021-11-07 01:30:00.000 Eastern Standard Time"},
|
||||||
|
{"yyyy-MM-dd HH:mm:ss.SSS zzzz", "2021-11-07 01:30:00.000 Eastern Daylight Time"},
|
||||||
|
|
||||||
|
{"yyyy-MM-dd HH:mm:ss.SSS v", "2021-10-31 02:30:00.000 CET"},
|
||||||
|
{"yyyy-MM-dd HH:mm:ss.SSS v", "2021-11-07 01:30:00.000 ET"},
|
||||||
|
{"yyyy-MM-dd HH:mm:ss.SSS vvvv", "2021-10-31 02:30:00.000 Central European Time"},
|
||||||
|
{"yyyy-MM-dd HH:mm:ss.SSS vvvv", "2021-11-07 01:30:00.000 Eastern Time"},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider="roundTripAtOverlap")
|
||||||
|
public void test_roundTripAtOverlap(String pattern, String input) {
|
||||||
|
var dtf = DateTimeFormatter.ofPattern(pattern);
|
||||||
|
assertEquals(dtf.format(ZonedDateTime.parse(input, dtf)), input);
|
||||||
|
var lc = input.toLowerCase(Locale.ROOT);
|
||||||
|
try {
|
||||||
|
ZonedDateTime.parse(lc, dtf);
|
||||||
|
fail("Should throw DateTimeParseException");
|
||||||
|
} catch (DateTimeParseException ignore) {}
|
||||||
|
|
||||||
|
dtf = new DateTimeFormatterBuilder().parseCaseInsensitive().appendPattern(pattern).toFormatter();
|
||||||
|
assertEquals(dtf.format(ZonedDateTime.parse(input, dtf)), input);
|
||||||
|
assertEquals(dtf.format(ZonedDateTime.parse(lc, dtf)), input);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue