Skip to content

Commit b959b49

Browse files
Update date time parser
1 parent 68fceac commit b959b49

File tree

3 files changed

+57
-150
lines changed

3 files changed

+57
-150
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/DateTimeFormatter.java

+12-122
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import java.util.TimeZone;
3030

3131
/**
32-
* JsonBuffer implementation borrowed from <a href=
32+
* DateTimeFormatter implementation borrowed from <a href=
3333
* "https://github.com/mongodb/mongo-java-driver/blob/master/bson/src/main/org/bson/json/DateTimeFormatter.java">MongoDB
3434
* Inc.</a> licensed under the Apache License, Version 2.0. <br />
3535
* Formatted and modified.
@@ -40,133 +40,23 @@
4040
*/
4141
class DateTimeFormatter {
4242

43-
private static final FormatterImpl FORMATTER_IMPL;
44-
45-
static {
46-
FormatterImpl dateTimeHelper;
47-
try {
48-
dateTimeHelper = loadDateTimeFormatter(
49-
"org.springframework.data.mongodb.util.json.DateTimeFormatter$Java8DateTimeFormatter");
50-
} catch (LinkageError e) {
51-
// this is expected if running on a release prior to Java 8: fallback to JAXB.
52-
dateTimeHelper = loadDateTimeFormatter(
53-
"org.springframework.data.mongodb.util.json.DateTimeFormatter$JaxbDateTimeFormatter");
54-
}
55-
56-
FORMATTER_IMPL = dateTimeHelper;
57-
}
58-
59-
private static FormatterImpl loadDateTimeFormatter(final String className) {
60-
43+
static long parse(final String dateTimeString) {
6144
try {
62-
return (FormatterImpl) Class.forName(className).getDeclaredConstructor().newInstance();
63-
} catch (ClassNotFoundException e) {
64-
// this is unexpected as it means the class itself is not found
65-
throw new ExceptionInInitializerError(e);
66-
} catch (InstantiationException e) {
67-
// this is unexpected as it means the class can't be instantiated
68-
throw new ExceptionInInitializerError(e);
69-
} catch (IllegalAccessException e) {
70-
// this is unexpected as it means the no-args constructor isn't accessible
71-
throw new ExceptionInInitializerError(e);
72-
} catch (NoSuchMethodException e) {
73-
throw new ExceptionInInitializerError(e);
74-
} catch (InvocationTargetException e) {
75-
throw new ExceptionInInitializerError(e);
45+
return ISO_OFFSET_DATE_TIME.parse(dateTimeString, new TemporalQuery<Instant>() {
46+
@Override
47+
public Instant queryFrom(final TemporalAccessor temporal) {
48+
return Instant.from(temporal);
49+
}
50+
}).toEpochMilli();
51+
} catch (DateTimeParseException e) {
52+
throw new IllegalArgumentException(e.getMessage());
7653
}
7754
}
7855

79-
static long parse(final String dateTimeString) {
80-
return FORMATTER_IMPL.parse(dateTimeString);
81-
}
82-
8356
static String format(final long dateTime) {
84-
return FORMATTER_IMPL.format(dateTime);
85-
}
86-
87-
private interface FormatterImpl {
88-
long parse(String dateTimeString);
89-
90-
String format(long dateTime);
91-
}
92-
93-
// Reflective use of DatatypeConverter avoids a compile-time dependency on the java.xml.bind module in Java 9
94-
static class JaxbDateTimeFormatter implements FormatterImpl {
95-
96-
private static final Method DATATYPE_CONVERTER_PARSE_DATE_TIME_METHOD;
97-
private static final Method DATATYPE_CONVERTER_PRINT_DATE_TIME_METHOD;
98-
99-
static {
100-
try {
101-
DATATYPE_CONVERTER_PARSE_DATE_TIME_METHOD = Class.forName("jakarta.xml.bind.DatatypeConverter")
102-
.getDeclaredMethod("parseDateTime", String.class);
103-
DATATYPE_CONVERTER_PRINT_DATE_TIME_METHOD = Class.forName("jakarta.xml.bind.DatatypeConverter")
104-
.getDeclaredMethod("printDateTime", Calendar.class);
105-
} catch (NoSuchMethodException e) {
106-
throw new ExceptionInInitializerError(e);
107-
} catch (ClassNotFoundException e) {
108-
throw new ExceptionInInitializerError(e);
109-
}
110-
}
111-
112-
@Override
113-
public long parse(final String dateTimeString) {
114-
try {
115-
return ((Calendar) DATATYPE_CONVERTER_PARSE_DATE_TIME_METHOD.invoke(null, dateTimeString)).getTimeInMillis();
116-
} catch (IllegalAccessException e) {
117-
throw new IllegalStateException(e);
118-
} catch (InvocationTargetException e) {
119-
throw (RuntimeException) e.getCause();
120-
}
121-
}
122-
123-
@Override
124-
public String format(final long dateTime) {
125-
Calendar calendar = Calendar.getInstance();
126-
calendar.setTimeInMillis(dateTime);
127-
calendar.setTimeZone(TimeZone.getTimeZone("Z"));
128-
try {
129-
return (String) DATATYPE_CONVERTER_PRINT_DATE_TIME_METHOD.invoke(null, calendar);
130-
} catch (IllegalAccessException e) {
131-
throw new IllegalStateException();
132-
} catch (InvocationTargetException e) {
133-
throw (RuntimeException) e.getCause();
134-
}
135-
}
57+
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(dateTime), ZoneId.of("Z")).format(ISO_OFFSET_DATE_TIME);
13658
}
13759

138-
static class Java8DateTimeFormatter implements FormatterImpl {
139-
140-
// if running on Java 8 or above then java.time.format.DateTimeFormatter will be available and initialization will
141-
// succeed.
142-
// Otherwise it will fail.
143-
static {
144-
try {
145-
Class.forName("java.time.format.DateTimeFormatter");
146-
} catch (ClassNotFoundException e) {
147-
throw new ExceptionInInitializerError(e);
148-
}
149-
}
150-
151-
@Override
152-
public long parse(final String dateTimeString) {
153-
try {
154-
return ISO_OFFSET_DATE_TIME.parse(dateTimeString, new TemporalQuery<Instant>() {
155-
@Override
156-
public Instant queryFrom(final TemporalAccessor temporal) {
157-
return Instant.from(temporal);
158-
}
159-
}).toEpochMilli();
160-
} catch (DateTimeParseException e) {
161-
throw new IllegalArgumentException(e.getMessage());
162-
}
163-
}
164-
165-
@Override
166-
public String format(final long dateTime) {
167-
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(dateTime), ZoneId.of("Z")).format(ISO_OFFSET_DATE_TIME);
168-
}
60+
private DateTimeFormatter() {
16961
}
170-
171-
private DateTimeFormatter() {}
17262
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java

+13-27
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@
2020
import java.text.DateFormat;
2121
import java.text.ParsePosition;
2222
import java.text.SimpleDateFormat;
23+
import java.time.format.DateTimeParseException;
24+
import java.util.Base64;
2325
import java.util.Calendar;
2426
import java.util.Collections;
2527
import java.util.Date;
2628
import java.util.HashMap;
2729
import java.util.Locale;
2830
import java.util.Map;
2931
import java.util.TimeZone;
30-
import java.util.UUID;
3132
import java.util.function.Supplier;
3233
import java.util.regex.Matcher;
3334
import java.util.regex.Pattern;
@@ -42,7 +43,6 @@
4243
import org.springframework.expression.EvaluationContext;
4344
import org.springframework.expression.spel.standard.SpelExpressionParser;
4445
import org.springframework.lang.Nullable;
45-
import org.springframework.util.Base64Utils;
4646
import org.springframework.util.ClassUtils;
4747
import org.springframework.util.NumberUtils;
4848
import org.springframework.util.ObjectUtils;
@@ -957,7 +957,7 @@ private BsonBinary visitBinDataConstructor() {
957957
}
958958
verifyToken(JsonTokenType.RIGHT_PAREN);
959959

960-
byte[] bytes = Base64Utils.decodeFromString(bytesToken.getValue(String.class));
960+
byte[] bytes = Base64.getDecoder().decode(bytesToken.getValue(String.class));
961961
return new BsonBinary(subTypeToken.getValue(Integer.class).byteValue(), bytes);
962962
}
963963

