diff --git a/src/main/java/com/cedarsoftware/util/Converter.java b/src/main/java/com/cedarsoftware/util/Converter.java
index 8b38f291..ac718ec1 100644
--- a/src/main/java/com/cedarsoftware/util/Converter.java
+++ b/src/main/java/com/cedarsoftware/util/Converter.java
@@ -28,7 +28,7 @@
* to all destinations per each source. Close to 500 "out-of-the-box" conversions ship with the library.
*
* The Converter can be used as statically or as an instance. See the public static methods on this Converter class
- * to use statically. Any added conversions will added to a singleton instance maintained inside this class.
+ * to use statically. Any added conversions are added to a singleton instance maintained inside this class.
* Alternatively, you can instantiate the Converter class to get an instance, and the conversions you add, remove, or
* change will be scoped to just that instance.
*
diff --git a/src/main/java/com/cedarsoftware/util/convert/CalendarConversions.java b/src/main/java/com/cedarsoftware/util/convert/CalendarConversions.java
index cbdd7a16..22e46472 100644
--- a/src/main/java/com/cedarsoftware/util/convert/CalendarConversions.java
+++ b/src/main/java/com/cedarsoftware/util/convert/CalendarConversions.java
@@ -3,16 +3,19 @@
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
-import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZonedDateTime;
+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 (kpartlow@gmail.com)
*
@@ -106,8 +109,22 @@ static Calendar create(long epochMilli, Converter converter) {
}
static String toString(Object from, Converter converter) {
- SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
- simpleDateFormat.setTimeZone(converter.getOptions().getTimeZone());
- return simpleDateFormat.format(((Calendar) from).getTime());
+ ZonedDateTime zdt = toZonedDateTime(from, converter);
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSS]XXX", converter.getOptions().getLocale());
+ return formatter.format(zdt);
+ }
+
+ static Map toMap(Object from, Converter converter) {
+ Calendar cal = (Calendar) from;
+ Map target = new CompactLinkedMap<>();
+ target.put(MapConversions.YEAR, cal.get(Calendar.YEAR));
+ target.put(MapConversions.MONTH, cal.get(Calendar.MONTH) + 1);
+ target.put(MapConversions.DAY, cal.get(Calendar.DAY_OF_MONTH));
+ target.put(MapConversions.HOUR, cal.get(Calendar.HOUR_OF_DAY));
+ target.put(MapConversions.MINUTE, cal.get(Calendar.MINUTE));
+ target.put(MapConversions.SECOND, cal.get(Calendar.SECOND));
+ target.put(MapConversions.MILLI_SECONDS, cal.get(Calendar.MILLISECOND));
+ target.put(MapConversions.ZONE, cal.getTimeZone().getID());
+ return target;
}
}
diff --git a/src/main/java/com/cedarsoftware/util/convert/Converter.java b/src/main/java/com/cedarsoftware/util/convert/Converter.java
index 0a7feb36..52a1625d 100644
--- a/src/main/java/com/cedarsoftware/util/convert/Converter.java
+++ b/src/main/java/com/cedarsoftware/util/convert/Converter.java
@@ -54,7 +54,7 @@
* Boolean, for example, however, for primitive types, it chooses zero for the numeric ones, `false` for boolean,
* and 0 for char.
*
- * A Map can be converted to almost all JDL "data" classes. For example, UUID can be converted to/from a Map.
+ * A Map can be converted to almost all JDK "data" classes. For example, UUID can be converted to/from a Map.
* It is expected for the Map to have certain keys ("mostSigBits", "leastSigBits"). For the older Java Date/Time
* related classes, it expects "time" or "nanos", and for all others, a Map as the source, the "value" key will be
* used to source the value for the conversion.
@@ -904,7 +904,7 @@ private static void buildFactoryConversions() {
CONVERSION_DB.put(pair(ZoneOffset.class, Map.class), ZoneOffsetConversions::toMap);
CONVERSION_DB.put(pair(Class.class, Map.class), MapConversions::initMap);
CONVERSION_DB.put(pair(UUID.class, Map.class), MapConversions::initMap);
- CONVERSION_DB.put(pair(Calendar.class, Map.class), MapConversions::initMap);
+ CONVERSION_DB.put(pair(Calendar.class, Map.class), CalendarConversions::toMap);
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);
diff --git a/src/main/java/com/cedarsoftware/util/convert/MapConversions.java b/src/main/java/com/cedarsoftware/util/convert/MapConversions.java
index 007d5dbd..05c1a7be 100644
--- a/src/main/java/com/cedarsoftware/util/convert/MapConversions.java
+++ b/src/main/java/com/cedarsoftware/util/convert/MapConversions.java
@@ -71,6 +71,7 @@ final class MapConversions {
static final String MINUTES = "minutes";
static final String SECOND = "second";
static final String SECONDS = "seconds";
+ static final String MILLI_SECONDS = "millis";
static final String NANO = "nano";
static final String NANOS = "nanos";
static final String OFFSET_HOUR = "offsetHour";
@@ -205,24 +206,52 @@ static TimeZone toTimeZone(Object from, Converter converter) {
}
}
- private static final String[] CALENDAR_PARAMS = new String[] { TIME, ZONE };
+ private static final String[] CALENDAR_PARAMS = new String[] { YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, MILLI_SECONDS, ZONE };
static Calendar toCalendar(Object from, Converter converter) {
Map, ?> map = (Map, ?>) from;
if (map.containsKey(TIME)) {
Object zoneRaw = map.get(ZONE);
TimeZone tz;
- ConverterOptions options = converter.getOptions();
if (zoneRaw instanceof String) {
String zone = (String) zoneRaw;
tz = TimeZone.getTimeZone(zone);
} else {
- tz = TimeZone.getTimeZone(options.getZoneId());
+ tz = TimeZone.getTimeZone(converter.getOptions().getZoneId());
}
-
+
Calendar cal = Calendar.getInstance(tz);
Date epochInMillis = converter.convert(map.get(TIME), Date.class);
cal.setTimeInMillis(epochInMillis.getTime());
+ return cal;
+ }
+ else if (map.containsKey(YEAR)) {
+ 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);
+ int hour = converter.convert(map.get(HOUR), int.class);
+ int minute = converter.convert(map.get(MINUTE), int.class);
+ int second = converter.convert(map.get(SECOND), int.class);
+ int ms = converter.convert(map.get(MILLI_SECONDS), int.class);
+ Object zoneRaw = map.get(ZONE);
+
+ TimeZone tz;
+
+ if (zoneRaw instanceof String) {
+ String zone = (String) zoneRaw;
+ tz = TimeZone.getTimeZone(zone);
+ } else {
+ tz = TimeZone.getTimeZone(converter.getOptions().getZoneId());
+ }
+
+ Calendar cal = Calendar.getInstance(tz);
+ cal.set(Calendar.YEAR, year);
+ cal.set(Calendar.MONTH, month - 1);
+ cal.set(Calendar.DAY_OF_MONTH, day);
+ cal.set(Calendar.HOUR_OF_DAY, hour);
+ cal.set(Calendar.MINUTE, minute);
+ cal.set(Calendar.SECOND, second);
+ cal.set(Calendar.MILLISECOND, ms);
cal.getTime();
return cal;
} else {
diff --git a/src/main/java/com/cedarsoftware/util/convert/PeriodConversions.java b/src/main/java/com/cedarsoftware/util/convert/PeriodConversions.java
index bde879fa..e03231ab 100644
--- a/src/main/java/com/cedarsoftware/util/convert/PeriodConversions.java
+++ b/src/main/java/com/cedarsoftware/util/convert/PeriodConversions.java
@@ -29,9 +29,9 @@ private PeriodConversions() {}
static Map toMap(Object from, Converter converter) {
Period period = (Period) from;
Map target = new CompactLinkedMap<>();
- target.put("years", period.getYears());
- target.put("months", period.getMonths());
- target.put("days", period.getDays());
+ target.put(MapConversions.YEARS, period.getYears());
+ target.put(MapConversions.MONTHS, period.getMonths());
+ target.put(MapConversions.DAYS, period.getDays());
return target;
}
}
diff --git a/src/main/java/com/cedarsoftware/util/convert/StringConversions.java b/src/main/java/com/cedarsoftware/util/convert/StringConversions.java
index 04fa8d94..82ef59e2 100644
--- a/src/main/java/com/cedarsoftware/util/convert/StringConversions.java
+++ b/src/main/java/com/cedarsoftware/util/convert/StringConversions.java
@@ -27,7 +27,6 @@
import java.time.format.DateTimeParseException;
import java.util.Calendar;
import java.util.Date;
-import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Optional;
import java.util.TimeZone;
@@ -362,7 +361,29 @@ static TimeZone toTimeZone(Object from, Converter converter) {
}
static Calendar toCalendar(Object from, Converter converter) {
- return parseDate(from, converter).map(GregorianCalendar::from).orElse(null);
+ String calStr = (String) from;
+ if (StringUtilities.isEmpty(calStr)) {
+ return null;
+ }
+ ZonedDateTime zdt = DateUtilities.parseDate(calStr, converter.getOptions().getZoneId(), true);
+ if (zdt == null) {
+ return null;
+ }
+ ZonedDateTime zdtUser = zdt.withZoneSameInstant(converter.getOptions().getZoneId());
+
+ // Must copy this way. Using the GregorianCalendar.from(zdt) does not pass .equals() later.
+ Calendar cal = Calendar.getInstance();
+ cal.set(Calendar.YEAR, zdt.getYear());
+ cal.set(Calendar.MONTH, zdt.getMonthValue() - 1);
+ cal.set(Calendar.DAY_OF_MONTH, zdt.getDayOfMonth());
+ cal.set(Calendar.HOUR_OF_DAY, zdt.getHour());
+ cal.set(Calendar.MINUTE, zdt.getMinute());
+ cal.set(Calendar.SECOND, zdt.getSecond());
+ cal.set(Calendar.MILLISECOND, zdt.getNano() / 1_000_000);
+ cal.setTimeZone(TimeZone.getTimeZone(zdtUser.getZone()));
+ cal.getTime();
+
+ return cal;
}
static LocalDate toLocalDate(Object from, Converter converter) {
diff --git a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java
index 835d06c8..b82245ca 100644
--- a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java
+++ b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java
@@ -42,6 +42,7 @@
import java.util.stream.Stream;
import com.cedarsoftware.util.ClassUtilities;
+import com.cedarsoftware.util.CompactLinkedMap;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
@@ -184,6 +185,26 @@ private static void loadMapTests() {
{BigDecimal.valueOf(1), mapOf(VALUE, BigDecimal.valueOf(1))},
{BigDecimal.valueOf(2), mapOf(VALUE, BigDecimal.valueOf(2))}
});
+ TEST_DB.put(pair(Calendar.class, Map.class), new Object[][]{
+ {(Supplier) () -> {
+ Calendar cal = Calendar.getInstance(TOKYO_TZ);
+ cal.set(2024, Calendar.FEBRUARY, 5, 22, 31, 17);
+ cal.set(Calendar.MILLISECOND, 409);
+ cal.getTime();
+ return cal;
+ }, (Supplier