• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

trydofor / professional-mirana / #68

31 Aug 2024 02:56AM UTC coverage: 84.4% (+1.0%) from 83.382%
#68

push

web-flow
Merge pull request #45 from trydofor/develop

v2.7.3 with minor change

474 of 568 new or added lines in 27 files covered. (83.45%)

8 existing lines in 6 files now uncovered.

5237 of 6205 relevant lines covered (84.4%)

0.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.14
/src/main/java/pro/fessional/mirana/time/DateParser.java
1
package pro.fessional.mirana.time;
2

3
import org.jetbrains.annotations.Contract;
4
import org.jetbrains.annotations.NotNull;
5
import org.jetbrains.annotations.Nullable;
6
import pro.fessional.mirana.text.HalfCharUtil;
7

8
import java.text.ParsePosition;
9
import java.time.DateTimeException;
10
import java.time.LocalDate;
11
import java.time.LocalDateTime;
12
import java.time.LocalTime;
13
import java.time.OffsetDateTime;
14
import java.time.ZoneId;
15
import java.time.ZoneOffset;
16
import java.time.ZonedDateTime;
17
import java.time.format.DateTimeFormatter;
18
import java.time.format.DateTimeParseException;
19
import java.time.temporal.TemporalAccessor;
20
import java.time.temporal.TemporalQueries;
21
import java.time.temporal.TemporalQuery;
22
import java.util.ArrayList;
23
import java.util.Arrays;
24
import java.util.Calendar;
25
import java.util.Date;
26
import java.util.List;
27

28
import static java.time.temporal.ChronoField.DAY_OF_MONTH;
29
import static java.time.temporal.ChronoField.EPOCH_DAY;
30
import static java.time.temporal.ChronoField.HOUR_OF_DAY;
31
import static java.time.temporal.ChronoField.MILLI_OF_SECOND;
32
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
33
import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
34
import static java.time.temporal.ChronoField.NANO_OF_DAY;
35
import static java.time.temporal.ChronoField.NANO_OF_SECOND;
36
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
37
import static java.time.temporal.ChronoField.YEAR;
38

39
/**
40
 * <pre>
41
 * Parses fixed-format strings containing date numbers, supporting the following formats
42
 * Can handle padding at the end, dates are padded with 01 and times are padded with 00.
43
 *
44
 * * date8 - yyyyMMdd
45
 * * datetime14 - yyyyMMddHHmmss
46
 * * datetime17 - yyyyMMddHHmmssSSS
47
 * * date8 - MMddyyyyy
48
 * * datetime14 - MMddyyyyyHHmmss
49
 * * datetime17 - MMddyyyyyHHmmssSSS
50
 * * time6 - HHmmss
51
 * * time9 - HHmmssSSS
52
 * </pre>
53
 *
54
 * @author trydofor
55
 * @see DateNumber
56
 * @since 2019-06-26
57
 */