@@ -1080,28 +1080,14 @@ private long visitISODateTimeConstructor() {
10801080
}
10811081

10821082
verifyToken(JsonTokenType.RIGHT_PAREN);
1083-
String[] patterns = { "yyyy-MM-dd", "yyyy-MM-dd'T'HH:mm:ssz", "yyyy-MM-dd'T'HH:mm:ss.SSSz" };
1083+
1084+
String dateTimeString = token.getValue(String.class);
10841085

1085-
SimpleDateFormat format = new SimpleDateFormat(patterns[0], Locale.ENGLISH);
1086-
ParsePosition pos = new ParsePosition(0);
1087-
String s = token.getValue(String.class);
1088-
1089-
if (s.endsWith("Z")) {
1090-
s = s.substring(0, s.length() - 1) + "GMT-00:00";
1091-
}
1092-
1093-
for (final String pattern : patterns) {
1094-
format.applyPattern(pattern);
1095-
format.setLenient(true);
1096-
pos.setIndex(0);
1097-
1098-
Date date = format.parse(s, pos);
1099-
1100-
if (date != null && pos.getIndex() == s.length()) {
1101-
return date.getTime();
1102-
}
1086+
try {
1087+
return DateTimeFormatter.parse(dateTimeString);
1088+
} catch (DateTimeParseException e) {
1089+
throw new JsonParseException("Failed to parse string as a date: " + dateTimeString, e);
11031090
}
1104-
throw new JsonParseException("Invalid date format.");
11051091
}
11061092

