This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new b1a116e721 Unit tests
b1a116e721 is described below
commit b1a116e7217ca608cf71d3f54b31c164dbc71ba9
Author: James Bognar <[email protected]>
AuthorDate: Wed Dec 3 22:16:44 2025 -0800
Unit tests
---
.../org/apache/juneau/bean/atom/CommonEntry.java | 6 +-
.../java/org/apache/juneau/bean/atom/Entry.java | 6 +-
.../org/apache/juneau/commons/utils/DateUtils.java | 350 ++-------------------
.../commons/utils/GranularZonedDateTime.java | 198 +++++++++++-
.../main/java/org/apache/juneau/BeanSession.java | 5 +-
.../apache/juneau/oapi/OpenApiParserSession.java | 6 +-
.../microservice/resources/LogsResource.java | 8 +-
.../a/rttests/RoundTripTransformBeans_Test.java | 10 +-
.../juneau/commons/utils/DateUtils_Test.java | 131 +-------
.../httppart/OpenApiPartSerializer_Test.java | 10 +-
.../java/org/apache/juneau/oapi/OpenApi_Test.java | 5 +-
.../juneau/objecttools/ObjectSearcher_Test.java | 3 +-
12 files changed, 265 insertions(+), 473 deletions(-)
diff --git
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/CommonEntry.java
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/CommonEntry.java
index 6c9906bf15..a6c83208c8 100644
---
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/CommonEntry.java
+++
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/CommonEntry.java
@@ -16,11 +16,13 @@
*/
package org.apache.juneau.bean.atom;
-import static org.apache.juneau.commons.utils.DateUtils.*;
+import static org.apache.juneau.commons.utils.StringUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
import static org.apache.juneau.xml.annotation.XmlFormat.*;
import java.util.*;
+import org.apache.juneau.commons.utils.*;
import org.apache.juneau.xml.annotation.*;
/**
@@ -372,7 +374,7 @@ public class CommonEntry extends Common {
* @return This object.
*/
public CommonEntry setUpdated(String value) {
- setUpdated(fromIso8601Calendar(value));
+ setUpdated(opt(value).filter(x1 -> ! isBlank(x1)).map(x ->
GranularZonedDateTime.parse(value).getZonedDateTime()).map(GregorianCalendar::from).orElse(null));
return this;
}
}
\ No newline at end of file
diff --git
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Entry.java
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Entry.java
index c6b5b534b1..e7ed17deef 100644
---
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Entry.java
+++
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Entry.java
@@ -16,11 +16,13 @@
*/
package org.apache.juneau.bean.atom;
-import static org.apache.juneau.commons.utils.DateUtils.*;
+import static org.apache.juneau.commons.utils.StringUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
import java.util.*;
import org.apache.juneau.annotation.*;
+import org.apache.juneau.commons.utils.*;
/**
* Represents an individual entry within an Atom feed or as a standalone Atom
document.
@@ -329,7 +331,7 @@ public class Entry extends CommonEntry {
* @return This object.
*/
public Entry setPublished(String value) {
- setPublished(fromIso8601Calendar(value));
+ setPublished(opt(value).filter(x1 -> ! isBlank(x1)).map(x ->
GranularZonedDateTime.parse(value).getZonedDateTime()).map(GregorianCalendar::from).orElse(null));
return this;
}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/DateUtils.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/DateUtils.java
index 07e567ec1f..b5eb48cd60 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/DateUtils.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/DateUtils.java
@@ -16,18 +16,12 @@
*/
package org.apache.juneau.commons.utils;
-import static org.apache.juneau.commons.utils.AssertionUtils.*;
-import static org.apache.juneau.commons.utils.StateEnum.*;
import static org.apache.juneau.commons.utils.StringUtils.*;
import static org.apache.juneau.commons.utils.ThrowableUtils.*;
-import static org.apache.juneau.commons.utils.Utils.*;
-import java.lang.ref.*;
-import java.text.*;
-import java.time.*;
import java.time.format.*;
-import java.time.temporal.*;
-import java.util.*;
+
+import org.apache.juneau.commons.collections.*;
/**
* A utility class for parsing and formatting HTTP dates as used in cookies
and other headers.
@@ -41,142 +35,23 @@ import java.util.*;
*/
public class DateUtils {
- /**
- * Parses an ISO8601 date string into a ZonedDateTime object.
- *
- * <p>
- * This method converts an ISO8601 formatted date/time string into a
ZonedDateTime object,
- * which provides full timezone context including DST handling. The
method automatically
- * normalizes the input string to ensure it can be parsed correctly.
- *
- * <p>
- * The method supports the same ISO8601 formats as {@link
#fromIso8601Calendar(String)},
- * but returns a modern {@link ZonedDateTime} instead of a legacy
{@link Calendar}.
- *
- * <h5 class='section'>Examples:</h5>
- * <p class='bjava'>
- * <jc>// Parse UTC timezone</jc>
- * ZonedDateTime <jv>utcZdt</jv> =
DateUtils.<jsm>fromIso8601</jsm>(<js>"2024-01-15T14:30:45Z"</js>);
- * <jc>// Result: ZonedDateTime with UTC timezone</jc>
- *
- * <jc>// Parse offset timezone</jc>
- * ZonedDateTime <jv>estZdt</jv> =
DateUtils.<jsm>fromIso8601</jsm>(<js>"2024-01-15T14:30:45-05:00"</js>);
- * <jc>// Result: ZonedDateTime with EST timezone (-05:00)</jc>
- *
- * <jc>// Parse date only</jc>
- * ZonedDateTime <jv>dateZdt</jv> =
DateUtils.<jsm>fromIso8601</jsm>(<js>"2024-01-15"</js>);
- * <jc>// Result: ZonedDateTime with time set to 00:00:00 in
system timezone</jc>
- * </p>
- *
- * <h5 class='section'>Advantages over Calendar:</h5>
- * <ul>
- * <li><c>Immutable</c> - Thread-safe by design
- * <li><c>DST Aware</c> - Automatic Daylight Saving Time handling
- * <li><c>Modern API</c> - Part of Java 8+ time package
- * <li><c>Better Performance</c> - Optimized for modern JVMs
- * <li><c>Type Safety</c> - Compile-time validation of operations
- * </ul>
- *
- * <h5 class='section'>Timezone Handling:</h5>
- * <p>
- * The method preserves the original timezone information from the
ISO8601 string.
- * If no timezone is specified, the system's default timezone is used.
The resulting
- * ZonedDateTime object will have the appropriate timezone set and will
automatically
- * handle DST transitions.
- * </p>
- *
- * <h5 class='section'>Input Normalization:</h5>
- * <p>
- * The method automatically normalizes incomplete ISO8601 strings by:
- * <ul>
- * <li>Adding missing time components (defaults to 00:00:00)
- * <li>Adding timezone information if missing (uses system default)
- * <li>Ensuring proper format compliance
- * </ul>
- * </p>
- *
- * See Also: <a class="doclink"
href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601 - Wikipedia</a>
- *
- * @param s The ISO8601 formatted string to parse (can be null or empty)
- * @return ZonedDateTime object representing the parsed date/time, or
null if input is null/empty
- * @throws DateTimeParseException if the string cannot be parsed as a
valid ISO8601 date
- * @see #fromIso8601Calendar(String)
- * @see ZonedDateTime
- */
- public static ZonedDateTime fromIso8601(String s) {
- if (StringUtils.isBlank(s))
- return null;
- String validDate = toValidIso8601DT(s);
- return ZonedDateTime.parse(validDate,
DateTimeFormatter.ISO_DATE_TIME);
- }
-
- /**
- * Parses an ISO8601 date string into a Calendar object.
- *
- * <p>
- * This method converts an ISO8601 formatted date/time string into a
Calendar object,
- * preserving timezone information and handling various ISO8601
formats. The method
- * automatically normalizes the input string to ensure it can be parsed
correctly.
- *
- * <p>
- * The method supports the following ISO8601 formats:
- * <ul>
- * <li><js>"2024-01-15T14:30:45Z"</js> - UTC timezone
- * <li><js>"2024-01-15T14:30:45-05:00"</js> - Offset timezone
- * <li><js>"2024-01-15T14:30:45+09:00"</js> - Positive offset
timezone
- * <li><js>"2024-01-15"</js> - Date only (time defaults to
00:00:00)
- * <li><js>"2024-01-15T14:30"</js> - Date and time (seconds
default to 00)
- * <li><js>"2024-01-15T14:30:45.123"</js> - With milliseconds
- * </ul>
- *
- * <h5 class='section'>Examples:</h5>
- * <p class='bjava'>
- * <jc>// Parse UTC timezone</jc>
- * Calendar <jv>utcCal</jv> =
DateUtils.<jsm>fromIso8601Calendar</jsm>(<js>"2024-01-15T14:30:45Z"</js>);
- * <jc>// Result: Calendar with UTC timezone</jc>
- *
- * <jc>// Parse offset timezone</jc>
- * Calendar <jv>estCal</jv> =
DateUtils.<jsm>fromIso8601Calendar</jsm>(<js>"2024-01-15T14:30:45-05:00"</js>);
- * <jc>// Result: Calendar with EST timezone (-05:00)</jc>
- *
- * <jc>// Parse date only</jc>
- * Calendar <jv>dateCal</jv> =
DateUtils.<jsm>fromIso8601Calendar</jsm>(<js>"2024-01-15"</js>);
- * <jc>// Result: Calendar with time set to 00:00:00 in system
timezone</jc>
- *
- * <jc>// Parse with milliseconds</jc>
- * Calendar <jv>msCal</jv> =
DateUtils.<jsm>fromIso8601Calendar</jsm>(<js>"2024-01-15T14:30:45.123Z"</js>);
- * <jc>// Result: Calendar with 123 milliseconds</jc>
- * </p>
- *
- * <h5 class='section'>Timezone Handling:</h5>
- * <p>
- * The method preserves the original timezone information from the
ISO8601 string.
- * If no timezone is specified, the system's default timezone is used.
The resulting
- * Calendar object will have the appropriate timezone set.
- * </p>
- *
- * <h5 class='section'>Input Normalization:</h5>
- * <p>
- * The method automatically normalizes incomplete ISO8601 strings by:
- * <ul>
- * <li>Adding missing time components (defaults to 00:00:00)
- * <li>Adding timezone information if missing (uses system default)
- * <li>Ensuring proper format compliance
- * </ul>
- * </p>
- *
- * See Also: <a class="doclink"
href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601 - Wikipedia</a>
- *
- * @param s The ISO8601 formatted string to parse (can be null or empty)
- * @return Calendar object representing the parsed date/time, or null
if input is null/empty
- * @throws DateTimeParseException if the string cannot be parsed as a
valid ISO8601 date
- * @see #fromIso8601(String)
- */
- public static Calendar fromIso8601Calendar(String s) {
- if (isBlank(s))
- return null;
- return GregorianCalendar.from(fromIso8601(s));
- }
+ private static final Cache<String, DateTimeFormatter>
DATE_TIME_FORMATTER_CACHE = Cache.of(String.class, DateTimeFormatter.class)
+ .maxSize(100)
+ .supplier(pattern -> {
+ if (isEmpty(pattern))
+ return DateTimeFormatter.ISO_INSTANT;
+ try {
+ for (var f :
DateTimeFormatter.class.getFields()) {
+ if (f.getName().equals(pattern)) {
+ return
(DateTimeFormatter)f.get(null);
+ }
+ }
+ return DateTimeFormatter.ofPattern(pattern);
+ } catch (IllegalArgumentException |
IllegalAccessException e) {
+ throw toRex(e);
+ }
+ })
+ .build();
/**
* Returns a {@link DateTimeFormatter} using either a pattern or
predefined pattern name.
@@ -185,191 +60,6 @@ public class DateUtils {
* @return The formatter.
*/
public static DateTimeFormatter getDateTimeFormatter(String pattern) {
- if (isEmpty(pattern))
- return DateTimeFormatter.ISO_INSTANT;
- try {
- for (var f : DateTimeFormatter.class.getFields()) {
- if (f.getName().equals(pattern)) {
- return (DateTimeFormatter)f.get(null);
- }
- }
- return DateTimeFormatter.ofPattern(pattern);
- } catch (IllegalArgumentException | IllegalAccessException e) {
- throw toRex(e);
- }
+ return DATE_TIME_FORMATTER_CACHE.get(pattern);
}
-
-
- //
================================================================================================================
- // ChronoField/ChronoUnit/Calendar conversion utilities
- //
================================================================================================================
-
-
- /**
- * Pads out an ISO8601 string so that it can be parsed using {@link
DatatypeConverter#parseDateTime(String)}.
- *
- * <ul>
- * <li><js>"2001-07-04T15:30:45-05:00"</js> ->
<js>"2001-07-04T15:30:45-05:00"</js>
- * <li><js>"2001-07-04T15:30:45Z"</js> ->
<js>"2001-07-04T15:30:45Z"</js>
- * <li><js>"2001-07-04T15:30:45.1Z"</js> ->
<js>"2001-07-04T15:30:45.1Z"</js>
- * <li><js>"2001-07-04T15:30Z"</js> ->
<js>"2001-07-04T15:30:00Z"</js>
- * <li><js>"2001-07-04T15:30"</js> ->
<js>"2001-07-04T15:30:00"</js>
- * <li><js>"2001-07-04"</js> ->
<li><js>"2001-07-04T00:00:00"</js>
- * <li><js>"2001-07"</js> -> <js>"2001-07-01T00:00:00"</js>
- * <li><js>"2001"</js> -> <js>"2001-01-01T00:00:00"</js>
- * </ul>
- *
- * @param in The string to pad.
- * @return The padded string.
- */
- public static String toValidIso8601DT(String in) {
- assertArgNotNull("in", in);
-
- in = in.trim();
-
- // "2001-07-04T15:30:45Z"
- // S1: Looking for -
- // S2: Found -, looking for -
- // S3: Found -, looking for T
- // S4: Found T, looking for :
- // S5: Found :, looking for :
- // S6: Found :, looking for Z or - or +
- // S7: Found time zone
-
- var state = S1;
- var needsT = false;
- var timezoneAfterHour = false; // Track if timezone found
after hour (S4)
- var timezoneAfterMinute = false; // Track if timezone found
after minute (S5)
- for (var i = 0; i < in.length(); i++) {
- var c = in.charAt(i);
- if (state == S1) {
- if (c == '-')
- state = S2;
- } else if (state == S2) {
- if (c == '-')
- state = S3;
- } else if (state == S3) {
- if (c == 'T')
- state = S4;
- if (c == ' ') {
- state = S4;
- needsT = true;
- }
- } else if (state == S4) {
- if (c == ':')
- state = S5;
- else if (c == 'Z' || c == '+' || c == '-') {
- state = S7; // Timezone immediately
after hour (e.g., "2011-01-15T12Z")
- timezoneAfterHour = true;
- }
- } else if (state == S5) {
- if (c == ':')
- state = S6;
- else if (c == 'Z' || c == '+' || c == '-') {
- state = S7; // Timezone immediately
after minute (e.g., "2011-01-15T12:30Z")
- timezoneAfterMinute = true;
- }
- } else if (state == S6) {
- if (c == 'Z' || c == '+' || c == '-')
- state = S7;
- }
- }
-
- if (needsT)
- in = in.replace(' ', 'T');
-
- var result = switch (state) {
- case S1 -> in + "-01-01T00:00:00";
- case S2 -> in + "-01T00:00:00";
- case S3 -> in + "T00:00:00";
- case S4 -> in + ":00:00";
- case S5 -> in + ":00";
- case S6 -> in; // Complete time, no timezone
- case S7 -> {
- // Complete time with timezone, but may need to
add missing components
- if (timezoneAfterHour) {
- // Timezone found after hour, need to
add :00:00 before timezone
- var tzIndex = in.length();
- for (var i = in.length() - 1; i >= 0;
i--) {
- var ch = in.charAt(i);
- if (ch == 'Z' || ch == '+' ||
ch == '-') {
- tzIndex = i;
- break;
- }
- }
- yield in.substring(0, tzIndex) +
":00:00" + in.substring(tzIndex);
- } else if (timezoneAfterMinute) {
- // Timezone found after minute, need to
add :00 before timezone
- var tzIndex = in.length();
- for (var i = in.length() - 1; i >= 0;
i--) {
- var ch = in.charAt(i);
- if (ch == 'Z' || ch == '+' ||
ch == '-') {
- tzIndex = i;
- break;
- }
- }
- yield in.substring(0, tzIndex) + ":00"
+ in.substring(tzIndex);
- } else {
- yield in; // Complete time with
timezone (already has seconds)
- }
- }
- default -> in;
- };
-
- if (state != S7)
- result +=
ZonedDateTime.now(ZoneId.systemDefault()).getOffset().toString();
-
- return result;
- }
-
- /**
- * Parses an ISO8601 string into a calendar.
- *
- * <p>
- * TODO-90: Investigate whether this helper can be removed in favor of
java.time parsing (see TODO.md).
- *
- * <p>
- * Supports any of the following formats:
- * <br><c>yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddThh, yyyy-MM-ddThh:mm,
yyyy-MM-ddThh:mm:ss, yyyy-MM-ddThh:mm:ss.SSS</c>
- *
- * @param date The date string.
- * @return The parsed calendar.
- * @throws IllegalArgumentException Value was not a valid date.
- */
- public static Calendar parseIsoCalendar(String date) throws
IllegalArgumentException {
- if (StringUtils.isEmpty(date))
- return null;
- date = date.trim().replace(' ', 'T'); // Convert to 'standard'
ISO8601
- if (date.indexOf(',') != -1) // Trim milliseconds
- date = date.substring(0, date.indexOf(','));
- if (date.matches("\\d{4}"))
- date += "-01-01T00:00:00";
- else if (date.matches("\\d{4}\\-\\d{2}"))
- date += "-01T00:00:00";
- else if (date.matches("\\d{4}\\-\\d{2}\\-\\d{2}"))
- date += "T00:00:00";
- else if (date.matches("\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}"))
- date += ":00:00";
- else if
(date.matches("\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}\\:\\d{2}"))
- date += ":00";
- return fromIso8601Calendar(date);
- }
-
- /**
- * Parses an ISO8601 string into a date.
- *
- * <p>
- * Supports any of the following formats:
- * <br><c>yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddThh, yyyy-MM-ddThh:mm,
yyyy-MM-ddThh:mm:ss, yyyy-MM-ddThh:mm:ss.SSS</c>
- *
- * @param date The date string.
- * @return The parsed date.
- * @throws IllegalArgumentException Value was not a valid date.
- */
- public static Date parseIsoDate(String date) throws
IllegalArgumentException {
- if (StringUtils.isEmpty(date))
- return null;
- return parseIsoCalendar(date).getTime(); // NOSONAR - NPE not
possible.
- }
-
}
\ No newline at end of file
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/GranularZonedDateTime.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/GranularZonedDateTime.java
index 0f6dea9260..2169cecd4c 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/GranularZonedDateTime.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/GranularZonedDateTime.java
@@ -16,13 +16,13 @@
*/
package org.apache.juneau.commons.utils;
-import static org.apache.juneau.commons.utils.DateUtils.*;
import static org.apache.juneau.commons.utils.StateEnum.*;
import static org.apache.juneau.commons.utils.StringUtils.*;
import static org.apache.juneau.commons.utils.ThrowableUtils.*;
import static org.apache.juneau.commons.utils.Utils.*;
import java.time.*;
+import java.time.format.*;
import java.time.temporal.*;
import java.util.*;
@@ -67,7 +67,8 @@ public class GranularZonedDateTime {
* @throws BasicRuntimeException If the string cannot be parsed as a
valid timestamp.
*/
public static GranularZonedDateTime parse(String seg) {
- // Try DateUtils.fromIso8601 first for consistency
+ //seg = seg.replace(' ', 'T').replace(',', '.');
+ //var precision = getPrecisionFromString(seg);
ZonedDateTime zdt = fromIso8601(seg);
if (nn(zdt)) {
// Determine precision based on the input string
@@ -218,9 +219,13 @@ public class GranularZonedDateTime {
// S13: Found ., looking for millisecond digits
(YYYY-MM-DDTHH:MM:SS.SSS)
// S14: Found timezone (Z, +HH:mm, -HH:mm)
+ seg = seg.replace(' ', 'T').replace(',', '.');
+
var state = S1;
var precision = ChronoField.YEAR; // Track precision as we go
+ int year, month, day, hour, minute, second, ms;
+
for (var i = 0; i < seg.length(); i++) {
var c = seg.charAt(i);
@@ -372,4 +377,193 @@ public class GranularZonedDateTime {
return precision;
}
+
+ /**
+ * Parses an ISO8601 date string into a ZonedDateTime object.
+ *
+ * <p>
+ * This method converts an ISO8601 formatted date/time string into a
ZonedDateTime object,
+ * which provides full timezone context including DST handling. The
method automatically
+ * normalizes the input string to ensure it can be parsed correctly.
+ *
+ * <p>
+ * The method supports the same ISO8601 formats as {@link
#fromIso8601Calendar(String)},
+ * but returns a modern {@link ZonedDateTime} instead of a legacy
{@link Calendar}.
+ *
+ * <h5 class='section'>Examples:</h5>
+ * <p class='bjava'>
+ * <jc>// Parse UTC timezone</jc>
+ * ZonedDateTime <jv>utcZdt</jv> =
DateUtils.<jsm>fromIso8601</jsm>(<js>"2024-01-15T14:30:45Z"</js>);
+ * <jc>// Result: ZonedDateTime with UTC timezone</jc>
+ *
+ * <jc>// Parse offset timezone</jc>
+ * ZonedDateTime <jv>estZdt</jv> =
DateUtils.<jsm>fromIso8601</jsm>(<js>"2024-01-15T14:30:45-05:00"</js>);
+ * <jc>// Result: ZonedDateTime with EST timezone (-05:00)</jc>
+ *
+ * <jc>// Parse date only</jc>
+ * ZonedDateTime <jv>dateZdt</jv> =
DateUtils.<jsm>fromIso8601</jsm>(<js>"2024-01-15"</js>);
+ * <jc>// Result: ZonedDateTime with time set to 00:00:00 in
system timezone</jc>
+ * </p>
+ *
+ * <h5 class='section'>Advantages over Calendar:</h5>
+ * <ul>
+ * <li><c>Immutable</c> - Thread-safe by design
+ * <li><c>DST Aware</c> - Automatic Daylight Saving Time handling
+ * <li><c>Modern API</c> - Part of Java 8+ time package
+ * <li><c>Better Performance</c> - Optimized for modern JVMs
+ * <li><c>Type Safety</c> - Compile-time validation of operations
+ * </ul>
+ *
+ * <h5 class='section'>Timezone Handling:</h5>
+ * <p>
+ * The method preserves the original timezone information from the
ISO8601 string.
+ * If no timezone is specified, the system's default timezone is used.
The resulting
+ * ZonedDateTime object will have the appropriate timezone set and will
automatically
+ * handle DST transitions.
+ * </p>
+ *
+ * <h5 class='section'>Input Normalization:</h5>
+ * <p>
+ * The method automatically normalizes incomplete ISO8601 strings by:
+ * <ul>
+ * <li>Adding missing time components (defaults to 00:00:00)
+ * <li>Adding timezone information if missing (uses system default)
+ * <li>Ensuring proper format compliance
+ * </ul>
+ * </p>
+ *
+ * See Also: <a class="doclink"
href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601 - Wikipedia</a>
+ *
+ * @param s The ISO8601 formatted string to parse (can be null or empty)
+ * @return ZonedDateTime object representing the parsed date/time, or
null if input is null/empty
+ * @throws DateTimeParseException if the string cannot be parsed as a
valid ISO8601 date
+ * @see #fromIso8601Calendar(String)
+ * @see ZonedDateTime
+ */
+ private static ZonedDateTime fromIso8601(String s) {
+ if (StringUtils.isBlank(s))
+ return null;
+
+ // Inline toValidIso8601DT logic
+ var in = s.trim();
+
+ // "2001-07-04T15:30:45Z"
+ // S1: Looking for -
+ // S2: Found -, looking for -
+ // S3: Found -, looking for T
+ // S4: Found T, looking for :
+ // S5: Found :, looking for :
+ // S6: Found :, looking for Z or - or + or . or ,
+ // S7: Found time zone
+ // S8: Found . or , after seconds, skipping milliseconds digits
+
+ var state = S1;
+ var needsT = false;
+ var timezoneAfterHour = false; // Track if timezone found
after hour (S4)
+ var timezoneAfterMinute = false; // Track if timezone found
after minute (S5)
+ var millisCommaIndex = -1; // Track where comma-separated
milliseconds start (to convert to dot)
+
+ for (var i = 0; i < in.length(); i++) {
+ var c = in.charAt(i);
+ if (state == S1) {
+ if (c == '-')
+ state = S2;
+ } else if (state == S2) {
+ if (c == '-')
+ state = S3;
+ } else if (state == S3) {
+ if (c == 'T')
+ state = S4;
+ if (c == ' ') {
+ state = S4;
+ needsT = true;
+ }
+ } else if (state == S4) {
+ if (c == ':')
+ state = S5;
+ else if (c == 'Z' || c == '+' || c == '-') {
+ state = S7; // Timezone immediately
after hour (e.g., "2011-01-15T12Z")
+ timezoneAfterHour = true;
+ }
+ } else if (state == S5) {
+ if (c == ':')
+ state = S6;
+ else if (c == 'Z' || c == '+' || c == '-') {
+ state = S7; // Timezone immediately
after minute (e.g., "2011-01-15T12:30Z")
+ timezoneAfterMinute = true;
+ }
+ } else if (state == S6) {
+ if (c == '.' || c == ',') {
+ state = S8; // Found milliseconds
separator
+ if (c == ',') {
+ millisCommaIndex = i; // Track
comma to convert to dot
+ }
+ } else if (c == 'Z' || c == '+' || c == '-') {
+ state = S7;
+ }
+ } else if (state == S8) {
+ // S8: Reading milliseconds digits, looking for
timezone
+ if (Character.isDigit(c)) {
+ // Continue reading milliseconds
+ } else if (c == 'Z' || c == '+' || c == '-') {
+ state = S7;
+ }
+ }
+ }
+
+ // Convert comma-separated milliseconds to dot-separated
(ISO_DATE_TIME expects dots)
+ if (millisCommaIndex >= 0) {
+ var chars = in.toCharArray();
+ chars[millisCommaIndex] = '.';
+ in = new String(chars);
+ }
+
+ if (needsT)
+ in = in.replace(' ', 'T');
+
+ var validDate = switch (state) {
+ case S1 -> in + "-01-01T00:00:00";
+ case S2 -> in + "-01T00:00:00";
+ case S3 -> in + "T00:00:00";
+ case S4 -> in + ":00:00";
+ case S5 -> in + ":00";
+ case S6 -> in; // Complete time, no timezone
+ case S7 -> {
+ // Complete time with timezone, but may need to
add missing components
+ if (timezoneAfterHour) {
+ // Timezone found after hour, need to
add :00:00 before timezone
+ var tzIndex = in.length();
+ for (var i = in.length() - 1; i >= 0;
i--) {
+ var ch = in.charAt(i);
+ if (ch == 'Z' || ch == '+' ||
ch == '-') {
+ tzIndex = i;
+ break;
+ }
+ }
+ yield in.substring(0, tzIndex) +
":00:00" + in.substring(tzIndex);
+ } else if (timezoneAfterMinute) {
+ // Timezone found after minute, need to
add :00 before timezone
+ var tzIndex = in.length();
+ for (var i = in.length() - 1; i >= 0;
i--) {
+ var ch = in.charAt(i);
+ if (ch == 'Z' || ch == '+' ||
ch == '-') {
+ tzIndex = i;
+ break;
+ }
+ }
+ yield in.substring(0, tzIndex) + ":00"
+ in.substring(tzIndex);
+ } else {
+ yield in; // Complete time with
timezone (already has seconds)
+ }
+ }
+ default -> in;
+ };
+
+
+ if (state != S7)
+ validDate +=
ZonedDateTime.now(ZoneId.systemDefault()).getOffset().toString();
+
+ return ZonedDateTime.parse(validDate,
DateTimeFormatter.ISO_DATE_TIME);
+ }
+
}
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
index 50d0aa1e65..0dc316ba1e 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
@@ -18,7 +18,6 @@ package org.apache.juneau;
import static org.apache.juneau.commons.utils.AssertionUtils.*;
import static org.apache.juneau.commons.utils.CollectionUtils.*;
-import static org.apache.juneau.commons.utils.DateUtils.*;
import static org.apache.juneau.commons.utils.IoUtils.*;
import static org.apache.juneau.commons.utils.StringUtils.*;
import static org.apache.juneau.commons.utils.ThrowableUtils.*;
@@ -1551,13 +1550,13 @@ public class BeanSession extends ContextSession {
return (T)c2;
}
}
- return
(T)GregorianCalendar.from(fromIso8601(value.toString()));
+ return
(T)GregorianCalendar.from(GranularZonedDateTime.parse(value.toString()).getZonedDateTime());
}
if (to.isDate() && to.getInnerClass() == Date.class) {
if (from.isCalendar())
return (T)((Calendar)value).getTime();
- return
(T)GregorianCalendar.from(fromIso8601(value.toString())).getTime();
+ return
(T)GregorianCalendar.from(GranularZonedDateTime.parse(value.toString()).getZonedDateTime()).getTime();
}
if (to.hasMutaterFrom(from))
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/oapi/OpenApiParserSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/oapi/OpenApiParserSession.java
index 7c1e6d38c8..174fef0815 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/oapi/OpenApiParserSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/oapi/OpenApiParserSession.java
@@ -279,8 +279,10 @@ public class OpenApiParserSession extends UonParserSession
{
if (sType.isObject()) {
if (f == BYTE)
return toType(base64Decode(in),
type);
- if (f == DATE || f == DATE_TIME)
- return
toType(DateUtils.parseIsoCalendar(in), type);
+ if (f == DATE || f == DATE_TIME) {
+ var in2 = in;
+ return toType(opt(in).filter(x1
-> ! isBlank(x1)).map(x ->
GranularZonedDateTime.parse(in2).getZonedDateTime()).map(GregorianCalendar::from).orElse(null),
type);
+ }
if (f == BINARY)
return toType(fromHex(in),
type);
if (f == BINARY_SPACED)
diff --git
a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogsResource.java
b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogsResource.java
index 601695a60c..6907d3b09b 100644
---
a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogsResource.java
+++
b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogsResource.java
@@ -227,8 +227,8 @@ public class LogsResource extends BasicRestServlet {
var f = getFile(path);
- var startDate = DateUtils.parseIsoDate(start);
- var endDate = DateUtils.parseIsoDate(end);
+ var startDate = opt(start).filter(x1 -> ! isBlank(x1)).map(x2
->
GranularZonedDateTime.parse(start).getZonedDateTime()).map(GregorianCalendar::from).map(x
-> x.getTime()).orElse(null);
+ var endDate = opt(end).filter(x11 -> ! isBlank(x11)).map(x4 ->
GranularZonedDateTime.parse(end).getZonedDateTime()).map(GregorianCalendar::from).map(x3
-> x3.getTime()).orElse(null);
if (! highlight) {
var o = getReader(f, startDate, endDate, thread,
loggers, severity);
@@ -297,8 +297,8 @@ public class LogsResource extends BasicRestServlet {
var f = getFile(path);
req.setAttribute("fullPath", f.getAbsolutePath());
- var startDate = DateUtils.parseIsoDate(start);
- var endDate = DateUtils.parseIsoDate(end);
+ var startDate = opt(start).filter(x1 -> ! isBlank(x1)).map(x2
->
GranularZonedDateTime.parse(start).getZonedDateTime()).map(GregorianCalendar::from).map(x
-> x.getTime()).orElse(null);
+ var endDate = opt(end).filter(x11 -> ! isBlank(x11)).map(x4 ->
GranularZonedDateTime.parse(end).getZonedDateTime()).map(GregorianCalendar::from).map(x3
-> x3.getTime()).orElse(null);
return getLogParser(f, startDate, endDate, thread, loggers,
severity);
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripTransformBeans_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripTransformBeans_Test.java
index 6f041b43ed..5cb0e03d8a 100755
---
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripTransformBeans_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripTransformBeans_Test.java
@@ -20,6 +20,7 @@ import static org.apache.juneau.TestUtils.*;
import static org.apache.juneau.commons.utils.CollectionUtils.*;
import static org.apache.juneau.commons.utils.DateUtils.*;
import static org.apache.juneau.commons.utils.IoUtils.*;
+import static org.apache.juneau.commons.utils.StringUtils.*;
import static org.apache.juneau.junit.bct.BctAssertions.*;
import static org.junit.jupiter.api.Assertions.*;
@@ -30,6 +31,7 @@ import javax.xml.datatype.*;
import org.apache.juneau.*;
import org.apache.juneau.annotation.*;
+import org.apache.juneau.commons.utils.*;
import org.apache.juneau.html.*;
import org.apache.juneau.json.*;
import org.apache.juneau.msgpack.*;
@@ -552,7 +554,7 @@ class RoundTripTransformBeans_Test extends TestBase {
public static F1 create() {
var x = new F1();
- x.setC(fromIso8601Calendar("2018-12-12T05:12:00Z"));
+ x.setC(opt("2018-12-12T05:12:00Z").filter(x2 -> !
isBlank(x2)).map(x1 ->
GranularZonedDateTime.parse("2018-12-12T05:12:00Z").getZonedDateTime()).map(GregorianCalendar::from).orElse(null));
return x;
}
}
@@ -587,7 +589,7 @@ class RoundTripTransformBeans_Test extends TestBase {
public static F1c create() {
var x = new F1c();
- x.setC(fromIso8601Calendar("2018-12-12T05:12:00Z"));
+ x.setC(opt("2018-12-12T05:12:00Z").filter(x2 -> !
isBlank(x2)).map(x1 ->
GranularZonedDateTime.parse("2018-12-12T05:12:00Z").getZonedDateTime()).map(GregorianCalendar::from).orElse(null));
return x;
}
}
@@ -621,7 +623,7 @@ class RoundTripTransformBeans_Test extends TestBase {
public static F2 create() {
var x = new F2();
- x.setC(fromIso8601Calendar("2018-12-12T05:12:00Z"));
+ x.setC(opt("2018-12-12T05:12:00Z").filter(x2 -> !
isBlank(x2)).map(x1 ->
GranularZonedDateTime.parse("2018-12-12T05:12:00Z").getZonedDateTime()).map(GregorianCalendar::from).orElse(null));
return x;
}
}
@@ -659,7 +661,7 @@ class RoundTripTransformBeans_Test extends TestBase {
public static F2c create() {
var x = new F2c();
- x.setC(fromIso8601Calendar("2018-12-12T05:12:00Z"));
+ x.setC(opt("2018-12-12T05:12:00Z").filter(x2 -> !
isBlank(x2)).map(x1 ->
GranularZonedDateTime.parse("2018-12-12T05:12:00Z").getZonedDateTime()).map(GregorianCalendar::from).orElse(null));
return x;
}
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/utils/DateUtils_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/utils/DateUtils_Test.java
index 5c2665a108..c8460cf7ae 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/utils/DateUtils_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/utils/DateUtils_Test.java
@@ -17,9 +17,9 @@
package org.apache.juneau.commons.utils;
import static java.time.temporal.ChronoField.*;
-import static java.time.temporal.ChronoUnit.*;
import static java.util.Calendar.*;
-import static org.apache.juneau.commons.utils.DateUtils.*;
+import static org.apache.juneau.commons.utils.StringUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
import static org.junit.jupiter.api.Assertions.*;
import java.time.*;
@@ -162,7 +162,8 @@ class DateUtils_Test extends TestBase {
@MethodSource("input")
void j01_fromIso8601Calendar(Input input) {
// Parse the ISO8601 string
- Calendar result =
fromIso8601Calendar(input.iso8601String);
+ String s = input.iso8601String;
+ Calendar result = opt(s).filter(x1 -> !
isBlank(x1)).map(x ->
GranularZonedDateTime.parse(s).getZonedDateTime()).map(GregorianCalendar::from).orElse(null);
// Verify the result is not null
assertNotNull(result, "Test " + input.index + ": Result
should not be null");
@@ -244,7 +245,7 @@ class DateUtils_Test extends TestBase {
@MethodSource("input")
void k01_fromIso8601(Input input) {
// Parse the ISO8601 string
- ZonedDateTime result = fromIso8601(input.iso8601String);
+ ZonedDateTime result =
GranularZonedDateTime.parse(input.iso8601String).getZonedDateTime();
// Verify the result is not null
assertNotNull(result, "Test " + input.index + ": Result
should not be null");
@@ -268,7 +269,7 @@ class DateUtils_Test extends TestBase {
@MethodSource("input")
void k02_fromIso8601_immutability(Input input) {
// Parse the ISO8601 string
- ZonedDateTime result = fromIso8601(input.iso8601String);
+ ZonedDateTime result =
GranularZonedDateTime.parse(input.iso8601String).getZonedDateTime();
assertNotNull(result, "Test " + input.index + ": Result
should not be null");
// Verify immutability - operations should return new
instances
@@ -288,44 +289,26 @@ class DateUtils_Test extends TestBase {
static class L_fromIso8601_edgeCases {
- @Test
- void l01_nullInput() {
- assertNull(fromIso8601Calendar(null));
- assertNull(fromIso8601(null));
- }
-
- @Test
- void l02_emptyInput() {
- assertNull(fromIso8601Calendar(""));
- assertNull(fromIso8601(""));
- }
-
- @Test
- void l03_whitespaceInput() {
- assertNull(fromIso8601Calendar(" "));
- assertNull(fromIso8601(" "));
- }
-
@Test
void l04_invalidFormat() {
// These should throw DateTimeParseException
assertThrows(Exception.class, () -> {
- fromIso8601Calendar("invalid-date");
+ opt("invalid-date").filter(x1 -> !
isBlank(x1)).map(x ->
GranularZonedDateTime.parse("invalid-date").getZonedDateTime()).map(GregorianCalendar::from).orElse(null);
});
assertThrows(Exception.class, () -> {
- fromIso8601("invalid-date");
+
GranularZonedDateTime.parse("invalid-date").getZonedDateTime();
});
}
@Test
void l05_minimumDate() {
- Calendar cal =
fromIso8601Calendar("0001-01-01T00:00:00Z");
+ Calendar cal = opt("0001-01-01T00:00:00Z").filter(x1 ->
! isBlank(x1)).map(x ->
GranularZonedDateTime.parse("0001-01-01T00:00:00Z").getZonedDateTime()).map(GregorianCalendar::from).orElse(null);
assertNotNull(cal);
assertEquals(1, cal.get(Calendar.YEAR));
assertEquals(Calendar.JANUARY, cal.get(Calendar.MONTH));
assertEquals(1, cal.get(Calendar.DAY_OF_MONTH));
- ZonedDateTime zdt = fromIso8601("0001-01-01T00:00:00Z");
+ ZonedDateTime zdt =
GranularZonedDateTime.parse("0001-01-01T00:00:00Z").getZonedDateTime();
assertNotNull(zdt);
assertEquals(1, zdt.getYear());
assertEquals(1, zdt.getMonthValue());
@@ -334,13 +317,13 @@ class DateUtils_Test extends TestBase {
@Test
void l06_maximumDate() {
- Calendar cal =
fromIso8601Calendar("9999-12-31T23:59:59Z");
+ Calendar cal = opt("9999-12-31T23:59:59Z").filter(x1 ->
! isBlank(x1)).map(x ->
GranularZonedDateTime.parse("9999-12-31T23:59:59Z").getZonedDateTime()).map(GregorianCalendar::from).orElse(null);
assertNotNull(cal);
assertEquals(9999, cal.get(Calendar.YEAR));
assertEquals(Calendar.DECEMBER,
cal.get(Calendar.MONTH));
assertEquals(31, cal.get(Calendar.DAY_OF_MONTH));
- ZonedDateTime zdt = fromIso8601("9999-12-31T23:59:59Z");
+ ZonedDateTime zdt =
GranularZonedDateTime.parse("9999-12-31T23:59:59Z").getZonedDateTime();
assertNotNull(zdt);
assertEquals(9999, zdt.getYear());
assertEquals(12, zdt.getMonthValue());
@@ -349,13 +332,13 @@ class DateUtils_Test extends TestBase {
@Test
void l07_leapYear() {
- Calendar cal =
fromIso8601Calendar("2024-02-29T12:00:00Z");
+ Calendar cal = opt("2024-02-29T12:00:00Z").filter(x1 ->
! isBlank(x1)).map(x ->
GranularZonedDateTime.parse("2024-02-29T12:00:00Z").getZonedDateTime()).map(GregorianCalendar::from).orElse(null);
assertNotNull(cal);
assertEquals(2024, cal.get(Calendar.YEAR));
assertEquals(Calendar.FEBRUARY,
cal.get(Calendar.MONTH));
assertEquals(29, cal.get(Calendar.DAY_OF_MONTH));
- ZonedDateTime zdt = fromIso8601("2024-02-29T12:00:00Z");
+ ZonedDateTime zdt =
GranularZonedDateTime.parse("2024-02-29T12:00:00Z").getZonedDateTime();
assertNotNull(zdt);
assertEquals(2024, zdt.getYear());
assertEquals(2, zdt.getMonthValue());
@@ -365,99 +348,17 @@ class DateUtils_Test extends TestBase {
@Test
void l08_dstTransition() {
// Test DST transition in America/New_York (Spring
forward)
- Calendar cal =
fromIso8601Calendar("2024-03-10T02:30:00-05:00");
+ Calendar cal =
opt("2024-03-10T02:30:00-05:00").filter(x1 -> ! isBlank(x1)).map(x ->
GranularZonedDateTime.parse("2024-03-10T02:30:00-05:00").getZonedDateTime()).map(GregorianCalendar::from).orElse(null);
assertNotNull(cal);
assertEquals(2024, cal.get(Calendar.YEAR));
assertEquals(Calendar.MARCH, cal.get(Calendar.MONTH));
assertEquals(10, cal.get(Calendar.DAY_OF_MONTH));
- ZonedDateTime zdt =
fromIso8601("2024-03-10T02:30:00-05:00");
+ ZonedDateTime zdt =
GranularZonedDateTime.parse("2024-03-10T02:30:00-05:00").getZonedDateTime();
assertNotNull(zdt);
assertEquals(2024, zdt.getYear());
assertEquals(3, zdt.getMonthValue());
assertEquals(10, zdt.getDayOfMonth());
}
}
-
-
//-----------------------------------------------------------------------------------------------------------------
- // parseIsoCalendar(String) tests
-
//-----------------------------------------------------------------------------------------------------------------
-
- @Test
- void m01_parseIsoCalendar() throws Exception {
- // Various ISO8601 formats
- var cal1 = parseIsoCalendar("2023");
- assertNotNull(cal1);
- assertEquals(2023, cal1.get(Calendar.YEAR));
-
- var cal2 = parseIsoCalendar("2023-12");
- assertNotNull(cal2);
- assertEquals(2023, cal2.get(Calendar.YEAR));
- assertEquals(Calendar.DECEMBER, cal2.get(Calendar.MONTH));
-
- var cal3 = parseIsoCalendar("2023-12-25");
- assertNotNull(cal3);
- assertEquals(2023, cal3.get(Calendar.YEAR));
- assertEquals(Calendar.DECEMBER, cal3.get(Calendar.MONTH));
- assertEquals(25, cal3.get(Calendar.DAY_OF_MONTH));
-
- var cal4 = parseIsoCalendar("2023-12-25T14:30:00");
- assertNotNull(cal4);
- assertEquals(14, cal4.get(Calendar.HOUR_OF_DAY));
- assertEquals(30, cal4.get(Calendar.MINUTE));
- assertEquals(0, cal4.get(Calendar.SECOND));
-
- // Should throw for invalid dates (DateTimeParseException is
thrown by DateUtils, not IllegalArgumentException)
- assertThrows(Exception.class, () ->
parseIsoCalendar("invalid"));
- assertThrows(Exception.class, () ->
parseIsoCalendar("2023-13-25")); // Invalid month
-
- // Test empty input - triggers code path
- assertNull(parseIsoCalendar(null));
- assertNull(parseIsoCalendar(""));
- assertNull(parseIsoCalendar(" "));
-
- // Test with milliseconds (comma) - triggers code path
- var cal5 = parseIsoCalendar("2023-12-25T14:30:00,123");
- assertNotNull(cal5);
- assertEquals(14, cal5.get(Calendar.HOUR_OF_DAY));
- assertEquals(30, cal5.get(Calendar.MINUTE));
- assertEquals(0, cal5.get(Calendar.SECOND)); // Milliseconds
trimmed
-
- // Test format yyyy-MM-ddThh - triggers code path
- var cal6 = parseIsoCalendar("2023-12-25T14");
- assertNotNull(cal6);
- assertEquals(14, cal6.get(Calendar.HOUR_OF_DAY));
- assertEquals(0, cal6.get(Calendar.MINUTE));
- assertEquals(0, cal6.get(Calendar.SECOND));
-
- // Test format yyyy-MM-ddThh:mm - triggers code path
- var cal7 = parseIsoCalendar("2023-12-25T14:30");
- assertNotNull(cal7);
- assertEquals(14, cal7.get(Calendar.HOUR_OF_DAY));
- assertEquals(30, cal7.get(Calendar.MINUTE));
- assertEquals(0, cal7.get(Calendar.SECOND));
- }
-
-
//-----------------------------------------------------------------------------------------------------------------
- // parseIsoDate(String) tests
-
//-----------------------------------------------------------------------------------------------------------------
-
- @Test
- void m02_parseIsoDate() throws Exception {
- // parseIsoDate wraps parseIsoCalendar, so test similar cases
- var date1 = parseIsoDate("2023-12-25");
- assertNotNull(date1);
-
- var date2 = parseIsoDate("2023-12-25T14:30:00");
- assertNotNull(date2);
-
- // Test empty input - triggers code path
- // Note: parseIsoDate checks isEmpty before calling
parseIsoCalendar, so it returns null
- assertNull(parseIsoDate(null));
- assertNull(parseIsoDate(""));
-
- // Should throw for invalid dates (DateTimeParseException is
thrown by DateUtils, not IllegalArgumentException)
- assertThrows(Exception.class, () -> parseIsoDate("invalid"));
- }
-
}
\ No newline at end of file
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/httppart/OpenApiPartSerializer_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/httppart/OpenApiPartSerializer_Test.java
index ed66342b39..9ebfbea400 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/httppart/OpenApiPartSerializer_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/httppart/OpenApiPartSerializer_Test.java
@@ -176,14 +176,14 @@ class OpenApiPartSerializer_Test extends TestBase {
@Test void c06_stringType_dateFormat() throws Exception {
var ps = T_DATE;
- var in = DateUtils.parseIsoCalendar("2012-12-21");
+ var in = opt("2012-12-21").filter(x1 -> ! isBlank(x1)).map(x ->
GranularZonedDateTime.parse("2012-12-21").getZonedDateTime()).map(GregorianCalendar::from).orElse(null);
assertTrue(serialize(ps, in).contains("2012"));
assertEquals("null", serialize(ps, null));
}
@Test void c07_stringType_dateTimeFormat() throws Exception {
var ps = T_DATETIME;
- var in = DateUtils.parseIsoCalendar("2012-12-21T12:34:56.789");
+ var in = opt("2012-12-21T12:34:56.789").filter(x1 -> !
isBlank(x1)).map(x ->
GranularZonedDateTime.parse("2012-12-21T12:34:56.789").getZonedDateTime()).map(GregorianCalendar::from).orElse(null);
assertTrue(serialize(ps, in).contains("2012"));
assertEquals("null", serialize(ps, null));
}
@@ -918,13 +918,13 @@ class OpenApiPartSerializer_Test extends TestBase {
assertEquals(
"f01=foo,f02=Zm9v,f04=2012-12-21T12:34:56Z,f05=666F6F,f06=66 6F
6F,f07=foo,f08=1,f09=2,f10=1.0,f11=1.0,f12=true,f99=1",
- serialize(ps, new
H2("foo",foob,DateUtils.parseIsoCalendar("2012-12-21T12:34:56Z"),foob,foob,"foo",1,2,1.0,1.0,true,1))
+ serialize(ps, new
H2("foo",foob,opt("2012-12-21T12:34:56Z").filter(x1 -> ! isBlank(x1)).map(x ->
GranularZonedDateTime.parse("2012-12-21T12:34:56Z").getZonedDateTime()).map(GregorianCalendar::from).orElse(null),foob,foob,"foo",1,2,1.0,1.0,true,1))
);
assertEquals("", serialize(ps, new
H2(null,null,null,null,null,null,null,null,null,null,null,null)));
assertEquals("null", serialize(ps, null));
assertEquals(
"f01=foo,f02=Zm9v,f04=2012-12-21T12:34:56Z,f05=666F6F,f06=66 6F
6F,f07=foo,f08=1,f09=2,f10=1.0,f11=1.0,f12=true,f99=1",
- serialize(ps,
JsonMap.of("f01","foo","f02",foob,"f04",DateUtils.parseIsoCalendar("2012-12-21T12:34:56Z"),"f05",foob,"f06",foob,"f07","foo","f08",1,"f09",2,"f10",1.0,"f11",1.0,"f12",true,"f99",1))
+ serialize(ps,
JsonMap.of("f01","foo","f02",foob,"f04",opt("2012-12-21T12:34:56Z").filter(x11
-> ! isBlank(x11)).map(x2 ->
GranularZonedDateTime.parse("2012-12-21T12:34:56Z").getZonedDateTime()).map(GregorianCalendar::from).orElse(null),"f05",foob,"f06",foob,"f07","foo","f08",1,"f09",2,"f10",1.0,"f11",1.0,"f12",true,"f99",1))
);
}
@@ -948,7 +948,7 @@ class OpenApiPartSerializer_Test extends TestBase {
assertEquals(
"(f01=@('a,b',null),f02=@(Zm9v,null),f04=@(2012-12-21T12:34:56Z,null),f05=@(666F6F,null),f06=@('66
6F
6F',null),f07=@(a,b,null),f08=@(1,2,null),f09=@(3,4,null),f10=@(1.0,2.0,null),f11=@(3.0,4.0,null),f12=@(true,false,null),f99=@(1,x,null))",
- serialize(ps, new H2(a("a,b",null),new
byte[][]{foob,null},a(DateUtils.parseIsoCalendar("2012-12-21T12:34:56Z"),null),new
byte[][]{foob,null},new
byte[][]{foob,null},a("a","b",null),a(1,2,null),a(3,4,null),a(1f,2f,null),a(3f,4f,null),a(true,false,null),a(1,"x",null)))
+ serialize(ps, new H2(a("a,b",null),new
byte[][]{foob,null},a(opt("2012-12-21T12:34:56Z").filter(x1 -> !
isBlank(x1)).map(x ->
GranularZonedDateTime.parse("2012-12-21T12:34:56Z").getZonedDateTime()).map(GregorianCalendar::from).orElse(null),null),new
byte[][]{foob,null},new
byte[][]{foob,null},a("a","b",null),a(1,2,null),a(3,4,null),a(1f,2f,null),a(3f,4f,null),a(true,false,null),a(1,"x",null)))
);
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/oapi/OpenApi_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/oapi/OpenApi_Test.java
index f8ad772d36..21f8d97cf0 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/oapi/OpenApi_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/oapi/OpenApi_Test.java
@@ -18,7 +18,7 @@ package org.apache.juneau.oapi;
import static org.apache.juneau.TestUtils.*;
import static org.apache.juneau.commons.utils.CollectionUtils.*;
-import static org.apache.juneau.commons.utils.DateUtils.*;
+import static org.apache.juneau.commons.utils.StringUtils.*;
import static org.apache.juneau.httppart.HttpPartSchema.*;
import static org.apache.juneau.junit.bct.BctAssertions.*;
import static org.junit.jupiter.api.Assertions.*;
@@ -27,6 +27,7 @@ import java.util.*;
import org.apache.juneau.*;
import org.apache.juneau.collections.*;
+import org.apache.juneau.commons.utils.*;
import org.apache.juneau.httppart.*;
import org.apache.juneau.serializer.*;
import org.junit.jupiter.api.*;
@@ -860,6 +861,6 @@ public class OpenApi_Test extends TestBase {
//---------------------------------------------------------------------------------------------
private static Calendar cal(String in) {
- return fromIso8601Calendar(in);
+ return opt(in).filter(x1 -> ! isBlank(x1)).map(x ->
GranularZonedDateTime.parse(in).getZonedDateTime()).map(GregorianCalendar::from).orElse(null);
}
}
\ No newline at end of file
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectSearcher_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectSearcher_Test.java
index 0be0aac263..c593b530ae 100755
---
a/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectSearcher_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectSearcher_Test.java
@@ -18,7 +18,6 @@ package org.apache.juneau.objecttools;
import static org.apache.juneau.TestUtils.*;
import static org.apache.juneau.commons.utils.CollectionUtils.*;
-import static org.apache.juneau.commons.utils.DateUtils.*;
import static org.apache.juneau.commons.utils.StringUtils.*;
import static org.apache.juneau.junit.bct.BctAssertions.*;
@@ -388,7 +387,7 @@ public class ObjectSearcher_Test extends TestBase {
var bb = new B[dates.length];
for (var i = 0; i < dates.length; i++) {
bb[i] = new B();
- bb[i].f = fromIso8601Calendar(dates[i]);
+ bb[i].f = opt(dates[i]).filter(x1 -> !
isBlank(x1)).map(x ->
GranularZonedDateTime.parse(x).getZonedDateTime()).map(GregorianCalendar::from).orElse(null);
}
return bb;
}