UNCOV
58
public class DateParser {
×
59

60
    public static final TemporalQuery<LocalTime> QueryTime = (temporal) -> {
1✔
61
        if (temporal instanceof LocalTime) {
1✔
62
            return (LocalTime) temporal;
×
63
        }
64

65
        if (temporal.isSupported(NANO_OF_DAY)) {
1✔
66
            return LocalTime.ofNanoOfDay(temporal.getLong(NANO_OF_DAY));
1✔
67
        }
68

69
        // commonly never here
70
        if (temporal.isSupported(HOUR_OF_DAY)) {
1✔
71
            int m = 0;
×
72
            if (temporal.isSupported(MINUTE_OF_HOUR)) {
×
73
                m = temporal.get(MINUTE_OF_HOUR);
×
74
            }
75

76
            int s = 0;
×
77
            if (temporal.isSupported(SECOND_OF_MINUTE)) {
×
78
                s = temporal.get(SECOND_OF_MINUTE);
×
79
            }
80

81
            int n = 0;
×
82
            if (temporal.isSupported(NANO_OF_SECOND)) {
×
83
                n = temporal.get(NANO_OF_SECOND);
×
84
            }
85
            else if (temporal.isSupported(MILLI_OF_SECOND)) {
×
86
                n = temporal.get(MILLI_OF_SECOND) * 1000_000;
×
87
            }
88
            return LocalTime.of(temporal.get(HOUR_OF_DAY), m, s, n);
×
89
        }
90

91
        return null;
1✔
92
    };
93

94
    public static final TemporalQuery<LocalDate> QueryDate = (temporal) -> {
1✔
95
        if (temporal instanceof LocalDate) {
1✔
96
            return (LocalDate) temporal;
×
97
        }
98

99
        if (temporal.isSupported(EPOCH_DAY)) {
1✔
100
            return LocalDate.ofEpochDay(temporal.getLong(EPOCH_DAY));
1✔
101
        }
102

103
        // commonly never here
104
        if (temporal.isSupported(YEAR)) {
1✔
105
            int m = 1;
1✔
106
            if (temporal.isSupported(MONTH_OF_YEAR)) {
1✔
107
                m = temporal.get(MONTH_OF_YEAR);
1✔
108
            }
109

110
            int d = 1;
1✔
111
            if (temporal.isSupported(DAY_OF_MONTH)) {
1✔
112
                d = temporal.get(DAY_OF_MONTH);
×
113
            }
114

115
            return LocalDate.of(temporal.get(YEAR), m, d);
1✔
116
        }
117

118
        return null;
×
119
    };
120

121
    public static final TemporalQuery<LocalDateTime> QueryDateTime = (temporal) -> {
1✔
122
        if (temporal instanceof LocalDateTime) {
1✔
123
            return (LocalDateTime) temporal;
×
124
        }
125

126
        if (temporal instanceof ZonedDateTime) {
1✔
127
            return ((ZonedDateTime) temporal).toLocalDateTime();
×
128
        }
129

130
        if (temporal instanceof OffsetDateTime) {
1✔
131
            return ((OffsetDateTime) temporal).toLocalDateTime();
×
132
        }
133

134
        LocalDate date = QueryDate.queryFrom(temporal);
1✔
135
        if (date == null) return null;
1✔
136

137
        LocalTime time = QueryTime.queryFrom(temporal);
1✔
138
        if (time == null) {
1✔
139
            time = LocalTime.of(0, 0);
1✔
140
        }
141

142
        return LocalDateTime.of(date, time);
1✔
143
    };
144

145
    /**
146
     * parse any digit string with date information to a date
147
     */
148
    @NotNull
149
    public static LocalTime parseTime(@NotNull CharSequence num) {
150
        return parseTime(num, 0);
1✔
151
    }
152

153
    /**
154
     * parse any digit string with date information to a date
155
     */
156
    @NotNull
157
    public static LocalDate parseDate(@NotNull CharSequence num) {
158
        return parseDate(num, 0);
1✔
159
    }
160

161
    /**
162
     * parse any digit string with date information to a date
163
     */
164
    @NotNull
165
    public static Date parseUtilDate(@NotNull CharSequence num) {
166
        return parseUtilDate(num, 0);
1✔
167
    }
168

169
    /**
170
     * parse any digit string with date information to a date
171
     */
172
    @NotNull
173
    public static LocalDateTime parseDateTime(@NotNull CharSequence num) {
174
        return parseDateTime(num, 0);
1✔
175
    }
176

177
    /**
178
     * parse any digit string with date information to a date
179
     */
180
    @NotNull
181
    public static ZonedDateTime parseZoned(@NotNull CharSequence str) {
182
        return parseZoned(str, null, 0);
1✔
183
    }
184

185
    /**
186
     * parse any digit string with date information to a date
187
     */
188
    @NotNull
189
    public static ZonedDateTime parseZoned(@NotNull CharSequence str, ZoneId zid) {
190
        return parseZoned(str, zid, 0);
1✔
191
    }
192

193
    /**
194
     * parse any digit string with date format information to a date
195
     */
196
    @NotNull
197
    public static LocalTime parseTime(@NotNull CharSequence str, Iterable<DateTimeFormatter> dtf) {
198
        final TemporalAccessor ta = parseTemporal(str, dtf, false);
1✔
199
        final LocalTime dt = ta.query(QueryTime);
1✔
200
        if (dt == null) {
1✔
201
            throw new DateTimeException("Unable to obtain LocalTime " + ta + " of type " + ta.getClass().getName());
×
202
        }
203
        return dt;
1✔
204
    }
205

206
    /**
207
     * parse any digit string with date format information to a date
208
     */
209
    @NotNull
210
    public static LocalDate parseDate(@NotNull CharSequence str, Iterable<DateTimeFormatter> dtf) {
211
        final TemporalAccessor ta = parseTemporal(str, dtf, false);
1✔
212
        final LocalDate dt = ta.query(QueryDate);
1✔
213
        if (dt == null) {
1✔
214
            throw new DateTimeException("Unable to obtain LocalDate " + ta + " of type " + ta.getClass().getName());
×
215
        }
216
        return dt;
1✔
217
    }
218

219
    /**
220
     * parse any digit string with date format information to a date
221
     */
222
    @NotNull
223
    public static LocalDateTime parseDateTime(@NotNull CharSequence str, Iterable<DateTimeFormatter> dtf) {
224
        final TemporalAccessor ta = parseTemporal(str, dtf, false);
1✔
225
        final LocalDateTime dt = ta.query(QueryDateTime);
1✔
226
        if (dt == null) {
1✔
227
            throw new DateTimeException("Unable to obtain LocalDateTime " + ta + " of type " + ta.getClass().getName());
×
228
        }
229
        return dt;
1✔
230
    }
231

232
    /**
233
     * parse any Temporal at zoneId
234
     */
235
    @NotNull
236
    public static ZonedDateTime parseZoned(@NotNull TemporalAccessor ta, ZoneId zid) {
237
        if (ta instanceof ZonedDateTime) {
1✔
238
            ZonedDateTime zdt = (ZonedDateTime) ta;
1✔
239
            return zid == null ? zdt : zdt.withZoneSameInstant(zid);
1✔
240
        }
241

242
        if (ta instanceof OffsetDateTime) {
1✔
243
            final OffsetDateTime odt = (OffsetDateTime) ta;
1✔
244
            return zid == null ? odt.toZonedDateTime() : odt.atZoneSameInstant(zid);
1✔
245
        }
246

247
        final LocalDateTime ldt = ta.query(QueryDateTime);
1✔
248
        final ZoneId z = ta.query(TemporalQueries.zone());
1✔
249
        return concatZoned(ldt, z, zid);
1✔
250
    }
251

252
    @NotNull
253
    private static ZonedDateTime concatZoned(LocalDateTime ldt, ZoneId may, ZoneId zid) {
254
        if (may == null) {
1✔
255
            if (zid == null) {
1✔
256
                return ZonedDateTime.of(ldt, ThreadNow.sysZoneId());
×
257
            }
258
            else {
259
                return ZonedDateTime.of(ldt, zid);
1✔
260
            }
261
        }
262
        else {
263
            if (zid == null) {
1✔
264
                return ZonedDateTime.of(ldt, may);
1✔
265
            }
266
            else {
267
                return ZonedDateTime.of(ldt, may).withZoneSameInstant(zid);
1✔
268
            }
269
        }
270
    }
271

272
    /**
273
     * parse any digit string with date format information to a date at zoneId
274
     */
275
    @NotNull
276
    public static ZonedDateTime parseZoned(@NotNull CharSequence str, ZoneId zid, Iterable<DateTimeFormatter> dtf) {
277
        final TemporalAccessor ta = parseTemporal(str, dtf, false);
1✔
278
        return parseZoned(ta, zid);
1✔
279
    }
280

281
    /**
282
     * parse any Temporal to a date at zoneId
283
     */
284
    @NotNull
285
    public static OffsetDateTime parseOffset(@NotNull TemporalAccessor ta, ZoneId zid) {
286
        if (ta instanceof ZonedDateTime) {
1✔
287
            ZonedDateTime zdt = (ZonedDateTime) ta;
×
288
            if (zid == null) {
×
289
                return zdt.toOffsetDateTime();
×
290
            }
291
            else {
292
                return zdt.withZoneSameInstant(zid).toOffsetDateTime();
×
293
            }
294
        }
295

296
        if (ta instanceof OffsetDateTime) {
1✔
297
            OffsetDateTime odt = (OffsetDateTime) ta;
×
298
            if (zid == null) {
×
299
                return odt;
×
300
            }
301
            else {
302
                return odt.atZoneSameInstant(zid).toOffsetDateTime();
×
303
            }
304
        }
305

306
        final LocalDateTime ldt = ta.query(QueryDateTime);
1✔
307
        ZoneOffset zof = ta.query(TemporalQueries.offset());
1✔
308
        if (zof != null) {
1✔
309
            final OffsetDateTime odt = OffsetDateTime.of(ldt, zof);
1✔
310
            if (zid == null) {
1✔
311
                return odt;
×
312
            }
313
            else {
314
                return odt.atZoneSameInstant(zid).toOffsetDateTime();
1✔
315
            }
316
        }
317

318
        final ZoneId z = ta.query(TemporalQueries.zone());
×
319
        return concatZoned(ldt, z, zid).toOffsetDateTime();
×
320
    }
321

322
    /**
323
     * parse any digit string with date format information to a date at zoneId
324
     */
325
    @NotNull
326
    public static OffsetDateTime parseOffset(@NotNull CharSequence str, ZoneId zid, Iterable<DateTimeFormatter> dtf) {
327
        final TemporalAccessor ta = parseTemporal(str, dtf, false);
1✔
328
        return parseOffset(ta, zid);
1✔
329
    }
330

331
    @NotNull
332
    public static LocalTime parseTime(@NotNull CharSequence str, DateTimeFormatter... dtf) {
333
        return parseTime(str, Arrays.asList(dtf));
1✔
334
    }
335

336
    @NotNull
337
    public static LocalDate parseDate(@NotNull CharSequence str, DateTimeFormatter... dtf) {
338
        return parseDate(str, Arrays.asList(dtf));
1✔
339
    }
340

341
    @NotNull
342
    public static LocalDateTime parseDateTime(@NotNull CharSequence str, DateTimeFormatter... dtf) {
343
        return parseDateTime(str, Arrays.asList(dtf));
1✔
344
    }
345

346
    @NotNull
347
    public static ZonedDateTime parseZoned(@NotNull CharSequence str, ZoneId zid, DateTimeFormatter... dtf) {
348
        return parseZoned(str, zid, Arrays.asList(dtf));
1✔
349
    }
350

351
    @NotNull
352
    public static OffsetDateTime parseOffset(@NotNull CharSequence str, ZoneId zid, DateTimeFormatter... dtf) {
353
        return parseOffset(str, zid, Arrays.asList(dtf));
1✔
354
    }
355

356
    /**
357
     * <pre>
358
     * from the offset to parse any digit string with date format information.
359
     * focusing only on numbers when parsing, ignoring non-numeric characters.
360
     *
361
     * * time6 - HHmmss
362
     * * time9 - HHmmssSSS
363
     * </pre>
364
     */
365
    @NotNull
366
    public static LocalTime parseTime(@NotNull CharSequence str, int off) {
367
        String num = digit(str, off, Ptn.TIME);
1✔
368
        int len = num.length();
1✔
369
        if (len != 6 && len != 9) {
1✔
370
            throw new DateTimeException("only support time6,time9 format, but " + num);
×
371
        }
372
        return time(num, 0);
1✔
373
    }
374

375
    /**
376
     * <pre>
377
     * from the offset to parse any digit string with date format information.
378
     * focusing only on numbers when parsing, ignoring non-numeric characters.
379
     *
380
     * * date8 - yyyyMMdd
381
     * * date8 - MMddyyyy
382
     * </pre>
383
     */
384
    @NotNull
385
    public static LocalDate parseDate(@NotNull CharSequence str, int off) {
386
        String num = digit(str, off, Ptn.DATE);
1✔
387
        int len = num.length();
1✔
388
        if (len != 8) {
1✔
389
            throw new DateTimeException("only support date8 format, but " + num);
×
390
        }
391

392
        return date(num);
1✔
393
    }
394

395
    /**
396
     * <pre>
397
     * from the offset to parse any digit string with date format information.
398
     * focusing only on numbers when parsing, ignoring non-numeric characters.
399
     * </pre>
400
     */
401
    @NotNull
402
    public static Date parseUtilDate(@NotNull CharSequence str, int off) {
403
        String num = digit(str, off, Ptn.FULL);
1✔
404
        final Calendar cal = Calendar.getInstance();
1✔
405
        cal.set(Calendar.YEAR, Integer.parseInt(num.substring(0, 4)));
1✔
406
        cal.set(Calendar.MONTH, Integer.parseInt(num.substring(4, 6)) - 1);
1✔
407
        cal.set(Calendar.DATE, Integer.parseInt(num.substring(6, 8)));
1✔
408
        cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(num.substring(8, 10)));
1✔
409
        cal.set(Calendar.MINUTE, Integer.parseInt(num.substring(10, 12)));
1✔
410
        cal.set(Calendar.SECOND, Integer.parseInt(num.substring(12, 14)));
1✔
411
        cal.set(Calendar.MILLISECOND, Integer.parseInt(num.substring(14)));
1✔
412
        return cal.getTime();
1✔
413
    }
