814 /** 815 * Appends an instant using ISO-8601 to the formatter, formatting fractional 816 * digits in groups of three. 817 * <p> 818 * Instants have a fixed output format. 819 * They are converted to a date-time with a zone-offset of UTC and formatted 820 * using the standard ISO-8601 format. 821 * With this method, formatting nano-of-second outputs zero, three, six 822 * or nine digits as necessary. 823 * The localized decimal style is not used. 824 * <p> 825 * The instant is obtained using {@link ChronoField#INSTANT_SECONDS INSTANT_SECONDS} 826 * and optionally {@code NANO_OF_SECOND}. The value of {@code INSTANT_SECONDS} 827 * may be outside the maximum range of {@code LocalDateTime}. 828 * <p> 829 * The {@linkplain ResolverStyle resolver style} has no effect on instant parsing. 830 * The end-of-day time of '24:00' is handled as midnight at the start of the following day. 831 * The leap-second time of '23:59:59' is handled to some degree, see 832 * {@link DateTimeFormatter#parsedLeapSecond()} for full details. 833 * <p> 834 * An alternative to this method is to format/parse the instant as a single 835 * epoch-seconds value. That is achieved using {@code appendValue(INSTANT_SECONDS)}. 836 * 837 * @return this, for chaining, not null 838 */ 839 public DateTimeFormatterBuilder appendInstant() { 840 appendInternal(new InstantPrinterParser(-2)); 841 return this; 842 } 843 844 /** 845 * Appends an instant using ISO-8601 to the formatter with control over 846 * the number of fractional digits. 847 * <p> 848 * Instants have a fixed output format, although this method provides some 849 * control over the fractional digits. They are converted to a date-time 850 * with a zone-offset of UTC and printed using the standard ISO-8601 format. 851 * The localized decimal style is not used. 852 * <p> 853 * The {@code fractionalDigits} parameter allows the output of the fractional 3445 buf.append((char) (digit + '0')); 3446 inNano = inNano - (digit * div); 3447 div = div / 10; 3448 } 3449 } 3450 buf.append('Z'); 3451 return true; 3452 } 3453 3454 @Override 3455 public int parse(DateTimeParseContext context, CharSequence text, int position) { 3456 // new context to avoid overwriting fields like year/month/day 3457 int minDigits = (fractionalDigits < 0 ? 0 : fractionalDigits); 3458 int maxDigits = (fractionalDigits < 0 ? 9 : fractionalDigits); 3459 CompositePrinterParser parser = new DateTimeFormatterBuilder() 3460 .append(DateTimeFormatter.ISO_LOCAL_DATE).appendLiteral('T') 3461 .appendValue(HOUR_OF_DAY, 2).appendLiteral(':') 3462 .appendValue(MINUTE_OF_HOUR, 2).appendLiteral(':') 3463 .appendValue(SECOND_OF_MINUTE, 2) 3464 .appendFraction(NANO_OF_SECOND, minDigits, maxDigits, true) 3465 .appendLiteral('Z') 3466 .toFormatter().toPrinterParser(false); 3467 DateTimeParseContext newContext = context.copy(); 3468 int pos = parser.parse(newContext, text, position); 3469 if (pos < 0) { 3470 return pos; 3471 } 3472 // parser restricts most fields to 2 digits, so definitely int 3473 // correctly parsed nano is also guaranteed to be valid 3474 long yearParsed = newContext.getParsed(YEAR); 3475 int month = newContext.getParsed(MONTH_OF_YEAR).intValue(); 3476 int day = newContext.getParsed(DAY_OF_MONTH).intValue(); 3477 int hour = newContext.getParsed(HOUR_OF_DAY).intValue(); 3478 int min = newContext.getParsed(MINUTE_OF_HOUR).intValue(); 3479 Long secVal = newContext.getParsed(SECOND_OF_MINUTE); 3480 Long nanoVal = newContext.getParsed(NANO_OF_SECOND); 3481 int sec = (secVal != null ? secVal.intValue() : 0); 3482 int nano = (nanoVal != null ? nanoVal.intValue() : 0); 3483 int days = 0; 3484 if (hour == 24 && min == 0 && sec == 0 && nano == 0) { 3485 hour = 0; 3486 days = 1; 3487 } else if (hour == 23 && min == 59 && sec == 60) { 3488 context.setParsedLeapSecond(); 3489 sec = 59; 3490 } 3491 int year = (int) yearParsed % 10_000; 3492 long instantSecs; 3493 try { 3494 LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, min, sec, 0).plusDays(days); 3495 instantSecs = ldt.toEpochSecond(ZoneOffset.UTC); 3496 instantSecs += Math.multiplyExact(yearParsed / 10_000L, SECONDS_PER_10000_YEARS); 3497 } catch (RuntimeException ex) { 3498 return ~position; 3499 } 3500 int successPos = pos; 3501 successPos = context.setParsedField(INSTANT_SECONDS, instantSecs, position, successPos); 3502 return context.setParsedField(NANO_OF_SECOND, nano, position, successPos); 3503 } 3504 3505 @Override 3506 public String toString() { 3507 return "Instant()"; 3508 } 3509 } 3510 3511 //----------------------------------------------------------------------- 3512 /** 3513 * Prints or parses an offset ID. 3514 */ 3515 static final class OffsetIdPrinterParser implements DateTimePrinterParser { | 814 /** 815 * Appends an instant using ISO-8601 to the formatter, formatting fractional 816 * digits in groups of three. 817 * <p> 818 * Instants have a fixed output format. 819 * They are converted to a date-time with a zone-offset of UTC and formatted 820 * using the standard ISO-8601 format. 821 * With this method, formatting nano-of-second outputs zero, three, six 822 * or nine digits as necessary. 823 * The localized decimal style is not used. 824 * <p> 825 * The instant is obtained using {@link ChronoField#INSTANT_SECONDS INSTANT_SECONDS} 826 * and optionally {@code NANO_OF_SECOND}. The value of {@code INSTANT_SECONDS} 827 * may be outside the maximum range of {@code LocalDateTime}. 828 * <p> 829 * The {@linkplain ResolverStyle resolver style} has no effect on instant parsing. 830 * The end-of-day time of '24:00' is handled as midnight at the start of the following day. 831 * The leap-second time of '23:59:59' is handled to some degree, see 832 * {@link DateTimeFormatter#parsedLeapSecond()} for full details. 833 * <p> 834 * When formatting, the instant will always be suffixed by 'Z' to indicate UTC. 835 * When parsing, the behaviour of {@link DateTimeFormatterBuilder#appendOffsetId()} will be used to parse the offset, 836 * converting the instant to UTC as necessary. 837 * <p> 838 * An alternative to this method is to format/parse the instant as a single 839 * epoch-seconds value. That is achieved using {@code appendValue(INSTANT_SECONDS)}. 840 * 841 * @return this, for chaining, not null 842 */ 843 public DateTimeFormatterBuilder appendInstant() { 844 appendInternal(new InstantPrinterParser(-2)); 845 return this; 846 } 847 848 /** 849 * Appends an instant using ISO-8601 to the formatter with control over 850 * the number of fractional digits. 851 * <p> 852 * Instants have a fixed output format, although this method provides some 853 * control over the fractional digits. They are converted to a date-time 854 * with a zone-offset of UTC and printed using the standard ISO-8601 format. 855 * The localized decimal style is not used. 856 * <p> 857 * The {@code fractionalDigits} parameter allows the output of the fractional 3449 buf.append((char) (digit + '0')); 3450 inNano = inNano - (digit * div); 3451 div = div / 10; 3452 } 3453 } 3454 buf.append('Z'); 3455 return true; 3456 } 3457 3458 @Override 3459 public int parse(DateTimeParseContext context, CharSequence text, int position) { 3460 // new context to avoid overwriting fields like year/month/day 3461 int minDigits = (fractionalDigits < 0 ? 0 : fractionalDigits); 3462 int maxDigits = (fractionalDigits < 0 ? 9 : fractionalDigits); 3463 CompositePrinterParser parser = new DateTimeFormatterBuilder() 3464 .append(DateTimeFormatter.ISO_LOCAL_DATE).appendLiteral('T') 3465 .appendValue(HOUR_OF_DAY, 2).appendLiteral(':') 3466 .appendValue(MINUTE_OF_HOUR, 2).appendLiteral(':') 3467 .appendValue(SECOND_OF_MINUTE, 2) 3468 .appendFraction(NANO_OF_SECOND, minDigits, maxDigits, true) 3469 .appendOffsetId() 3470 .toFormatter().toPrinterParser(false); 3471 DateTimeParseContext newContext = context.copy(); 3472 int pos = parser.parse(newContext, text, position); 3473 if (pos < 0) { 3474 return pos; 3475 } 3476 // parser restricts most fields to 2 digits, so definitely int 3477 // correctly parsed nano is also guaranteed to be valid 3478 long yearParsed = newContext.getParsed(YEAR); 3479 int month = newContext.getParsed(MONTH_OF_YEAR).intValue(); 3480 int day = newContext.getParsed(DAY_OF_MONTH).intValue(); 3481 int hour = newContext.getParsed(HOUR_OF_DAY).intValue(); 3482 int min = newContext.getParsed(MINUTE_OF_HOUR).intValue(); 3483 Long secVal = newContext.getParsed(SECOND_OF_MINUTE); 3484 Long nanoVal = newContext.getParsed(NANO_OF_SECOND); 3485 int sec = (secVal != null ? secVal.intValue() : 0); 3486 int nano = (nanoVal != null ? nanoVal.intValue() : 0); 3487 int offset = newContext.getParsed(OFFSET_SECONDS).intValue(); 3488 int days = 0; 3489 if (hour == 24 && min == 0 && sec == 0 && nano == 0) { 3490 hour = 0; 3491 days = 1; 3492 } else if (hour == 23 && min == 59 && sec == 60) { 3493 context.setParsedLeapSecond(); 3494 sec = 59; 3495 } 3496 int year = (int) yearParsed % 10_000; 3497 long instantSecs; 3498 try { 3499 LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, min, sec, 0).plusDays(days); 3500 instantSecs = ldt.toEpochSecond(ZoneOffset.ofTotalSeconds(offset)); 3501 instantSecs += Math.multiplyExact(yearParsed / 10_000L, SECONDS_PER_10000_YEARS); 3502 } catch (RuntimeException ex) { 3503 return ~position; 3504 } 3505 int successPos = pos; 3506 successPos = context.setParsedField(INSTANT_SECONDS, instantSecs, position, successPos); 3507 return context.setParsedField(NANO_OF_SECOND, nano, position, successPos); 3508 } 3509 3510 @Override 3511 public String toString() { 3512 return "Instant()"; 3513 } 3514 } 3515 3516 //----------------------------------------------------------------------- 3517 /** 3518 * Prints or parses an offset ID. 3519 */ 3520 static final class OffsetIdPrinterParser implements DateTimePrinterParser { |