< 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.
 898      * The colons are required if the specified pattern contains a colon.
 899      * If the specified pattern is "+HH", the presence of colons is
 900      * determined by whether the character after the hour digits is a colon
 901      * or not.
 902      * If the offset cannot be parsed then an exception is thrown unless
 903      * the section of the formatter is optional.
 904      * <p>
 905      * The format of the offset is controlled by a pattern which must be one
 906      * of the following:
 907      * <ul>
 908      * <li>{@code +HH} - hour only, ignoring minute and second
 909      * <li>{@code +HHmm} - hour, with minute if non-zero, ignoring second, no colon
 910      * <li>{@code +HH:mm} - hour, with minute if non-zero, ignoring second, with colon
 911      * <li>{@code +HHMM} - hour and minute, ignoring second, no colon
 912      * <li>{@code +HH:MM} - hour and minute, ignoring second, with colon
 913      * <li>{@code +HHMMss} - hour and minute, with second if non-zero, no colon
 914      * <li>{@code +HH:MM:ss} - hour and minute, with second if non-zero, with colon
 915      * <li>{@code +HHMMSS} - hour, minute and second, no colon
 916      * <li>{@code +HH:MM:SS} - hour, minute and second, with colon
 917      * <li>{@code +HHmmss} - hour, with minute if non-zero or with minute and
 918      * second if non-zero, no colon
 919      * <li>{@code +HH:mm:ss} - hour, with minute if non-zero or with minute and
 920      * second if non-zero, with colon
 921      * </ul>
 922      * The "no offset" text controls what text is printed when the total amount of
 923      * the offset fields to be output is zero.
 924      * Example values would be 'Z', '+00:00', 'UTC' or 'GMT'.
 925      * Three formats are accepted for parsing UTC - the "no offset" text, and the
 926      * plus and minus versions of zero defined by the pattern.
 927      *
 928      * @param pattern  the pattern to use, not null
 929      * @param noOffsetText  the text to use when the offset is zero, not null
 930      * @return this, for chaining, not null
 931      * @throws IllegalArgumentException if the pattern is invalid
 932      */
 933     public DateTimeFormatterBuilder appendOffset(String pattern, String noOffsetText) {
 934         appendInternal(new OffsetIdPrinterParser(pattern, noOffsetText));
 935         return this;
 936     }
 937 
 938     /**
 939      * Appends the localized zone offset, such as 'GMT+01:00', to the formatter.
 940      * <p>


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


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


< prev index next >