diff --git a/src/main/java/com/cedarsoftware/util/convert/Converter.java b/src/main/java/com/cedarsoftware/util/convert/Converter.java index e7be99bc..8bd7571d 100644 --- a/src/main/java/com/cedarsoftware/util/convert/Converter.java +++ b/src/main/java/com/cedarsoftware/util/convert/Converter.java @@ -18,6 +18,7 @@ import java.time.Year; import java.time.YearMonth; import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.AbstractMap; import java.util.Calendar; @@ -678,6 +679,7 @@ private static void buildFactoryConversions() { DEFAULT_FACTORY.put(pair(YearMonth.class, String.class), StringConversions::toString); DEFAULT_FACTORY.put(pair(Period.class, String.class), StringConversions::toString); DEFAULT_FACTORY.put(pair(ZoneId.class, String.class), StringConversions::toString); + DEFAULT_FACTORY.put(pair(ZoneOffset.class, String.class), StringConversions::toString); DEFAULT_FACTORY.put(pair(OffsetTime.class, String.class), OffsetTimeConversions::toString); DEFAULT_FACTORY.put(pair(OffsetDateTime.class, String.class), OffsetDateTimeConversions::toString); DEFAULT_FACTORY.put(pair(Year.class, String.class), YearConversions::toString); @@ -718,7 +720,12 @@ private static void buildFactoryConversions() { DEFAULT_FACTORY.put(pair(String.class, ZoneId.class), StringConversions::toZoneId); DEFAULT_FACTORY.put(pair(Map.class, ZoneId.class), MapConversions::toZoneId); -// java.time.ZoneOffset = com.cedarsoftware.util.io.DEFAULT_FACTORY.ZoneOffsetFactory + // ZoneOffset conversions supported + DEFAULT_FACTORY.put(pair(Void.class, ZoneOffset.class), VoidConversions::toNull); + DEFAULT_FACTORY.put(pair(ZoneOffset.class, ZoneOffset.class), Converter::identity); + DEFAULT_FACTORY.put(pair(String.class, ZoneOffset.class), StringConversions::toZoneOffset); + DEFAULT_FACTORY.put(pair(Map.class, ZoneOffset.class), MapConversions::toZoneOffset); + // java.time.ZoneRegion = com.cedarsoftware.util.io.DEFAULT_FACTORY.ZoneIdFactory // MonthDay conversions supported @@ -838,6 +845,7 @@ private static void buildFactoryConversions() { DEFAULT_FACTORY.put(pair(YearMonth.class, Map.class), YearMonthConversions::toMap); DEFAULT_FACTORY.put(pair(Period.class, Map.class), PeriodConversions::toMap); DEFAULT_FACTORY.put(pair(ZoneId.class, Map.class), ZoneIdConversions::toMap); + DEFAULT_FACTORY.put(pair(ZoneOffset.class, Map.class), ZoneOffsetConversions::toMap); DEFAULT_FACTORY.put(pair(Class.class, Map.class), MapConversions::initMap); DEFAULT_FACTORY.put(pair(UUID.class, Map.class), MapConversions::initMap); DEFAULT_FACTORY.put(pair(Calendar.class, Map.class), MapConversions::initMap); @@ -875,6 +883,7 @@ public Converter(ConverterOptions options) { * many other JDK classes, including Map. For Map, often it will seek a 'value' * field, however, for some complex objects, like UUID, it will look for specific * fields within the Map to perform the conversion. + * @see #getSupportedConversions() * @return An instanceof targetType class, based upon the value passed in. */ public T convert(Object from, Class toType) { @@ -907,6 +916,7 @@ public T convert(Object from, Class toType) { * fields within the Map to perform the conversion. * @param options ConverterOptions - allows you to specify locale, ZoneId, etc. to support conversion * operations. + * @see #getSupportedConversions() * @return An instanceof targetType class, based upon the value passed in. */ @SuppressWarnings("unchecked") diff --git a/src/main/java/com/cedarsoftware/util/convert/MapConversions.java b/src/main/java/com/cedarsoftware/util/convert/MapConversions.java index d1fb44dd..f5577b2a 100644 --- a/src/main/java/com/cedarsoftware/util/convert/MapConversions.java +++ b/src/main/java/com/cedarsoftware/util/convert/MapConversions.java @@ -62,7 +62,9 @@ public final class MapConversions { private static final String DAY = "day"; private static final String DAYS = "days"; private static final String HOUR = "hour"; + private static final String HOURS = "hours"; private static final String MINUTE = "minute"; + private static final String MINUTES = "minutes"; private static final String SECOND = "second"; private static final String SECONDS = "seconds"; private static final String NANO = "nano"; @@ -336,6 +338,19 @@ static ZoneId toZoneId(Object from, Converter converter, ConverterOptions option } } + private static final String[] ZONE_OFFSET_PARAMS = new String[] { HOURS, MINUTES, SECONDS }; + static ZoneOffset toZoneOffset(Object from, Converter converter, ConverterOptions options) { + Map map = (Map) from; + if (map.containsKey(HOURS)) { + int hours = converter.convert(map.get(HOURS), int.class, options); + int minutes = converter.convert(map.get(MINUTES), int.class, options); // optional + int seconds = converter.convert(map.get(SECONDS), int.class, options); // optional + return ZoneOffset.ofHoursMinutesSeconds(hours, minutes, seconds); + } else { + return fromValueForMultiKey(from, converter, options, ZoneOffset.class, ZONE_OFFSET_PARAMS); + } + } + static Year toYear(Object from, Converter converter, ConverterOptions options) { return fromSingleKey(from, converter, options, YEAR, Year.class); } diff --git a/src/main/java/com/cedarsoftware/util/convert/StringConversions.java b/src/main/java/com/cedarsoftware/util/convert/StringConversions.java index 5ed8c4f2..b4128a4f 100644 --- a/src/main/java/com/cedarsoftware/util/convert/StringConversions.java +++ b/src/main/java/com/cedarsoftware/util/convert/StringConversions.java @@ -18,6 +18,7 @@ import java.time.Year; import java.time.YearMonth; import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -407,6 +408,19 @@ static ZoneId toZoneId(Object from, Converter converter, ConverterOptions option } } + static ZoneOffset toZoneOffset(Object from, Converter converter, ConverterOptions options) { + String s = StringUtilities.trimToNull(asString(from)); + if (s == null) { + return null; + } + try { + return ZoneOffset.of(s); + } + catch (Exception e) { + throw new IllegalArgumentException("Unknown time-zone offset: '" + s + "'"); + } + } + static OffsetDateTime toOffsetDateTime(Object from, Converter converter, ConverterOptions options) { String s = StringUtilities.trimToNull(asString(from)); if (s == null) { diff --git a/src/main/java/com/cedarsoftware/util/convert/ZoneOffsetConversions.java b/src/main/java/com/cedarsoftware/util/convert/ZoneOffsetConversions.java new file mode 100644 index 00000000..4c7c2e0d --- /dev/null +++ b/src/main/java/com/cedarsoftware/util/convert/ZoneOffsetConversions.java @@ -0,0 +1,45 @@ +package com.cedarsoftware.util.convert; + +import java.time.ZoneOffset; +import java.util.Map; + +import com.cedarsoftware.util.CompactLinkedMap; + +/** + * @author John DeRegnaucourt (jdereg@gmail.com) + *
+ * Copyright (c) Cedar Software LLC + *

+ * Licensed 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 + *

+ * License + *

+ * 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. + */ +public final class ZoneOffsetConversions { + + private ZoneOffsetConversions() {} + + static Map toMap(Object from, Converter converter, ConverterOptions options) { + ZoneOffset offset = (ZoneOffset) from; + Map target = new CompactLinkedMap<>(); + int totalSeconds = offset.getTotalSeconds(); + + // Calculate hours, minutes, and seconds + int hours = totalSeconds / 3600; + int minutes = (totalSeconds % 3600) / 60; + int seconds = totalSeconds % 60; + target.put("hours", hours); + target.put("minutes", minutes); + if (seconds != 0) { + target.put("seconds", seconds); + } + return target; + } +} diff --git a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java index a0a1d508..02e198d4 100644 --- a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java +++ b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java @@ -7,6 +7,7 @@ import java.time.Year; import java.time.YearMonth; import java.time.ZoneId; +import java.time.ZoneOffset; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -364,6 +365,32 @@ class ConverterEverythingTest { mapOf("_v", TOKYO_Z), TOKYO_Z }, { mapOf("zone", mapOf("_v", TOKYO_Z)), TOKYO_Z }, }); + + // ZoneOffset + TEST_FACTORY.put(pair(Void.class, ZoneOffset.class), new Object[][] { + { null, null }, + }); + TEST_FACTORY.put(pair(ZoneOffset.class, ZoneOffset.class), new Object[][] { + { ZoneOffset.of("-05:00"), ZoneOffset.of("-05:00") }, + { ZoneOffset.of("+5"), ZoneOffset.of("+05:00") }, + }); + TEST_FACTORY.put(pair(String.class, ZoneOffset.class), new Object[][] { + { "-00:00", ZoneOffset.of("+00:00") }, + { "-05:00", ZoneOffset.of("-05:00") }, + { "+5", ZoneOffset.of("+05:00") }, + { "+05:00:01", ZoneOffset.of("+05:00:01") }, + { "America/New_York", new IllegalArgumentException("Unknown time-zone offset: 'America/New_York'") }, + }); + TEST_FACTORY.put(pair(Map.class, ZoneOffset.class), new Object[][] { + { mapOf("_v", "-10"), ZoneOffset.of("-10:00") }, + { mapOf("hours", -10L), ZoneOffset.of("-10:00") }, + { mapOf("hours", -10L, "minutes", "0"), ZoneOffset.of("-10:00") }, + { mapOf("hrs", -10L, "mins", "0"), new IllegalArgumentException("Map to ZoneOffset the map must include one of the following: [hours, minutes, seconds], [_v], or [value]") }, + { mapOf("hours", -10L, "minutes", "0", "seconds", 0), ZoneOffset.of("-10:00") }, + { mapOf("hours", "-10", "minutes", (byte)-15, "seconds", "-1"), ZoneOffset.of("-10:15:01") }, + { mapOf("hours", "10", "minutes", (byte)15, "seconds", true), ZoneOffset.of("+10:15:01") }, + { mapOf("hours", mapOf("_v","10"), "minutes", mapOf("_v", (byte)15), "seconds", mapOf("_v", true)), ZoneOffset.of("+10:15:01") }, // full recursion + }); } @BeforeEach