414

415
    /**
416
     * <pre>
417
     * from the offset to parse any digit string with date format information.
418
     * focusing only on numbers when parsing, ignoring non-numeric characters.
419
     *
420
     * * datetime14 - yyyyMMddHHmmss
421
     * * datetime17 - yyyyMMddHHmmssSSS
422
     * * datetime14 - MMddyyyyHHmmss
423
     * * datetime17 - MMddyyyyHHmmssSSS
424
     * </pre>
425
     */
426
    @NotNull
427
    public static LocalDateTime parseDateTime(@NotNull CharSequence str, int off) {
428
        String num = digit(str, off, Ptn.FULL);
1✔
429
        int len = num.length();
1✔
430
        if (len != 14 && len != 17) {
1✔
431
            throw new DateTimeException("only support datetime14,datetime17 format, but " + num);
×
432
        }
433

434
        LocalDate ld = date(num);
1✔
435
        LocalTime lt = time(num, 8);
1✔
436
        return LocalDateTime.of(ld, lt);
1✔
437
    }
438

439
    /**
440
     * <pre>
441
     * from the offset to parse any digit string with date format information.
442
     * focusing only on numbers when parsing, ignoring non-numeric characters.
443
     *
444
     * * datetime14 - yyyyMMddHHmmss
445
     * * datetime17 - yyyyMMddHHmmssSSS
446
     * * datetime14 - MMddyyyyHHmmss
447
     * * datetime17 - MMddyyyyHHmmssSSS
448
     *
449
     * The timezone part comes after the time separated by `+- [ `,
450
     * only offset and zid formats are supported, and zid takes precedence over offset.
451
     *
452
     * V time-zone ID             zone-id   America/Los_Angeles; Z; -08:30
453
     * O localized zone-offset    offset-O  GMT+8; GMT+08:00; UTC-08:00;
454
     * X zone-offset 'Z' for zero offset-X  Z; -08; -0830; -08:30; -083015; -08:30:15;
455
     * x zone-offset              offset-x  +0000; -08; -0830; -08:30; -083015; -08:30:15;
456
     * Z zone-offset              offset-Z  +0000; -0800; -08:00;
457
     * ISO_ZONED_DATE_TIME +01:00[Europe/Paris]
458
     * </pre>
459
     */
