Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,40 @@
package org.apache.johnzon.mapper.converter;

import org.apache.johnzon.mapper.Converter;
import org.apache.johnzon.mapper.util.DateUtil;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Date;
import java.util.Locale;

public class DateConverter implements Converter<Date> {
// TODO: see if we can clean it
private final ThreadLocal<DateFormat> format;
// Almost ISO 8601 basic, but zone is defined by generic name instead of zone-offset
public static final DateConverter ISO_8601_SHORT = new DateConverter("yyyyMMddHHmmssz");

private static final ZoneId UTC = ZoneId.of("UTC");

private final DateTimeFormatter formatter;

public DateConverter(final String pattern) {
format = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat(pattern);
}
};
this.formatter = DateTimeFormatter.ofPattern(pattern, Locale.ROOT);
}

@Override
public String toString(final Date instance) {
return format.get().format(instance);
return formatter.format(ZonedDateTime.ofInstant(instance.toInstant(), UTC));
}

@Override
public Date fromString(final String text) {
try {
return format.get().parse(text);
} catch (final ParseException e) {
throw new IllegalArgumentException(e);
return Date.from(DateUtil.parseZonedDateTime(text, formatter, UTC).toInstant());
} catch (final DateTimeParseException dpe) {
return Date.from(LocalDateTime.parse(text).toInstant(ZoneOffset.UTC));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class DateWithCopyConverter implements Adapter<Date, String> {
private final Adapter<Date, String> delegate;

public DateWithCopyConverter(final Adapter<Date, String> delegate) {
this.delegate = delegate == null ? new ConverterAdapter<>(new DateConverter("yyyyMMddHHmmssZ"), Date.class) : delegate;
this.delegate = delegate == null ? new ConverterAdapter<>(DateConverter.ISO_8601_SHORT, Date.class) : delegate;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.johnzon.mapper.converter;

import org.apache.johnzon.mapper.Converter;

import java.util.UUID;

public class UUIDConverter implements Converter<UUID> {
@Override
public String toString(UUID instance) {
return instance.toString();
}

@Override
public UUID fromString(String text) {
return UUID.fromString(text);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@
import org.apache.johnzon.mapper.converter.StringConverter;
import org.apache.johnzon.mapper.converter.URIConverter;
import org.apache.johnzon.mapper.converter.URLConverter;
import org.apache.johnzon.mapper.converter.UUIDConverter;
import org.apache.johnzon.mapper.internal.AdapterKey;
import org.apache.johnzon.mapper.internal.ConverterAdapter;
import org.apache.johnzon.mapper.util.DateUtil;

import java.lang.reflect.Type;
import java.math.BigDecimal;
Expand All @@ -50,28 +52,19 @@
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQueries;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Stream;

import static java.time.temporal.ChronoField.DAY_OF_MONTH;
import static java.time.temporal.ChronoField.HOUR_OF_DAY;
import static java.time.temporal.ChronoField.MILLI_OF_SECOND;
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
import static java.time.temporal.ChronoField.YEAR;
import static java.util.stream.Collectors.toSet;

// important: override all usages,
Expand Down Expand Up @@ -297,6 +290,9 @@ public OffsetTime fromString(final String text) {
}
}, OffsetTime.class));
}
if (from == UUID.class) {
return add(key, new ConverterAdapter<>(new UUIDConverter(), UUID.class));
}
return null;
}

Expand All @@ -312,7 +308,7 @@ public String toString(final OffsetDateTime instance) {
@Override
public OffsetDateTime fromString(final String text) {
try {
return parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC).toOffsetDateTime();
return DateUtil.parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC).toOffsetDateTime();
} catch (final DateTimeParseException dpe) {
return OffsetDateTime.parse(text);
}
Expand Down Expand Up @@ -344,7 +340,7 @@ public String toString(final ZonedDateTime instance) {
@Override
public ZonedDateTime fromString(final String text) {
try {
return parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC);
return DateUtil.parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC);
} catch (final DateTimeParseException dpe) {
return ZonedDateTime.parse(text);
}
Expand Down Expand Up @@ -377,7 +373,7 @@ public String toString(final LocalDateTime instance) {
@Override
public LocalDateTime fromString(final String text) {
try {
return parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC).toLocalDateTime();
return DateUtil.parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC).toLocalDateTime();
} catch (final DateTimeParseException dpe) {
return LocalDateTime.parse(text);
}
Expand Down Expand Up @@ -409,7 +405,7 @@ public String toString(final LocalDate instance) {
@Override
public LocalDate fromString(final String text) {
try {
return parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC).toLocalDate();
return DateUtil.parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC).toLocalDate();
} catch (final DateTimeParseException dpe) {
return LocalDate.parse(text);
}
Expand Down Expand Up @@ -440,7 +436,7 @@ public String toString(final Instant instance) {

@Override
public Instant fromString(final String text) {
return parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC).toInstant();
return DateUtil.parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC).toInstant();
}
}, Instant.class));
}
Expand Down Expand Up @@ -468,7 +464,7 @@ public String toString(final GregorianCalendar instance) {

@Override
public GregorianCalendar fromString(final String text) {
final ZonedDateTime zonedDateTime = parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC);
final ZonedDateTime zonedDateTime = DateUtil.parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC);
final Calendar instance = GregorianCalendar.getInstance();
instance.setTimeZone(TimeZone.getTimeZone(zonedDateTime.getZone()));
instance.setTime(Date.from(zonedDateTime.toInstant()));
Expand Down Expand Up @@ -500,7 +496,7 @@ public String toString(final Calendar instance) {

@Override
public Calendar fromString(final String text) {
final ZonedDateTime zonedDateTime = parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC);
final ZonedDateTime zonedDateTime = DateUtil.parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC);
final Calendar instance = Calendar.getInstance();
instance.setTimeZone(TimeZone.getTimeZone(zonedDateTime.getZone()));
instance.setTime(Date.from(zonedDateTime.toInstant()));
Expand Down Expand Up @@ -529,7 +525,7 @@ public Calendar fromString(final String text) {

private Adapter<?, ?> addDateConverter(final AdapterKey key) {
if (useShortISO8601Format) {
return add(key, new ConverterAdapter<>(new DateConverter("yyyyMMddHHmmssZ"), Date.class));
return add(key, new ConverterAdapter<>(DateConverter.ISO_8601_SHORT, Date.class));
}
final ZoneId zoneIDUTC = ZoneId.of("UTC");
if (dateTimeFormatter != null) {
Expand All @@ -543,7 +539,7 @@ public String toString(final Date instance) {
@Override
public Date fromString(final String text) {
try {
return Date.from(parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC).toInstant());
return Date.from(DateUtil.parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC).toInstant());
} catch (final DateTimeParseException dpe) {
return Date.from(LocalDateTime.parse(text).toInstant(ZoneOffset.UTC));
}
Expand All @@ -568,22 +564,6 @@ public Date fromString(final String text) {
}, Date.class));
}

