< prev index next >

src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java

Print this page




 849      * epoch-seconds value. That is achieved using {@code appendValue(INSTANT_SECONDS)}.
 850      *
 851      * @param fractionalDigits  the number of fractional second digits to format with,
 852      *  from 0 to 9, or -1 to use as many digits as necessary
 853      * @return this, for chaining, not null
 854      * @throws IllegalArgumentException if the number of fractional digits is invalid
 855      */
 856     public DateTimeFormatterBuilder appendInstant(int fractionalDigits) {
 857         if (fractionalDigits < -1 || fractionalDigits > 9) {
 858             throw new IllegalArgumentException("The fractional digits must be from -1 to 9 inclusive but was " + fractionalDigits);
 859         }
 860         appendInternal(new InstantPrinterParser(fractionalDigits));
 861         return this;
 862     }
 863 
 864     //-----------------------------------------------------------------------
 865     /**
 866      * Appends the zone offset, such as '+01:00', to the formatter.
 867      * <p>
 868      * This appends an instruction to format/parse the offset ID to the builder.
 869      * This is equivalent to calling {@code appendOffset("+HH:MM:ss", "Z")}.


 870      *
 871      * @return this, for chaining, not null
 872      */
 873     public DateTimeFormatterBuilder appendOffsetId() {
 874         appendInternal(OffsetIdPrinterParser.INSTANCE_ID_Z);
 875         return this;
 876     }
 877 
 878     /**
 879      * Appends the zone offset, such as '+01:00', to the formatter.
 880      * <p>
 881      * This appends an instruction to format/parse the offset ID to the builder.
 882      * <p>
 883      * During formatting, the offset is obtained using a mechanism equivalent
 884      * to querying the temporal with {@link TemporalQueries#offset()}.
 885      * It will be printed using the format defined below.
 886      * If the offset cannot be obtained then an exception is thrown unless the
 887      * section of the formatter is optional.
 888      * <p>
 889      * During parsing, the offset is parsed using the format defined below.
 890      * If the offset cannot be parsed then an exception is thrown unless the
 891      * section of the formatter is optional.









 892      * <p>
 893      * The format of the offset is controlled by a pattern which must be one
 894      * of the following:
 895      * <ul>
 896      * <li>{@code +HH} - hour only, ignoring minute and second
 897      * <li>{@code +HHmm} - hour, with minute if non-zero, ignoring second, no colon
 898      * <li>{@code +HH:mm} - hour, with minute if non-zero, ignoring second, with colon
 899      * <li>{@code +HHMM} - hour and minute, ignoring second, no colon
 900      * <li>{@code +HH:MM} - hour and minute, ignoring second, with colon
 901      * <li>{@code +HHMMss} - hour and minute, with second if non-zero, no colon
 902      * <li>{@code +HH:MM:ss} - hour and minute, with second if non-zero, with colon
 903      * <li>{@code +HHMMSS} - hour, minute and second, no colon
 904      * <li>{@code +HH:MM:SS} - hour, minute and second, with colon




 905      * </ul>
 906      * The "no offset" text controls what text is printed when the total amount of
 907      * the offset fields to be output is zero.
 908      * Example values would be 'Z', '+00:00', 'UTC' or 'GMT'.
 909      * Three formats are accepted for parsing UTC - the "no offset" text, and the
 910      * plus and minus versions of zero defined by the pattern.
 911      *
 912      * @param pattern  the pattern to use, not null
 913      * @param noOffsetText  the text to use when the offset is zero, not null
 914      * @return this, for chaining, not null
 915      * @throws IllegalArgumentException if the pattern is invalid
 916      */
 917     public DateTimeFormatterBuilder appendOffset(String pattern, String noOffsetText) {
 918         appendInternal(new OffsetIdPrinterParser(pattern, noOffsetText));
 919         return this;
 920     }
 921 
 922     /**
 923      * Appends the localized zone offset, such as 'GMT+01:00', to the formatter.
 924      * <p>


3298             } catch (RuntimeException ex) {
3299                 return ~position;
3300             }
3301             int successPos = pos;
3302             successPos = context.setParsedField(INSTANT_SECONDS, instantSecs, position, successPos);
3303             return context.setParsedField(NANO_OF_SECOND, nano, position, successPos);
3304         }
3305 
3306         @Override
3307         public String toString() {
3308             return "Instant()";
3309         }
3310     }
3311 
3312     //-----------------------------------------------------------------------
3313     /**
3314      * Prints or parses an offset ID.
3315      */
3316     static final class OffsetIdPrinterParser implements DateTimePrinterParser {
3317         static final String[] PATTERNS = new String[] {
3318             "+HH", "+HHmm", "+HH:mm", "+HHMM", "+HH:MM", "+HHMMss", "+HH:MM:ss", "+HHMMSS", "+HH:MM:SS",
3319         };  // order used in pattern builder
3320         static final OffsetIdPrinterParser INSTANCE_ID_Z = new OffsetIdPrinterParser("+HH:MM:ss", "Z");
3321         static final OffsetIdPrinterParser INSTANCE_ID_ZERO = new OffsetIdPrinterParser("+HH:MM:ss", "0");
3322 
3323         private final String noOffsetText;
3324         private final int type;
3325 
3326         /**
3327          * Constructor.
3328          *
3329          * @param pattern  the pattern
3330          * @param noOffsetText  the text to use for UTC, not null
3331          */
3332         OffsetIdPrinterParser(String pattern, String noOffsetText) {
3333             Objects.requireNonNull(pattern, "pattern");
3334             Objects.requireNonNull(noOffsetText, "noOffsetText");
3335             this.type = checkPattern(pattern);
3336             this.noOffsetText = noOffsetText;
3337         }
3338 


3345             throw new IllegalArgumentException("Invalid zone offset pattern: " + pattern);
3346         }
3347 
3348         @Override
3349         public boolean format(DateTimePrintContext context, StringBuilder buf) {
3350             Long offsetSecs = context.getValue(OFFSET_SECONDS);
3351             if (offsetSecs == null) {
3352                 return false;
3353             }
3354             int totalSecs = Math.toIntExact(offsetSecs);
3355             if (totalSecs == 0) {
3356                 buf.append(noOffsetText);
3357             } else {
3358                 int absHours = Math.abs((totalSecs / 3600) % 100);  // anything larger than 99 silently dropped
3359                 int absMinutes = Math.abs((totalSecs / 60) % 60);
3360                 int absSeconds = Math.abs(totalSecs % 60);
3361                 int bufPos = buf.length();
3362                 int output = absHours;
3363                 buf.append(totalSecs < 0 ? "-" : "+")
3364                     .append((char) (absHours / 10 + '0')).append((char) (absHours % 10 + '0'));
3365                 if (type >= 3 || (type >= 1 && absMinutes > 0)) {
3366                     buf.append((type % 2) == 0 ? ":" : "")
3367                         .append((char) (absMinutes / 10 + '0')).append((char) (absMinutes % 10 + '0'));
3368                     output += absMinutes;
3369                     if (type >= 7 || (type >= 5 && absSeconds > 0)) {
3370                         buf.append((type % 2) == 0 ? ":" : "")
3371                             .append((char) (absSeconds / 10 + '0')).append((char) (absSeconds % 10 + '0'));
3372                         output += absSeconds;
3373                     }
3374                 }
3375                 if (output == 0) {
3376                     buf.setLength(bufPos);
3377                     buf.append(noOffsetText);
3378                 }
3379             }
3380             return true;
3381         }
3382 
3383         @Override
3384         public int parse(DateTimeParseContext context, CharSequence text, int position) {
3385             int length = text.length();
3386             int noOffsetLen = noOffsetText.length();









3387             if (noOffsetLen == 0) {
3388                 if (position == length) {
3389                     return context.setParsedField(OFFSET_SECONDS, 0, position, position);
3390                 }
3391             } else {
3392                 if (position == length) {
3393                     return ~position;
3394                 }
3395                 if (context.subSequenceEquals(text, position, noOffsetText, 0, noOffsetLen)) {
3396                     return context.setParsedField(OFFSET_SECONDS, 0, position, position + noOffsetLen);
3397                 }
3398             }
3399 
3400             // parse normal plus/minus offset
3401             char sign = text.charAt(position);  // IOOBE if invalid position
3402             if (sign == '+' || sign == '-') {
3403                 // starts
3404                 int negative = (sign == '-' ? -1 : 1);
3405                 int[] array = new int[4];
3406                 array[0] = position + 1;
3407                 if ((parseNumber(array, 1, text, true) ||
3408                         parseNumber(array, 2, text, type >=3) ||
3409                         parseNumber(array, 3, text, false)) == false) {
3410                     // success
3411                     long offsetSecs = negative * (array[1] * 3600L + array[2] * 60L + array[3]);
3412                     return context.setParsedField(OFFSET_SECONDS, offsetSecs, position, array[0]);
3413                 }
3414             }
3415             // handle special case of empty no offset text
3416             if (noOffsetLen == 0) {
3417                 return context.setParsedField(OFFSET_SECONDS, 0, position, position + noOffsetLen);
3418             }
3419             return ~position;
3420         }
3421 
3422         /**
3423          * Parse a two digit zero-prefixed number.
3424          *
3425          * @param array  the array of parsed data, 0=pos,1=hours,2=mins,3=secs, not null
3426          * @param arrayIndex  the index to parse the value into
3427          * @param parseText  the offset ID, not null
3428          * @param required  whether this number is required

3429          * @return true if an error occurred
3430          */
3431         private boolean parseNumber(int[] array, int arrayIndex, CharSequence parseText, boolean required) {
3432             if ((type + 3) / 2 < arrayIndex) {
3433                 return false;  // ignore seconds/minutes
3434             }
3435             int pos = array[0];
3436             if ((type % 2) == 0 && arrayIndex > 1) {
3437                 if (pos + 1 > parseText.length() || parseText.charAt(pos) != ':') {
3438                     return required;
3439                 }
3440                 pos++;
3441             }
3442             if (pos + 2 > parseText.length()) {
3443                 return required;
3444             }
3445             char ch1 = parseText.charAt(pos++);
3446             char ch2 = parseText.charAt(pos++);
3447             if (ch1 < '0' || ch1 > '9' || ch2 < '0' || ch2 > '9') {
3448                 return required;
3449             }
3450             int value = (ch1 - 48) * 10 + (ch2 - 48);
3451             if (value < 0 || value > 59) {
3452                 return required;
3453             }
3454             array[arrayIndex] = value;
3455             array[0] = pos;
3456             return false;




 849      * epoch-seconds value. That is achieved using {@code appendValue(INSTANT_SECONDS)}.
 850      *
 851      * @param fractionalDigits  the number of fractional second digits to format with,
 852      *  from 0 to 9, or -1 to use as many digits as necessary
 853      * @return this, for chaining, not null
 854      * @throws IllegalArgumentException if the number of fractional digits is invalid
 855      */
 856     public DateTimeFormatterBuilder appendInstant(int fractionalDigits) {
 857         if (fractionalDigits < -1 || fractionalDigits > 9) {
 858             throw new IllegalArgumentException("The fractional digits must be from -1 to 9 inclusive but was " + fractionalDigits);
 859         }
 860         appendInternal(new InstantPrinterParser(fractionalDigits));
 861         return this;
 862     }
 863 
 864     //-----------------------------------------------------------------------
 865     /**
 866      * Appends the zone offset, such as '+01:00', to the formatter.
 867      * <p>
 868      * This appends an instruction to format/parse the offset ID to the builder.
 869      * This is equivalent to calling {@code appendOffset("+HH:mm:ss", "Z")}.
 870      * See {@link #appendOffset(String, String)} for details on formatting
 871      * and parsing.
 872      *
 873      * @return this, for chaining, not null
 874      */
 875     public DateTimeFormatterBuilder appendOffsetId() {
 876         appendInternal(OffsetIdPrinterParser.INSTANCE_ID_Z);
 877         return this;
 878     }
 879 
 880     /**
 881      * Appends the zone offset, such as '+01:00', to the formatter.
 882      * <p>
 883      * This appends an instruction to format/parse the offset ID to the builder.
 884      * <p>
 885      * During formatting, the offset is obtained using a mechanism equivalent
 886      * to querying the temporal with {@link TemporalQueries#offset()}.
 887      * It will be printed using the format defined below.
 888      * If the offset cannot be obtained then an exception is thrown unless the
 889      * section of the formatter is optional.
 890      * <p>
 891      * When parsing in strict mode, the input must contain the mandatory
 892      * and optional elements are defined by the specified pattern.
 893      * If the offset cannot be parsed then an exception is thrown unless
 894      * the section of the formatter is optional.
 895      * <p>
 896      * When parsing in lenient mode, only the hours are mandatory - minutes
 897      * and seconds are optional. The colons are required if the specified
 898      * pattern contains a colon. If the specified pattern is "+HH", the
 899      * presence of colons is determined by whether the character after the
 900      * hour digits is a colon or not.
 901      * If the offset cannot be parsed then an exception is thrown unless
 902      * the section of the formatter is optional.
 903      * <p>
 904      * The format of the offset is controlled by a pattern which must be one
 905      * of the following:
 906      * <ul>
 907      * <li>{@code +HH} - hour only, ignoring minute and second
 908      * <li>{@code +HHmm} - hour, with minute if non-zero, ignoring second, no colon
 909      * <li>{@code +HH:mm} - hour, with minute if non-zero, ignoring second, with colon
 910      * <li>{@code +HHMM} - hour and minute, ignoring second, no colon
 911      * <li>{@code +HH:MM} - hour and minute, ignoring second, with colon
 912      * <li>{@code +HHMMss} - hour and minute, with second if non-zero, no colon
 913      * <li>{@code +HH:MM:ss} - hour and minute, with second if non-zero, with colon
 914      * <li>{@code +HHMMSS} - hour, minute and second, no colon
 915      * <li>{@code +HH:MM:SS} - hour, minute and second, with colon
 916      * <li>{@code +HHmmss} - hour, with minute if non-zero or with minute and
 917      * second if non-zero, no colon
 918      * <li>{@code +HH:mm:ss} - hour, with minute if non-zero or with minute and
 919      * second if non-zero, with colon
 920      * </ul>
 921      * The "no offset" text controls what text is printed when the total amount of
 922      * the offset fields to be output is zero.
 923      * Example values would be 'Z', '+00:00', 'UTC' or 'GMT'.
 924      * Three formats are accepted for parsing UTC - the "no offset" text, and the
 925      * plus and minus versions of zero defined by the pattern.
 926      *
 927      * @param pattern  the pattern to use, not null
 928      * @param noOffsetText  the text to use when the offset is zero, not null
 929      * @return this, for chaining, not null
 930      * @throws IllegalArgumentException if the pattern is invalid
 931      */
 932     public DateTimeFormatterBuilder appendOffset(String pattern, String noOffsetText) {
 933         appendInternal(new OffsetIdPrinterParser(pattern, noOffsetText));
 934         return this;
 935     }
 936 
 937     /**
 938      * Appends the localized zone offset, such as 'GMT+01:00', to the formatter.
 939      * <p>


3313             } catch (RuntimeException ex) {
3314                 return ~position;
3315             }
3316             int successPos = pos;
3317             successPos = context.setParsedField(INSTANT_SECONDS, instantSecs, position, successPos);
3318             return context.setParsedField(NANO_OF_SECOND, nano, position, successPos);
3319         }
3320 
3321         @Override
3322         public String toString() {
3323             return "Instant()";
3324         }
3325     }
3326 
3327     //-----------------------------------------------------------------------
3328     /**
3329      * Prints or parses an offset ID.
3330      */
3331     static final class OffsetIdPrinterParser implements DateTimePrinterParser {
3332         static final String[] PATTERNS = new String[] {
3333             "+HH", "+HHmm", "+HH:mm", "+HHMM", "+HH:MM", "+HHMMss", "+HH:MM:ss", "+HHMMSS", "+HH:MM:SS", "+HHmmss", "+HH:mm:ss",
3334         };  // order used in pattern builder
3335         static final OffsetIdPrinterParser INSTANCE_ID_Z = new OffsetIdPrinterParser("+HH:MM:ss", "Z");
3336         static final OffsetIdPrinterParser INSTANCE_ID_ZERO = new OffsetIdPrinterParser("+HH:MM:ss", "0");
3337 
3338         private final String noOffsetText;
3339         private final int type;
3340 
3341         /**
3342          * Constructor.
3343          *
3344          * @param pattern  the pattern
3345          * @param noOffsetText  the text to use for UTC, not null
3346          */
3347         OffsetIdPrinterParser(String pattern, String noOffsetText) {
3348             Objects.requireNonNull(pattern, "pattern");
3349             Objects.requireNonNull(noOffsetText, "noOffsetText");
3350             this.type = checkPattern(pattern);
3351             this.noOffsetText = noOffsetText;
3352         }
3353 


3360             throw new IllegalArgumentException("Invalid zone offset pattern: " + pattern);
3361         }
3362 
3363         @Override
3364         public boolean format(DateTimePrintContext context, StringBuilder buf) {
3365             Long offsetSecs = context.getValue(OFFSET_SECONDS);
3366             if (offsetSecs == null) {
3367                 return false;
3368             }
3369             int totalSecs = Math.toIntExact(offsetSecs);
3370             if (totalSecs == 0) {
3371                 buf.append(noOffsetText);
3372             } else {
3373                 int absHours = Math.abs((totalSecs / 3600) % 100);  // anything larger than 99 silently dropped
3374                 int absMinutes = Math.abs((totalSecs / 60) % 60);
3375                 int absSeconds = Math.abs(totalSecs % 60);
3376                 int bufPos = buf.length();
3377                 int output = absHours;
3378                 buf.append(totalSecs < 0 ? "-" : "+")
3379                     .append((char) (absHours / 10 + '0')).append((char) (absHours % 10 + '0'));
3380                 if ((type >= 3 && type < 9) || (type >= 9 && absSeconds > 0) || (type >= 1 && absMinutes > 0)) {
3381                     buf.append((type % 2) == 0 ? ":" : "")
3382                         .append((char) (absMinutes / 10 + '0')).append((char) (absMinutes % 10 + '0'));
3383                     output += absMinutes;
3384                     if (type == 7  || type == 8 || (type >= 5 && absSeconds > 0)) {
3385                         buf.append((type % 2) == 0 ? ":" : "")
3386                             .append((char) (absSeconds / 10 + '0')).append((char) (absSeconds % 10 + '0'));
3387                         output += absSeconds;
3388                     }
3389                 }
3390                 if (output == 0) {
3391                     buf.setLength(bufPos);
3392                     buf.append(noOffsetText);
3393                 }
3394             }
3395             return true;
3396         }
3397 
3398         @Override
3399         public int parse(DateTimeParseContext context, CharSequence text, int position) {
3400             int length = text.length();
3401             int noOffsetLen = noOffsetText.length();
3402             int parseType = type;
3403             if (context.isStrict() == false) {
3404                 if ((parseType > 0 && (parseType % 2) == 0) ||
3405                     (parseType == 0 && length > position + 3 && text.charAt(position + 3) == ':')) {
3406                     parseType = 10;
3407                 } else {
3408                     parseType = 9;
3409                 }
3410             }
3411             if (noOffsetLen == 0) {
3412                 if (position == length) {
3413                     return context.setParsedField(OFFSET_SECONDS, 0, position, position);
3414                 }
3415             } else {
3416                 if (position == length) {
3417                     return ~position;
3418                 }
3419                 if (context.subSequenceEquals(text, position, noOffsetText, 0, noOffsetLen)) {
3420                     return context.setParsedField(OFFSET_SECONDS, 0, position, position + noOffsetLen);
3421                 }
3422             }
3423 
3424             // parse normal plus/minus offset
3425             char sign = text.charAt(position);  // IOOBE if invalid position
3426             if (sign == '+' || sign == '-') {
3427                 // starts
3428                 int negative = (sign == '-' ? -1 : 1);
3429                 int[] array = new int[4];
3430                 array[0] = position + 1;
3431                 if ((parseNumber(array, 1, text, true, parseType) ||
3432                         parseNumber(array, 2, text, parseType >= 3 && parseType < 9, parseType) ||
3433                         parseNumber(array, 3, text, parseType == 7 || parseType == 8, parseType)) == false) {
3434                     // success
3435                     long offsetSecs = negative * (array[1] * 3600L + array[2] * 60L + array[3]);
3436                     return context.setParsedField(OFFSET_SECONDS, offsetSecs, position, array[0]);
3437                 }
3438             }
3439             // handle special case of empty no offset text
3440             if (noOffsetLen == 0) {
3441                 return context.setParsedField(OFFSET_SECONDS, 0, position, position);
3442             }
3443             return ~position;
3444         }
3445 
3446         /**
3447          * Parse a two digit zero-prefixed number.
3448          *
3449          * @param array  the array of parsed data, 0=pos,1=hours,2=mins,3=secs, not null
3450          * @param arrayIndex  the index to parse the value into
3451          * @param parseText  the offset ID, not null
3452          * @param required  whether this number is required
3453          * @param parseType the offset pattern type
3454          * @return true if an error occurred
3455          */
3456         private boolean parseNumber(int[] array, int arrayIndex, CharSequence parseText, boolean required, int parseType) {
3457             if ((parseType + 3) / 2 < arrayIndex) {
3458                 return false;  // ignore seconds/minutes
3459             }
3460             int pos = array[0];
3461             if ((parseType % 2) == 0 && arrayIndex > 1) {
3462                 if (pos + 1 > parseText.length() || parseText.charAt(pos) != ':') {
3463                     return required;
3464                 }
3465                 pos++;
3466             }
3467             if (pos + 2 > parseText.length()) {
3468                 return required;
3469             }
3470             char ch1 = parseText.charAt(pos++);
3471             char ch2 = parseText.charAt(pos++);
3472             if (ch1 < '0' || ch1 > '9' || ch2 < '0' || ch2 > '9') {
3473                 return required;
3474             }
3475             int value = (ch1 - 48) * 10 + (ch2 - 48);
3476             if (value < 0 || value > 59) {
3477                 return required;
3478             }
3479             array[arrayIndex] = value;
3480             array[0] = pos;
3481             return false;


< prev index next >