Skip to content

Commit

Permalink
Add better errorhandling and more map support for date-time data types.
Browse files Browse the repository at this point in the history
  • Loading branch information
jdereg committed Mar 23, 2024
1 parent 2f07faf commit d5ba29e
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 59 deletions.
2 changes: 1 addition & 1 deletion src/main/java/com/cedarsoftware/util/DateUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ public static ZonedDateTime parseDate(String dateStr, ZoneId defaultZoneId, bool
} else {
matcher = unixDateTimePattern.matcher(dateStr);
if (matcher.replaceFirst("").length() == dateStr.length()) {
throw new IllegalArgumentException("Unable to parse: " + dateStr + " as a date");
throw new IllegalArgumentException("Unable to parse: " + dateStr + " as a date-time");
}
year = matcher.group(6);
String mon = matcher.group(2);
Expand Down
115 changes: 68 additions & 47 deletions src/main/java/com/cedarsoftware/util/convert/MapConversions.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,16 @@ private MapConversions() {}
static Object toUUID(Object from, Converter converter) {
Map<String, Object> map = (Map<String, Object>) from;

if (map.containsKey(MapConversions.UUID)) {
return converter.convert(map.get(UUID), UUID.class);
Object uuid = map.get(UUID);
if (uuid != null) {
return converter.convert(uuid, UUID.class);
}

if (map.containsKey(MOST_SIG_BITS) && map.containsKey(LEAST_SIG_BITS)) {
long most = converter.convert(map.get(MOST_SIG_BITS), long.class);
long least = converter.convert(map.get(LEAST_SIG_BITS), long.class);
Object mostSigBits = map.get(MOST_SIG_BITS);
Object leastSigBits = map.get(LEAST_SIG_BITS);
if (mostSigBits != null && leastSigBits != null) {
long most = converter.convert(mostSigBits, long.class);
long least = converter.convert(leastSigBits, long.class);
return new UUID(most, least);
}

Expand Down Expand Up @@ -172,59 +175,32 @@ static AtomicBoolean toAtomicBoolean(Object from, Converter converter) {
}

static java.sql.Date toSqlDate(Object from, Converter converter) {
Map<String, Object> map = (Map<String, Object>) from;
if (map.containsKey(EPOCH_MILLIS)) {
Long epochMillis = toEpochMillis(from, converter);
if (epochMillis == null) {
return fromMap(from, converter, java.sql.Date.class, EPOCH_MILLIS);
} else if (map.containsKey(TIME) && !map.containsKey(DATE)) {
return fromMap(from, converter, java.sql.Date.class, TIME);
} else if (map.containsKey(TIME) && map.containsKey(DATE)) {
LocalDate date = converter.convert(map.get(DATE), LocalDate.class);
LocalTime time = converter.convert(map.get(TIME), LocalTime.class);
ZoneId zoneId = converter.convert(map.get(ZONE), ZoneId.class);
ZonedDateTime zdt = ZonedDateTime.of(date, time, zoneId);
return new java.sql.Date(zdt.toInstant().toEpochMilli());
}
return fromMap(from, converter, java.sql.Date.class, EPOCH_MILLIS);
return new java.sql.Date(epochMillis);
}

static Date toDate(Object from, Converter converter) {
Map<String, Object> map = (Map<String, Object>) from;
if (map.containsKey(EPOCH_MILLIS)) {
Long epochMillis = toEpochMillis(from, converter);
if (epochMillis == null) {
return fromMap(from, converter, Date.class, EPOCH_MILLIS);
} else if (map.containsKey(TIME) && !map.containsKey(DATE)) {
return fromMap(from, converter, Date.class, TIME);
} else if (map.containsKey(TIME) && map.containsKey(DATE)) {
LocalDate date = converter.convert(map.get(DATE), LocalDate.class);
LocalTime time = converter.convert(map.get(TIME), LocalTime.class);
ZoneId zoneId = converter.convert(map.get(ZONE), ZoneId.class);
ZonedDateTime zdt = ZonedDateTime.of(date, time, zoneId);
return new Date(zdt.toInstant().toEpochMilli());
}
return fromMap(map, converter, Date.class, EPOCH_MILLIS, NANOS);
return new Date(epochMillis);
}

static Timestamp toTimestamp(Object from, Converter converter) {
Map<String, Object> map = (Map<String, Object>) from;
if (map.containsKey(EPOCH_MILLIS)) {
long time = converter.convert(map.get(EPOCH_MILLIS), long.class);
int ns = converter.convert(map.get(NANOS), int.class);
Timestamp timeStamp = new Timestamp(time);
timeStamp.setNanos(ns);
return timeStamp;
} else if (map.containsKey(DATE) && map.containsKey(TIME) && map.containsKey(ZONE)) {
LocalDate date = converter.convert(map.get(DATE), LocalDate.class);
LocalTime time = converter.convert(map.get(TIME), LocalTime.class);
ZoneId zoneId = converter.convert(map.get(ZONE), ZoneId.class);
ZonedDateTime zdt = ZonedDateTime.of(date, time, zoneId);
return Timestamp.from(zdt.toInstant());
} else if (map.containsKey(TIME) && map.containsKey(NANOS)) {
long time = converter.convert(map.get(TIME), long.class);
int ns = converter.convert(map.get(NANOS), int.class);
Timestamp timeStamp = new Timestamp(time);
timeStamp.setNanos(ns);
return timeStamp;
Long epochMillis = toEpochMillis(from, converter);
if (epochMillis == null) {
return fromMap(from, converter, Timestamp.class, EPOCH_MILLIS, NANOS);
}
return fromMap(map, converter, Timestamp.class, EPOCH_MILLIS, NANOS);

Map<String, Object> map = (Map<String, Object>) from;
Timestamp timestamp = new Timestamp(epochMillis);
int ns = converter.convert(map.get(NANOS), int.class);
timestamp.setNanos(ns);
return timestamp;
}

static TimeZone toTimeZone(Object from, Converter converter) {
Expand Down Expand Up @@ -272,6 +248,51 @@ static Calendar toCalendar(Object from, Converter converter) {
return fromMap(from, converter, Calendar.class, DATE, TIME, ZONE);
}

static Long toEpochMillis(Object from, Converter converter) {
Map<String, Object> map = (Map<String, Object>) from;

Object epochMillis = map.get(EPOCH_MILLIS);
if (epochMillis != null) {
return converter.convert(epochMillis, long.class);
}

Object time = map.get(TIME);
Object date = map.get(DATE);
Object zone = map.get(ZONE);

// All 3 (date, time, zone)
if (time != null && date != null && zone != null) {
LocalDate ld = converter.convert(date, LocalDate.class);
LocalTime lt = converter.convert(time, LocalTime.class);
ZoneId zoneId = converter.convert(zone, ZoneId.class);
ZonedDateTime zdt = ZonedDateTime.of(ld, lt, zoneId);
return zdt.toInstant().toEpochMilli();
}

// Time only
if (time != null && date == null && zone == null) {
return converter.convert(time, Date.class).getTime();
}

// Time & Zone, no Date
if (time != null && date == null && zone != null) {
LocalDateTime ldt = converter.convert(time, LocalDateTime.class);
ZoneId zoneId = converter.convert(zone, ZoneId.class);
ZonedDateTime zdt = ZonedDateTime.of(ldt, zoneId);
return zdt.toInstant().toEpochMilli();
}

// Time & Date, no zone
if (time != null && date != null && zone == null) {
LocalDate ld = converter.convert(date, LocalDate.class);
LocalTime lt = converter.convert(time, LocalTime.class);
ZonedDateTime zdt = ZonedDateTime.of(ld, lt, converter.getOptions().getZoneId());
return zdt.toInstant().toEpochMilli();
}

return null;
}

static Locale toLocale(Object from, Converter converter) {
Map<String, String> map = (Map<String, String>) from;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -485,12 +485,6 @@ private static void loadMapTests() {
return map;
}, true},
});
TEST_DB.put(pair(java.sql.Date.class, Map.class), new Object[][] {
{ new java.sql.Date(-1L), mapOf(EPOCH_MILLIS, -1L, DATE, "1970-01-01", TIME, "08:59:59.999", ZONE, TOKYO_Z.toString()), true},
{ new java.sql.Date(0L), mapOf(EPOCH_MILLIS, 0L, DATE, "1970-01-01", TIME, "09:00", ZONE, TOKYO_Z.toString()), true},
{ new java.sql.Date(1L), mapOf(EPOCH_MILLIS, 1L, DATE, "1970-01-01", TIME, "09:00:00.001", ZONE, TOKYO_Z.toString()), true},
{ new java.sql.Date(1710714535152L), mapOf(EPOCH_MILLIS, 1710714535152L, DATE, "2024-03-18", TIME, "07:28:55.152", ZONE, TOKYO_Z.toString()), true},
});
TEST_DB.put(pair(Timestamp.class, Map.class), new Object[][] {
{ timestamp("1969-12-31T23:59:59.999999999Z"), mapOf(EPOCH_MILLIS, -1L, NANOS, 999999999, DATE, "1970-01-01", TIME, "08:59:59.999999999", ZONE, TOKYO_Z.toString()), true},
{ new Timestamp(-1L), mapOf(EPOCH_MILLIS, -1L, NANOS, 999000000, DATE, "1970-01-01", TIME, "08:59:59.999", ZONE, TOKYO_Z.toString()), true},
Expand Down Expand Up @@ -1720,6 +1714,26 @@ private static void loadSqlDateTests() {
{zdt("1970-01-01T00:00:00.001Z"), new java.sql.Date(1), true},
{zdt("1970-01-01T00:00:00.999Z"), new java.sql.Date(999), true},
});
TEST_DB.put(pair(Map.class, java.sql.Date.class), new Object[][] {
{ mapOf(EPOCH_MILLIS, -1L, DATE, "1970-01-01", TIME, "08:59:59.999", ZONE, TOKYO_Z.toString()), new java.sql.Date(-1L), true},
{ mapOf(EPOCH_MILLIS, 0L, DATE, "1970-01-01", TIME, "09:00", ZONE, TOKYO_Z.toString()), new java.sql.Date(0L), true},
{ mapOf(EPOCH_MILLIS, 1L, DATE, "1970-01-01", TIME, "09:00:00.001", ZONE, TOKYO_Z.toString()), new java.sql.Date(1L), true},
{ mapOf(EPOCH_MILLIS, 1710714535152L, DATE, "2024-03-18", TIME, "07:28:55.152", ZONE, TOKYO_Z.toString()), new java.sql.Date(1710714535152L), true},
{ mapOf(DATE, "1970-01-01", TIME, "00:00", ZONE, "Z"), new java.sql.Date(0L)},
{ mapOf(DATE, "X1970-01-01", TIME, "00:00", ZONE, "Z"), new IllegalArgumentException("Issue parsing date-time, other characters present: X")},
{ mapOf(DATE, "1970-01-01", TIME, "X00:00", ZONE, "Z"), new IllegalArgumentException("Unable to parse: X00:00 as a date-time")},
{ mapOf(DATE, "1970-01-01", TIME, "00:00", ZONE, "bad zone"), new IllegalArgumentException("Unknown time-zone ID: 'bad zone'")},
{ mapOf(TIME, "1970-01-01T00:00Z"), new java.sql.Date(0L)},
{ mapOf(TIME, "1970-01-01 00:00Z"), new java.sql.Date(0L)},
{ mapOf(TIME, "X1970-01-01 00:00Z"), new IllegalArgumentException("Issue parsing date-time, other characters present: X")},
{ mapOf(DATE, "1970-01-01", TIME, "09:00"), new java.sql.Date(0L)},
{ mapOf(DATE, "X1970-01-01", TIME, "09:00"), new IllegalArgumentException("Issue parsing date-time, other characters present: X")},
{ mapOf(DATE, "1970-01-01", TIME, "X09:00"), new IllegalArgumentException("Unable to parse: X09:00 as a date-time")},
{ mapOf(TIME, "1970-01-01T00:00", ZONE, "Z"), new java.sql.Date(0L)},
{ mapOf(TIME, "X1970-01-01T00:00", ZONE, "Z"), new IllegalArgumentException("Issue parsing date-time, other characters present: X")},
{ mapOf(TIME, "1970-01-01T00:00", ZONE, "bad zone"), new IllegalArgumentException("Unknown time-zone ID: 'bad zone'")},
{ mapOf("foo", "bar"), new IllegalArgumentException("Map to java.sql.Date the map must include one of the following: [epochMillis], [_v], or [value] with associated values")},
});
}

/**
Expand Down Expand Up @@ -1802,11 +1816,20 @@ private static void loadDateTests() {
{ mapOf(EPOCH_MILLIS, 0L, DATE, "1970-01-01", TIME, "09:00", ZONE, TOKYO_Z.toString()), new Date(0L), true},
{ mapOf(EPOCH_MILLIS, 1L, DATE, "1970-01-01", TIME, "09:00:00.001", ZONE, TOKYO_Z.toString()), new Date(1L), true},
{ mapOf(EPOCH_MILLIS, 1710714535152L, DATE, "2024-03-18", TIME, "07:28:55.152", ZONE, TOKYO_Z.toString()), new Date(1710714535152L), true},
{ mapOf(EPOCH_MILLIS, "bad date", DATE, "2024-03-18", TIME, "07:28:55.152", ZONE, TOKYO_Z.toString()), new IllegalArgumentException("Unable to parse: bad date")},
{ mapOf(DATE, "1970-01-01", TIME, "09:00:00", ZONE, TOKYO_Z.toString()), new Date(0)},
{ mapOf(DATE, "bad date", TIME, "09:00:00", ZONE, TOKYO_Z.toString()), new IllegalArgumentException("Unable to parse: bad date")},
{ mapOf(DATE, "1970-01-01", TIME, "bad time", ZONE, TOKYO_Z.toString()), new IllegalArgumentException("Unable to parse: bad time")},
{ mapOf(DATE, "1970-01-01", TIME, "09:00:00", ZONE, "bad zone"), new IllegalArgumentException("Unknown time-zone ID: 'bad zone'")},
{ mapOf(DATE, "1970-01-01", TIME, "00:00", ZONE, "Z"), new Date(0L)},
{ mapOf(DATE, "X1970-01-01", TIME, "00:00", ZONE, "Z"), new IllegalArgumentException("Issue parsing date-time, other characters present: X")},
{ mapOf(DATE, "1970-01-01", TIME, "X00:00", ZONE, "Z"), new IllegalArgumentException("Unable to parse: X00:00 as a date-time")},
{ mapOf(DATE, "1970-01-01", TIME, "00:00", ZONE, "bad zone"), new IllegalArgumentException("Unknown time-zone ID: 'bad zone'")},
{ mapOf(TIME, "1970-01-01T00:00Z"), new Date(0L)},
{ mapOf(TIME, "1970-01-01 00:00Z"), new Date(0L)},
{ mapOf(TIME, "X1970-01-01 00:00Z"), new IllegalArgumentException("Issue parsing date-time, other characters present: X")},
{ mapOf(DATE, "1970-01-01", TIME, "09:00"), new Date(0L)},
{ mapOf(DATE, "X1970-01-01", TIME, "09:00"), new IllegalArgumentException("Issue parsing date-time, other characters present: X")},
{ mapOf(DATE, "1970-01-01", TIME, "X09:00"), new IllegalArgumentException("Unable to parse: X09:00 as a date-time")},
{ mapOf(TIME, "1970-01-01T00:00", ZONE, "Z"), new Date(0L)},
{ mapOf(TIME, "X1970-01-01T00:00", ZONE, "Z"), new IllegalArgumentException("Issue parsing date-time, other characters present: X")},
{ mapOf(TIME, "1970-01-01T00:00", ZONE, "bad zone"), new IllegalArgumentException("Unknown time-zone ID: 'bad zone'")},
{ mapOf("foo", "bar"), new IllegalArgumentException("Map to Date the map must include one of the following: [epochMillis], [_v], or [value] with associated values")},
});
}

Expand Down

0 comments on commit d5ba29e

Please sign in to comment.