460
    @NotNull
461
    public static ZonedDateTime parseZoned(@NotNull CharSequence str, ZoneId zid, int off) {
462
        String ptn = digit(str, off, Ptn.ZONE);
1✔
463
        int ztk = 0;
1✔
464
        if (ptn.length() > 14 && ptn.charAt(14) == '@') {
1✔
465
            ztk = 14;
×
466
        }
467
        else if (ptn.length() > 17 && ptn.charAt(17) == '@') {
1✔
468
            ztk = 17;
1✔
469
        }
470

471
        if (ztk != 14 && ztk != 17) {
1✔
472
            throw new DateTimeException("only support datetime14, datetime17 format, but " + ptn);
×
473
        }
474

475
        final String num = ptn.substring(0, ztk);
1✔
476
        final String zzz = ptn.substring(ztk + 1);
1✔
477

478
        ZoneId z = zid(zzz);
1✔
479
        LocalDateTime ldt = LocalDateTime.of(date(num), time(num, 8));
1✔
480
        return concatZoned(ldt, z, zid);
1✔
481
    }
482

483
    /**
484
     * Parse without exceptions, returns the best match (no exceptions, highest number of correct parses)
485
     *
486
     * @param str   Any string containing full or half-width digits.
487
     * @param dtf   The formatter to try.
488
     * @param quiet return null instead of an exception.
489
     */
