From d5ba29e1465aaf70932822f2f34c801b0a6b9c13 Mon Sep 17 00:00:00 2001 From: John DeRegnaucourt Date: Sat, 23 Mar 2024 12:01:03 -0400 Subject: [PATCH] Add better errorhandling and more map support for date-time data types. --- .../com/cedarsoftware/util/DateUtilities.java | 2 +- .../util/convert/MapConversions.java | 115 +++++++++++------- .../util/convert/ConverterEverythingTest.java | 45 +++++-- 3 files changed, 103 insertions(+), 59 deletions(-) diff --git a/src/main/java/com/cedarsoftware/util/DateUtilities.java b/src/main/java/com/cedarsoftware/util/DateUtilities.java index b5016ea3..847fd299 100644 --- a/src/main/java/com/cedarsoftware/util/DateUtilities.java +++ b/src/main/java/com/cedarsoftware/util/DateUtilities.java @@ -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); diff --git a/src/main/java/com/cedarsoftware/util/convert/MapConversions.java b/src/main/java/com/cedarsoftware/util/convert/MapConversions.java index d1f3b70a..417e8bfe 100644 --- a/src/main/java/com/cedarsoftware/util/convert/MapConversions.java +++ b/src/main/java/com/cedarsoftware/util/convert/MapConversions.java @@ -102,13 +102,16 @@ private MapConversions() {} static Object toUUID(Object from, Converter converter) { Map map = (Map) 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); } @@ -172,59 +175,32 @@ static AtomicBoolean toAtomicBoolean(Object from, Converter converter) { } static java.sql.Date toSqlDate(Object from, Converter converter) { - Map map = (Map) 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 map = (Map) 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 map = (Map) 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 map = (Map) 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) { @@ -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 map = (Map) 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 map = (Map) from; diff --git a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java index 03959321..326823c0 100644 --- a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java +++ b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java @@ -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}, @@ -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")}, + }); } /** @@ -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")}, }); }