|
|
|
@ -17,19 +17,14 @@ package org.springframework.data.mongodb.util.json; |
|
|
|
|
|
|
|
|
|
|
|
import static java.time.format.DateTimeFormatter.*; |
|
|
|
import static java.time.format.DateTimeFormatter.*; |
|
|
|
|
|
|
|
|
|
|
|
import java.lang.reflect.InvocationTargetException; |
|
|
|
|
|
|
|
import java.lang.reflect.Method; |
|
|
|
|
|
|
|
import java.time.Instant; |
|
|
|
import java.time.Instant; |
|
|
|
|
|
|
|
import java.time.LocalDate; |
|
|
|
import java.time.ZoneId; |
|
|
|
import java.time.ZoneId; |
|
|
|
|
|
|
|
import java.time.ZoneOffset; |
|
|
|
import java.time.ZonedDateTime; |
|
|
|
import java.time.ZonedDateTime; |
|
|
|
import java.time.format.DateTimeParseException; |
|
|
|
|
|
|
|
import java.time.temporal.TemporalAccessor; |
|
|
|
|
|
|
|
import java.time.temporal.TemporalQuery; |
|
|
|
|
|
|
|
import java.util.Calendar; |
|
|
|
|
|
|
|
import java.util.TimeZone; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* JsonBuffer implementation borrowed from <a href= |
|
|
|
* DateTimeFormatter implementation borrowed from <a href= |
|
|
|
* "https://github.com/mongodb/mongo-java-driver/blob/master/bson/src/main/org/bson/json/DateTimeFormatter.java">MongoDB |
|
|
|
* "https://github.com/mongodb/mongo-java-driver/blob/master/bson/src/main/org/bson/json/DateTimeFormatter.java">MongoDB |
|
|
|
* Inc.</a> licensed under the Apache License, Version 2.0. <br /> |
|
|
|
* Inc.</a> licensed under the Apache License, Version 2.0. <br /> |
|
|
|
* Formatted and modified. |
|
|
|
* Formatted and modified. |
|
|
|
@ -40,133 +35,22 @@ import java.util.TimeZone; |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
class DateTimeFormatter { |
|
|
|
class DateTimeFormatter { |
|
|
|
|
|
|
|
|
|
|
|
private static final FormatterImpl FORMATTER_IMPL; |
|
|
|
private static final int DATE_STRING_LENGTH = "1970-01-01".length(); |
|
|
|
|
|
|
|
|
|
|
|
static { |
|
|
|
|
|
|
|
FormatterImpl dateTimeHelper; |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
dateTimeHelper = loadDateTimeFormatter( |
|
|
|
|
|
|
|
"org.springframework.data.mongodb.util.json.DateTimeFormatter$Java8DateTimeFormatter"); |
|
|
|
|
|
|
|
} catch (LinkageError e) { |
|
|
|
|
|
|
|
// this is expected if running on a release prior to Java 8: fallback to JAXB.
|
|
|
|
|
|
|
|
dateTimeHelper = loadDateTimeFormatter( |
|
|
|
|
|
|
|
"org.springframework.data.mongodb.util.json.DateTimeFormatter$JaxbDateTimeFormatter"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FORMATTER_IMPL = dateTimeHelper; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static FormatterImpl loadDateTimeFormatter(final String className) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
return (FormatterImpl) Class.forName(className).getDeclaredConstructor().newInstance(); |
|
|
|
|
|
|
|
} catch (ClassNotFoundException e) { |
|
|
|
|
|
|
|
// this is unexpected as it means the class itself is not found
|
|
|
|
|
|
|
|
throw new ExceptionInInitializerError(e); |
|
|
|
|
|
|
|
} catch (InstantiationException e) { |
|
|
|
|
|
|
|
// this is unexpected as it means the class can't be instantiated
|
|
|
|
|
|
|
|
throw new ExceptionInInitializerError(e); |
|
|
|
|
|
|
|
} catch (IllegalAccessException e) { |
|
|
|
|
|
|
|
// this is unexpected as it means the no-args constructor isn't accessible
|
|
|
|
|
|
|
|
throw new ExceptionInInitializerError(e); |
|
|
|
|
|
|
|
} catch (NoSuchMethodException e) { |
|
|
|
|
|
|
|
throw new ExceptionInInitializerError(e); |
|
|
|
|
|
|
|
} catch (InvocationTargetException e) { |
|
|
|
|
|
|
|
throw new ExceptionInInitializerError(e); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static long parse(final String dateTimeString) { |
|
|
|
static long parse(final String dateTimeString) { |
|
|
|
return FORMATTER_IMPL.parse(dateTimeString); |
|
|
|
// ISO_OFFSET_DATE_TIME will not parse date strings consisting of just year-month-day, so use ISO_LOCAL_DATE for
|
|
|
|
|
|
|
|
// those
|
|
|
|
|
|
|
|
if (dateTimeString.length() == DATE_STRING_LENGTH) { |
|
|
|
|
|
|
|
return LocalDate.parse(dateTimeString, ISO_LOCAL_DATE).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli(); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return ISO_OFFSET_DATE_TIME.parse(dateTimeString, Instant::from).toEpochMilli(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static String format(final long dateTime) { |
|
|
|
static String format(final long dateTime) { |
|
|
|
return FORMATTER_IMPL.format(dateTime); |
|
|
|
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(dateTime), ZoneId.of("Z")).format(ISO_OFFSET_DATE_TIME); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private interface FormatterImpl { |
|
|
|
|
|
|
|
long parse(String dateTimeString); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String format(long dateTime); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Reflective use of DatatypeConverter avoids a compile-time dependency on the java.xml.bind module in Java 9
|
|
|
|
|
|
|
|
static class JaxbDateTimeFormatter implements FormatterImpl { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final Method DATATYPE_CONVERTER_PARSE_DATE_TIME_METHOD; |
|
|
|
|
|
|
|
private static final Method DATATYPE_CONVERTER_PRINT_DATE_TIME_METHOD; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
DATATYPE_CONVERTER_PARSE_DATE_TIME_METHOD = Class.forName("jakarta.xml.bind.DatatypeConverter") |
|
|
|
|
|
|
|
.getDeclaredMethod("parseDateTime", String.class); |
|
|
|
|
|
|
|
DATATYPE_CONVERTER_PRINT_DATE_TIME_METHOD = Class.forName("jakarta.xml.bind.DatatypeConverter") |
|
|
|
|
|
|
|
.getDeclaredMethod("printDateTime", Calendar.class); |
|
|
|
|
|
|
|
} catch (NoSuchMethodException e) { |
|
|
|
|
|
|
|
throw new ExceptionInInitializerError(e); |
|
|
|
|
|
|
|
} catch (ClassNotFoundException e) { |
|
|
|
|
|
|
|
throw new ExceptionInInitializerError(e); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public long parse(final String dateTimeString) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
return ((Calendar) DATATYPE_CONVERTER_PARSE_DATE_TIME_METHOD.invoke(null, dateTimeString)).getTimeInMillis(); |
|
|
|
|
|
|
|
} catch (IllegalAccessException e) { |
|
|
|
|
|
|
|
throw new IllegalStateException(e); |
|
|
|
|
|
|
|
} catch (InvocationTargetException e) { |
|
|
|
|
|
|
|
throw (RuntimeException) e.getCause(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public String format(final long dateTime) { |
|
|
|
|
|
|
|
Calendar calendar = Calendar.getInstance(); |
|
|
|
|
|
|
|
calendar.setTimeInMillis(dateTime); |
|
|
|
|
|
|
|
calendar.setTimeZone(TimeZone.getTimeZone("Z")); |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
return (String) DATATYPE_CONVERTER_PRINT_DATE_TIME_METHOD.invoke(null, calendar); |
|
|
|
|
|
|
|
} catch (IllegalAccessException e) { |
|
|
|
|
|
|
|
throw new IllegalStateException(); |
|
|
|
|
|
|
|
} catch (InvocationTargetException e) { |
|
|
|
|
|
|
|
throw (RuntimeException) e.getCause(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static class Java8DateTimeFormatter implements FormatterImpl { |
|
|
|
private DateTimeFormatter() { |
|
|
|
|
|
|
|
|
|
|
|
// if running on Java 8 or above then java.time.format.DateTimeFormatter will be available and initialization will
|
|
|
|
|
|
|
|
// succeed.
|
|
|
|
|
|
|
|
// Otherwise it will fail.
|
|
|
|
|
|
|
|
static { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
Class.forName("java.time.format.DateTimeFormatter"); |
|
|
|
|
|
|
|
} catch (ClassNotFoundException e) { |
|
|
|
|
|
|
|
throw new ExceptionInInitializerError(e); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public long parse(final String dateTimeString) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
return ISO_OFFSET_DATE_TIME.parse(dateTimeString, new TemporalQuery<Instant>() { |
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public Instant queryFrom(final TemporalAccessor temporal) { |
|
|
|
|
|
|
|
return Instant.from(temporal); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}).toEpochMilli(); |
|
|
|
|
|
|
|
} catch (DateTimeParseException e) { |
|
|
|
|
|
|
|
throw new IllegalArgumentException(e.getMessage()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public String format(final long dateTime) { |
|
|
|
|
|
|
|
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(dateTime), ZoneId.of("Z")).format(ISO_OFFSET_DATE_TIME); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private DateTimeFormatter() {} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|