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.
|
||||
*
|
||||
* 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.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;
|
||||
|
@ -373,15 +374,15 @@ import sun.util.locale.provider.TimeZoneNameUtility;
|
|||
* 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>
|
||||
|
@ -502,7 +503,10 @@ import sun.util.locale.provider.TimeZoneNameUtility;
|
|||
* {@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
|
||||
|
|
|
@ -4324,9 +4324,10 @@ public final class DateTimeFormatterBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
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<>();
|
||||
|
||||
|
@ -4433,11 +4434,11 @@ public final class DateTimeFormatterBuilder {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4450,7 +4451,7 @@ public final class DateTimeFormatterBuilder {
|
|||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -4465,7 +4466,7 @@ public final class DateTimeFormatterBuilder {
|
|||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4571,15 +4572,16 @@ public final class DateTimeFormatterBuilder {
|
|||
// 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();
|
||||
}
|
||||
|
||||
|
@ -4641,14 +4643,16 @@ public final class DateTimeFormatterBuilder {
|
|||
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;
|
||||
|
@ -4664,13 +4668,10 @@ public final class DateTimeFormatterBuilder {
|
|||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4683,7 +4684,7 @@ public final class DateTimeFormatterBuilder {
|
|||
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;
|
||||
}
|
||||
|
@ -4692,7 +4693,7 @@ public final class DateTimeFormatterBuilder {
|
|||
* 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();
|
||||
}
|
||||
|
@ -4710,11 +4711,11 @@ public final class DateTimeFormatterBuilder {
|
|||
* @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()) {
|
||||
|
@ -4723,12 +4724,12 @@ public final class DateTimeFormatterBuilder {
|
|||
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;
|
||||
|
@ -4738,18 +4739,20 @@ public final class DateTimeFormatterBuilder {
|
|||
// 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;
|
||||
}
|
||||
|
@ -4760,9 +4763,9 @@ public final class DateTimeFormatterBuilder {
|
|||
* @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;
|
||||
}
|
||||
|
@ -4770,16 +4773,16 @@ public final class DateTimeFormatterBuilder {
|
|||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4789,9 +4792,9 @@ public final class DateTimeFormatterBuilder {
|
|||
* @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)){
|
||||
|
@ -4803,7 +4806,7 @@ public final class DateTimeFormatterBuilder {
|
|||
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;
|
||||
}
|
||||
|
@ -4813,15 +4816,15 @@ public final class DateTimeFormatterBuilder {
|
|||
} 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) {
|
||||
|
@ -4861,13 +4864,13 @@ public final class DateTimeFormatterBuilder {
|
|||
*/
|
||||
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
|
||||
|
@ -4890,86 +4893,6 @@ public final class DateTimeFormatterBuilder {
|
|||
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.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
|
@ -417,6 +417,24 @@ final class DateTimeParseContext {
|
|||
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.
|
||||
*/
|
||||
|
|
|
@ -87,6 +87,7 @@ import java.time.LocalTime;
|
|||
import java.time.Period;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.chrono.ChronoLocalDate;
|
||||
import java.time.chrono.ChronoLocalDateTime;
|
||||
import java.time.chrono.ChronoZonedDateTime;
|
||||
|
@ -132,6 +133,10 @@ final class Parsed implements TemporalAccessor {
|
|||
* The parsed zone.
|
||||
*/
|
||||
ZoneId zone;
|
||||
/**
|
||||
* The parsed zone name type.
|
||||
*/
|
||||
int zoneNameType = DateTimeFormatterBuilder.ZoneTextPrinterParser.UNDEFINED;
|
||||
/**
|
||||
* The parsed chronology.
|
||||
*/
|
||||
|
@ -175,6 +180,7 @@ final class Parsed implements TemporalAccessor {
|
|||
Parsed cloned = new Parsed();
|
||||
cloned.fieldValues.putAll(this.fieldValues);
|
||||
cloned.zone = this.zone;
|
||||
cloned.zoneNameType = this.zoneNameType;
|
||||
cloned.chrono = this.chrono;
|
||||
cloned.leapSecond = this.leapSecond;
|
||||
cloned.dayPeriod = this.dayPeriod;
|
||||
|
@ -652,8 +658,12 @@ final class Parsed implements TemporalAccessor {
|
|||
fieldValues.put(INSTANT_SECONDS, instant);
|
||||
} else {
|
||||
if (zone != null) {
|
||||
long instant = date.atTime(time).atZone(zone).toEpochSecond();
|
||||
fieldValues.put(INSTANT_SECONDS, instant);
|
||||
var czdt = date.atTime(time).atZone(zone);
|
||||
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);
|
||||
if (zone != null) {
|
||||
buf.append(',').append(zone);
|
||||
buf.append(',').append(zoneNameType);
|
||||
}
|
||||
if (date != null || time != null) {
|
||||
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.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
|
@ -24,10 +24,12 @@
|
|||
package test.java.time.format;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.fail;
|
||||
|
||||
import java.text.DateFormatSymbols;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.format.DecimalStyle;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeFormatterBuilder;
|
||||
|
@ -49,7 +51,7 @@ import org.testng.annotations.Test;
|
|||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8081022 8151876 8166875 8189784 8206980
|
||||
* @bug 8081022 8151876 8166875 8177819 8189784 8206980 8277049
|
||||
* @key randomness
|
||||
*/
|
||||
|
||||
|
@ -236,4 +238,37 @@ public class TestZoneTextPrinterParser extends AbstractTestPrinterParser {
|
|||
.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