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> -&gt; 
<js>"2001-07-04T15:30:45-05:00"</js>
-        *      <li><js>"2001-07-04T15:30:45Z"</js> -&gt; 
<js>"2001-07-04T15:30:45Z"</js>
-        *      <li><js>"2001-07-04T15:30:45.1Z"</js> -&gt; 
<js>"2001-07-04T15:30:45.1Z"</js>
-        *      <li><js>"2001-07-04T15:30Z"</js> -&gt; 
<js>"2001-07-04T15:30:00Z"</js>
-        *      <li><js>"2001-07-04T15:30"</js> -&gt; 
<js>"2001-07-04T15:30:00"</js>
-        *      <li><js>"2001-07-04"</js> -&gt; 
<li><js>"2001-07-04T00:00:00"</js>
-        *      <li><js>"2001-07"</js> -&gt; <js>"2001-07-01T00:00:00"</js>
-        *      <li><js>"2001"</js> -&gt; <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;
                }

Reply via email to