Skip to content

Commit

Permalink
Merge pull request #96 from kpartlow/master
Browse files Browse the repository at this point in the history
Added Instant.parse before DateUtiltities.parse, made ZoneDateTime come back equivalent round trip.
  • Loading branch information
jdereg committed Feb 13, 2024
2 parents eb67242 + 5a3ff6a commit ec18bca
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 26 deletions.
7 changes: 4 additions & 3 deletions src/main/java/com/cedarsoftware/util/DateUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,10 @@ public static ZonedDateTime parseDate(String dateStr, ZoneId defaultZoneId, bool
tz = matcher.group(5).trim();
}
if (matcher.group(6) != null) {
if (StringUtilities.isEmpty(tz)) { // Only use timezone name when offset is not used
tz = stripBrackets(matcher.group(6).trim());
}
// to make round trip of ZonedDateTime equivalent we need to use the original Zone as ZoneId
// ZoneId is a much broader definition handling multiple possible dates, and we want this to
// be equivalent to the original zone that was used if one was present.
tz = stripBrackets(matcher.group(6).trim());
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/cedarsoftware/util/convert/Converter.java
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@ private static void buildFactoryConversions() {
CONVERSION_DB.put(pair(OffsetDateTime.class, OffsetDateTime.class), Converter::identity);
CONVERSION_DB.put(pair(Map.class, OffsetDateTime.class), MapConversions::toOffsetDateTime);
CONVERSION_DB.put(pair(String.class, OffsetDateTime.class), StringConversions::toOffsetDateTime);
CONVERSION_DB.put(pair(Long.class, OffsetDateTime.class), NumberConversions::toOffsetDateTime);

// toOffsetTime
CONVERSION_DB.put(pair(Void.class, OffsetTime.class), VoidConversions::toNull);
Expand Down Expand Up @@ -852,6 +853,7 @@ private static void buildFactoryConversions() {
CONVERSION_DB.put(pair(Number.class, Map.class), MapConversions::initMap);
CONVERSION_DB.put(pair(Map.class, Map.class), MapConversions::toMap);
CONVERSION_DB.put(pair(Enum.class, Map.class), MapConversions::initMap);
CONVERSION_DB.put(pair(OffsetDateTime.class, Map.class), OffsetDateTimeConversions::toMap);
}

public Converter(ConverterOptions options) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ static Date toDate(Object from, Converter converter) {
static Calendar toCalendar(Object from, Converter converter) {
return CalendarConversions.create(toLong(from, converter), converter);
}

static BigInteger toBigInteger(Object from, Converter converter) {
return BigInteger.valueOf(toLong(from, converter));
}
Expand Down
43 changes: 39 additions & 4 deletions src/main/java/com/cedarsoftware/util/convert/MapConversions.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
final class MapConversions {
private static final String V = "_v";
private static final String VALUE = "value";
private static final String DATE = "date";
private static final String TIME = "time";
private static final String ZONE = "zone";
private static final String YEAR = "year";
Expand All @@ -73,6 +74,14 @@ final class MapConversions {
private static final String MOST_SIG_BITS = "mostSigBits";
private static final String LEAST_SIG_BITS = "leastSigBits";

static final String OFFSET = "offset";

private static final String TOTAL_SECONDS = "totalSeconds";

static final String DATE_TIME = "dateTime";

private static final String ID = "id";

private MapConversions() {}

public static final String KEY_VALUE_ERROR_MESSAGE = "To convert from Map to %s the map must include one of the following: %s[_v], or [value] with associated values.";
Expand Down Expand Up @@ -245,8 +254,12 @@ static OffsetTime toOffsetTime(Object from, Converter converter) {
private static final String[] OFFSET_DATE_TIME_PARAMS = new String[] { YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, NANO, OFFSET_HOUR, OFFSET_MINUTE };
static OffsetDateTime toOffsetDateTime(Object from, Converter converter) {
Map<?, ?> map = (Map<?, ?>) from;
if (map.containsKey(YEAR) && map.containsKey(OFFSET_HOUR)) {
ConverterOptions options = converter.getOptions();
ConverterOptions options = converter.getOptions();
if (map.containsKey(DATE_TIME) && map.containsKey(OFFSET)) {
LocalDateTime dateTime = converter.convert(map.get(DATE_TIME), LocalDateTime.class);
ZoneOffset zoneOffset = converter.convert(map.get(OFFSET), ZoneOffset.class);
return OffsetDateTime.of(dateTime, zoneOffset);
} else if (map.containsKey(YEAR) && map.containsKey(OFFSET_HOUR)) {
int year = converter.convert(map.get(YEAR), int.class);
int month = converter.convert(map.get(MONTH), int.class);
int day = converter.convert(map.get(DAY), int.class);
Expand All @@ -263,12 +276,30 @@ static OffsetDateTime toOffsetDateTime(Object from, Converter converter) {
}
}

private static final String[] LOCAL_DATE_TIME_PARAMS = new String[] { DATE, TIME };

static LocalDateTime toLocalDateTime(Object from, Converter converter) {
return fromValue(from, converter, LocalDateTime.class);
Map<?, ?> map = (Map<?, ?>) from;
if (map.containsKey(DATE)) {
LocalDate localDate = converter.convert(map.get(DATE), LocalDate.class);
LocalTime localTime = map.containsKey(TIME) ? converter.convert(map.get(TIME), LocalTime.class) : LocalTime.MIDNIGHT;
// validate date isn't null?
return LocalDateTime.of(localDate, localTime);
} else {
return fromValueForMultiKey(from, converter, LocalDateTime.class, LOCAL_DATE_TIME_PARAMS);
}
}

private static final String[] ZONED_DATE_TIME_PARAMS = new String[] { ZONE, DATE_TIME };
static ZonedDateTime toZonedDateTime(Object from, Converter converter) {
return fromValue(from, converter, ZonedDateTime.class);
Map<?, ?> map = (Map<?, ?>) from;
if (map.containsKey(ZONE) && map.containsKey(DATE_TIME)) {
ZoneId zoneId = converter.convert(map.get(ZONE), ZoneId.class);
LocalDateTime localDateTime = converter.convert(map.get(DATE_TIME), LocalDateTime.class);
return ZonedDateTime.of(localDateTime, zoneId);
} else {
return fromValueForMultiKey(from, converter, ZonedDateTime.class, ZONED_DATE_TIME_PARAMS);
}
}

static Class<?> toClass(Object from, Converter converter) {
Expand Down Expand Up @@ -347,6 +378,10 @@ static ZoneId toZoneId(Object from, Converter converter) {
ConverterOptions options = converter.getOptions();
ZoneId zoneId = converter.convert(map.get(ZONE), ZoneId.class);
return zoneId;
} else if (map.containsKey(ID)) {
ConverterOptions options = converter.getOptions();
ZoneId zoneId = converter.convert(map.get(ID), ZoneId.class);
return zoneId;
} else {
return fromSingleKey(from, converter, ZONE, ZoneId.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.Year;
import java.time.ZonedDateTime;
import java.util.Calendar;
Expand Down Expand Up @@ -229,11 +230,15 @@ static LocalDateTime toLocalDateTime(Object from, Converter converter) {
static LocalTime toLocalTime(Object from, Converter converter) {
return toZonedDateTime(from, converter).toLocalTime();
}

static ZonedDateTime toZonedDateTime(Object from, Converter converter) {
return toInstant(from, converter).atZone(converter.getOptions().getZoneId());
}

static OffsetDateTime toOffsetDateTime(Object from, Converter converter) {
return toZonedDateTime(from, converter).toOffsetDateTime();
}

static Year toYear(Object from, Converter converter) {
if (from instanceof Byte) {
throw new IllegalArgumentException("Cannot convert Byte to Year, not enough precision.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

import com.cedarsoftware.util.CompactLinkedMap;

/**
* @author Kenny Partlow ([email protected])
* <br>
Expand All @@ -34,11 +38,6 @@
final class OffsetDateTimeConversions {
private OffsetDateTimeConversions() {}

static OffsetDateTime toDifferentZone(Object from, Converter converter) {
OffsetDateTime offsetDateTime = (OffsetDateTime) from;
return offsetDateTime.toInstant().atZone(converter.getOptions().getZoneId()).toOffsetDateTime();
}

static Instant toInstant(Object from, Converter converter) {
return ((OffsetDateTime)from).toInstant();
}
Expand All @@ -48,15 +47,15 @@ static long toLong(Object from, Converter converter) {
}

static LocalDateTime toLocalDateTime(Object from, Converter converter) {
return toDifferentZone(from, converter).toLocalDateTime();
return ((OffsetDateTime)from).toLocalDateTime();
}

static LocalDate toLocalDate(Object from, Converter converter) {
return toDifferentZone(from, converter).toLocalDate();
return ((OffsetDateTime)from).toLocalDate();
}

static LocalTime toLocalTime(Object from, Converter converter) {
return toDifferentZone(from, converter).toLocalTime();
return ((OffsetDateTime)from).toLocalTime();
}

static AtomicLong toAtomicLong(Object from, Converter converter) {
Expand Down Expand Up @@ -98,4 +97,16 @@ static String toString(Object from, Converter converter) {
OffsetDateTime offsetDateTime = (OffsetDateTime) from;
return offsetDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}

static Map<String, Object> toMap(Object from, Converter converter) {
OffsetDateTime offsetDateTime = (OffsetDateTime) from;

LocalDateTime localDateTime = offsetDateTime.toLocalDateTime();
ZoneOffset zoneOffset = offsetDateTime.getOffset();

Map<String, Object> target = new CompactLinkedMap<>();
target.put(MapConversions.DATE_TIME, converter.convert(localDateTime, String.class));
target.put(MapConversions.OFFSET, converter.convert(zoneOffset, String.class));
return target;
}
}
12 changes: 6 additions & 6 deletions src/test/java/com/cedarsoftware/util/TestDateUtilities.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
package com.cedarsoftware.util;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.text.SimpleDateFormat;
Expand All @@ -17,6 +11,12 @@
import java.util.TimeZone;
import java.util.stream.Stream;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;

import com.cedarsoftware.util.DeepEquals;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
Expand All @@ -38,6 +37,8 @@
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.NullSource;

import com.cedarsoftware.util.DeepEquals;

import static com.cedarsoftware.util.ArrayUtilities.EMPTY_BYTE_ARRAY;
import static com.cedarsoftware.util.ArrayUtilities.EMPTY_CHAR_ARRAY;
import static com.cedarsoftware.util.Converter.zonedDateTimeToMillis;
Expand Down Expand Up @@ -3059,7 +3060,7 @@ void testMapToLocalDateTime()
map.clear();
assertThatThrownBy(() -> this.converter.convert(map, LocalDateTime.class))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("To convert from Map to LocalDateTime the map must include one of the following: [_v], or [value] with associated values");
.hasMessageContaining("To convert from Map to LocalDateTime the map must include one of the following: [date, time], [_v], or [value] with associated values");
}

@Test
Expand All @@ -3078,7 +3079,7 @@ void testMapToZonedDateTime()
map.clear();
assertThatThrownBy(() -> this.converter.convert(map, ZonedDateTime.class))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("To convert from Map to ZonedDateTime the map must include one of the following: [_v], or [value] with associated values");
.hasMessageContaining("To convert from Map to ZonedDateTime the map must include one of the following: [zone, dateTime], [_v], or [value] with associated values");

}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.cedarsoftware.util.convert;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.stream.Stream;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import com.cedarsoftware.util.DeepEquals;

import static org.junit.jupiter.api.Assertions.assertTrue;

class ZonedDateTimeConversionsTests {

private Converter converter;


private static final ZoneId TOKYO = ZoneId.of("Asia/Tokyo");
private static final ZoneId CHICAGO = ZoneId.of("America/Chicago");
private static final ZoneId ALASKA = ZoneId.of("America/Anchorage");

private static final ZonedDateTime ZDT_1 = ZonedDateTime.of(LocalDateTime.of(2019, 12, 15, 9, 7, 16, 2000), CHICAGO);
private static final ZonedDateTime ZDT_2 = ZonedDateTime.of(LocalDateTime.of(2027, 12, 23, 9, 7, 16, 2000), TOKYO);
private static final ZonedDateTime ZDT_3 = ZonedDateTime.of(LocalDateTime.of(2027, 12, 23, 9, 7, 16, 2000), ALASKA);

@BeforeEach
public void before() {
// create converter with default options
this.converter = new Converter(new DefaultConverterOptions());
}

private static Stream<Arguments> roundTripZDT() {
return Stream.of(
Arguments.of(ZDT_1),
Arguments.of(ZDT_2),
Arguments.of(ZDT_3)
);
}

@ParameterizedTest
@MethodSource("roundTripZDT")
void testZonedDateTime(ZonedDateTime zdt) {

String value = this.converter.convert(zdt, String.class);
ZonedDateTime actual = this.converter.convert(value, ZonedDateTime.class);

assertTrue(DeepEquals.deepEquals(actual, zdt));

value = DateTimeFormatter.ISO_ZONED_DATE_TIME.format(zdt);
actual = this.converter.convert(value, ZonedDateTime.class);

assertTrue(DeepEquals.deepEquals(actual, zdt));
}
}

0 comments on commit ec18bca

Please sign in to comment.