490
    @Contract("_,_,false->!null")
491
    public static TemporalAccessor parseTemporal(@NotNull CharSequence str, @NotNull Iterable<DateTimeFormatter> dtf, boolean quiet) {
492
        QuietPos best = null;
1✔
493

494
        for (QuietPos qp : parseTemporal(dtf, str, true)) {
1✔
495
            if (best == null) {
1✔
496
                best = qp;
1✔
497
            }
498
            else {
499
                final TemporalAccessor ta = qp.getTemporal();
1✔
500
                if (best.getTemporal() == null && ta != null) {
1✔
501
                    best = qp;
1✔
502
                    continue;
1✔
503
                }
504
                final int ix = qp.getIndex();
1✔
505
                if (best.getIndex() < ix) {
1✔
506
                    best = qp;
1✔
507
                }
508
            }
509
        }
1✔
510

511
        if (best == null) {
1✔
512
            if (quiet) {
×
513
                return null;
×
514
            }
515
            else {
516
                throw new DateTimeParseException("can not apply any Formatter to parse", str, -1);
×
517
            }
518
        }
519

520
        final TemporalAccessor tp = best.getTemporal();
1✔
521
        if (tp == null) {
1✔
522
            if (quiet) {
×
523
                return null;
×
524
            }
525

526
            final RuntimeException ex = best.getException();
×
527
            if (ex != null) {
×
528
                throw ex;
×
529
            }
530

531
            final String message = best.getFormatter().toString();
×
532
            throw new DateTimeParseException(message, str, best.getErrorIndexReal());
×
533
        }
534

535
        return tp;
1✔
536
    }