11071093
private BsonBinary visitHexDataConstructor() {
@@ -1219,7 +1205,7 @@ private BsonBinary visitBinDataExtendedJson(final String firstKey) {
12191205
byte type;
12201206
if (firstNestedKey.equals("base64")) {
12211207
verifyToken(JsonTokenType.COLON);
1222-
data = Base64Utils.decodeFromString(readStringFromExtendedJson());
1208+
data = Base64.getDecoder().decode(readStringFromExtendedJson());
12231209
verifyToken(JsonTokenType.COMMA);
12241210
verifyString("subType");
12251211
verifyToken(JsonTokenType.COLON);
@@ -1230,7 +1216,7 @@ private BsonBinary visitBinDataExtendedJson(final String firstKey) {
12301216
verifyToken(JsonTokenType.COMMA);
12311217
verifyString("base64");
12321218
verifyToken(JsonTokenType.COLON);
1233-
data = Base64Utils.decodeFromString(readStringFromExtendedJson());
1219+
data = Base64.getDecoder().decode(readStringFromExtendedJson());
12341220
} else {
12351221
throw new JsonParseException("Unexpected key for $binary: " + firstNestedKey);
12361222
}
@@ -1258,7 +1244,7 @@ private BsonBinary visitLegacyBinaryExtendedJson(final String firstKey) {
12581244
byte type;
12591245

12601246
if (firstKey.equals("$binary")) {
1261-
data = Base64Utils.decodeFromString(readStringFromExtendedJson());
1247+
data = Base64.getDecoder().decode(readStringFromExtendedJson());
12621248
verifyToken(JsonTokenType.COMMA);
12631249
verifyString("$type");
12641250
verifyToken(JsonTokenType.COLON);
@@ -1268,7 +1254,7 @@ private BsonBinary visitLegacyBinaryExtendedJson(final String firstKey) {
12681254
verifyToken(JsonTokenType.COMMA);
12691255
verifyString("$binary");
12701256
verifyToken(JsonTokenType.COLON);
1271-
data = Base64Utils.decodeFromString(readStringFromExtendedJson());
1257+
data = Base64.getDecoder().decode(readStringFromExtendedJson());
12721258
}
12731259
verifyToken(JsonTokenType.END_OBJECT);
12741260

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java

+32-1
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,38 @@ void bindNumberAsDate() {
211211
assertThat(target).isEqualTo(Document.parse("{ 'end_date' : { $gte : { $date : " + time + " } } } "));
212212
}
213213

214+
@Test // GH-3750
215+
public void shouldParseISODate() {
216+
217+
String json = "{ 'value' : ISODate(\"1970-01-01T00:00:00Z\") }";
218+
Date value = parse(json).get("value", Date.class);
219+
assertThat(value.getTime()).isZero();
220+
}
221+
222+
@Test // GH-3750
223+
public void shouldParseISODateWith24HourTimeSpecification() {
224+
225+
String json = "{ 'value' : ISODate(\"2013-10-04T12:07:30.443Z\") }";
226+
Date value = parse(json).get("value", Date.class);
227+
assertThat(value.getTime()).isEqualTo(1380888450443L);
228+
}
229+
230+
@Test // GH-3750
231+
public void shouldParse$date() {
232+
233+
String json = "{ 'value' : { \"$date\" : \"2015-04-16T14:55:57.626Z\" } }";
234+
Date value = parse(json).get("value", Date.class);
235+
assertThat(value.getTime()).isEqualTo(1429196157626L);
236+
}
237+
238+
@Test // GH-3750
239+
public void shouldParse$dateWithTimeOffset() {
240+
241+
String json = "{ 'value' :{ \"$date\" : \"2015-04-16T16:55:57.626+02:00\" } }";
242+
Date value = parse(json).get("value", Date.class);
243+
assertThat(value.getTime()).isEqualTo(1429196157626L);
244+
}
245+
214246
@Test // DATAMONGO-2418
215247
void shouldNotAccessSpElEvaluationContextWhenNoSpElPresentInBindableTarget() {
216248

@@ -486,7 +518,6 @@ void parsesNullValue() {
486518
assertThat(target).isEqualTo(new Document("parent", null));
487519
}
488520

489-
490521
@Test // GH-4089
491522
void retainsSpelArgumentTypeViaArgumentIndex() {
492523

0 commit comments

Comments
 (0)