Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8177819: DateTimeFormatterBuilder zone parsing should recognise DST #6527

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -81,6 +81,7 @@
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.chrono.ChronoLocalDateTime;
import java.time.chrono.ChronoZonedDateTime;
import java.time.chrono.Chronology;
import java.time.chrono.IsoChronology;
import java.time.format.DateTimeFormatterBuilder.CompositePrinterParser;
Expand Down Expand Up @@ -373,15 +374,15 @@
* letters throws {@code IllegalArgumentException}.
* <p>
* <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,
* 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 four, then the full name is output.
* Five or more letters throws {@code IllegalArgumentException}.
* <p>
* 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.
* Two, three and five or more letters throw {@code IllegalArgumentException}.
* <p>
Expand Down Expand Up @@ -502,7 +503,10 @@
* {@code LocalDateTime} to form the instant, with any zone ignored.
* 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
* 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>
*
* @implSpec
Expand Down
Expand Up @@ -4324,9 +4324,10 @@ static final class ZoneTextPrinterParser extends ZoneIdPrinterParser {
}
}

private static final int STD = 0;
private static final int DST = 1;
private static final int GENERIC = 2;
static final int UNDEFINED = -1;
static final int STD = 0;
static final int DST = 1;
static final int GENERIC = 2;
private static final Map<String, SoftReference<Map<Locale, String[]>>> cache =
new ConcurrentHashMap<>();

Expand Down Expand Up @@ -4433,11 +4434,11 @@ protected PrefixTree getTree(DateTimeParseContext context) {
nonRegionIds.add(zid);
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);
int i = textStyle == TextStyle.FULL ? 1 : 2;
for (; i < names.length; i += 2) {
tree.add(names[i], zid);
tree.add(names[i], zid, (i - 1) / 2);
}
}

Expand All @@ -4450,7 +4451,7 @@ protected PrefixTree getTree(DateTimeParseContext context) {
int i = textStyle == TextStyle.FULL ? 1 : 2;
for (; i < cidNames.length; i += 2) {
if (cidNames[i] != null && !cidNames[i].isEmpty()) {
t.add(cidNames[i], cid);
t.add(cidNames[i], cid, (i - 1) / 2);
}
}
});
Expand All @@ -4465,7 +4466,7 @@ protected PrefixTree getTree(DateTimeParseContext context) {
}
int i = textStyle == TextStyle.FULL ? 1 : 2;
for (; i < names.length; i += 2) {
tree.add(names[i], zid);
tree.add(names[i], zid, (i - 1) / 2);
}
}
}
Expand Down Expand Up @@ -4571,15 +4572,16 @@ public int parse(DateTimeParseContext context, CharSequence text, int position)
// parse
PrefixTree tree = getTree(context);
ParsePosition ppos = new ParsePosition(position);
String parsedZoneId = tree.match(text, ppos);
if (parsedZoneId == null) {
PrefixTree parsedZoneId = tree.match(text, ppos);
if (parsedZoneId.value == null) {
if (context.charEquals(nextChar, 'Z')) {
context.setParsed(ZoneOffset.UTC);
return position + 1;
}
return ~position;
}
context.setParsed(ZoneId.of(parsedZoneId));
context.setParsed(ZoneId.of(parsedZoneId.value));
context.setParsedZoneNameType(parsedZoneId.type);
return ppos.getIndex();
}

