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
commit 4cfe1de3259cdaf914db48b48d2101959fb99795 Author: James Bognar <[email protected]> AuthorDate: Wed Dec 3 18:07:06 2025 -0800 Unit tests --- .../org/apache/juneau/commons/utils/DateUtils.java | 535 ++------------------ .../commons/utils/GranularZonedDateTime.java | 230 ++++++++- .../apache/juneau/commons/utils/StringUtils.java | 50 -- .../apache/juneau/oapi/OpenApiParserSession.java | 2 +- .../microservice/resources/LogsResource.java | 9 +- .../juneau/rest/mock/MockServletResponse.java | 14 +- .../juneau/commons/utils/DateUtils_Test.java | 557 +++------------------ .../juneau/commons/utils/StringUtils_Test.java | 78 --- .../httppart/OpenApiPartSerializer_Test.java | 10 +- 9 files changed, 379 insertions(+), 1106 deletions(-) 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 32e9c7912e..07e567ec1f 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 @@ -41,125 +41,6 @@ import java.util.*; */ public class DateUtils { - /** - * A factory for {@link SimpleDateFormat}s. - * - * <p> - * The instances are stored in a thread-local way because SimpleDateFormat is not thread-safe as noted in - * {@link SimpleDateFormat its javadoc}. - */ - static class DateFormatHolder { - private static final ThreadLocal<SoftReference<Map<String,SimpleDateFormat>>> THREADLOCAL_FORMATS = new ThreadLocal<>() { - @Override - protected SoftReference<Map<String,SimpleDateFormat>> initialValue() { - var m = new HashMap<String,SimpleDateFormat>(); - return new SoftReference<>(m); - } - }; - - public static void clearThreadLocal() { - THREADLOCAL_FORMATS.remove(); - } - - /** - * Creates a {@link SimpleDateFormat} for the requested format string. - * - * @param pattern - * A non-<c>null</c> format String according to {@link SimpleDateFormat}. - * The format is not checked against <c>null</c> since all paths go through {@link DateUtils}. - * @return - * The requested format. - * This simple date-format should not be used to {@link SimpleDateFormat#applyPattern(String) apply} to a - * different pattern. - */ - public static SimpleDateFormat formatFor(String pattern) { - var ref = THREADLOCAL_FORMATS.get(); - var formats = ref.get(); - if (formats == null) { - formats = new HashMap<>(); - THREADLOCAL_FORMATS.set(new SoftReference<>(formats)); - } - var format = formats.get(pattern); - if (format == null) { - format = new SimpleDateFormat(pattern, Locale.US); - format.setTimeZone(TimeZone.getTimeZone("GMT")); - formats.put(pattern, format); - } - return format; - } - } - - /** - * Date format pattern used to parse HTTP date headers in RFC 1123 format. - */ - public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz"; - - /** - * Date format pattern used to parse HTTP date headers in RFC 1036 format. - */ - public static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz"; - /** - * Date format pattern used to parse HTTP date headers in ANSI C <c>asctime()</c> format. - */ - public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy"; - private static final TimeZone GMT = TimeZone.getTimeZone("GMT"); - - static { - var calendar = Calendar.getInstance(); - calendar.setTimeZone(GMT); - calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0); - calendar.set(Calendar.MILLISECOND, 0); - } - - /** - * Adds to a field of a calendar. - * - * @param c The calendar to modify. - * @param field The calendar field to modify (e.g., {@link Calendar#DATE}, {@link Calendar#MONTH}). - * @param amount The amount to add. - * @return The same calendar with the field modified. - */ - public static Calendar add(Calendar c, int field, int amount) { - c.add(field, amount); - return c; - } - - /** - * Adds or subtracts a number of days from the specified calendar. - * - * <p>Creates a clone of the calendar before modifying it. - * - * @param c The calendar to modify. - * @param days The number of days to add (positive) or subtract (negative). - * @return A cloned calendar with the updated date, or <jk>null</jk> if the input was <jk>null</jk>. - */ - public static Calendar addSubtractDays(Calendar c, int days) { - return opt(c).map(x -> (Calendar)x.clone()).map(x -> add(x, Calendar.DATE, days)).orElse(null); - } - - /** - * Clears thread-local variable containing {@link java.text.DateFormat} cache. - */ - public static void clearThreadLocal() { - DateFormatHolder.clearThreadLocal(); - } - - /** - * Formats the given date according to the specified pattern. - * - * <p> - * The pattern must conform to that used by the {@link SimpleDateFormat simple date format} class. - * - * @param date The date to format. - * @param pattern The pattern to use for formatting the date. - * @return A formatted date string. - * @throws IllegalArgumentException If the given date pattern is invalid. - * @see SimpleDateFormat - */ - public static String formatDate(Date date, String pattern) { - return DateFormatHolder.formatFor(pattern).format(date); - } - /** * Parses an ISO8601 date string into a ZonedDateTime object. * @@ -290,7 +171,6 @@ public class DateUtils { * @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) - * @see #toIso8601(Calendar) */ public static Calendar fromIso8601Calendar(String s) { if (isBlank(s)) @@ -319,375 +199,11 @@ public class DateUtils { } } - /** - * Determines the precision level of an ISO8601 date/time string using a state machine. - * - * <p> - * This method analyzes the structure of a date/time string to determine the finest level of precision - * represented. It uses a state machine to parse the string character by character, tracking the precision - * level as it encounters different components. - * - * <p> - * The method supports the following ISO8601 formats: - * <ul> - * <li><js>"YYYY"</js> → {@link ChronoField#YEAR} - * <li><js>"YYYY-MM"</js> → {@link ChronoField#MONTH_OF_YEAR} - * <li><js>"YYYY-MM-DD"</js> → {@link ChronoField#DAY_OF_MONTH} - * <li><js>"YYYY-MM-DDTHH"</js> → {@link ChronoField#HOUR_OF_DAY} - * <li><js>"YYYY-MM-DDTHH:MM"</js> → {@link ChronoField#MINUTE_OF_HOUR} - * <li><js>"YYYY-MM-DDTHH:MM:SS"</js> → {@link ChronoField#SECOND_OF_MINUTE} - * <li><js>"YYYY-MM-DDTHH:MM:SS.SSS"</js> → {@link ChronoField#MILLI_OF_SECOND} - * </ul> - * - * <p> - * Timezone information (Z, +HH:mm, -HH:mm) is preserved but doesn't affect the precision level. - * Invalid or unrecognized formats default to {@link ChronoField#MILLI_OF_SECOND}. - * - * <h5 class='section'>Examples:</h5> - * <p class='bjava'> - * <jc>// Year precision</jc> - * ChronoField <jv>precision1</jv> = DateUtils.<jsm>getPrecisionFromString</jsm>(<js>"2011"</js>); - * <jc>// Returns ChronoField.YEAR</jc> - * - * <jc>// Month precision</jc> - * ChronoField <jv>precision2</jv> = DateUtils.<jsm>getPrecisionFromString</jsm>(<js>"2011-01"</js>); - * <jc>// Returns ChronoField.MONTH_OF_YEAR</jc> - * - * <jc>// Day precision</jc> - * ChronoField <jv>precision3</jv> = DateUtils.<jsm>getPrecisionFromString</jsm>(<js>"2011-01-01"</js>); - * <jc>// Returns ChronoField.DAY_OF_MONTH</jc> - * - * <jc>// Hour precision with timezone</jc> - * ChronoField <jv>precision4</jv> = DateUtils.<jsm>getPrecisionFromString</jsm>(<js>"2011-01-01T12Z"</js>); - * <jc>// Returns ChronoField.HOUR_OF_DAY</jc> - * - * <jc>// Millisecond precision</jc> - * ChronoField <jv>precision5</jv> = DateUtils.<jsm>getPrecisionFromString</jsm>(<js>"2011-01-01T12:30:45.123"</js>); - * <jc>// Returns ChronoField.MILLI_OF_SECOND</jc> - * </p> - * - * See Also: <a class="doclink" href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601 - Wikipedia</a> - * - * @param seg The date/time string to analyze (can be null or empty) - * @return The ChronoField representing the precision level, or {@link ChronoField#MILLI_OF_SECOND} for invalid/empty strings - * @see ChronoField - */ - public static ChronoField getPrecisionFromString(String seg) { - if (isEmpty(seg)) - return ChronoField.MILLI_OF_SECOND; - - // States: - // S1: Looking for year digits (YYYY) - // S2: Found year, looking for - or T or end (YYYY) - // S3: Found -, looking for month digits (YYYY-MM) - // S4: Found month, looking for - or T or end (YYYY-MM) - // S5: Found -, looking for day digits (YYYY-MM-DD) - // S6: Found day, looking for T or end (YYYY-MM-DD) - // S7: Found T, looking for hour digits (YYYY-MM-DDTHH) - // S8: Found hour, looking for : or end (YYYY-MM-DDTHH) - // S9: Found :, looking for minute digits (YYYY-MM-DDTHH:MM) - // S10: Found minute, looking for : or end (YYYY-MM-DDTHH:MM) - // S11: Found :, looking for second digits (YYYY-MM-DDTHH:MM:SS) - // S12: Found second, looking for . or end (YYYY-MM-DDTHH:MM:SS) - // S13: Found ., looking for millisecond digits (YYYY-MM-DDTHH:MM:SS.SSS) - // S14: Found timezone (Z, +HH:mm, -HH:mm) - - var state = S1; - var precision = ChronoField.YEAR; // Track precision as we go - - for (var i = 0; i < seg.length(); i++) { - var c = seg.charAt(i); - - if (state == S1) { - // S1: Looking for year digits (YYYY) - if (Character.isDigit(c)) { - state = S2; - } else if (c == '-') { - state = S3; - precision = ChronoField.MONTH_OF_YEAR; - } else if (c == 'T') { - state = S7; - precision = ChronoField.HOUR_OF_DAY; - } else if (c == 'Z' || c == '+' || c == '-') { - state = S14; - // Keep current precision (YEAR) - } - } else if (state == S2) { - // S2: Found year, looking for - or T or end (YYYY) - if (c == '-') { - state = S3; - precision = ChronoField.MONTH_OF_YEAR; - } else if (c == 'T') { - state = S7; - precision = ChronoField.HOUR_OF_DAY; - } else if (c == 'Z' || c == '+' || c == '-') { - state = S14; - // Keep current precision (YEAR) - } - } else if (state == S3) { - // S3: Found -, looking for month digits (YYYY-MM) - if (Character.isDigit(c)) { - state = S4; - } else if (c == '-') { - state = S5; - precision = ChronoField.DAY_OF_MONTH; - } else if (c == 'T') { - state = S7; - precision = ChronoField.HOUR_OF_DAY; - } else if (c == 'Z' || c == '+' || c == '-') { - state = S14; - // Keep current precision (MONTH_OF_YEAR) - } - } else if (state == S4) { - // S4: Found month, looking for - or T or end (YYYY-MM) - if (c == '-') { - state = S5; - precision = ChronoField.DAY_OF_MONTH; - } else if (c == 'T') { - state = S7; - precision = ChronoField.HOUR_OF_DAY; - } else if (c == 'Z' || c == '+' || c == '-') { - state = S14; - // Keep current precision (MONTH_OF_YEAR) - } - } else if (state == S5) { - // S5: Found -, looking for day digits (YYYY-MM-DD) - if (Character.isDigit(c)) { - state = S6; - } else if (c == 'T') { - state = S7; - precision = ChronoField.HOUR_OF_DAY; - } else if (c == 'Z' || c == '+' || c == '-') { - state = S14; - // Keep current precision (DAY_OF_MONTH) - } - } else if (state == S6) { - // S6: Found day, looking for T or end (YYYY-MM-DD) - if (c == 'T') { - state = S7; - precision = ChronoField.HOUR_OF_DAY; - } else if (c == 'Z' || c == '+' || c == '-') { - state = S14; - // Keep current precision (DAY_OF_MONTH) - } - } else if (state == S7) { - // S7: Found T, looking for hour digits (YYYY-MM-DDTHH) - if (Character.isDigit(c)) { - state = S8; - } else if (c == ':') { - state = S9; - precision = ChronoField.MINUTE_OF_HOUR; - } else if (c == 'Z' || c == '+' || c == '-') { - state = S14; - // Keep current precision (HOUR_OF_DAY) - } - } else if (state == S8) { - // S8: Found hour, looking for : or end (YYYY-MM-DDTHH) - if (c == ':') { - state = S9; - precision = ChronoField.MINUTE_OF_HOUR; - } else if (c == 'Z' || c == '+' || c == '-') { - state = S14; - // Keep current precision (HOUR_OF_DAY) - } - } else if (state == S9) { - // S9: Found :, looking for minute digits (YYYY-MM-DDTHH:MM) - if (Character.isDigit(c)) { - state = S10; - } else if (c == ':') { - state = S11; - precision = ChronoField.SECOND_OF_MINUTE; - } else if (c == 'Z' || c == '+' || c == '-') { - state = S14; - // Keep current precision (MINUTE_OF_HOUR) - } - } else if (state == S10) { - // S10: Found minute, looking for : or end (YYYY-MM-DDTHH:MM) - if (c == ':') { - state = S11; - precision = ChronoField.SECOND_OF_MINUTE; - } else if (c == 'Z' || c == '+' || c == '-') { - state = S14; - // Keep current precision (MINUTE_OF_HOUR) - } - } else if (state == S11) { - // S11: Found :, looking for second digits (YYYY-MM-DDTHH:MM:SS) - if (Character.isDigit(c)) { - state = S12; - } else if (c == '.') { - state = S13; - precision = ChronoField.MILLI_OF_SECOND; - } else if (c == 'Z' || c == '+' || c == '-') { - state = S14; - // Keep current precision (SECOND_OF_MINUTE) - } - } else if (state == S12) { - // S12: Found second, looking for . or end (YYYY-MM-DDTHH:MM:SS) - if (c == '.') { - state = S13; - precision = ChronoField.MILLI_OF_SECOND; - } else if (c == 'Z' || c == '+' || c == '-') { - state = S14; - // Keep current precision (SECOND_OF_MINUTE) - } - } else if (state == S13) { - // S13: Found ., looking for millisecond digits (YYYY-MM-DDTHH:MM:SS.SSS) - if (Character.isDigit(c)) { - // Continue reading millisecond digits - } else if (c == 'Z' || c == '+' || c == '-') { - state = S14; - // Keep current precision (MILLI_OF_SECOND) - } - } else if (state == S14) { - // S14: Found timezone (Z, +HH:mm, -HH:mm) - precision already determined - // Just continue reading timezone characters - } - } - - return precision; - } // ================================================================================================================ // ChronoField/ChronoUnit/Calendar conversion utilities // ================================================================================================================ - /** - * Converts a ChronoField to its corresponding Calendar field constant. - * - * <p> - * This method provides a mapping from modern ChronoField values to legacy - * Calendar field constants for use with Calendar.add() and similar methods. - * - * @param field The ChronoField to convert - * @return The corresponding Calendar field constant - * @see ChronoField - * @see Calendar - */ - public static int toCalendarField(ChronoField field) { - return switch (field) { - case YEAR -> Calendar.YEAR; - case MONTH_OF_YEAR -> Calendar.MONTH; - case DAY_OF_MONTH -> Calendar.DAY_OF_MONTH; - case HOUR_OF_DAY -> Calendar.HOUR_OF_DAY; - case MINUTE_OF_HOUR -> Calendar.MINUTE; - case SECOND_OF_MINUTE -> Calendar.SECOND; - case MILLI_OF_SECOND -> Calendar.MILLISECOND; - default -> Calendar.MILLISECOND; - }; - } - - /** - * Converts a ChronoUnit to its corresponding ChronoField. - * - * <p> - * This method provides a mapping from time units to date/time fields. - * Not all ChronoUnit values have direct ChronoField equivalents. - * - * @param unit The ChronoUnit to convert - * @return The corresponding ChronoField, or null if no direct mapping exists - * @see ChronoUnit - * @see ChronoField - */ - public static ChronoField toChronoField(ChronoUnit unit) { - return switch (unit) { - case YEARS -> ChronoField.YEAR; - case MONTHS -> ChronoField.MONTH_OF_YEAR; - case DAYS -> ChronoField.DAY_OF_MONTH; - case HOURS -> ChronoField.HOUR_OF_DAY; - case MINUTES -> ChronoField.MINUTE_OF_HOUR; - case SECONDS -> ChronoField.SECOND_OF_MINUTE; - case MILLIS -> ChronoField.MILLI_OF_SECOND; - default -> null; - }; - } - - /** - * Converts a ChronoField to its corresponding ChronoUnit. - * - * <p> - * This method provides a mapping from date/time fields to time units. - * Not all ChronoField values have direct ChronoUnit equivalents. - * - * @param field The ChronoField to convert - * @return The corresponding ChronoUnit, or null if no direct mapping exists - * @see ChronoField - * @see ChronoUnit - */ - public static ChronoUnit toChronoUnit(ChronoField field) { - return switch (field) { - case YEAR -> ChronoUnit.YEARS; - case MONTH_OF_YEAR -> ChronoUnit.MONTHS; - case DAY_OF_MONTH -> ChronoUnit.DAYS; - case HOUR_OF_DAY -> ChronoUnit.HOURS; - case MINUTE_OF_HOUR -> ChronoUnit.MINUTES; - case SECOND_OF_MINUTE -> ChronoUnit.SECONDS; - case MILLI_OF_SECOND -> ChronoUnit.MILLIS; - default -> null; - }; - } - - /** - * Converts a Calendar object to an ISO8601 formatted string. - * - * <p> - * This method formats a Calendar object into a standard ISO8601 date/time string - * with timezone information. The output format follows the pattern: - * <code>yyyy-MM-dd'T'HH:mm:ssXXX</code> - * - * <p> - * The method preserves the timezone information from the Calendar object and - * formats it according to ISO8601 standards, including the timezone offset. - * - * <h5 class='section'>Examples:</h5> - * <p class='bjava'> - * <jc>// Create a Calendar with a specific timezone</jc> - * Calendar <jv>cal</jv> = Calendar.getInstance(TimeZone.getTimeZone(<js>"America/New_York"</js>)); - * <jv>cal</jv>.set(2024, Calendar.JANUARY, 15, 14, 30, 45); - * <jv>cal</jv>.set(Calendar.MILLISECOND, 123); - * - * <jc>// Convert to ISO8601 string</jc> - * String <jv>iso8601</jv> = DateUtils.<jsm>toIso8601</jsm>(<jv>cal</jv>); - * <jc>// Result: "2024-01-15T14:30:45-05:00" (or -04:00 during DST)</jc> - * - * <jc>// UTC timezone example</jc> - * Calendar <jv>utcCal</jv> = Calendar.getInstance(TimeZone.getTimeZone(<js>"UTC"</js>)); - * <jv>utcCal</jv>.set(2024, Calendar.JANUARY, 15, 19, 30, 45); - * String <jv>utcIso</jv> = DateUtils.<jsm>toIso8601</jsm>(<jv>utcCal</jv>); - * <jc>// Result: "2024-01-15T19:30:45Z"</jc> - * </p> - * - * <h5 class='section'>Format Details:</h5> - * <ul> - * <li><c>yyyy</c> - 4-digit year - * <li><c>MM</c> - 2-digit month (01-12) - * <li><c>dd</c> - 2-digit day of month (01-31) - * <li><c>T</c> - Literal 'T' separator between date and time - * <li><c>HH</c> - 2-digit hour in 24-hour format (00-23) - * <li><c>mm</c> - 2-digit minute (00-59) - * <li><c>ss</c> - 2-digit second (00-59) - * <li><c>XXX</c> - Timezone offset (+HH:mm, -HH:mm, or Z for UTC) - * </ul> - * - * <h5 class='section'>Timezone Handling:</h5> - * <p> - * The method uses the Calendar's timezone to determine the appropriate offset. - * UTC timezones are represented as 'Z', while other timezones show their - * offset from UTC (e.g., -05:00 for EST, -04:00 for EDT). - * </p> - * - * See Also: <a class="doclink" href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601 - Wikipedia</a> - * - * @param c The Calendar object to convert (cannot be null) - * @return ISO8601 formatted string representation of the Calendar - * @throws NullPointerException if the Calendar parameter is null - * @see SimpleDateFormat - * @see Calendar#getTimeZone() - */ - public static String toIso8601(Calendar c) { - var sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); - sdf.setTimeZone(c.getTimeZone()); - return sdf.format(c.getTime()); - } /** * Pads out an ISO8601 string so that it can be parsed using {@link DatatypeConverter#parseDateTime(String)}. @@ -807,12 +323,53 @@ public class DateUtils { } /** - * Converts a calendar to a {@link ZonedDateTime}. + * 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 c The calendar to convert. - * @return An {@link Optional} containing the {@link ZonedDateTime}, or empty if the input was <jk>null</jk>. + * @param date The date string. + * @return The parsed calendar. + * @throws IllegalArgumentException Value was not a valid date. */ - public static Optional<ZonedDateTime> toZonedDateTime(Calendar c) { - return opt(c).map(GregorianCalendar.class::cast).map(GregorianCalendar::toZonedDateTime); + 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 60b72acff6..0f6dea9260 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 @@ -17,6 +17,8 @@ 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.*; @@ -58,7 +60,7 @@ public class GranularZonedDateTime { * * <p> * This method uses {@link DateUtils#fromIso8601(String)} for parsing and - * {@link DateUtils#getPrecisionFromString(String)} for determining precision. + * determines precision based on the input string format. * * @param seg The string segment to parse. * @return A GranularZonedDateTime representing the parsed timestamp. @@ -128,7 +130,6 @@ public class GranularZonedDateTime { * @return A new GranularZonedDateTime with the rolled value. */ public GranularZonedDateTime roll(ChronoField field, int amount) { - // Use DateUtils utility method to convert ChronoField to ChronoUnit ChronoUnit unit = toChronoUnit(field); if (nn(unit)) { ZonedDateTime newZdt = zdt.plus(amount, unit); @@ -146,4 +147,229 @@ public class GranularZonedDateTime { public GranularZonedDateTime roll(int amount) { return roll(precision, amount); } + + /** + * Converts a ChronoField to its corresponding ChronoUnit. + * + * <p> + * This method provides a mapping from date/time fields to time units. + * Not all ChronoField values have direct ChronoUnit equivalents. + * + * @param field The ChronoField to convert + * @return The corresponding ChronoUnit, or null if no direct mapping exists + */ + private static ChronoUnit toChronoUnit(ChronoField field) { + return switch (field) { + case YEAR -> ChronoUnit.YEARS; + case MONTH_OF_YEAR -> ChronoUnit.MONTHS; + case DAY_OF_MONTH -> ChronoUnit.DAYS; + case HOUR_OF_DAY -> ChronoUnit.HOURS; + case MINUTE_OF_HOUR -> ChronoUnit.MINUTES; + case SECOND_OF_MINUTE -> ChronoUnit.SECONDS; + case MILLI_OF_SECOND -> ChronoUnit.MILLIS; + default -> null; + }; + } + + /** + * Determines the precision level of an ISO8601 date/time string using a state machine. + * + * <p> + * This method analyzes the structure of a date/time string to determine the finest level of precision + * represented. It uses a state machine to parse the string character by character, tracking the precision + * level as it encounters different components. + * + * <p> + * The method supports the following ISO8601 formats: + * <ul> + * <li><js>"YYYY"</js> → {@link ChronoField#YEAR} + * <li><js>"YYYY-MM"</js> → {@link ChronoField#MONTH_OF_YEAR} + * <li><js>"YYYY-MM-DD"</js> → {@link ChronoField#DAY_OF_MONTH} + * <li><js>"YYYY-MM-DDTHH"</js> → {@link ChronoField#HOUR_OF_DAY} + * <li><js>"YYYY-MM-DDTHH:MM"</js> → {@link ChronoField#MINUTE_OF_HOUR} + * <li><js>"YYYY-MM-DDTHH:MM:SS"</js> → {@link ChronoField#SECOND_OF_MINUTE} + * <li><js>"YYYY-MM-DDTHH:MM:SS.SSS"</js> → {@link ChronoField#MILLI_OF_SECOND} + * </ul> + * + * <p> + * Timezone information (Z, +HH:mm, -HH:mm) is preserved but doesn't affect the precision level. + * Invalid or unrecognized formats default to {@link ChronoField#MILLI_OF_SECOND}. + * + * @param seg The date/time string to analyze (can be null or empty) + * @return The ChronoField representing the precision level, or {@link ChronoField#MILLI_OF_SECOND} for invalid/empty strings + */ + private static ChronoField getPrecisionFromString(String seg) { + if (isEmpty(seg)) + return ChronoField.MILLI_OF_SECOND; + + // States: + // S1: Looking for year digits (YYYY) + // S2: Found year, looking for - or T or end (YYYY) + // S3: Found -, looking for month digits (YYYY-MM) + // S4: Found month, looking for - or T or end (YYYY-MM) + // S5: Found -, looking for day digits (YYYY-MM-DD) + // S6: Found day, looking for T or end (YYYY-MM-DD) + // S7: Found T, looking for hour digits (YYYY-MM-DDTHH) + // S8: Found hour, looking for : or end (YYYY-MM-DDTHH) + // S9: Found :, looking for minute digits (YYYY-MM-DDTHH:MM) + // S10: Found minute, looking for : or end (YYYY-MM-DDTHH:MM) + // S11: Found :, looking for second digits (YYYY-MM-DDTHH:MM:SS) + // S12: Found second, looking for . or end (YYYY-MM-DDTHH:MM:SS) + // S13: Found ., looking for millisecond digits (YYYY-MM-DDTHH:MM:SS.SSS) + // S14: Found timezone (Z, +HH:mm, -HH:mm) + + var state = S1; + var precision = ChronoField.YEAR; // Track precision as we go + + for (var i = 0; i < seg.length(); i++) { + var c = seg.charAt(i); + + if (state == S1) { + // S1: Looking for year digits (YYYY) + if (Character.isDigit(c)) { + state = S2; + } else if (c == '-') { + state = S3; + precision = ChronoField.MONTH_OF_YEAR; + } else if (c == 'T') { + state = S7; + precision = ChronoField.HOUR_OF_DAY; + } else if (c == 'Z' || c == '+' || c == '-') { + state = S14; + // Keep current precision (YEAR) + } + } else if (state == S2) { + // S2: Found year, looking for - or T or end (YYYY) + if (c == '-') { + state = S3; + precision = ChronoField.MONTH_OF_YEAR; + } else if (c == 'T') { + state = S7; + precision = ChronoField.HOUR_OF_DAY; + } else if (c == 'Z' || c == '+' || c == '-') { + state = S14; + // Keep current precision (YEAR) + } + } else if (state == S3) { + // S3: Found -, looking for month digits (YYYY-MM) + if (Character.isDigit(c)) { + state = S4; + } else if (c == '-') { + state = S5; + precision = ChronoField.DAY_OF_MONTH; + } else if (c == 'T') { + state = S7; + precision = ChronoField.HOUR_OF_DAY; + } else if (c == 'Z' || c == '+' || c == '-') { + state = S14; + // Keep current precision (MONTH_OF_YEAR) + } + } else if (state == S4) { + // S4: Found month, looking for - or T or end (YYYY-MM) + if (c == '-') { + state = S5; + precision = ChronoField.DAY_OF_MONTH; + } else if (c == 'T') { + state = S7; + precision = ChronoField.HOUR_OF_DAY; + } else if (c == 'Z' || c == '+' || c == '-') { + state = S14; + // Keep current precision (MONTH_OF_YEAR) + } + } else if (state == S5) { + // S5: Found -, looking for day digits (YYYY-MM-DD) + if (Character.isDigit(c)) { + state = S6; + } else if (c == 'T') { + state = S7; + precision = ChronoField.HOUR_OF_DAY; + } else if (c == 'Z' || c == '+' || c == '-') { + state = S14; + // Keep current precision (DAY_OF_MONTH) + } + } else if (state == S6) { + // S6: Found day, looking for T or end (YYYY-MM-DD) + if (c == 'T') { + state = S7; + precision = ChronoField.HOUR_OF_DAY; + } else if (c == 'Z' || c == '+' || c == '-') { + state = S14; + // Keep current precision (DAY_OF_MONTH) + } + } else if (state == S7) { + // S7: Found T, looking for hour digits (YYYY-MM-DDTHH) + if (Character.isDigit(c)) { + state = S8; + } else if (c == ':') { + state = S9; + precision = ChronoField.MINUTE_OF_HOUR; + } else if (c == 'Z' || c == '+' || c == '-') { + state = S14; + // Keep current precision (HOUR_OF_DAY) + } + } else if (state == S8) { + // S8: Found hour, looking for : or end (YYYY-MM-DDTHH) + if (c == ':') { + state = S9; + precision = ChronoField.MINUTE_OF_HOUR; + } else if (c == 'Z' || c == '+' || c == '-') { + state = S14; + // Keep current precision (HOUR_OF_DAY) + } + } else if (state == S9) { + // S9: Found :, looking for minute digits (YYYY-MM-DDTHH:MM) + if (Character.isDigit(c)) { + state = S10; + } else if (c == ':') { + state = S11; + precision = ChronoField.SECOND_OF_MINUTE; + } else if (c == 'Z' || c == '+' || c == '-') { + state = S14; + // Keep current precision (MINUTE_OF_HOUR) + } + } else if (state == S10) { + // S10: Found minute, looking for : or end (YYYY-MM-DDTHH:MM) + if (c == ':') { + state = S11; + precision = ChronoField.SECOND_OF_MINUTE; + } else if (c == 'Z' || c == '+' || c == '-') { + state = S14; + // Keep current precision (MINUTE_OF_HOUR) + } + } else if (state == S11) { + // S11: Found :, looking for second digits (YYYY-MM-DDTHH:MM:SS) + if (Character.isDigit(c)) { + state = S12; + } else if (c == '.') { + state = S13; + precision = ChronoField.MILLI_OF_SECOND; + } else if (c == 'Z' || c == '+' || c == '-') { + state = S14; + // Keep current precision (SECOND_OF_MINUTE) + } + } else if (state == S12) { + // S12: Found second, looking for . or end (YYYY-MM-DDTHH:MM:SS) + if (c == '.') { + state = S13; + precision = ChronoField.MILLI_OF_SECOND; + } else if (c == 'Z' || c == '+' || c == '-') { + state = S14; + // Keep current precision (SECOND_OF_MINUTE) + } + } else if (state == S13) { + // S13: Found ., looking for millisecond digits (YYYY-MM-DDTHH:MM:SS.SSS) + if (Character.isDigit(c)) { + // Continue reading millisecond digits + } else if (c == 'Z' || c == '+' || c == '-') { + state = S14; + // Keep current precision (MILLI_OF_SECOND) + } + } else if (state == S14) { + // S14: Found timezone (Z, +HH:mm, -HH:mm) - precision already determined + // Just continue reading timezone characters + } + } + + return precision; + } } diff --git a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/StringUtils.java b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/StringUtils.java index 941d0878bc..2500475717 100644 --- a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/StringUtils.java +++ b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/StringUtils.java @@ -5075,56 +5075,6 @@ public class StringUtils { return Integer.decode(s.substring(0, s.length() - 1).trim()) * m; // NOSONAR - NPE not possible here. } - /** - * 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 (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 DateUtils.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 (isEmpty(date)) - return null; - return parseIsoCalendar(date).getTime(); // NOSONAR - NPE not possible. - } - /** * Same as {@link Long#parseLong(String)} but removes any underscore characters first. * 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 bffa714a51..7c1e6d38c8 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 @@ -280,7 +280,7 @@ public class OpenApiParserSession extends UonParserSession { if (f == BYTE) return toType(base64Decode(in), type); if (f == DATE || f == DATE_TIME) - return toType(parseIsoCalendar(in), type); + return toType(DateUtils.parseIsoCalendar(in), 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 a9610b0a86..601695a60c 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 @@ -25,6 +25,7 @@ import java.util.*; import org.apache.juneau.annotation.*; import org.apache.juneau.bean.*; +import org.apache.juneau.commons.utils.*; import org.apache.juneau.config.*; import org.apache.juneau.html.annotation.*; import org.apache.juneau.http.annotation.*; @@ -226,8 +227,8 @@ public class LogsResource extends BasicRestServlet { var f = getFile(path); - var startDate = parseIsoDate(start); - var endDate = parseIsoDate(end); + var startDate = DateUtils.parseIsoDate(start); + var endDate = DateUtils.parseIsoDate(end); if (! highlight) { var o = getReader(f, startDate, endDate, thread, loggers, severity); @@ -296,8 +297,8 @@ public class LogsResource extends BasicRestServlet { var f = getFile(path); req.setAttribute("fullPath", f.getAbsolutePath()); - var startDate = parseIsoDate(start); - var endDate = parseIsoDate(end); + var startDate = DateUtils.parseIsoDate(start); + var endDate = DateUtils.parseIsoDate(end); return getLogParser(f, startDate, endDate, thread, loggers, severity); } diff --git a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockServletResponse.java b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockServletResponse.java index d101f3e408..5a932e5939 100644 --- a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockServletResponse.java +++ b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockServletResponse.java @@ -17,10 +17,12 @@ package org.apache.juneau.rest.mock; import static org.apache.juneau.commons.utils.CollectionUtils.*; -import static org.apache.juneau.commons.utils.DateUtils.*; import static org.apache.juneau.commons.utils.Utils.*; import java.io.*; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.*; import org.apache.juneau.rest.util.*; @@ -60,7 +62,10 @@ public class MockServletResponse implements HttpServletResponse { @Override /* Overridden from HttpServletResponse */ public void addDateHeader(String name, long date) { - headerMap.put(name, a(formatDate(new Date(date), PATTERN_RFC1123))); + Instant instant = Instant.ofEpochMilli(date); + DateTimeFormatter formatter = DateTimeFormatter.RFC_1123_DATE_TIME + .withZone(ZoneId.of("GMT")); + headerMap.put(name, a(formatter.format(instant))); } @Override /* Overridden from HttpServletResponse */ @@ -205,7 +210,10 @@ public class MockServletResponse implements HttpServletResponse { @Override /* Overridden from HttpServletResponse */ public void setDateHeader(String name, long date) { - headerMap.put(name, a(formatDate(new Date(date), PATTERN_RFC1123))); + Instant instant = Instant.ofEpochMilli(date); + DateTimeFormatter formatter = DateTimeFormatter.RFC_1123_DATE_TIME + .withZone(ZoneId.of("GMT")); + headerMap.put(name, a(formatter.format(instant))); } @Override /* Overridden from HttpServletResponse */ 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 a77d758fc3..5c2665a108 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 @@ -45,223 +45,16 @@ class DateUtils_Test extends TestBase { } //----------------------------------------------------------------------------------------------------------------- - // Test getPrecisionFromString method (state machine implementation) + // Test getPrecisionFromString method (now in GranularZonedDateTime) //----------------------------------------------------------------------------------------------------------------- - - static class A_getPrecisionFromString { - - private static final Input[] INPUT = { - /* 01 */ input(1, "2011", ChronoField.YEAR), - /* 02 */ input(2, "2024", ChronoField.YEAR), - /* 03 */ input(3, "1999", ChronoField.YEAR), - /* 04 */ input(4, "2011-01", MONTH_OF_YEAR), - /* 05 */ input(5, "2024-12", MONTH_OF_YEAR), - /* 06 */ input(6, "1999-06", MONTH_OF_YEAR), - /* 07 */ input(7, "2011-01-01", ChronoField.DAY_OF_MONTH), - /* 08 */ input(8, "2024-12-31", ChronoField.DAY_OF_MONTH), - /* 09 */ input(9, "1999-06-15", ChronoField.DAY_OF_MONTH), - /* 10 */ input(10, "2011-01-01T12", ChronoField.HOUR_OF_DAY), - /* 11 */ input(11, "2024-12-31T23", ChronoField.HOUR_OF_DAY), - /* 12 */ input(12, "1999-06-15T00", ChronoField.HOUR_OF_DAY), - /* 13 */ input(13, "2011-01-01T12:30", MINUTE_OF_HOUR), - /* 14 */ input(14, "2024-12-31T23:59", MINUTE_OF_HOUR), - /* 15 */ input(15, "1999-06-15T00:00", MINUTE_OF_HOUR), - /* 16 */ input(16, "2011-01-01T12:30:45", SECOND_OF_MINUTE), - /* 17 */ input(17, "2024-12-31T23:59:59", SECOND_OF_MINUTE), - /* 18 */ input(18, "1999-06-15T00:00:00", SECOND_OF_MINUTE), - /* 19 */ input(19, "2011-01-01T12:30:45.123", MILLI_OF_SECOND), - /* 20 */ input(20, "2024-12-31T23:59:59.999", MILLI_OF_SECOND), - /* 21 */ input(21, "1999-06-15T00:00:00.000", MILLI_OF_SECOND), - /* 22 */ input(22, "0000", ChronoField.YEAR), - /* 23 */ input(23, "9999", ChronoField.YEAR), - /* 24 */ input(24, "0000-01", MONTH_OF_YEAR), - /* 25 */ input(25, "9999-12", MONTH_OF_YEAR), - /* 26 */ input(26, "0000-01-01", ChronoField.DAY_OF_MONTH), - /* 27 */ input(27, "9999-12-31", ChronoField.DAY_OF_MONTH), - /* 28 */ input(28, "", MILLI_OF_SECOND), - /* 35 */ input(35, "2011Z", ChronoField.YEAR), - /* 36 */ input(36, "2011-01Z", MONTH_OF_YEAR), - /* 37 */ input(37, "2011-01-01Z", ChronoField.DAY_OF_MONTH), - /* 38 */ input(38, "2011-01-01T12Z", ChronoField.HOUR_OF_DAY), - /* 39 */ input(39, "2011-01-01T12:30Z", MINUTE_OF_HOUR), - /* 40 */ input(40, "2011-01-01T12:30:45Z", SECOND_OF_MINUTE), - /* 41 */ input(41, "2011-01-01T12:30:45.123Z", MILLI_OF_SECOND) - }; - - private static Input input(int index, String dateString, ChronoField expectedPrecision) { - return new Input(index, dateString, expectedPrecision); - } - - private static class Input { - final String dateString; - final ChronoField expectedPrecision; - - public Input(int index, String dateString, ChronoField expectedPrecision) { - this.dateString = dateString; - this.expectedPrecision = expectedPrecision; - } - } - - static Input[] input() { - return INPUT; - } - - @ParameterizedTest - @MethodSource("input") - void a01_basic(Input input) { - assertEquals(input.expectedPrecision, getPrecisionFromString(input.dateString)); - } - } + // Note: getPrecisionFromString has been moved to GranularZonedDateTime as a private method. + // Tests for this functionality are now in GranularZonedDateTime_Test via the parse() method. //----------------------------------------------------------------------------------------------------------------- - // toChronoField(ChronoUnit) tests + // toChronoUnit(ChronoField) tests (now in GranularZonedDateTime) //----------------------------------------------------------------------------------------------------------------- - - static class D_toChronoField { - - private static final Input[] INPUT = { - /* 01 */ input(1, YEARS, ChronoField.YEAR), - /* 02 */ input(2, MONTHS, MONTH_OF_YEAR), - /* 03 */ input(3, DAYS, ChronoField.DAY_OF_MONTH), - /* 04 */ input(4, HOURS, ChronoField.HOUR_OF_DAY), - /* 05 */ input(5, MINUTES, MINUTE_OF_HOUR), - /* 06 */ input(6, SECONDS, SECOND_OF_MINUTE), - /* 07 */ input(7, MILLIS, MILLI_OF_SECOND), - /* 08 */ input(8, NANOS, null), - /* 09 */ input(9, MICROS, null), - /* 10 */ input(10, WEEKS, null), - /* 11 */ input(11, DECADES, null), - /* 12 */ input(12, CENTURIES, null), - /* 13 */ input(13, MILLENNIA, null), - /* 14 */ input(14, ERAS, null) - }; - - private static Input input(int index, ChronoUnit unit, ChronoField expectedField) { - return new Input(index, unit, expectedField); - } - - private static class Input { - final int index; - final ChronoUnit unit; - final ChronoField expectedField; - - public Input(int index, ChronoUnit unit, ChronoField expectedField) { - this.index = index; - this.unit = unit; - this.expectedField = expectedField; - } - } - - static Input[] input() { - return INPUT; - } - - @ParameterizedTest - @MethodSource("input") - void d01_toChronoField(Input input) { - ChronoField result = toChronoField(input.unit); - assertEquals(input.expectedField, result, "Test " + input.index + ": " + input.unit); - } - } - - //----------------------------------------------------------------------------------------------------------------- - // toChronoUnit(ChronoField) tests - //----------------------------------------------------------------------------------------------------------------- - - static class E_toChronoUnit { - - private static final Input[] INPUT = { - /* 01 */ input(1, ChronoField.YEAR, YEARS), - /* 02 */ input(2, MONTH_OF_YEAR, MONTHS), - /* 03 */ input(3, ChronoField.DAY_OF_MONTH, DAYS), - /* 04 */ input(4, ChronoField.HOUR_OF_DAY, HOURS), - /* 05 */ input(5, MINUTE_OF_HOUR, MINUTES), - /* 06 */ input(6, SECOND_OF_MINUTE, SECONDS), - /* 07 */ input(7, MILLI_OF_SECOND, MILLIS), - /* 08 */ input(8, ChronoField.DAY_OF_WEEK, null), - /* 09 */ input(9, ChronoField.DAY_OF_YEAR, null), - /* 11 */ input(11, ALIGNED_DAY_OF_WEEK_IN_MONTH, null), - /* 12 */ input(12, ALIGNED_WEEK_OF_MONTH, null), - /* 13 */ input(13, ALIGNED_WEEK_OF_YEAR, null), - /* 14 */ input(14, NANO_OF_SECOND, null), - /* 15 */ input(15, MICRO_OF_SECOND, null) - }; - - private static Input input(int index, ChronoField field, ChronoUnit expectedUnit) { - return new Input(index, field, expectedUnit); - } - - private static class Input { - final int index; - final ChronoField field; - final ChronoUnit expectedUnit; - - public Input(int index, ChronoField field, ChronoUnit expectedUnit) { - this.index = index; - this.field = field; - this.expectedUnit = expectedUnit; - } - } - - static Input[] input() { - return INPUT; - } - - @ParameterizedTest - @MethodSource("input") - void e01_toChronoUnit(Input input) { - ChronoUnit result = toChronoUnit(input.field); - assertEquals(input.expectedUnit, result, "Test " + input.index + ": " + input.field); - } - } - - //----------------------------------------------------------------------------------------------------------------- - // toCalendarField(ChronoField) tests - //----------------------------------------------------------------------------------------------------------------- - - static class F_toCalendarField { - - private static final Input[] INPUT = { - /* 01 */ input(1, ChronoField.YEAR, Calendar.YEAR), - /* 02 */ input(2, MONTH_OF_YEAR, MONTH), - /* 03 */ input(3, ChronoField.DAY_OF_MONTH, Calendar.DAY_OF_MONTH), - /* 04 */ input(4, ChronoField.HOUR_OF_DAY, Calendar.HOUR_OF_DAY), - /* 05 */ input(5, MINUTE_OF_HOUR, MINUTE), - /* 06 */ input(6, SECOND_OF_MINUTE, SECOND), - /* 07 */ input(7, MILLI_OF_SECOND, MILLISECOND), - /* 08 */ input(8, ChronoField.DAY_OF_WEEK, MILLISECOND), // Should default to MILLISECOND - /* 09 */ input(9, ChronoField.DAY_OF_YEAR, MILLISECOND), // Should default to MILLISECOND - /* 11 */ input(11, NANO_OF_SECOND, MILLISECOND), // Should default to MILLISECOND - /* 12 */ input(12, MICRO_OF_SECOND, MILLISECOND) // Should default to MILLISECOND - }; - - private static Input input(int index, ChronoField field, int expectedCalendarField) { - return new Input(index, field, expectedCalendarField); - } - - private static class Input { - final int index; - final ChronoField field; - final int expectedCalendarField; - - public Input(int index, ChronoField field, int expectedCalendarField) { - this.index = index; - this.field = field; - this.expectedCalendarField = expectedCalendarField; - } - } - - static Input[] input() { - return INPUT; - } - - @ParameterizedTest - @MethodSource("input") - void f01_toCalendarField(Input input) { - int result = toCalendarField(input.field); - assertEquals(input.expectedCalendarField, result, "Test " + input.index + ": " + input.field); - } - } + // Note: toChronoUnit has been moved to GranularZonedDateTime as a private method. + // Tests for this functionality are now in GranularZonedDateTime_Test via the roll() method. //----------------------------------------------------------------------------------------------------------------- // Round-trip conversion tests @@ -297,191 +90,16 @@ class DateUtils_Test extends TestBase { return INPUT; } - @ParameterizedTest - @MethodSource("input") - void g01_chronoFieldToChronoUnitToChronoField(Input input) { - // ChronoField -> ChronoUnit -> ChronoField should be idempotent - ChronoUnit unit = toChronoUnit(input.field); - if (unit != null) { - ChronoField result = toChronoField(unit); - assertEquals(input.field, result, "Test " + input.index + ": " + input.field + " -> " + unit + " -> " + result); - } - } - - @ParameterizedTest - @MethodSource("input") - void g02_chronoFieldToCalendarField(Input input) { - // ChronoField -> Calendar field should always work - int calendarField = toCalendarField(input.field); - assertTrue(calendarField >= 0, "Test " + input.index + ": Calendar field should be non-negative"); - assertTrue(calendarField <= 18, "Test " + input.index + ": Calendar field should be valid Calendar constant"); - } - } - - //----------------------------------------------------------------------------------------------------------------- - // toIso8601(Calendar) tests - //----------------------------------------------------------------------------------------------------------------- - - static class H_toIso8601 { - - private static final Input[] INPUT = { - /* 01 */ input(1, 2024, JANUARY, 15, 14, 30, 45, 123, "UTC", "2024-01-15T14:30:45Z"), - /* 02 */ input(2, 2024, JANUARY, 15, 14, 30, 45, 123, "America/New_York", "2024-01-15T14:30:45-05:00"), - /* 03 */ input(3, 2024, JANUARY, 15, 14, 30, 45, 123, "America/Los_Angeles", "2024-01-15T14:30:45-08:00"), - /* 04 */ input(4, 2024, JANUARY, 15, 14, 30, 45, 123, "Europe/London", "2024-01-15T14:30:45Z"), - /* 05 */ input(5, 2024, JANUARY, 15, 14, 30, 45, 123, "Asia/Tokyo", "2024-01-15T14:30:45+09:00"), - /* 06 */ input(6, 2024, JULY, 15, 14, 30, 45, 123, "America/New_York", "2024-07-15T14:30:45-04:00"), // DST - /* 07 */ input(7, 2024, JULY, 15, 14, 30, 45, 123, "America/Los_Angeles", "2024-07-15T14:30:45-07:00"), // DST - /* 08 */ input(8, 2024, JULY, 15, 14, 30, 45, 123, "Europe/London", "2024-07-15T14:30:45+01:00"), // DST - /* 09 */ input(9, 2000, FEBRUARY, 29, 12, 0, 0, 0, "UTC", "2000-02-29T12:00:00Z"), // Leap year - /* 10 */ input(10, 2024, DECEMBER, 31, 23, 59, 59, 999, "UTC", "2024-12-31T23:59:59Z"), // End of year - /* 11 */ input(11, 2024, JANUARY, 1, 0, 0, 0, 0, "UTC", "2024-01-01T00:00:00Z"), // Start of year - /* 12 */ input(12, 2024, JANUARY, 15, 0, 0, 0, 0, "UTC", "2024-01-15T00:00:00Z"), // Midnight - /* 13 */ input(13, 2024, JANUARY, 15, 23, 59, 59, 999, "UTC", "2024-01-15T23:59:59Z"), // End of day - /* 14 */ input(14, 2024, JANUARY, 15, 12, 0, 0, 0, "GMT+05:30", "2024-01-15T12:00:00+05:30"), // Custom offset - /* 15 */ input(15, 2024, JANUARY, 15, 12, 0, 0, 0, "GMT-05:30", "2024-01-15T12:00:00-05:30") // Custom offset - }; - - private static Input input(int index, int year, int month, int day, int hour, int minute, int second, int millisecond, String timezone, String expectedIso8601) { - return new Input(index, year, month, day, hour, minute, second, millisecond, timezone, expectedIso8601); - } - - private static class Input { - final int index; - final int year; - final int month; - final int day; - final int hour; - final int minute; - final int second; - final int millisecond; - final String timezone; - final String expectedIso8601; - - public Input(int index, int year, int month, int day, int hour, int minute, int second, int millisecond, String timezone, String expectedIso8601) { - this.index = index; - this.year = year; - this.month = month; - this.day = day; - this.hour = hour; - this.minute = minute; - this.second = second; - this.millisecond = millisecond; - this.timezone = timezone; - this.expectedIso8601 = expectedIso8601; - } - } - - static Input[] input() { - return INPUT; - } - - @ParameterizedTest - @MethodSource("input") - void h01_toIso8601(Input input) { - // Create Calendar with specified timezone and date/time - var cal = Calendar.getInstance(TimeZone.getTimeZone(input.timezone)); - cal.set(input.year, input.month, input.day, input.hour, input.minute, input.second); - cal.set(Calendar.MILLISECOND, input.millisecond); - - // Convert to ISO8601 string - String result = toIso8601(cal); - - // Verify the result matches expected format - assertEquals(input.expectedIso8601, result, "Test " + input.index + ": " + input.year + "-" + (input.month + 1) + "-" + input.day + " " + input.timezone); - } - - @ParameterizedTest - @MethodSource("input") - void h02_toIso8601_formatValidation(Input input) { - // Create Calendar with specified timezone and date/time - var cal = Calendar.getInstance(TimeZone.getTimeZone(input.timezone)); - cal.set(input.year, input.month, input.day, input.hour, input.minute, input.second); - cal.set(Calendar.MILLISECOND, input.millisecond); - - // Convert to ISO8601 string - String result = toIso8601(cal); - - // Validate format structure - assertTrue(result.matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}[+-]\\d{2}:\\d{2}|\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z"), - "Test " + input.index + ": Result should match ISO8601 format: " + result); - - // Validate timezone format - if (result.endsWith("Z")) { - // UTC timezone - assertTrue(result.endsWith("Z"), "Test " + input.index + ": UTC timezone should end with 'Z'"); - } else { - // Offset timezone - assertTrue(result.matches(".*[+-]\\d{2}:\\d{2}$"), "Test " + input.index + ": Offset timezone should end with +/-HH:MM"); - } - } } //----------------------------------------------------------------------------------------------------------------- - // toIso8601(Calendar) edge cases and error handling + // Helper method for converting Calendar to ISO8601 string //----------------------------------------------------------------------------------------------------------------- - static class I_toIso8601_edgeCases { - - @Test - void i01_nullCalendar() { - assertThrows(NullPointerException.class, () -> { - toIso8601(null); - }); - } - - @Test - void i02_minimumDate() { - var cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - cal.set(1, Calendar.JANUARY, 1, 0, 0, 0); - cal.set(Calendar.MILLISECOND, 0); - - String result = toIso8601(cal); - assertEquals("0001-01-01T00:00:00Z", result); - } - - @Test - void i03_maximumDate() { - var cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - cal.set(9999, Calendar.DECEMBER, 31, 23, 59, 59); - cal.set(Calendar.MILLISECOND, 999); - - String result = toIso8601(cal); - assertEquals("9999-12-31T23:59:59Z", result); - } - - @Test - void i04_leapYear() { - var cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - cal.set(2024, Calendar.FEBRUARY, 29, 12, 0, 0); - cal.set(Calendar.MILLISECOND, 0); - - String result = toIso8601(cal); - assertEquals("2024-02-29T12:00:00Z", result); - } - - @Test - void i05_nonLeapYear() { - var cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - cal.set(2023, Calendar.FEBRUARY, 28, 12, 0, 0); - cal.set(Calendar.MILLISECOND, 0); - - String result = toIso8601(cal); - assertEquals("2023-02-28T12:00:00Z", result); - } - - @Test - void i06_dstTransition() { - // Test DST transition in America/New_York (Spring forward) - var cal = Calendar.getInstance(TimeZone.getTimeZone("America/New_York")); - cal.set(2024, Calendar.MARCH, 10, 2, 30, 0); // 2:30 AM on DST transition day - cal.set(Calendar.MILLISECOND, 0); - - String result = toIso8601(cal); - // The exact result depends on how Java handles the DST transition - assertTrue(result.contains("2024-03-10T"), "Should contain the date"); - assertTrue(result.contains(":30:00"), "Should contain the time"); - } + private static String toIso8601(Calendar c) { + var sdf = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); + sdf.setTimeZone(c.getTimeZone()); + return sdf.format(c.getTime()); } //----------------------------------------------------------------------------------------------------------------- @@ -564,21 +182,6 @@ class DateUtils_Test extends TestBase { } } - @ParameterizedTest - @MethodSource("input") - void j02_fromIso8601Calendar_roundTrip(Input input) { - // Parse the ISO8601 string - Calendar cal = fromIso8601Calendar(input.iso8601String); - assertNotNull(cal, "Test " + input.index + ": Calendar should not be null"); - - // Convert back to ISO8601 - String result = toIso8601(cal); - - // The result should be a valid ISO8601 string - assertNotNull(result, "Test " + input.index + ": Result should not be null"); - assertTrue(result.matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}[+-]\\d{2}:\\d{2}|\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z"), - "Test " + input.index + ": Result should be valid ISO8601 format: " + result); - } } //----------------------------------------------------------------------------------------------------------------- @@ -776,79 +379,85 @@ class DateUtils_Test extends TestBase { } } - //==================================================================================================== - // addSubtractDays / add / toZonedDateTime - //==================================================================================================== - @Test - void test_addSubtractDays() { - var cal = Calendar.getInstance(); - cal.set(2024, Calendar.JANUARY, 15, 0, 0, 0); - cal.set(Calendar.MILLISECOND, 0); - - Calendar result = addSubtractDays(cal, 10); - assertNotNull(result); - assertNotSame(cal, result); // Should be a clone - assertEquals(25, result.get(Calendar.DAY_OF_MONTH)); - - result = addSubtractDays(cal, -5); - assertNotNull(result); - assertEquals(10, result.get(Calendar.DAY_OF_MONTH)); - - // Null calendar - assertNull(addSubtractDays(null, 10)); - } + //----------------------------------------------------------------------------------------------------------------- + // parseIsoCalendar(String) tests + //----------------------------------------------------------------------------------------------------------------- @Test - void test_add() { - var cal = Calendar.getInstance(); - cal.set(2024, Calendar.JANUARY, 15, 12, 30, 45); - cal.set(Calendar.MILLISECOND, 0); - - // Add days - Calendar result = add(cal, Calendar.DAY_OF_MONTH, 5); - assertSame(cal, result); // Returns same instance - assertEquals(20, cal.get(Calendar.DAY_OF_MONTH)); - - // Add months - cal.set(2024, Calendar.JANUARY, 15, 0, 0, 0); - add(cal, Calendar.MONTH, 2); - assertEquals(Calendar.MARCH, cal.get(Calendar.MONTH)); - - // Add hours - cal.set(2024, Calendar.JANUARY, 15, 10, 0, 0); - add(cal, Calendar.HOUR_OF_DAY, 5); - assertEquals(15, cal.get(Calendar.HOUR_OF_DAY)); + 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)); } - @Test - void test_toZonedDateTime() { - var cal = new GregorianCalendar(2024, Calendar.JANUARY, 15, 12, 30, 45); - - Optional<ZonedDateTime> result = toZonedDateTime(cal); - assertTrue(result.isPresent()); - - ZonedDateTime zdt = result.get(); - assertEquals(2024, zdt.getYear()); - assertEquals(1, zdt.getMonthValue()); - assertEquals(15, zdt.getDayOfMonth()); - assertEquals(12, zdt.getHour()); - assertEquals(30, zdt.getMinute()); - assertEquals(45, zdt.getSecond()); - - // Null calendar - assertFalse(toZonedDateTime(null).isPresent()); - } + //----------------------------------------------------------------------------------------------------------------- + // parseIsoDate(String) tests + //----------------------------------------------------------------------------------------------------------------- @Test - void test_toZonedDateTime_preservesTimezone() { - var tz = TimeZone.getTimeZone("America/New_York"); - Calendar cal = new GregorianCalendar(tz); - cal.set(2024, Calendar.JANUARY, 15, 12, 30, 45); + void m02_parseIsoDate() throws Exception { + // parseIsoDate wraps parseIsoCalendar, so test similar cases + var date1 = parseIsoDate("2023-12-25"); + assertNotNull(date1); - Optional<ZonedDateTime> result = toZonedDateTime(cal); - assertTrue(result.isPresent()); + var date2 = parseIsoDate("2023-12-25T14:30:00"); + assertNotNull(date2); - ZonedDateTime zdt = result.get(); - assertEquals(tz.toZoneId(), zdt.getZone()); + // 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/commons/utils/StringUtils_Test.java b/juneau-utest/src/test/java/org/apache/juneau/commons/utils/StringUtils_Test.java index ac2ffe6c68..c6949cdc82 100755 --- a/juneau-utest/src/test/java/org/apache/juneau/commons/utils/StringUtils_Test.java +++ b/juneau-utest/src/test/java/org/apache/juneau/commons/utils/StringUtils_Test.java @@ -4410,84 +4410,6 @@ class StringUtils_Test extends TestBase { assertThrows(IllegalArgumentException.class, () -> parseIntWithSuffix(null)); } - //==================================================================================================== - // parseIsoCalendar(String) - //==================================================================================================== - @Test - void a148_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) - //==================================================================================================== - @Test - void a149_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")); - } //==================================================================================================== // parseLong(String) 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 9df3725530..ed66342b39 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 = StringUtils.parseIsoCalendar("2012-12-21"); + var in = DateUtils.parseIsoCalendar("2012-12-21"); assertTrue(serialize(ps, in).contains("2012")); assertEquals("null", serialize(ps, null)); } @Test void c07_stringType_dateTimeFormat() throws Exception { var ps = T_DATETIME; - var in = StringUtils.parseIsoCalendar("2012-12-21T12:34:56.789"); + var in = DateUtils.parseIsoCalendar("2012-12-21T12:34:56.789"); 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,parseIsoCalendar("2012-12-21T12:34:56Z"),foob,foob,"foo",1,2,1.0,1.0,true,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)) ); 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",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",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)) ); } @@ -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(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(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))) ); }
