[FEAT] Jackson update date serializer to be more permissive
This commit is contained in:
parent
1d375f8580
commit
f044473a67
@ -0,0 +1,19 @@
|
||||
package org.kar.archidata.converter.jackson;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
import org.kar.archidata.tools.DateTools;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
|
||||
public class DateDeserializer extends JsonDeserializer<Date> {
|
||||
@Override
|
||||
public Date deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException {
|
||||
final String value = p.getText();
|
||||
final Date ret = DateTools.parseDate(value);
|
||||
return ret;
|
||||
}
|
||||
}
|
18
src/org/kar/archidata/converter/jackson/DateSerializer.java
Normal file
18
src/org/kar/archidata/converter/jackson/DateSerializer.java
Normal file
@ -0,0 +1,18 @@
|
||||
package org.kar.archidata.converter.jackson;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
import org.kar.archidata.tools.DateTools;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
public class DateSerializer extends JsonSerializer<Date> {
|
||||
@Override
|
||||
public void serialize(final Date value, final JsonGenerator gen, final SerializerProvider serializers)
|
||||
throws IOException {
|
||||
gen.writeString(DateTools.serializeMilliWithUTCTimeZone(value));
|
||||
}
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
package org.kar.archidata.converter.jackson;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
@ -9,6 +12,10 @@ public class JacksonModules {
|
||||
final SimpleModule module = new SimpleModule();
|
||||
module.addSerializer(ObjectId.class, new ObjectIdSerializer());
|
||||
module.addDeserializer(ObjectId.class, new ObjectIdDeserializer());
|
||||
module.addSerializer(Date.class, new DateSerializer());
|
||||
module.addDeserializer(Date.class, new DateDeserializer());
|
||||
module.addSerializer(OffsetDateTime.class, new OffsetDateTimeSerializer());
|
||||
module.addDeserializer(OffsetDateTime.class, new OffsetDateTimeDeserializer());
|
||||
return module;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
package org.kar.archidata.converter.jackson;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
import org.kar.archidata.tools.DateTools;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
|
||||
public class OffsetDateTimeDeserializer extends JsonDeserializer<OffsetDateTime> {
|
||||
@Override
|
||||
public OffsetDateTime deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException {
|
||||
final String value = p.getText();
|
||||
final OffsetDateTime ret = DateTools.parseOffsetDateTime(value);
|
||||
return ret;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package org.kar.archidata.converter.jackson;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
import org.kar.archidata.tools.DateTools;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
public class OffsetDateTimeSerializer extends JsonSerializer<OffsetDateTime> {
|
||||
@Override
|
||||
public void serialize(final OffsetDateTime value, final JsonGenerator gen, final SerializerProvider serializers)
|
||||
throws IOException {
|
||||
gen.writeString(DateTools.serializeMilliWithUTCTimeZone(value));
|
||||
}
|
||||
}
|
@ -12,11 +12,11 @@ public class ContextGenericTools {
|
||||
|
||||
public static ObjectMapper createObjectMapper() {
|
||||
final ObjectMapper objectMapper = new ObjectMapper();
|
||||
// Configure Jackson for dates and times
|
||||
objectMapper.registerModule(new JavaTimeModule()); // Module for Java 8+ Date and Time API
|
||||
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
// configure the local serialization modules
|
||||
objectMapper.registerModule(JacksonModules.getAllModules());
|
||||
// Add java time module at the end to prevent use it in first but in backup
|
||||
objectMapper.registerModule(new JavaTimeModule()); // Module for Java 8+ Date and Time API
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
|
@ -1,53 +1,193 @@
|
||||
package org.kar.archidata.tools;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class DateTools {
|
||||
static private List<SimpleDateFormat> knownPatterns = new ArrayList<>();
|
||||
{
|
||||
// SYSTEM mode
|
||||
DateTools.knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"));
|
||||
DateTools.knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm.ss.SSS'Z'"));
|
||||
DateTools.knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"));
|
||||
DateTools.knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"));
|
||||
// Human mode
|
||||
DateTools.knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss"));
|
||||
DateTools.knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss.SSS"));
|
||||
// date mode
|
||||
DateTools.knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd"));
|
||||
// time mode
|
||||
DateTools.knownPatterns.add(new SimpleDateFormat("HH:mm:ss"));
|
||||
DateTools.knownPatterns.add(new SimpleDateFormat("HH:mm:ss.SSS"));
|
||||
}
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DateTools {
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(DateTools.class);
|
||||
|
||||
/**
|
||||
* Parses a date string using a given pattern into a java.util.Date.
|
||||
*
|
||||
* @param inputDate the string to parse
|
||||
* @param pattern the pattern to use (e.g., "yyyy-MM-dd")
|
||||
* @return parsed Date object
|
||||
* @throws ParseException if parsing fails
|
||||
*/
|
||||
@Deprecated
|
||||
public static Date parseDate(final String inputDate, final String pattern) throws ParseException {
|
||||
final SimpleDateFormat format = new SimpleDateFormat(pattern);
|
||||
return format.parse(inputDate);
|
||||
}
|
||||
|
||||
public static Date parseDate(final String inputDate) throws ParseException {
|
||||
for (final SimpleDateFormat pattern : DateTools.knownPatterns) {
|
||||
try {
|
||||
return pattern.parse(inputDate);
|
||||
} catch (final ParseException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw new ParseException("Can not parse the date-time format: '" + inputDate + "'", 0);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a java.util.Date using the specified format pattern.
|
||||
*
|
||||
* @param date the Date to format
|
||||
* @param requiredDateFormat the format string (e.g., "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
|
||||
* @return formatted string
|
||||
*/
|
||||
@Deprecated
|
||||
public static String formatDate(final Date date, final String requiredDateFormat) {
|
||||
final SimpleDateFormat df = new SimpleDateFormat(requiredDateFormat);
|
||||
final String outputDateFormatted = df.format(date);
|
||||
return outputDateFormatted;
|
||||
return df.format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a java.util.Date using a default ISO 8601-like format.
|
||||
*
|
||||
* @param date the Date to format
|
||||
* @return formatted string in pattern "yyyy-MM-dd'T'HH:mm.ss.SSS'Z'"
|
||||
*/
|
||||
@Deprecated
|
||||
public static String formatDate(final Date date) {
|
||||
return formatDate(date, "yyyy-MM-dd'T'HH:mm.ss.SSS'Z'");
|
||||
return serializeMilliWithUTCTimeZone(date);
|
||||
}
|
||||
|
||||
// List of supported parsers for flexible date string parsing.
|
||||
// Includes patterns with optional parts, slashes, and ISO standard formats.
|
||||
static final List<DateTimeFormatter> FORMATTERS = Arrays.asList(
|
||||
DateTimeFormatter.ofPattern("[yyyy[-MM[-dd]]]['T'][' '][HH[:mm[:ss['.'nnnnnnnnn]]]]XXXXX"),
|
||||
DateTimeFormatter.ofPattern("[yyyy[/MM[/dd]]]['T'][' '][HH[:mm[:ss['.'nnnnnnnnn]]]]XXXXX"),
|
||||
DateTimeFormatter.ISO_OFFSET_DATE_TIME, // e.g., 2025-04-04T15:30:00+02:00
|
||||
DateTimeFormatter.ISO_ZONED_DATE_TIME, // e.g., 2025-04-04T15:30:00+02:00[Europe/Paris]
|
||||
DateTimeFormatter.ISO_INSTANT // e.g., 2025-04-04T13:30:00Z
|
||||
);
|
||||
|
||||
/**
|
||||
* Attempts to parse a date string into an OffsetDateTime using a flexible list of patterns.
|
||||
* Supports ISO 8601 formats, optional zone, and fallback to LocalDate or LocalTime if needed.
|
||||
*
|
||||
* @param dateString the date string to parse
|
||||
* @return OffsetDateTime representation of the parsed input
|
||||
* @throws IOException if no supported format matches the input
|
||||
*/
|
||||
public static OffsetDateTime parseOffsetDateTime(final String dateString) throws IOException {
|
||||
for (final DateTimeFormatter formatter : FORMATTERS) {
|
||||
try {
|
||||
return OffsetDateTime.parse(dateString, formatter);
|
||||
} catch (final DateTimeParseException ex) {
|
||||
// If the date string is missing a zone, try appending "Z"
|
||||
try {
|
||||
if (dateString.endsWith("Z") || dateString.endsWith("z")) {
|
||||
continue;
|
||||
}
|
||||
return OffsetDateTime.parse(dateString + "Z", formatter);
|
||||
} catch (final DateTimeParseException ex2) {
|
||||
// Still failed, try next pattern
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: Try parsing as LocalDate (assume start of day UTC)
|
||||
try {
|
||||
final LocalDate dateTmp = LocalDate.parse(dateString);
|
||||
return dateTmp.atStartOfDay(ZoneOffset.UTC).toInstant().atZone(ZoneOffset.UTC).toOffsetDateTime();
|
||||
} catch (final DateTimeParseException e) {
|
||||
// Ignore and try next fallback
|
||||
}
|
||||
|
||||
// Fallback: Try parsing as LocalTime (assume date is 0000-01-01 UTC)
|
||||
try {
|
||||
final LocalTime timeTmp = LocalTime.parse(dateString);
|
||||
return OffsetDateTime.of(0, 1, 1, // year, month, day
|
||||
timeTmp.getHour(), timeTmp.getMinute(), timeTmp.getSecond(), timeTmp.getNano(), ZoneOffset.UTC);
|
||||
} catch (final DateTimeParseException e) {
|
||||
// All parsing attempts failed
|
||||
}
|
||||
|
||||
throw new IOException("Unrecognized DATE format: '" + dateString + "' supported format ISO8601");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a flexible date string and returns a java.util.Date,
|
||||
* using system default timezone for conversion.
|
||||
*
|
||||
* @param dateString the input string to parse
|
||||
* @return java.util.Date object
|
||||
* @throws ParseException if parsing fails entirely
|
||||
*/
|
||||
public static Date parseDate(final String dateString) throws IOException {
|
||||
final OffsetDateTime dateTime = parseOffsetDateTime(dateString);
|
||||
dateTime.atZoneSameInstant(ZoneId.systemDefault());
|
||||
return Date.from(dateTime.toInstant());
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatter for date-time with milliseconds and original timezone offset (e.g., 2025-04-06T15:00:00.123+02:00)
|
||||
*/
|
||||
public static final DateTimeFormatter PATTERN_MS_TIME_WITH_ZONE = DateTimeFormatter
|
||||
.ofPattern("yyyy-MM-dd'T'HH:mm:ss'.'SSSXXX");
|
||||
|
||||
/**
|
||||
* Serializes an OffsetDateTime to a string including milliseconds and the original timezone offset.
|
||||
* Example output: 2025-04-06T15:00:00.123+02:00
|
||||
*/
|
||||
public static String serializeMilliWithOriginalTimeZone(final OffsetDateTime offsetDateTime) {
|
||||
return offsetDateTime.format(PATTERN_MS_TIME_WITH_ZONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a java.util.Date to OffsetDateTime using the system's default timezone,
|
||||
* then serializes it to a string with milliseconds and original timezone offset.
|
||||
*/
|
||||
public static String serializeMilliWithOriginalTimeZone(final Date date) {
|
||||
return serializeMilliWithOriginalTimeZone(date.toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatter for date-time with milliseconds in UTC offset (e.g., 2025-04-06T13:00:00.123Z)
|
||||
*/
|
||||
public static final DateTimeFormatter PATTERN_MS_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'.'SSSX");
|
||||
|
||||
/**
|
||||
* Serializes an OffsetDateTime to a string with milliseconds in UTC.
|
||||
* The offset is explicitly changed to UTC before formatting.
|
||||
*/
|
||||
public static String serializeMilliWithUTCTimeZone(final OffsetDateTime offsetDateTime) {
|
||||
return offsetDateTime.withOffsetSameInstant(ZoneOffset.UTC).format(PATTERN_MS_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a java.util.Date to OffsetDateTime in the system's default timezone,
|
||||
* then serializes it with milliseconds in UTC.
|
||||
*/
|
||||
public static String serializeMilliWithUTCTimeZone(final Date date) {
|
||||
return serializeMilliWithUTCTimeZone(date.toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatter for date-time with nanoseconds in UTC offset (e.g., 2025-04-06T13:00:00.123456789Z)
|
||||
*/
|
||||
public static final DateTimeFormatter PATTERN_NS_TIME = DateTimeFormatter
|
||||
.ofPattern("yyyy-MM-dd'T'HH:mm:ss'.'nnnnnnnnnX");
|
||||
|
||||
/**
|
||||
* Serializes an OffsetDateTime to a string with nanosecond precision in UTC.
|
||||
*/
|
||||
public static String serializeNanoWithUTCTimeZone(final OffsetDateTime offsetDateTime) {
|
||||
return offsetDateTime.withOffsetSameInstant(ZoneOffset.UTC).format(PATTERN_NS_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a java.util.Date to OffsetDateTime in the system's default timezone,
|
||||
* then serializes it with nanoseconds in UTC.
|
||||
*/
|
||||
public static String serializeNanoWithUTCTimeZone(final Date date) {
|
||||
return serializeNanoWithUTCTimeZone(date.toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime());
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user