Skip to content

Commit 307d502

Browse files
committed
JAVA-2507: When available (starting with Java 8), use java.time.format.DateFormatter.ISO_OFFSET_DATE_TIME instead of javax.xml.bind.DatatypeConverter.parseDateTime
1 parent 935d762 commit 307d502

File tree

2 files changed

+126
-2
lines changed

2 files changed

+126
-2
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright 2017 MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
package org.bson.json;
19+
20+
import java.lang.reflect.InvocationTargetException;
21+
import java.lang.reflect.Method;
22+
import java.time.Instant;
23+
import java.time.format.DateTimeParseException;
24+
import java.time.temporal.TemporalAccessor;
25+
import java.time.temporal.TemporalQuery;
26+
import java.util.Calendar;
27+
28+
import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
29+
30+
final class DateTimeFormatter {
31+
private static final FormatterImpl FORMATTER_IMPL;
32+
33+
static {
34+
FormatterImpl dateTimeHelper;
35+
try {
36+
dateTimeHelper = loadDateTimeFormatter("org.bson.json.DateTimeFormatter$Java8DateTimeFormatter");
37+
} catch (LinkageError e) {
38+
// this is expected if running on a release prior to Java 8: fallback to JAXB.
39+
dateTimeHelper = loadDateTimeFormatter("org.bson.json.DateTimeFormatter$JaxbDateTimeFormatter");
40+
}
41+
42+
FORMATTER_IMPL = dateTimeHelper;
43+
}
44+
45+
private static FormatterImpl loadDateTimeFormatter(final String className) {
46+
try {
47+
return (FormatterImpl) Class.forName(className).newInstance();
48+
} catch (ClassNotFoundException e) {
49+
// this is unexpected as it means the class itself is not found
50+
throw new ExceptionInInitializerError(e);
51+
} catch (InstantiationException e) {
52+
// this is unexpected as it means the class can't be instantiated
53+
throw new ExceptionInInitializerError(e);
54+
} catch (IllegalAccessException e) {
55+
// this is unexpected as it means the no-args constructor isn't accessible
56+
throw new ExceptionInInitializerError(e);
57+
}
58+
}
59+
60+
static long parse(final String dateTimeString) {
61+
return FORMATTER_IMPL.parse(dateTimeString);
62+
}
63+
64+
private interface FormatterImpl {
65+
long parse(String dateTimeString);
66+
}
67+
68+
// Reflective use of DatatypeConverter avoids a compile-time dependency on the java.xml.bind module in Java 9
69+
static class JaxbDateTimeFormatter implements FormatterImpl {
70+
71+
private static final Method DATATYPE_CONVERTER_PARSE_DATE_TIME_METHOD;
72+
73+
static {
74+
try {
75+
DATATYPE_CONVERTER_PARSE_DATE_TIME_METHOD = Class.forName("javax.xml.bind.DatatypeConverter")
76+
.getDeclaredMethod("parseDateTime", String.class);
77+
} catch (NoSuchMethodException e) {
78+
throw new ExceptionInInitializerError(e);
79+
} catch (ClassNotFoundException e) {
80+
throw new ExceptionInInitializerError(e);
81+
}
82+
}
83+
84+
@Override
85+
public long parse(final String dateTimeString) {
86+
try {
87+
return ((Calendar) DATATYPE_CONVERTER_PARSE_DATE_TIME_METHOD.invoke(null, dateTimeString)).getTimeInMillis();
88+
} catch (IllegalAccessException e) {
89+
throw new IllegalStateException(e);
90+
} catch (InvocationTargetException e) {
91+
throw (RuntimeException) e.getCause();
92+
}
93+
}
94+
}
95+
96+
static class Java8DateTimeFormatter implements FormatterImpl {
97+
98+
// if running on Java 8 or above then java.time.format.DateTimeFormatter will be available and initialization will succeed.
99+
// Otherwise it will fail.
100+
static {
101+
try {
102+
Class.forName("java.time.format.DateTimeFormatter");
103+
} catch (ClassNotFoundException e) {
104+
throw new ExceptionInInitializerError(e);
105+
}
106+
}
107+
108+
@Override
109+
public long parse(final String dateTimeString) {
110+
try {
111+
return ISO_OFFSET_DATE_TIME.parse(dateTimeString, new TemporalQuery<Instant>() {
112+
@Override
113+
public Instant queryFrom(final TemporalAccessor temporal) {
114+
return Instant.from(temporal);
115+
}
116+
}).toEpochMilli();
117+
} catch (DateTimeParseException e) {
118+
throw new IllegalArgumentException(e.getMessage());
119+
}
120+
}
121+
}
122+
123+
private DateTimeFormatter() {
124+
}
125+
}

bson/src/main/org/bson/json/JsonReader.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import org.bson.types.MinKey;
3737
import org.bson.types.ObjectId;
3838

39-
import javax.xml.bind.DatatypeConverter;
4039
import java.text.DateFormat;
4140
import java.text.ParsePosition;
4241
import java.text.SimpleDateFormat;
@@ -989,7 +988,7 @@ private long visitDateTimeExtendedJson() {
989988
} else if (valueToken.getType() == JsonTokenType.STRING) {
990989
String dateTimeString = valueToken.getValue(String.class);
991990
try {
992-
value = DatatypeConverter.parseDateTime(dateTimeString).getTimeInMillis();
991+
value = DateTimeFormatter.parse(dateTimeString);
993992
} catch (IllegalArgumentException e) {
994993
throw new JsonParseException("Failed to parse string as a date", e);
995994
}

0 commit comments

Comments
 (0)