private static ZonedDateTime parseZonedDateTime(final String text, final DateTimeFormatter formatter, final ZoneId defaultZone) {
final TemporalAccessor parse = formatter.parse(text);
ZoneId zone = parse.query(TemporalQueries.zone());
if (Objects.isNull(zone)) {
zone = defaultZone;
}
final int year = parse.isSupported(YEAR) ? parse.get(YEAR) : 0;
final int month = parse.isSupported(MONTH_OF_YEAR) ? parse.get(MONTH_OF_YEAR) : 0;
final int day = parse.isSupported(DAY_OF_MONTH) ? parse.get(DAY_OF_MONTH) : 0;
final int hour = parse.isSupported(HOUR_OF_DAY) ? parse.get(HOUR_OF_DAY) : 0;
final int minute = parse.isSupported(MINUTE_OF_HOUR) ? parse.get(MINUTE_OF_HOUR) : 0;
final int second = parse.isSupported(SECOND_OF_MINUTE) ? parse.get(SECOND_OF_MINUTE) : 0;
final int millisecond = parse.isSupported(MILLI_OF_SECOND) ? parse.get(MILLI_OF_SECOND) : 0;
return ZonedDateTime.of(year, month, day, hour, minute, second, millisecond, zone);
}

private static void checkForDeprecatedTimeZone(final String text) {
switch (text) {
case "CST": // really for TCK, this sucks for end users so we don't fail for all deprecated zones
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.johnzon.mapper.util;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQueries;

public final class DateUtil {
private DateUtil() {

}

public static ZonedDateTime parseZonedDateTime(final String text, final DateTimeFormatter formatter, ZoneId defaultZone) {
final TemporalAccessor parse = formatter.parse(text);
ZoneId zone = parse.query(TemporalQueries.zone());
if (zone == null) {
zone = defaultZone;
}

return ZonedDateTime.of(
ifSupported(parse, ChronoField.YEAR),
ifSupported(parse, ChronoField.MONTH_OF_YEAR),
ifSupported(parse, ChronoField.DAY_OF_MONTH),
ifSupported(parse, ChronoField.HOUR_OF_DAY),
ifSupported(parse, ChronoField.MINUTE_OF_HOUR),
ifSupported(parse, ChronoField.SECOND_OF_MINUTE),
ifSupported(parse, ChronoField.MILLI_OF_SECOND),
zone);
}

public static int ifSupported(TemporalAccessor temporal, ChronoField unit) {
if (temporal.isSupported(unit)) {
return temporal.get(unit);
}

return 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ public void dateFromObject() throws Exception {
public void dateFromString() throws Exception {
assertMessage("{ \"date\" : \"Supercalifragilisticexpialidocious\" }",
"Widget property 'date' of type Date cannot be mapped to json string value: \"Supercalifragilisti...\n" +
"java.text.ParseException: Unparseable date: \"Supercalifragilisticexpialidocious\"");
"Text 'Supercalifragilisticexpialidocious' could not be parsed at index 0");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.johnzon.mapper.converter;

import org.apache.johnzon.mapper.Mapper;
import org.apache.johnzon.mapper.MapperBuilder;
import org.junit.Test;

import java.util.UUID;

import static org.junit.Assert.assertEquals;

public class UUIDConverterTest {
private static final UUID THE_UUID = UUID.randomUUID();

@Test
public void serialize() {
Mapper mapper = new MapperBuilder().build();
String json = mapper.writeObjectAsString(THE_UUID);

assertEquals("\"" + THE_UUID + "\"", json);
}

@Test
public void deserialize() {
Mapper mapper = new MapperBuilder().build();
UUID uuid = mapper.readObject("\"" + THE_UUID + "\"", UUID.class);

assertEquals(THE_UUID, uuid);
}
}