Expand Down Expand Up @@ -4641,14 +4643,16 @@ public String toString() {
static class PrefixTree {
protected String key;
protected String value;
protected int type;
protected char c0; // performance optimization to avoid the
// boundary check cost of key.charat(0)
protected PrefixTree child;
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.value = v;
this.type = type;
this.child = child;
if (k.isEmpty()) {
c0 = 0xffff;
Expand All @@ -4664,13 +4668,10 @@ private PrefixTree(String k, String v, PrefixTree child) {
* @return the tree, not null
*/
public static PrefixTree newTree(DateTimeParseContext context) {
//if (!context.isStrict()) {
// return new LENIENT("", null, null);
//}
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);
}

/**
Expand All @@ -4683,7 +4684,7 @@ public static PrefixTree newTree(DateTimeParseContext context) {
public static PrefixTree newTree(Set<String> keys, DateTimeParseContext context) {
PrefixTree tree = newTree(context);
for (String k : keys) {
tree.add0(k, k);
tree.add0(k, k, ZoneTextPrinterParser.UNDEFINED);
}
return tree;
}
Expand All @@ -4692,7 +4693,7 @@ public static PrefixTree newTree(Set<String> keys, DateTimeParseContext context
* Clone a copy of this tree
*/
public PrefixTree copyTree() {
PrefixTree copy = new PrefixTree(key, value, null);
PrefixTree copy = new PrefixTree(key, value, type, null);
if (child != null) {
copy.child = child.copyTree();
}
Expand All @@ -4710,11 +4711,11 @@ public PrefixTree copyTree() {
* @param v the value, not null
* @return true if the pair is added successfully
*/
public boolean add(String k, String v) {
return add0(k, v);
public boolean add(String k, String v, int t) {
return add0(k, v, t);
}

private boolean add0(String k, String v) {
private boolean add0(String k, String v, int t) {
k = toKey(k);
int prefixLen = prefixLength(k);
if (prefixLen == key.length()) {
Expand All @@ -4723,12 +4724,12 @@ private boolean add0(String k, String v) {
PrefixTree c = child;
while (c != null) {
if (isEqual(c.c0, subKey.charAt(0))) {
return c.add0(subKey, v);
return c.add0(subKey, v, t);
}
c = c.sibling;
}
// add the node as the child of the current node
c = newNode(subKey, v, null);
c = newNode(subKey, v, t, null);
c.sibling = child;
child = c;
return true;
Expand All @@ -4738,18 +4739,20 @@ private boolean add0(String k, String v) {
// return false;
//}
value = v;
type = t;
return true;
}
// 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);
child = n1;
if (prefixLen < k.length()) {
PrefixTree n2 = newNode(k.substring(prefixLen), v, null);
PrefixTree n2 = newNode(k.substring(prefixLen), v, t, null);
child.sibling = n2;
value = null;
} else {
value = v;
type = t;
}
return true;
}
Expand All @@ -4760,26 +4763,26 @@ private boolean add0(String k, String v) {
* @param text the input text to parse, not null
* @param off the offset position to start parsing at
* @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)){
return null;
}
if (child != null && (off += key.length()) != end) {
PrefixTree c = child;
do {
if (isEqual(c.c0, text.charAt(off))) {
String found = c.match(text, off, end);
PrefixTree found = c.match(text, off, end);
if (found != null) {
return found;
}
return value;
return this;
}
c = c.sibling;
} while (c != null);
}
return value;
return this;
}

/**
Expand All @@ -4789,9 +4792,9 @@ public String match(CharSequence text, int off, int end) {
* @param pos the position to start parsing at, from 0 to the text
* length. Upon return, position will be updated to the new parse
* 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 end = text.length();
if (!prefixOf(text, off, end)){
Expand All @@ -4803,7 +4806,7 @@ public String match(CharSequence text, ParsePosition pos) {
do {
if (isEqual(c.c0, text.charAt(off))) {
pos.setIndex(off);
String found = c.match(text, pos);
PrefixTree found = c.match(text, pos);
if (found != null) {
return found;
}
Expand All @@ -4813,15 +4816,15 @@ public String match(CharSequence text, ParsePosition pos) {
} while (c != null);
}
pos.setIndex(off);
return value;
return this;
}

protected String toKey(String k) {
return k;
}

protected PrefixTree newNode(String k, String v, PrefixTree child) {
return new PrefixTree(k, v, child);
protected PrefixTree newNode(String k, String v, int t, PrefixTree child) {
return new PrefixTree(k, v, t, child);
}

protected boolean isEqual(char c1, char c2) {
Expand Down Expand Up @@ -4861,13 +4864,13 @@ private int prefixLength(String k) {
*/
private static class CI extends PrefixTree {

private CI(String k, String v, PrefixTree child) {
super(k, v, child);
private CI(String k, String v, int t, PrefixTree child) {
super(k, v, t, child);
}

@Override
protected CI newNode(String k, String v, PrefixTree child) {
return new CI(k, v, child);
protected CI newNode(String k, String v, int t, PrefixTree child) {
return new CI(k, v, t, child);
}

@Override
Expand All @@ -4890,86 +4893,6 @@ protected boolean prefixOf(CharSequence text, int off, int end) {
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;
}
}
}

//-----------------------------------------------------------------------
Expand Down