537

538
    /**
539
     * Parse them all and returns the results in the order in which they were entered.
540
     *
541
     * @param dtf           format
542
     * @param str           String
543
     * @param stopOnSuccess Whether to stop subsequent matches when there are completed matchers, performance related
544
     */
545
    @NotNull
546
    public static List<QuietPos> parseTemporal(@NotNull Iterable<DateTimeFormatter> dtf, @NotNull CharSequence str, boolean stopOnSuccess) {
547
        List<QuietPos> result = new ArrayList<>();
1✔
548
        for (DateTimeFormatter ft : dtf) {
1✔
549
            QuietPos pos = new QuietPos(0);
1✔
550
            pos.formatter = ft;
1✔
551
            try {
552
                pos.temporal = ft.parse(str, pos);
1✔
553
            }
554
            catch (RuntimeException e) {
1✔
555
                pos.exception = e;
1✔
556
            }
1✔
557

558
            result.add(pos);
1✔
559

560
            if (stopOnSuccess && pos.temporal != null && pos.getErrorIndexReal() < 0) {
1✔
561
                break;
×
562
            }
563
        }
1✔
564
        return result;
1✔
565
    }
566

567
    @NotNull
568
    public static String digit(@Nullable CharSequence str, int off, Ptn ptn) {
569
        if (str == null) return "";
1✔
570

571
        int idx = 0;
1✔
572
        StringBuilder[] buff = new StringBuilder[ptn.pad.length];
1✔
573
        buff[idx] = new StringBuilder(ptn.len);
1✔
574

575
        int cnt = 0;
1✔
576
        int nan = 0;
1✔
577
        int chi = 0;
1✔
578
        final int len = str.length();
1✔
579
        for (; chi < len && cnt - off < ptn.len; chi++) {
1✔
580
            char c = HalfCharUtil.half(str.charAt(chi));
1✔
581
            if (c >= '0' && c <= '9') {
1✔
582
                cnt++;
1✔
583
                if (cnt > off) {
1✔
584
                    buff[idx].append(c);
1✔
585
                    nan = 1;
1✔
586
                }
587
            }
588
            else {
589
                if (nan == 1 && idx < ptn.pad.length - 1) {
1✔
590
                    buff[++idx] = new StringBuilder(ptn.len);
1✔
591
                    nan = 2;
1✔
592
                }
593

594
                if (ptn == Ptn.ZONE && idx > 3 && (c == ' ' || c == '\t' || (c != ':' && isZidChar(c)))) {
1✔
595
                    break;
1✔
596
                }
597
            }
598
        }
599

600
        // handle MMddyyyy
601
        if (ptn != Ptn.TIME && idx >= 2 && buff[1].length() <= 2 && isMonth(buff[0]) && !isMonth(buff[2])) {
1✔
602
            StringBuilder tp = buff[2];
1✔
603
            buff[2] = buff[1];
1✔
604
            buff[1] = buff[0];
1✔
605
            buff[0] = tp;
1✔
606
        }
607

608
        // handle padding
609
        for (int i = 0; i < ptn.pad.length; i++) {
1✔
610
            if (i <= idx) {
1✔
611
                int cln = buff[i].length();
1✔
612
                int sln = ptn.pad[i].length();
1✔
613
                if (cln == sln) {
1✔
614
                    continue;
1✔
615
                }
616
                else if (cln > sln) {
1✔
617
                    break;
1✔
618
                }
619

620
                boolean az = true;
1✔
621
                for (int j = 0; j < cln; j++) {
1✔
622
                    if (buff[i].charAt(j) != '0') {
1✔
623
                        az = false;
1✔
624
                        break;
1✔
625
                    }
626
                }
627
                if (az) {
1✔
628
                    buff[i].replace(0, cln, ptn.pad[i]);
1✔
629
                }
630
                else {
631
                    buff[i].insert(0, ptn.pad[i], 0, sln - cln);
1✔
632
                }
633
            }
1✔
634
            else {
635
                buff[idx].append(ptn.pad[i]);
1✔
636
            }
637
        }
638

639
        // concat
640
        StringBuilder sb = buff[0];
1✔
641
        for (int i = 1; i <= idx; i++) {
1✔
642
            sb.append(buff[i]);
1✔
643
        }
644

645
        // trim
646
        if (sb.length() > ptn.len) {
1✔
647
            sb.setLength(ptn.len);
1✔
648
        }
649

650
        // handle timezone at part 1
651
        if (ptn == Ptn.ZONE) {
1✔
652
            sb.append('@');
1✔
653
            boolean spc = false;
1✔
654
            for (int i = chi; i < len; i++) {
1✔
655
                char c = str.charAt(i);
1✔
656
                // +-:0-9a-z/[]
657
                if (isZidChar(c)) {
1✔
658
                    sb.append(c);
1✔
659
                    spc = true;
1✔
660
                }
661
                else {
662
                    if (spc) break;
1✔
663
                }
664
            }
665
        }
666

667
        return sb.toString();
1✔
668
    }
