Work> toNoExpString = Object::toString;
conversionToString.put(Double.class, toNoExpString);
conversionToString.put(Float.class, toNoExpString);
-
+ conversionToString.put(Class.class, fromInstance -> {
+ Class> clazz = (Class>) fromInstance;
+ return clazz.getName();
+ });
+ conversionToString.put(UUID.class, Object::toString);
conversionToString.put(Date.class, fromInstance -> SafeSimpleDateFormat.getDateFormat("yyyy-MM-dd'T'HH:mm:ss").format(fromInstance));
conversionToString.put(Character.class, fromInstance -> "" + fromInstance);
conversionToString.put(LocalDate.class, fromInstance -> {
@@ -232,6 +239,50 @@ else if (fromInstance instanceof Enum)
return nope(fromInstance, "String");
}
+ public static Class> convertToClass(Object fromInstance) {
+ if (fromInstance instanceof Class) {
+ return (Class>)fromInstance;
+ } else if (fromInstance instanceof String) {
+ try {
+ Class> clazz = Class.forName((String)fromInstance);
+ return clazz;
+ }
+ catch (ClassNotFoundException ignore) {
+ }
+ }
+ throw new IllegalArgumentException("value [" + name(fromInstance) + "] could not be converted to a 'Class'");
+ }
+
+ public static UUID convertToUUID(Object fromInstance) {
+ try {
+ if (fromInstance instanceof UUID) {
+ return (UUID)fromInstance;
+ } else if (fromInstance instanceof String) {
+ return UUID.fromString((String)fromInstance);
+ } else if (fromInstance instanceof BigInteger) {
+ BigInteger bigInteger = (BigInteger) fromInstance;
+ BigInteger mask = BigInteger.valueOf(Long.MAX_VALUE);
+ long mostSignificantBits = bigInteger.shiftRight(64).and(mask).longValue();
+ long leastSignificantBits = bigInteger.and(mask).longValue();
+ return new UUID(mostSignificantBits, leastSignificantBits);
+ }
+ else if (fromInstance instanceof Map) {
+ Map, ?> map = (Map, ?>) fromInstance;
+ if (map.containsKey("mostSigBits") && map.containsKey("leastSigBits")) {
+ long mostSigBits = convert2long(map.get("mostSigBits"));
+ long leastSigBits = convert2long(map.get("leastSigBits"));
+ return new UUID(mostSigBits, leastSigBits);
+ } else {
+ throw new IllegalArgumentException("To convert Map to UUID, the Map must contain both a 'mostSigBits' and 'leastSigBits' key.");
+ }
+ }
+ } catch (Exception e) {
+ throw new IllegalArgumentException("value [" + name(fromInstance) + "] could not be converted to a 'UUID'", e);
+ }
+ nope(fromInstance, "UUID");
+ return null;
+ }
+
/**
* Convert from the passed in instance to a BigDecimal. If null or "" is passed in, this method will return a
* BigDecimal with the value of 0. Possible inputs are String (base10 numeric values in string), BigInteger,
@@ -374,6 +425,12 @@ else if (fromInstance instanceof BigDecimal)
else if (fromInstance instanceof Number)
{
return new BigInteger(Long.toString(((Number) fromInstance).longValue()));
+ } else if (fromInstance instanceof UUID) {
+ UUID uuid = (UUID) fromInstance;
+ BigInteger mostSignificant = BigInteger.valueOf(uuid.getMostSignificantBits());
+ BigInteger leastSignificant = BigInteger.valueOf(uuid.getLeastSignificantBits());
+ // Shift the most significant bits to the left and add the least significant bits
+ return mostSignificant.shiftLeft(64).add(leastSignificant);
}
else if (fromInstance instanceof Boolean)
{
@@ -823,7 +880,7 @@ else if (fromInstance instanceof AtomicLong)
}
catch (Exception e)
{
- throw new IllegalArgumentException("value [" + name(fromInstance) + "] could not be converted to a 'LocalDateTime'", e);
+ throw new IllegalArgumentException("value [" + name(fromInstance) + "] could not be converted to a 'ZonedDateTime'", e);
}
nope(fromInstance, "LocalDateTime");
return null;
@@ -1541,6 +1598,9 @@ private static String nope(Object fromInstance, String targetType)
private static String name(Object fromInstance)
{
+ if (fromInstance == null) {
+ return "null";
+ }
return fromInstance.getClass().getName() + " (" + fromInstance.toString() + ")";
}
diff --git a/src/main/java/com/cedarsoftware/util/DateUtilities.java b/src/main/java/com/cedarsoftware/util/DateUtilities.java
index 691afa28c..0e6c2a442 100644
--- a/src/main/java/com/cedarsoftware/util/DateUtilities.java
+++ b/src/main/java/com/cedarsoftware/util/DateUtilities.java
@@ -1,5 +1,8 @@
package com.cedarsoftware.util;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
@@ -9,7 +12,62 @@
import java.util.regex.Pattern;
/**
- * Handy utilities for working with Java Dates.
+ * Utility for parsing String dates with optional times, especially when the input String formats
+ * may be inconsistent. This will parse the following formats (constrained only by java.util.Date limitations...best
+ * time resolution is milliseconds):
+ *
+ * 12-31-2023 -or- 12/31/2023 mm is 1-12 or 01-12, dd is 1-31 or 01-31, and yyyy can be 0000 to 9999.
+ *
+ * 2023-12-31 -or- 2023/12/31 mm is 1-12 or 01-12, dd is 1-31 or 01-31, and yyyy can be 0000 to 9999.
+ *
+ * January 6th, 2024 Month (3-4 digit abbreviation or full English name), white-space and optional comma,
+ * day of month (1-31 or 0-31) with optional suffixes 1st, 3rd, 22nd, whitespace and
+ * optional comma, and yyyy (0000-9999)
+ *
+ * 17th January 2024 day of month (1-31 or 0-31) with optional suffixes (e.g. 1st, 3rd, 22nd),
+ * Month (3-4 digit abbreviation or full English name), whites space and optional comma,
+ * and yyyy (0000-9999)
+ *
+ * 2024 January 31st 4 digit year, white space and optional comma, Month (3-4 digit abbreviation or full
+ * English name), white space and optional command, and day of month with optional
+ * suffixes (1st, 3rd, 22nd)
+ *
+ * Sat Jan 6 11:06:10 EST 2024 Unix/Linux style. Day of week (3-letter or full name), Month (3-4 digit or full
+ * English name), time hh:mm:ss, TimeZone (Java supported Timezone names), Year
+ *
+ * All dates can be followed by a Time, or the time can precede the Date. Whitespace or a single letter T must separate the
+ * date and the time for the non-Unix time formats. The Time formats supported:
+ *
+ * hh:mm hours (00-23), minutes (00-59). 24 hour format.
+ *
+ * hh:mm:ss hours (00-23), minutes (00-59), seconds (00-59). 24 hour format.
+ *
+ * hh:mm:ss.sssss hh:mm:ss and fractional seconds. Variable fractional seconds supported. Date only
+ * supports up to millisecond precision, so anything after 3 decimal places is
+ * effectively ignored.
+ *
+ * hh:mm:offset -or- offset can be specified as +HH:mm, +HHmm, +HH, -HH:mm, -HHmm, -HH, or Z (GMT)
+ * hh:mm:ss.sss:offset which will match: "12:34", "12:34:56", "12:34.789", "12:34:56.789", "12:34+01:00",
+ * "12:34:56+1:00", "12:34-01", "12:34:56-1", "12:34Z", "12:34:56Z"
+ *
+ * hh:mm:zone -or- Zone can be specified as Z (Zooloo = UTC), older short forms: GMT, EST, CST, MST,
+ * hh:mm:ss.sss:zone PST, IST, JST, BST etc. as well as the long forms: "America/New York", "Asia/Saigon",
+ * etc. See ZoneId.getAvailableZoneIds().
+ *
+ * DateUtilities will parse Epoch-based integer-based values. It supports the following 3 types:
+ *
+ * "0" through "999999" A string of digits in this range will be parsed and returned as the number of days
+ * since the Unix Epoch, January 1st, 1970 00:00:00 UTC.
+ *
+ * "1000000" through "999999999999" A string of digits in this range will be parsed and returned as the number of seconds
+ * since the Unix Epoch, January 1st, 1970 00:00:00 UTC.
+ *
+ * "1000000000000" or larger A string of digits in this range will be parsed and returned as the number of milli-
+ * seconds since the Unix Epoch, January 1st, 1970 00:00:00 UTC.
+ *
+ * On all patterns above, if a day-of-week (e.g. Thu, Sunday, etc.) is included (front, back, or between date and time),
+ * it will be ignored, allowing for even more formats than what is listed here. The day-of-week is not be used to
+ * influence the Date calculation.
*
* @author John DeRegnaucourt (jdereg@gmail.com)
*
@@ -27,24 +85,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-public final class DateUtilities
-{
+public final class DateUtilities {
+ private static final Pattern allDigits = Pattern.compile("^\\d+$");
private static final String days = "(monday|mon|tuesday|tues|tue|wednesday|wed|thursday|thur|thu|friday|fri|saturday|sat|sunday|sun)"; // longer before shorter matters
- private static final String mos = "(Jan|January|Feb|February|Mar|March|Apr|April|May|Jun|June|Jul|July|Aug|August|Sep|Sept|September|Oct|October|Nov|November|Dec|December)";
- private static final Pattern datePattern1 = Pattern.compile("(\\d{4})[./-](\\d{1,2})[./-](\\d{1,2})");
- private static final Pattern datePattern2 = Pattern.compile("(\\d{1,2})[./-](\\d{1,2})[./-](\\d{4})");
- private static final Pattern datePattern3 = Pattern.compile(mos + "[ ]*+[,]?+[ ]*+(\\d{1,2}+)(st|nd|rd|th|)[ ]*+[,]?+[ ]*+(\\d{4})", Pattern.CASE_INSENSITIVE);
- private static final Pattern datePattern4 = Pattern.compile("(\\d{1,2}+)(st|nd|rd|th|)[ ]*+[,]?+[ ]*+" + mos + "[ ]*+[,]?+[ ]*+(\\d{4})", Pattern.CASE_INSENSITIVE);
- private static final Pattern datePattern5 = Pattern.compile("(\\d{4})[ ]*+[,]?+[ ]*+" + mos + "[ ]*+[,]?+[ ]*(\\d{1,2}+)(st|nd|rd|th|)", Pattern.CASE_INSENSITIVE);
- private static final Pattern datePattern6 = Pattern.compile(days+"[ ]++" + mos + "[ ]++(\\d{1,2}+)[ ]++(\\d{2}:\\d{2}:\\d{2})[ ]++[A-Z]{1,3}+\\s++(\\d{4})", Pattern.CASE_INSENSITIVE);
- private static final Pattern timePattern1 = Pattern.compile("(\\d{2})[:.](\\d{2})[:.](\\d{2})[.](\\d{1,10}+)([+-]\\d{2}[:]?+\\d{2}|Z)?");
- private static final Pattern timePattern2 = Pattern.compile("(\\d{2})[:.](\\d{2})[:.](\\d{2})([+-]\\d{2}[:]?\\d{2}|Z)?");
- private static final Pattern timePattern3 = Pattern.compile("(\\d{2})[:.](\\d{2})([+-]\\d{2}[:]?+\\d{2}|Z)?");
+ private static final String mos = "(January|Jan|February|Feb|March|Mar|April|Apr|May|June|Jun|July|Jul|August|Aug|September|Sept|Sep|October|Oct|November|Nov|December|Dec)";
+ private static final Pattern datePattern1 = Pattern.compile("(\\d{4})([./-])(\\d{1,2})\\2(\\d{1,2})|(\\d{1,2})([./-])(\\d{1,2})\\6(\\d{4})"); // \2 and \6 references the separator, enforcing same
+ private static final Pattern datePattern2 = Pattern.compile(mos + "[ ,]*(\\d{1,2})(st|nd|rd|th)?[ ,]*(\\d{4})", Pattern.CASE_INSENSITIVE);
+ private static final Pattern datePattern3 = Pattern.compile("(\\d{1,2})(st|nd|rd|th)?[ ,]*" + mos + "[ ,]*(\\d{4})", Pattern.CASE_INSENSITIVE);
+ private static final Pattern datePattern4 = Pattern.compile("(\\d{4})[ ,]*" + mos + "[ ,]*(\\d{1,2})(st|nd|rd|th)?", Pattern.CASE_INSENSITIVE);
+ private static final Pattern unixDatePattern = Pattern.compile(days + "\\s+" + mos + "\\s+(\\d{1,2})\\s+(\\d{2}:\\d{2}:\\d{2})\\s*([A-Z]{1,3})?\\s*(\\d{4})", Pattern.CASE_INSENSITIVE);
+ private static final Pattern timePattern = Pattern.compile("(\\d{2}):(\\d{2})(?::(\\d{2}))?(\\.\\d+)?([+-]\\d{1,2}:\\d{2}|[+-]\\d{4}|[+-]\\d{1,2}|Z|\\s+[A-Za-z][A-Za-z0-9~/._+-]+)?", Pattern.CASE_INSENSITIVE);
private static final Pattern dayPattern = Pattern.compile(days, Pattern.CASE_INSENSITIVE);
private static final Map months = new ConcurrentHashMap<>();
-
- static
- {
+ // Sat Jan 6 22:06:58 EST 2024
+ static {
// Month name to number map
months.put("jan", "1");
months.put("january", "1");
@@ -73,216 +127,169 @@ public final class DateUtilities
}
private DateUtilities() {
- super();
}
- public static Date parseDate(String dateStr)
- {
- if (dateStr == null)
- {
+ public static Date parseDate(String dateStr) {
+ if (dateStr == null) {
return null;
}
+
dateStr = dateStr.trim();
- if ("".equals(dateStr))
- {
+ if (dateStr.isEmpty()) {
return null;
}
+ if (allDigits.matcher(dateStr).matches()) {
+ return parseEpochString(dateStr);
+ }
+
// Determine which date pattern (Matcher) to use
Matcher matcher = datePattern1.matcher(dateStr);
- String year, month = null, day, mon = null, remains;
+ String year = null, month = null, day = null, mon = null, remains = "", sep, tz = null;
- if (matcher.find())
- {
- year = matcher.group(1);
- month = matcher.group(2);
- day = matcher.group(3);
+ if (matcher.find()) {
+ if (matcher.group(1) != null) {
+ year = matcher.group(1);
+ month = matcher.group(3);
+ day = matcher.group(4);
+ } else {
+ year = matcher.group(8);
+ month = matcher.group(5);
+ day = matcher.group(7);
+ }
remains = matcher.replaceFirst("");
- }
- else
- {
+ } else {
matcher = datePattern2.matcher(dateStr);
- if (matcher.find())
- {
- month = matcher.group(1);
+ if (matcher.find()) {
+ mon = matcher.group(1);
day = matcher.group(2);
- year = matcher.group(3);
+ year = matcher.group(4);
remains = matcher.replaceFirst("");
- }
- else
- {
+ } else {
matcher = datePattern3.matcher(dateStr);
- if (matcher.find())
- {
- mon = matcher.group(1);
- day = matcher.group(2);
+ if (matcher.find()) {
+ day = matcher.group(1);
+ mon = matcher.group(3);
year = matcher.group(4);
remains = matcher.replaceFirst("");
- }
- else
- {
+ } else {
matcher = datePattern4.matcher(dateStr);
- if (matcher.find())
- {
- day = matcher.group(1);
- mon = matcher.group(3);
- year = matcher.group(4);
+ if (matcher.find()) {
+ year = matcher.group(1);
+ mon = matcher.group(2);
+ day = matcher.group(3);
remains = matcher.replaceFirst("");
- }
- else
- {
- matcher = datePattern5.matcher(dateStr);
- if (matcher.find())
- {
- year = matcher.group(1);
- mon = matcher.group(2);
- day = matcher.group(3);
- remains = matcher.replaceFirst("");
- }
- else
- {
- matcher = datePattern6.matcher(dateStr);
- if (!matcher.find())
- {
- error("Unable to parse: " + dateStr);
- }
- year = matcher.group(5);
- mon = matcher.group(2);
- day = matcher.group(3);
- remains = matcher.group(4);
+ } else {
+ matcher = unixDatePattern.matcher(dateStr);
+ if (!matcher.find()) {
+ throw new IllegalArgumentException("Unable to parse: " + dateStr + " as a date");
}
+ year = matcher.group(6);
+ mon = matcher.group(2);
+ day = matcher.group(3);
+ tz = matcher.group(5);
+ remains = matcher.group(4);
}
}
}
}
- if (mon != null)
- { // Month will always be in Map, because regex forces this.
+ if (mon != null) { // Month will always be in Map, because regex forces this.
month = months.get(mon.trim().toLowerCase());
}
// Determine which date pattern (Matcher) to use
- String hour = null, min = null, sec = "00", milli = "0", tz = null;
+ String hour = null, min = null, sec = "00", milli = "0";
remains = remains.trim();
- matcher = timePattern1.matcher(remains);
- if (matcher.find())
- {
+ matcher = timePattern.matcher(remains);
+ if (matcher.find()) {
hour = matcher.group(1);
min = matcher.group(2);
- sec = matcher.group(3);
- milli = matcher.group(4);
- if (matcher.groupCount() > 4)
- {
- tz = matcher.group(5);
- }
- }
- else
- {
- matcher = timePattern2.matcher(remains);
- if (matcher.find())
- {
- hour = matcher.group(1);
- min = matcher.group(2);
+ if (matcher.group(3) != null) {
sec = matcher.group(3);
- if (matcher.groupCount() > 3)
- {
- tz = matcher.group(4);
- }
}
- else
- {
- matcher = timePattern3.matcher(remains);
- if (matcher.find())
- {
- hour = matcher.group(1);
- min = matcher.group(2);
- if (matcher.groupCount() > 2)
- {
- tz = matcher.group(3);
- }
- }
- else
- {
- matcher = null;
- }
+ if (matcher.group(4) != null) {
+ milli = matcher.group(4).substring(1);
}
+ if (matcher.group(5) != null) {
+ tz = matcher.group(5).trim();
+ }
+ } else {
+ matcher = null;
}
- if (matcher != null)
- {
+ if (matcher != null) {
remains = matcher.replaceFirst("");
}
// Clear out day of week (mon, tue, wed, ...)
- if (StringUtilities.length(remains) > 0)
- {
+ if (StringUtilities.length(remains) > 0) {
Matcher dayMatcher = dayPattern.matcher(remains);
- if (dayMatcher.find())
- {
+ if (dayMatcher.find()) {
remains = dayMatcher.replaceFirst("").trim();
}
}
- if (StringUtilities.length(remains) > 0)
- {
+ if (StringUtilities.length(remains) > 0) {
remains = remains.trim();
- if (!remains.equals(",") && (!remains.equals("T")))
- {
- error("Issue parsing data/time, other characters present: " + remains);
+ if (!remains.equals(",") && (!remains.equals("T"))) {
+ throw new IllegalArgumentException("Issue parsing data/time, other characters present: " + remains);
}
}
Calendar c = Calendar.getInstance();
- c.clear();
- if (tz != null)
- {
- if ("z".equalsIgnoreCase(tz))
- {
- c.setTimeZone(TimeZone.getTimeZone("GMT"));
- }
- else
- {
- c.setTimeZone(TimeZone.getTimeZone("GMT" + tz));
+ if (tz != null) {
+ if (tz.startsWith("-") || tz.startsWith("+")) {
+ ZoneOffset offset = ZoneOffset.of(tz);
+ ZoneId zoneId = ZoneId.ofOffset("UTC", offset);
+ TimeZone timeZone = TimeZone.getTimeZone(zoneId);
+ c.setTimeZone(timeZone);
+ } else {
+ try {
+ ZoneId zoneId = ZoneId.of(tz);
+ TimeZone timeZone = TimeZone.getTimeZone(zoneId);
+ c.setTimeZone(timeZone);
+ } catch (Exception e) {
+ TimeZone timeZone = TimeZone.getTimeZone(tz);
+ if (timeZone.getRawOffset() != 0) {
+ c.setTimeZone(timeZone);
+ } else {
+ throw e;
+ }
+ }
}
}
+ c.clear();
// Regex prevents these from ever failing to parse
int y = Integer.parseInt(year);
int m = Integer.parseInt(month) - 1; // months are 0-based
int d = Integer.parseInt(day);
- if (m < 0 || m > 11)
- {
- error("Month must be between 1 and 12 inclusive, date: " + dateStr);
+ if (m < 0 || m > 11) {
+ throw new IllegalArgumentException("Month must be between 1 and 12 inclusive, date: " + dateStr);
}
- if (d < 1 || d > 31)
- {
- error("Day must be between 1 and 31 inclusive, date: " + dateStr);
+ if (d < 1 || d > 31) {
+ throw new IllegalArgumentException("Day must be between 1 and 31 inclusive, date: " + dateStr);
}
- if (matcher == null)
- { // no [valid] time portion
+ if (matcher == null) { // no [valid] time portion
c.set(y, m, d);
- }
- else
- {
+ } else {
// Regex prevents these from ever failing to parse.
int h = Integer.parseInt(hour);
int mn = Integer.parseInt(min);
int s = Integer.parseInt(sec);
- int ms = Integer.parseInt(prepareMillis(milli));
+ int ms = Integer.parseInt(prepareMillis(milli)); // Must be between 0 and 999.
- if (h > 23)
- {
- error("Hour must be between 0 and 23 inclusive, time: " + dateStr);
+ if (h > 23) {
+ throw new IllegalArgumentException("Hour must be between 0 and 23 inclusive, time: " + dateStr);
}
- if (mn > 59)
- {
- error("Minute must be between 0 and 59 inclusive, time: " + dateStr);
+ if (mn > 59) {
+ throw new IllegalArgumentException("Minute must be between 0 and 59 inclusive, time: " + dateStr);
}
- if (s > 59)
- {
- error("Second must be between 0 and 59 inclusive, time: " + dateStr);
+ if (s > 59) {
+ throw new IllegalArgumentException("Second must be between 0 and 59 inclusive, time: " + dateStr);
}
// regex enforces millis to number
@@ -292,29 +299,31 @@ public static Date parseDate(String dateStr)
return c.getTime();
}
- private static String prepareMillis(String milli)
- {
- if (StringUtilities.isEmpty(milli))
- {
+ /**
+ * Calendar & Date are only accurate to milliseconds.
+ */
+ private static String prepareMillis(String milli) {
+ if (StringUtilities.isEmpty(milli)) {
return "000";
}
final int len = milli.length();
- if (len == 1)
- {
+ if (len == 1) {
return milli + "00";
- }
- else if (len == 2)
- {
+ } else if (len == 2) {
return milli + "0";
- }
- else
- {
+ } else {
return milli.substring(0, 3);
}
}
- private static void error(String msg)
- {
- throw new IllegalArgumentException(msg);
+ private static Date parseEpochString(String dateStr) {
+ long num = Long.parseLong(dateStr);
+ if (dateStr.length() < 7) { // days since epoch
+ return new Date(LocalDate.ofEpochDay(num).atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli());
+ } else if (dateStr.length() < 12) { // seconds since epoch
+ return new Date(num * 1000);
+ } else { // millis since epoch
+ return new Date(num);
+ }
}
}
\ No newline at end of file
diff --git a/src/test/java/com/cedarsoftware/util/TestConverter.java b/src/test/java/com/cedarsoftware/util/TestConverter.java
index 842db09c7..bb4822de0 100644
--- a/src/test/java/com/cedarsoftware/util/TestConverter.java
+++ b/src/test/java/com/cedarsoftware/util/TestConverter.java
@@ -12,7 +12,9 @@
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
+import java.util.Map;
import java.util.TimeZone;
+import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -40,8 +42,10 @@
import static com.cedarsoftware.util.Converter.convertToAtomicInteger;
import static com.cedarsoftware.util.Converter.convertToAtomicLong;
import static com.cedarsoftware.util.Converter.convertToBigDecimal;
+import static com.cedarsoftware.util.Converter.convertToBigInteger;
import static com.cedarsoftware.util.Converter.convertToByte;
import static com.cedarsoftware.util.Converter.convertToCharacter;
+import static com.cedarsoftware.util.Converter.convertToClass;
import static com.cedarsoftware.util.Converter.convertToDate;
import static com.cedarsoftware.util.Converter.convertToDouble;
import static com.cedarsoftware.util.Converter.convertToFloat;
@@ -53,12 +57,14 @@
import static com.cedarsoftware.util.Converter.convertToSqlDate;
import static com.cedarsoftware.util.Converter.convertToString;
import static com.cedarsoftware.util.Converter.convertToTimestamp;
+import static com.cedarsoftware.util.Converter.convertToUUID;
import static com.cedarsoftware.util.Converter.convertToZonedDateTime;
import static com.cedarsoftware.util.Converter.localDateTimeToMillis;
import static com.cedarsoftware.util.Converter.localDateToMillis;
import static com.cedarsoftware.util.Converter.zonedDateTimeToMillis;
import static com.cedarsoftware.util.TestConverter.fubar.bar;
import static com.cedarsoftware.util.TestConverter.fubar.foo;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -1175,7 +1181,7 @@ public void testZonedDateTimeToOthers()
}
catch (IllegalArgumentException e)
{
- TestUtil.assertContainsIgnoreCase(e.getMessage(), "value", "not", "convert", "local");
+ TestUtil.assertContainsIgnoreCase(e.getMessage(), "value", "not", "convert", "zoned");
}
assert convertToZonedDateTime(null) == null;
@@ -1678,4 +1684,121 @@ public void testLocalZonedDateTimeToBig()
AtomicLong atomicLong = convertToAtomicLong(ZonedDateTime.of(2020, 9, 8, 13, 11, 1, 0, ZoneId.systemDefault()));
assert atomicLong.get() == cal.getTime().getTime();
}
+
+ @Test
+ public void testStringToClass()
+ {
+ Class> clazz = convertToClass("java.math.BigInteger");
+ assert clazz.getName().equals("java.math.BigInteger");
+
+ assertThatThrownBy(() -> convertToClass("foo.bar.baz.Qux"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("value [java.lang.String (foo.bar.baz.Qux)] could not be converted to a 'Class'");
+
+ assertThatThrownBy(() -> convertToClass(null))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("value [null] could not be converted to a 'Class'");
+
+ assertThatThrownBy(() -> convertToClass(16.0))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("value [java.lang.Double (16.0)] could not be converted to a 'Class'");
+ }
+
+ @Test
+ void testClassToClass()
+ {
+ Class> clazz = convertToClass(TestConverter.class);
+ assert clazz.getName() == TestConverter.class.getName();
+ }
+
+ @Test
+ public void testStringToUUID()
+ {
+ UUID uuid = Converter.convertToUUID("00000000-0000-0000-0000-000000000064");
+ BigInteger bigInt = Converter.convertToBigInteger(uuid);
+ assert bigInt.intValue() == 100;
+
+ assertThatThrownBy(() -> Converter.convertToUUID("00000000"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("value [java.lang.String (00000000)] could not be converted to a 'UUID'");
+ }
+
+ @Test
+ public void testUUIDToUUID()
+ {
+ UUID uuid = Converter.convertToUUID("00000007-0000-0000-0000-000000000064");
+ UUID uuid2 = Converter.convertToUUID(uuid);
+ assert uuid.equals(uuid2);
+ }
+
+ @Test
+ public void testBogusToUUID()
+ {
+ assertThatThrownBy(() -> Converter.convertToUUID((short)77))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Unsupported value type [java.lang.Short (77)] attempting to convert to 'UUID'");
+ }
+
+ @Test
+ public void testBigIntegerToUUID()
+ {
+ UUID uuid = convertToUUID(new BigInteger("100"));
+ BigInteger hundred = convertToBigInteger(uuid);
+ assert hundred.intValue() == 100;
+ }
+
+ @Test
+ public void testMapToUUID()
+ {
+ UUID uuid = convertToUUID(new BigInteger("100"));
+ Map map = new HashMap<>();
+ map.put("mostSigBits", uuid.getMostSignificantBits());
+ map.put("leastSigBits", uuid.getLeastSignificantBits());
+ UUID hundred = convertToUUID(map);
+ assertEquals("00000000-0000-0000-0000-000000000064", hundred.toString());
+ }
+
+ @Test
+ public void testBadMapToUUID()
+ {
+ UUID uuid = convertToUUID(new BigInteger("100"));
+ Map map = new HashMap<>();
+ map.put("leastSigBits", uuid.getLeastSignificantBits());
+ assertThatThrownBy(() -> convertToUUID(map))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("value [java.util.HashMap ({leastSigBits=100})] could not be converted to a 'UUID'");
+ }
+
+ @Test
+ public void testUUIDToBigInteger()
+ {
+ BigInteger bigInt = Converter.convertToBigInteger(UUID.fromString("00000000-0000-0000-0000-000000000064"));
+ assert bigInt.intValue() == 100;
+
+ bigInt = Converter.convertToBigInteger(UUID.fromString("ffffffff-ffff-ffff-ffff-ffffffffffff"));
+ assert bigInt.toString().equals("-18446744073709551617");
+
+ bigInt = Converter.convertToBigInteger(UUID.fromString("00000000-0000-0000-0000-000000000000"));
+ assert bigInt.intValue() == 0;
+
+ assertThatThrownBy(() -> convertToClass(16.0))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("value [java.lang.Double (16.0)] could not be converted to a 'Class'");
+ }
+
+ @Test
+ public void testClassToString()
+ {
+ String str = Converter.convertToString(BigInteger.class);
+ assert str.equals("java.math.BigInteger");
+
+ str = Converter.convert2String(BigInteger.class);
+ assert str.equals("java.math.BigInteger");
+
+ str = Converter.convert2String(null);
+ assert "".equals(str);
+
+ str = Converter.convertToString(null);
+ assert str == null;
+ }
}
diff --git a/src/test/java/com/cedarsoftware/util/TestDateUtilities.java b/src/test/java/com/cedarsoftware/util/TestDateUtilities.java
index 531e22736..1d12a4777 100644
--- a/src/test/java/com/cedarsoftware/util/TestDateUtilities.java
+++ b/src/test/java/com/cedarsoftware/util/TestDateUtilities.java
@@ -7,6 +7,7 @@
import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -31,10 +32,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-public class TestDateUtilities
+class TestDateUtilities
{
@Test
- public void testXmlDates()
+ void testXmlDates()
{
Date t12 = DateUtilities.parseDate("2013-08-30T22:00Z");
Date t22 = DateUtilities.parseDate("2013-08-30T22:00+00:00");
@@ -92,10 +93,12 @@ public void testXmlDates()
}
@Test
- public void testXmlDatesWithOffsets()
+ void testXmlDatesWithOffsets()
{
Date t1 = DateUtilities.parseDate("2013-08-30T22:00Z");
Date t2 = DateUtilities.parseDate("2013-08-30T22:00+01:00");
+ assertEquals(60 * 60 * 1000, t1.getTime() - t2.getTime());
+
Date t3 = DateUtilities.parseDate("2013-08-30T22:00-01:00");
Date t4 = DateUtilities.parseDate("2013-08-30T22:00+0100");
Date t5 = DateUtilities.parseDate("2013-08-30T22:00-0100");
@@ -151,7 +154,7 @@ public void testXmlDatesWithOffsets()
}
@Test
- public void testXmlDatesWithMinuteOffsets()
+ void testXmlDatesWithMinuteOffsets()
{
Date t1 = DateUtilities.parseDate("2013-08-30T22:17:34.123456789Z");
Date t2 = DateUtilities.parseDate("2013-08-30T22:17:34.123456789+00:01");
@@ -176,12 +179,12 @@ public void testXmlDatesWithMinuteOffsets()
assertEquals(-60 * 1000, t1.getTime() - t5.getTime());
}
@Test
- public void testConstructorIsPrivate() throws Exception
+ void testConstructorIsPrivate() throws Exception
{
Class> c = DateUtilities.class;
assertEquals(Modifier.FINAL, c.getModifiers() & Modifier.FINAL);
- Constructor con = c.getDeclaredConstructor();
+ Constructor> con = c.getDeclaredConstructor();
assertEquals(Modifier.PRIVATE, con.getModifiers() & Modifier.PRIVATE);
con.setAccessible(true);
@@ -189,12 +192,12 @@ public void testConstructorIsPrivate() throws Exception
}
@Test
- public void testDateAloneNumbers()
+ void testDateAloneNumbers()
{
Date d1 = DateUtilities.parseDate("2014-01-18");
Calendar c = Calendar.getInstance();
c.clear();
- c.set(2014, 0, 18, 0, 0, 0);
+ c.set(2014, Calendar.JANUARY, 18, 0, 0, 0);
assertEquals(c.getTime(), d1);
d1 = DateUtilities.parseDate("2014/01/18");
assertEquals(c.getTime(), d1);
@@ -207,12 +210,12 @@ public void testDateAloneNumbers()
}
@Test
- public void testDateAloneNames()
+ void testDateAloneNames()
{
Date d1 = DateUtilities.parseDate("2014 Jan 18");
Calendar c = Calendar.getInstance();
c.clear();
- c.set(2014, 0, 18, 0, 0, 0);
+ c.set(2014, Calendar.JANUARY, 18, 0, 0, 0);
assertEquals(c.getTime(), d1);
d1 = DateUtilities.parseDate("2014 January 18");
assertEquals(c.getTime(), d1);
@@ -229,12 +232,12 @@ public void testDateAloneNames()
}
@Test
- public void testDate24TimeParse()
+ void testDate24TimeParse()
{
Date d1 = DateUtilities.parseDate("2014-01-18 16:43");
Calendar c = Calendar.getInstance();
c.clear();
- c.set(2014, 0, 18, 16, 43, 0);
+ c.set(2014, Calendar.JANUARY, 18, 16, 43, 0);
assertEquals(c.getTime(), d1);
d1 = DateUtilities.parseDate("2014/01/18 16:43");
assertEquals(c.getTime(), d1);
@@ -258,12 +261,12 @@ public void testDate24TimeParse()
}
@Test
- public void testDate24TimeSecParse()
+ void testDate24TimeSecParse()
{
Date d1 = DateUtilities.parseDate("2014-01-18 16:43:27");
Calendar c = Calendar.getInstance();
c.clear();
- c.set(2014, 0, 18, 16, 43, 27);
+ c.set(2014, Calendar.JANUARY, 18, 16, 43, 27);
assertEquals(c.getTime(), d1);
d1 = DateUtilities.parseDate("2014/1/18 16:43:27");
assertEquals(c.getTime(), d1);
@@ -274,12 +277,12 @@ public void testDate24TimeSecParse()
}
@Test
- public void testDate24TimeSecMilliParse()
+ void testDate24TimeSecMilliParse()
{
Date d1 = DateUtilities.parseDate("2014-01-18 16:43:27.123");
Calendar c = Calendar.getInstance();
c.clear();
- c.set(2014, 0, 18, 16, 43, 27);
+ c.set(2014, Calendar.JANUARY, 18, 16, 43, 27);
c.setTimeInMillis(c.getTime().getTime() + 123);
assertEquals(c.getTime(), d1);
d1 = DateUtilities.parseDate("2014/1/18 16:43:27.123");
@@ -300,7 +303,7 @@ public void testDate24TimeSecMilliParse()
}
@Test
- public void testParseWithNull()
+ void testParseWithNull()
{
assertNull(DateUtilities.parseDate(null));
assertNull(DateUtilities.parseDate(""));
@@ -308,7 +311,7 @@ public void testParseWithNull()
}
@Test
- public void testDayOfWeek()
+ void testDayOfWeek()
{
DateUtilities.parseDate("thu, Dec 25, 2014");
DateUtilities.parseDate("thur, Dec 25, 2014");
@@ -334,25 +337,19 @@ public void testDayOfWeek()
DateUtilities.parseDate(" Dec 25, 2014, thur ");
DateUtilities.parseDate(" Dec 25, 2014, thursday ");
- try
- {
+ try {
DateUtilities.parseDate("text Dec 25, 2014");
fail();
- }
- catch (Exception ignored)
- { }
+ } catch (Exception ignored) { }
- try
- {
+ try {
DateUtilities.parseDate("Dec 25, 2014 text");
fail();
- }
- catch (Exception ignored)
- { }
+ } catch (Exception ignored) { }
}
@Test
- public void testDaySuffixesLower()
+ void testDaySuffixesLower()
{
Date x = DateUtilities.parseDate("January 21st, 1994");
Calendar c = Calendar.getInstance();
@@ -402,7 +399,7 @@ public void testDaySuffixesLower()
}
@Test
- public void testDaySuffixesUpper()
+ void testDaySuffixesUpper()
{
Date x = DateUtilities.parseDate("January 21ST, 1994");
Calendar c = Calendar.getInstance();
@@ -452,7 +449,7 @@ public void testDaySuffixesUpper()
}
@Test
- public void testWeirdSpacing()
+ void testWeirdSpacing()
{
Date x = DateUtilities.parseDate("January 21ST , 1994");
Calendar c = Calendar.getInstance();
@@ -512,20 +509,16 @@ public void testWeirdSpacing()
}
@Test
- public void test2DigitYear()
+ void test2DigitYear()
{
- try
- {
+ try {
DateUtilities.parseDate("07/04/19");
fail("should not make it here");
- }
- catch (IllegalArgumentException e)
- {
- }
+ } catch (IllegalArgumentException ignored) {}
}
@Test
- public void testDateToStringFormat()
+ void testDateToStringFormat()
{
Date x = new Date();
Date y = DateUtilities.parseDate(x.toString());
@@ -533,7 +526,7 @@ public void testDateToStringFormat()
}
@Test
- public void testDatePrecision()
+ void testDatePrecision()
{
Date x = DateUtilities.parseDate("2021-01-13T13:01:54.6747552-05:00");
Date y = DateUtilities.parseDate("2021-01-13T13:01:55.2589242-05:00");
@@ -541,87 +534,177 @@ public void testDatePrecision()
}
@Test
- public void testParseErrors()
+ void testTimeZoneValidShortNames() {
+ // Support for some of the oldie but goodies (when the TimeZone returned does not have a 0 offset)
+ DateUtilities.parseDate("2021-01-13T13:01:54.6747552 JST"); // Japan
+ DateUtilities.parseDate("2021-01-13T13:01:54.6747552 IST"); // India
+ DateUtilities.parseDate("2021-01-13T13:01:54.6747552 CET"); // France
+ DateUtilities.parseDate("2021-01-13T13:01:54.6747552 BST"); // British Summer Time
+ DateUtilities.parseDate("2021-01-13T13:01:54.6747552 EST"); // Eastern Standard
+ DateUtilities.parseDate("2021-01-13T13:01:54.6747552 CST"); // Central Standard
+ DateUtilities.parseDate("2021-01-13T13:01:54.6747552 MST"); // Mountain Standard
+ DateUtilities.parseDate("2021-01-13T13:01:54.6747552 PST"); // Pacific Standard
+ DateUtilities.parseDate("2021-01-13T13:01:54.6747552 CAT"); // Central Africa Time
+ DateUtilities.parseDate("2021-01-13T13:01:54.6747552 EAT"); // Eastern Africa Time
+ DateUtilities.parseDate("2021-01-13T13:01:54.6747552 ART"); // Argentina Time
+ DateUtilities.parseDate("2021-01-13T13:01:54.6747552 ECT"); // Ecuador Time
+ DateUtilities.parseDate("2021-01-13T13:01:54.6747552 NST"); // Newfoundland Time
+ DateUtilities.parseDate("2021-01-13T13:01:54.6747552 AST"); // Atlantic Standard Time
+ DateUtilities.parseDate("2021-01-13T13:01:54.6747552 HST"); // Hawaii Standard Time
+ }
+
+ @Test
+ void testTimeZoneLongName()
+ {
+ DateUtilities.parseDate("2021-01-13T13:01:54.6747552 Asia/Saigon");
+ DateUtilities.parseDate("2021-01-13T13:01:54.6747552 America/New_York");
+
+ assertThatThrownBy(() -> DateUtilities.parseDate("2021-01-13T13:01:54 Mumbo/Jumbo"))
+ .isInstanceOf(java.time.zone.ZoneRulesException.class)
+ .hasMessageContaining("Unknown time-zone ID: Mumbo/Jumbo");
+ }
+
+ @Test
+ void testOffsetTimezone()
+ {
+ Date london = DateUtilities.parseDate("2024-01-06T00:00:01 GMT");
+ Date london_pos_short_offset = DateUtilities.parseDate("2024-01-6T00:00:01+00");
+ Date london_pos_med_offset = DateUtilities.parseDate("2024-01-6T00:00:01+0000");
+ Date london_pos_offset = DateUtilities.parseDate("2024-01-6T00:00:01+00:00");
+ Date london_neg_short_offset = DateUtilities.parseDate("2024-01-6T00:00:01-00");
+ Date london_neg_med_offset = DateUtilities.parseDate("2024-01-6T00:00:01-0000");
+ Date london_neg_offset = DateUtilities.parseDate("2024-01-6T00:00:01-00:00");
+ Date london_z = DateUtilities.parseDate("2024-01-6T00:00:01Z");
+ Date london_utc = DateUtilities.parseDate("2024-01-06T00:00:01 UTC");
+
+ assertEquals(london, london_pos_short_offset);
+ assertEquals(london_pos_short_offset, london_pos_med_offset);
+ assertEquals(london_pos_med_offset, london_pos_short_offset);
+ assertEquals(london_pos_short_offset, london_pos_offset);
+ assertEquals(london_pos_offset, london_neg_short_offset);
+ assertEquals(london_neg_short_offset, london_neg_med_offset);
+ assertEquals(london_neg_med_offset, london_neg_offset);
+ assertEquals(london_neg_offset, london_z);
+ assertEquals(london_z, london_utc);
+
+ Date ny = DateUtilities.parseDate("2024-01-06T00:00:01 America/New_York");
+ assert ny.getTime() - london.getTime() == 5*60*60*1000;
+
+ Date ny_offset = DateUtilities.parseDate("2024-01-6T00:00:01-5");
+ assert ny_offset.getTime() - london.getTime() == 5*60*60*1000;
+
+ Date la_offset = DateUtilities.parseDate("2024-01-6T00:00:01-08:00");
+ assert la_offset.getTime() - london.getTime() == 8*60*60*1000;
+ }
+
+ @Test
+ void testTimeBeforeDate()
+ {
+ Date x = DateUtilities.parseDate("13:01:54 2021-01-14");
+ Calendar c = Calendar.getInstance();
+ c.clear();
+ c.set(2021, Calendar.JANUARY, 14, 13, 1, 54);
+ assertEquals(x, c.getTime());
+
+ x = DateUtilities.parseDate("13:01:54T2021-01-14");
+ c.clear();
+ c.set(2021, Calendar.JANUARY, 14, 13, 1, 54);
+ assertEquals(x, c.getTime());
+
+ x = DateUtilities.parseDate("13:01:54.1234567T2021-01-14");
+ c.clear();
+ c.set(2021, Calendar.JANUARY, 14, 13, 1, 54);
+ c.set(Calendar.MILLISECOND, 123);
+ assertEquals(x, c.getTime());
+
+ DateUtilities.parseDate("13:01:54.1234567ZT2021-01-14");
+ DateUtilities.parseDate("13:01:54.1234567-10T2021-01-14");
+ DateUtilities.parseDate("13:01:54.1234567-10:00T2021-01-14");
+ x = DateUtilities.parseDate("13:01:54.1234567 America/New_York T2021-01-14");
+ Date y = DateUtilities.parseDate("13:01:54.1234567-0500T2021-01-14");
+ assertEquals(x, y);
+ }
+
+ @Test
+ void testParseErrors()
{
- try
- {
+ try {
DateUtilities.parseDate("2014-11-j 16:43:27.123");
fail("should not make it here");
- }
- catch (Exception ignored)
- {
- }
+ } catch (Exception ignored) {}
- try
- {
+ try {
DateUtilities.parseDate("2014-6-10 24:43:27.123");
fail("should not make it here");
- }
- catch (Exception ignored)
- {
- }
+ } catch (Exception ignored) {}
- try
- {
+ try {
DateUtilities.parseDate("2014-6-10 23:61:27.123");
fail("should not make it here");
- }
- catch (Exception ignored)
- {
- }
+ } catch (Exception ignored) {}
- try
- {
+ try {
DateUtilities.parseDate("2014-6-10 23:00:75.123");
fail("should not make it here");
- }
- catch (Exception igored)
- {
- }
+ } catch (Exception ignored) {}
- try
- {
+ try {
DateUtilities.parseDate("27 Jume 2014");
fail("should not make it here");
- }
- catch (Exception ignored)
- {
- }
+ } catch (Exception ignored) {}
- try
- {
+ try {
DateUtilities.parseDate("13/01/2014");
fail("should not make it here");
- }
- catch (Exception ignored)
- {
- }
+ } catch (Exception ignored) {}
- try
- {
+ try {
DateUtilities.parseDate("00/01/2014");
fail("should not make it here");
- }
- catch (Exception ignored)
- {
- }
+ } catch (Exception ignored) {}
- try
- {
+ try {
DateUtilities.parseDate("12/32/2014");
fail("should not make it here");
- }
- catch (Exception ignored)
- {
- }
+ } catch (Exception ignored) {}
- try
- {
+ try {
DateUtilities.parseDate("12/00/2014");
fail("should not make it here");
- }
- catch (Exception ignored)
- {
- }
+ } catch (Exception ignored) {}
+ }
+
+ @Test
+ void testMacUnixDateFormat()
+ {
+ Date date = DateUtilities.parseDate("Sat Jan 6 20:06:58 EST 2024");
+ Calendar calendar = Calendar.getInstance();
+ calendar.clear();
+ calendar.set(2024, Calendar.JANUARY, 6, 20, 6, 58);
+ assertEquals(calendar.getTime(), date);
+ Date date2 = DateUtilities.parseDate("Sat Jan 6 20:06:58 PST 2024");
+ assertEquals(date2.getTime(), date.getTime() + 3*60*60*1000);
+ }
+
+ @Test
+ void testUnixDateFormat()
+ {
+ Date date = DateUtilities.parseDate("Sat Jan 6 20:06:58 2024");
+ Calendar calendar = Calendar.getInstance();
+ calendar.clear();
+ calendar.set(2024, Calendar.JANUARY, 6, 20, 6, 58);
+ assertEquals(calendar.getTime(), date);
+ }
+
+ @Test
+ void testInconsistentDateSeparators()
+ {
+ assertThatThrownBy(() -> DateUtilities.parseDate("12/24-1996"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Unable to parse: 12/24-1996 as a date");
+
+ assertThatThrownBy(() -> DateUtilities.parseDate("1996-12/24"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Unable to parse: 1996-12/24 as a date");
}
}
\ No newline at end of file
diff --git a/src/test/java/com/cedarsoftware/util/TestExceptionUtilities.java b/src/test/java/com/cedarsoftware/util/TestExceptionUtilities.java
index cbd947b40..1edca9792 100644
--- a/src/test/java/com/cedarsoftware/util/TestExceptionUtilities.java
+++ b/src/test/java/com/cedarsoftware/util/TestExceptionUtilities.java
@@ -69,7 +69,7 @@ public void testGetDeepestException()
{
Throwable t = ExceptionUtilities.getDeepestException(e);
assert t != e;
- assert t.getMessage().equals("Unable to parse: foo");
+ assert t.getMessage().contains("Unable to parse: foo");
}
}
}