Ticket #22921: 22921.patch

File 22921.patch, 14.1 KB (added by taylor.smock, 3 years ago)
  • src/org/openstreetmap/josm/tools/date/DateUtils.java

    Subject: [PATCH] Fix #22921: DateTimeException: Invalid value for DayOfMonth
    
    This occurs when a user enters a value such as 2015-00-01 for an expanded
    overpass query (e.g. [date:{{date:"2015-00-01"}}]).
    ---
    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/tools/date/DateUtils.java b/src/org/openstreetmap/josm/tools/date/DateUtils.java
    a b  
    1111import java.time.ZonedDateTime;
    1212import java.time.format.DateTimeFormatter;
    1313import java.time.format.DateTimeFormatterBuilder;
    14 import java.time.format.DateTimeParseException;
    1514import java.time.format.FormatStyle;
    1615import java.util.Date;
    1716import java.util.Locale;
     
    8079     */
    8180    public static Instant parseInstant(String str) {
    8281        // "2007-07-25T09:26:24{Z|{+|-}01[:00]}"
    83         if (checkLayout(str, "xxxx-xx-xx") ||
    84                 checkLayout(str, "xxxx-xx") ||
    85                 checkLayout(str, "xxxx")) {
    86             final ZonedDateTime local = ZonedDateTime.of(
    87                     parsePart4(str, 0),
    88                     str.length() > 5 ? parsePart2(str, 5) : 1,
    89                     str.length() > 8 ? parsePart2(str, 8) : 1,
    90                     0, 0, 0, 0, ZoneOffset.UTC);
    91             return local.toInstant();
    92         } else if (checkLayout(str, "xxxx-xx-xxTxx:xx:xxZ") ||
    93                 checkLayout(str, "xxxx-xx-xxTxx:xx:xx") ||
    94                 checkLayout(str, "xxxx:xx:xx xx:xx:xx") ||
    95                 checkLayout(str, "xxxx/xx/xx xx:xx:xx") ||
    96                 checkLayout(str, "xxxx-xx-xx xx:xx:xxZ") ||
    97                 checkLayout(str, "xxxx-xx-xx xx:xx:xx UTC") ||
    98                 checkLayout(str, "xxxx-xx-xxTxx:xx:xx+xx") ||
    99                 checkLayout(str, "xxxx-xx-xxTxx:xx:xx-xx") ||
    100                 checkLayout(str, "xxxx-xx-xxTxx:xx:xx+xx:00") ||
    101                 checkLayout(str, "xxxx-xx-xxTxx:xx:xx-xx:00")) {
    102             final ZonedDateTime local = ZonedDateTime.of(
    103                 parsePart4(str, 0),
    104                 parsePart2(str, 5),
    105                 parsePart2(str, 8),
    106                 parsePart2(str, 11),
    107                 parsePart2(str, 14),
    108                 parsePart2(str, 17),
    109                 0,
    110                 ZoneOffset.UTC
    111             );
    112             if (str.length() == 22 || str.length() == 25) {
    113                 final int plusHr = parsePart2(str, 20);
    114                 return local.plusHours(str.charAt(19) == '+' ? -plusHr : plusHr).toInstant();
    115             }
    116             return local.toInstant();
    117         } else if (checkLayout(str, "xxxx-xx-xxTxx:xx:xx.xxxZ") ||
    118                 checkLayout(str, "xxxx-xx-xxTxx:xx:xx.xxx") ||
    119                 checkLayout(str, "xxxx:xx:xx xx:xx:xx.xxx") ||
    120                 checkLayout(str, "xxxx/xx/xx xx:xx:xx.xxx") ||
    121                 checkLayout(str, "xxxx-xx-xxTxx:xx:xx.xxx+xx:00") ||
    122                 checkLayout(str, "xxxx-xx-xxTxx:xx:xx.xxx-xx:00")) {
    123             final ZonedDateTime local = ZonedDateTime.of(
    124                 parsePart4(str, 0),
    125                 parsePart2(str, 5),
    126                 parsePart2(str, 8),
    127                 parsePart2(str, 11),
    128                 parsePart2(str, 14),
    129                 parsePart2(str, 17),
    130                 parsePart3(str, 20) * 1_000_000,
    131                 ZoneOffset.UTC
    132             );
    133             if (str.length() == 29) {
    134                 final int plusHr = parsePart2(str, 24);
    135                 return local.plusHours(str.charAt(23) == '+' ? -plusHr : plusHr).toInstant();
    136             }
    137             return local.toInstant();
    138         } else if (checkLayout(str, "xxxx/xx/xx xx:xx:xx.xxxxxx")) {
    139             return ZonedDateTime.of(
    140                 parsePart4(str, 0),
    141                 parsePart2(str, 5),
    142                 parsePart2(str, 8),
    143                 parsePart2(str, 11),
    144                 parsePart2(str, 14),
    145                 parsePart2(str, 17),
    146                 parsePart6(str, 20) * 1_000,
    147                 ZoneOffset.UTC
    148             ).toInstant();
    149         } else {
    150             // example date format "18-AUG-08 13:33:03"
    151             SimpleDateFormat f = new SimpleDateFormat("dd-MMM-yy HH:mm:ss");
    152             Date d = f.parse(str, new ParsePosition(0));
    153             if (d != null)
    154                 return d.toInstant();
    155         }
     82        try {
     83            if (checkLayout(str, "xxxx-xx-xx") ||
     84                    checkLayout(str, "xxxx-xx") ||
     85                    checkLayout(str, "xxxx")) {
     86                final ZonedDateTime local = ZonedDateTime.of(
     87                        parsePart4(str, 0),
     88                        str.length() > 5 ? parsePart2(str, 5) : 1,
     89                        str.length() > 8 ? parsePart2(str, 8) : 1,
     90                        0, 0, 0, 0, ZoneOffset.UTC);
     91                return local.toInstant();
     92            } else if (checkLayout(str, "xxxx-xx-xxTxx:xx:xxZ") ||
     93                    checkLayout(str, "xxxx-xx-xxTxx:xx:xx") ||
     94                    checkLayout(str, "xxxx:xx:xx xx:xx:xx") ||
     95                    checkLayout(str, "xxxx/xx/xx xx:xx:xx") ||
     96                    checkLayout(str, "xxxx-xx-xx xx:xx:xxZ") ||
     97                    checkLayout(str, "xxxx-xx-xx xx:xx:xx UTC") ||
     98                    checkLayout(str, "xxxx-xx-xxTxx:xx:xx+xx") ||
     99                    checkLayout(str, "xxxx-xx-xxTxx:xx:xx-xx") ||
     100                    checkLayout(str, "xxxx-xx-xxTxx:xx:xx+xx:00") ||
     101                    checkLayout(str, "xxxx-xx-xxTxx:xx:xx-xx:00")) {
     102                final ZonedDateTime local = ZonedDateTime.of(
     103                        parsePart4(str, 0),
     104                        parsePart2(str, 5),
     105                        parsePart2(str, 8),
     106                        parsePart2(str, 11),
     107                        parsePart2(str, 14),
     108                        parsePart2(str, 17),
     109                        0,
     110                        ZoneOffset.UTC
     111                );
     112                if (str.length() == 22 || str.length() == 25) {
     113                    final int plusHr = parsePart2(str, 20);
     114                    return local.plusHours(str.charAt(19) == '+' ? -plusHr : plusHr).toInstant();
     115                }
     116                return local.toInstant();
     117            } else if (checkLayout(str, "xxxx-xx-xxTxx:xx:xx.xxxZ") ||
     118                    checkLayout(str, "xxxx-xx-xxTxx:xx:xx.xxx") ||
     119                    checkLayout(str, "xxxx:xx:xx xx:xx:xx.xxx") ||
     120                    checkLayout(str, "xxxx/xx/xx xx:xx:xx.xxx") ||
     121                    checkLayout(str, "xxxx-xx-xxTxx:xx:xx.xxx+xx:00") ||
     122                    checkLayout(str, "xxxx-xx-xxTxx:xx:xx.xxx-xx:00")) {
     123                final ZonedDateTime local = ZonedDateTime.of(
     124                        parsePart4(str, 0),
     125                        parsePart2(str, 5),
     126                        parsePart2(str, 8),
     127                        parsePart2(str, 11),
     128                        parsePart2(str, 14),
     129                        parsePart2(str, 17),
     130                        parsePart3(str, 20) * 1_000_000,
     131                        ZoneOffset.UTC
     132                );
     133                if (str.length() == 29) {
     134                    final int plusHr = parsePart2(str, 24);
     135                    return local.plusHours(str.charAt(23) == '+' ? -plusHr : plusHr).toInstant();
     136                }
     137                return local.toInstant();
     138            } else if (checkLayout(str, "xxxx/xx/xx xx:xx:xx.xxxxxx")) {
     139                return ZonedDateTime.of(
     140                        parsePart4(str, 0),
     141                        parsePart2(str, 5),
     142                        parsePart2(str, 8),
     143                        parsePart2(str, 11),
     144                        parsePart2(str, 14),
     145                        parsePart2(str, 17),
     146                        parsePart6(str, 20) * 1_000,
     147                        ZoneOffset.UTC
     148                ).toInstant();
     149            } else {
     150                // example date format "18-AUG-08 13:33:03"
     151                SimpleDateFormat f = new SimpleDateFormat("dd-MMM-yy HH:mm:ss");
     152                Date d = f.parse(str, new ParsePosition(0));
     153                if (d != null)
     154                    return d.toInstant();
     155            }
    156156
    157         try {
    158157            // slow path for fractional seconds different from millisecond precision
    159158            return ZonedDateTime.parse(str).toInstant();
    160         } catch (IllegalArgumentException | DateTimeParseException ex) {
     159        } catch (IllegalArgumentException | DateTimeException ex) {
    161160            throw new UncheckedParseException("The date string (" + str + ") could not be parsed.", ex);
    162161        }
    163162    }
     
    212211        return d != null ? (Date) d.clone() : null;
    213212    }
    214213
     214    /**
     215     * Check text for a specified layout
     216     * @param text The text to check
     217     * @param pattern The pattern to use
     218     * @return {@code true} if the layout matches, otherwise {@code false}
     219     */
    215220    private static boolean checkLayout(String text, String pattern) {
    216221        if (text.length() != pattern.length())
    217222            return false;
     
    243248    }
    244249
    245250    private static int parsePart6(String str, int off) {
    246         return 100000 * num(str.charAt(off))
    247               + 10000 * num(str.charAt(off + 1))
    248                + 1000 * num(str.charAt(off + 2))
    249                 + 100 * num(str.charAt(off + 3))
    250                  + 10 * num(str.charAt(off + 4))
    251                       + num(str.charAt(off + 5));
     251        return 100_000 * num(str.charAt(off))
     252              + 10_000 * num(str.charAt(off + 1))
     253               + 1_000 * num(str.charAt(off + 2))
     254                 + 100 * num(str.charAt(off + 3))
     255                  + 10 * num(str.charAt(off + 4))
     256                       + num(str.charAt(off + 5));
    252257    }
    253258
    254259    /**
     
    267272     * @since 7299
    268273     */
    269274    public static DateFormat getDateFormat(int dateStyle) {
    270         if (PROP_ISO_DATES.get()) {
     275        if (Boolean.TRUE.equals(PROP_ISO_DATES.get())) {
    271276            return newIsoDateFormat();
    272277        } else {
    273278            return DateFormat.getDateInstance(dateStyle, Locale.getDefault());
     
    280285     * @return The date format
    281286     */
    282287    public static DateTimeFormatter getDateFormatter(FormatStyle dateStyle) {
    283         DateTimeFormatter formatter = PROP_ISO_DATES.get()
     288        DateTimeFormatter formatter = Boolean.TRUE.equals(PROP_ISO_DATES.get())
    284289                ? DateTimeFormatter.ISO_LOCAL_DATE
    285290                : DateTimeFormatter.ofLocalizedDate(dateStyle);
    286291        return formatter.withZone(ZoneId.systemDefault());
     
    305310     * @since 7299
    306311     */
    307312    public static DateFormat getTimeFormat(int timeStyle) {
    308         if (PROP_ISO_DATES.get()) {
     313        if (Boolean.TRUE.equals(PROP_ISO_DATES.get())) {
    309314            // This is not strictly conform to ISO 8601. We just want to avoid US-style times such as 3.30pm
    310315            return new SimpleDateFormat("HH:mm:ss");
    311316        } else {
     
    319324     * @return The time format
    320325     */
    321326    public static DateTimeFormatter getTimeFormatter(FormatStyle timeStyle) {
    322         DateTimeFormatter formatter = PROP_ISO_DATES.get()
     327        DateTimeFormatter formatter = Boolean.TRUE.equals(PROP_ISO_DATES.get())
    323328                ? DateTimeFormatter.ISO_LOCAL_TIME
    324329                : DateTimeFormatter.ofLocalizedTime(timeStyle);
    325330        return formatter.withZone(ZoneId.systemDefault());
     
    345350     * @since 7299
    346351     */
    347352    public static DateFormat getDateTimeFormat(int dateStyle, int timeStyle) {
    348         if (PROP_ISO_DATES.get()) {
     353        if (Boolean.TRUE.equals(PROP_ISO_DATES.get())) {
    349354            // This is not strictly conform to ISO 8601. We just want to avoid US-style times such as 3.30pm
    350355            // and we don't want to use the 'T' separator as a space character is much more readable
    351356            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
     
    371376     * @return The date/time format
    372377     */
    373378    public static DateTimeFormatter getDateTimeFormatter(FormatStyle dateStyle, FormatStyle timeStyle) {
    374         DateTimeFormatter formatter = PROP_ISO_DATES.get()
     379        DateTimeFormatter formatter = Boolean.TRUE.equals(PROP_ISO_DATES.get())
    375380                ? ISO_LOCAL_DATE_TIME
    376381                : DateTimeFormatter.ofLocalizedDateTime(dateStyle, timeStyle);
    377382        return formatter.withZone(ZoneId.systemDefault());
  • test/unit/org/openstreetmap/josm/tools/date/DateUtilsTest.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/test/unit/org/openstreetmap/josm/tools/date/DateUtilsTest.java b/test/unit/org/openstreetmap/josm/tools/date/DateUtilsTest.java
    a b  
    1616import java.util.TimeZone;
    1717import java.util.concurrent.ForkJoinPool;
    1818
     19import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     20import net.trajano.commons.testing.UtilityClassTestUtil;
    1921import org.junit.jupiter.api.Disabled;
    2022import org.junit.jupiter.api.Test;
    2123import org.junit.jupiter.api.extension.RegisterExtension;
     24import org.junit.jupiter.params.ParameterizedTest;
     25import org.junit.jupiter.params.provider.ValueSource;
    2226import org.openstreetmap.josm.testutils.JOSMTestRules;
    2327import org.openstreetmap.josm.tools.UncheckedParseException;
    2428
    25 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    26 import net.trajano.commons.testing.UtilityClassTestUtil;
    27 
    2829/**
    2930 * Unit tests of {@link DateUtils} class.
    3031 */
     
    115116    /**
    116117     * Verifies that parsing an illegal date throws a {@link UncheckedParseException}
    117118     */
    118     @Test
    119     void testIllegalDate() {
    120         assertThrows(UncheckedParseException.class, () -> DateUtils.fromString("2014-"));
     119    @ParameterizedTest
     120    @ValueSource(strings = {"2014-", "2014-01-", "2014-01-01T", "2014-00-01", "2014-01-00"})
     121    void testIllegalDate(String date) {
     122        assertThrows(UncheckedParseException.class, () -> DateUtils.fromString(date));
    121123    }
    122124
    123125    /**