669

670
    public static boolean isZidChar(char c) {
671
        return c == '+' || c == '-' || c == ':' || c == '/' || c == '[' || c == ']'
1✔
672
               || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
673
    }
674

675
    public static class QuietPos extends ParsePosition {
676

677
        private TemporalAccessor temporal;
678
        private DateTimeFormatter formatter;
679
        private RuntimeException exception;
680
        private int error;
681

682
        public QuietPos(int index) {
683
            super(index);
1✔
684
        }
1✔
685

686
        public int getErrorIndexReal() {
687
            return error;
1✔
688
        }
689

690
        public TemporalAccessor getTemporal() {
691
            return temporal;
1✔
692
        }
693

694
        public DateTimeFormatter getFormatter() {
695
            return formatter;
1✔
696
        }
697

698
        public RuntimeException getException() {
699
            return exception;
1✔
700
        }
701

702
        @Override
703
        public void setErrorIndex(int ei) {
704
            this.error = ei;
1✔
705
        }
1✔
706

707
        @Override
708
        public int getErrorIndex() {
709
            return -1;
1✔
710
        }
711
    }
712

713
    // /////////////////////////////
714

715
    public enum Ptn {
1✔
716
        DATE(8, new String[]{ "2000", "01", "01" }),
1✔
717
        TIME(9, new String[]{ "00", "00", "00", "000" }),
1✔
718
        FULL(17, new String[]{ "2000", "01", "01", "00", "00", "00", "000" }),
1✔
719
        ZONE(17, new String[]{ "2000", "01", "01", "00", "00", "00", "000" }),
1✔
720
        ;
721
        final int len;
722
        final String[] pad;
723

724
        Ptn(int len, String[] pad) {
1✔
725
            this.len = len;
1✔
726
            this.pad = pad;
1✔
727
        }
1✔
728
    }
729

730
    private static ZoneId zid(String str) {
731
        try {
732
            int p1 = str.indexOf('[');
1✔
733
            if (p1 >= 0) {
1✔
734
                int p2 = str.indexOf(']', p1);
1✔
735
                if (p2 > p1) {
1✔
736
                    return ZoneId.of(str.substring(p1 + 1, p2));
1✔
737
                }
738
                else {
739
                    return ZoneId.of(str.substring(p1 + 1));
×
740
                }
741
            }
742
            else {
743
                return ZoneId.of(str);
1✔
744
            }
745
        }
746
        catch (Exception e) {
×
747
            return null;
×
748
        }
749
    }
750

751
    private static boolean isMonth(CharSequence str) {
752
        int len = str.length();
1✔
753
        if (len == 1) {
1✔
754
            char c = str.charAt(0);
×
755
            return c >= '1' && c <= '9';
×
756
        }
757
        else if (len == 2) {
1✔
758
            char c1 = str.charAt(0);
1✔
759
            char c2 = str.charAt(1);
1✔
760
            if (c1 == '0' && c2 >= '1' && c2 <= '9') {
1✔
761
                return true;
1✔
762
            }
763
            return c1 == '1' && c2 >= '0' && c2 <= '2';
1✔
764
        }
765

766
        return false;
1✔
767
    }
768

769
    private static LocalDate date(String num) {
770
        int y = Integer.parseInt(num.substring(0, 4));
1✔
771
        int m = Integer.parseInt(num.substring(4, 6));
1✔
772
        int d = Integer.parseInt(num.substring(6, 8));
1✔
773

774
        return LocalDate.of(y, m, d);
1✔
775
    }
776

777
    private static LocalTime time(String num, int off) {
778
        int h = Integer.parseInt(num.substring(off, off + 2));
1✔
779
        int m = Integer.parseInt(num.substring(off + 2, off + 4));
1✔
780
        int s = Integer.parseInt(num.substring(off + 4, off + 6));
1✔
781
        int n = num.length() - off <= 6 ? 0 : Integer.parseInt(num.substring(off + 6)) * 1_000_000;
1✔
782

783
        return LocalTime.of(h, m, s, n);
1✔
784
    }
785
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc