1 /*
2 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
649 active.valueParserIndex = appendInternal(pp);
650 }
651 return this;
652 }
653
654 //-----------------------------------------------------------------------
655 /**
656 * Appends the fractional value of a date-time field to the formatter.
657 * <p>
658 * The fractional value of the field will be output including the
659 * preceding decimal point. The preceding value is not output.
660 * For example, the second-of-minute value of 15 would be output as {@code .25}.
661 * <p>
662 * The width of the printed fraction can be controlled. Setting the
663 * minimum width to zero will cause no output to be generated.
664 * The printed fraction will have the minimum width necessary between
665 * the minimum and maximum widths - trailing zeroes are omitted.
666 * No rounding occurs due to the maximum width - digits are simply dropped.
667 * <p>
668 * When parsing in strict mode, the number of parsed digits must be between
669 * the minimum and maximum width. When parsing in lenient mode, the minimum
670 * width is considered to be zero and the maximum is nine.
671 * <p>
672 * If the value cannot be obtained then an exception will be thrown.
673 * If the value is negative an exception will be thrown.
674 * If the field does not have a fixed set of valid values then an
675 * exception will be thrown.
676 * If the field value in the date-time to be printed is invalid it
677 * cannot be printed and an exception will be thrown.
678 *
679 * @param field the field to append, not null
680 * @param minWidth the minimum width of the field excluding the decimal point, from 0 to 9
681 * @param maxWidth the maximum width of the field excluding the decimal point, from 1 to 9
682 * @param decimalPoint whether to output the localized decimal point symbol
683 * @return this, for chaining, not null
684 * @throws IllegalArgumentException if the field has a variable set of valid values or
685 * either width is invalid
686 */
687 public DateTimeFormatterBuilder appendFraction(
688 TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) {
689 appendInternal(new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint));
690 return this;
691 }
692
693 //-----------------------------------------------------------------------
694 /**
695 * Appends the text of a date-time field to the formatter using the full
696 * text style.
697 * <p>
698 * The text of the field will be output during a format.
699 * The value must be within the valid range of the field.
700 * If the value cannot be obtained then an exception will be thrown.
701 * If the field has no textual representation, then the numeric value will be used.
702 * <p>
703 * The value will be printed as per the normal format of an integer value.
704 * Only negative numbers will be signed. No padding will be added.
705 *
706 * @param field the field to append, not null
707 * @return this, for chaining, not null
708 */
709 public DateTimeFormatterBuilder appendText(TemporalField field) {
2902 */
2903 @Override
2904 boolean isFixedWidth(DateTimeParseContext context) {
2905 if (context.isStrict() == false) {
2906 return false;
2907 }
2908 return super.isFixedWidth(context);
2909 }
2910
2911 @Override
2912 public String toString() {
2913 return "ReducedValue(" + field + "," + minWidth + "," + maxWidth +
2914 "," + Objects.requireNonNullElse(baseDate, baseValue) + ")";
2915 }
2916 }
2917
2918 //-----------------------------------------------------------------------
2919 /**
2920 * Prints and parses a numeric date-time field with optional padding.
2921 */
2922 static final class FractionPrinterParser implements DateTimePrinterParser {
2923 private final TemporalField field;
2924 private final int minWidth;
2925 private final int maxWidth;
2926 private final boolean decimalPoint;
2927
2928 /**
2929 * Constructor.
2930 *
2931 * @param field the field to output, not null
2932 * @param minWidth the minimum width to output, from 0 to 9
2933 * @param maxWidth the maximum width to output, from 0 to 9
2934 * @param decimalPoint whether to output the localized decimal point symbol
2935 */
2936 FractionPrinterParser(TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) {
2937 Objects.requireNonNull(field, "field");
2938 if (field.range().isFixed() == false) {
2939 throw new IllegalArgumentException("Field must have a fixed set of values: " + field);
2940 }
2941 if (minWidth < 0 || minWidth > 9) {
2942 throw new IllegalArgumentException("Minimum width must be from 0 to 9 inclusive but was " + minWidth);
2943 }
2944 if (maxWidth < 1 || maxWidth > 9) {
2945 throw new IllegalArgumentException("Maximum width must be from 1 to 9 inclusive but was " + maxWidth);
2946 }
2947 if (maxWidth < minWidth) {
2948 throw new IllegalArgumentException("Maximum width must exceed or equal the minimum width but " +
2949 maxWidth + " < " + minWidth);
2950 }
2951 this.field = field;
2952 this.minWidth = minWidth;
2953 this.maxWidth = maxWidth;
2954 this.decimalPoint = decimalPoint;
2955 }
2956
2957 @Override
2958 public boolean format(DateTimePrintContext context, StringBuilder buf) {
2959 Long value = context.getValue(field);
2960 if (value == null) {
2961 return false;
2962 }
2963 DecimalStyle decimalStyle = context.getDecimalStyle();
2964 BigDecimal fraction = convertToFraction(value);
2965 if (fraction.scale() == 0) { // scale is zero if value is zero
2966 if (minWidth > 0) {
2967 if (decimalPoint) {
2968 buf.append(decimalStyle.getDecimalSeparator());
2969 }
2970 for (int i = 0; i < minWidth; i++) {
2971 buf.append(decimalStyle.getZeroDigit());
2972 }
2973 }
2974 } else {
2975 int outputScale = Math.min(Math.max(fraction.scale(), minWidth), maxWidth);
2976 fraction = fraction.setScale(outputScale, RoundingMode.FLOOR);
2977 String str = fraction.toPlainString().substring(2);
2978 str = decimalStyle.convertNumberToI18N(str);
2979 if (decimalPoint) {
2980 buf.append(decimalStyle.getDecimalSeparator());
2981 }
2982 buf.append(str);
2983 }
2984 return true;
2985 }
2986
2987 @Override
2988 public int parse(DateTimeParseContext context, CharSequence text, int position) {
2989 int effectiveMin = (context.isStrict() ? minWidth : 0);
2990 int effectiveMax = (context.isStrict() ? maxWidth : 9);
2991 int length = text.length();
2992 if (position == length) {
2993 // valid if whole field is optional, invalid if minimum width
2994 return (effectiveMin > 0 ? ~position : position);
2995 }
2996 if (decimalPoint) {
2997 if (text.charAt(position) != context.getDecimalStyle().getDecimalSeparator()) {
2998 // valid if whole field is optional, invalid if minimum width
2999 return (effectiveMin > 0 ? ~position : position);
3000 }
3001 position++;
3002 }
3003 int minEndPos = position + effectiveMin;
3004 if (minEndPos > length) {
3005 return ~position; // need at least min width digits
3006 }
3007 int maxEndPos = Math.min(position + effectiveMax, length);
3008 int total = 0; // can use int because we are only parsing up to 9 digits
3009 int pos = position;
3010 while (pos < maxEndPos) {
|
1 /*
2 * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
649 active.valueParserIndex = appendInternal(pp);
650 }
651 return this;
652 }
653
654 //-----------------------------------------------------------------------
655 /**
656 * Appends the fractional value of a date-time field to the formatter.
657 * <p>
658 * The fractional value of the field will be output including the
659 * preceding decimal point. The preceding value is not output.
660 * For example, the second-of-minute value of 15 would be output as {@code .25}.
661 * <p>
662 * The width of the printed fraction can be controlled. Setting the
663 * minimum width to zero will cause no output to be generated.
664 * The printed fraction will have the minimum width necessary between
665 * the minimum and maximum widths - trailing zeroes are omitted.
666 * No rounding occurs due to the maximum width - digits are simply dropped.
667 * <p>
668 * When parsing in strict mode, the number of parsed digits must be between
669 * the minimum and maximum width. In strict mode, if the minimum and maximum widths
670 * are equal and there is no decimal point then the parser will
671 * participate in adjacent value parsing, see
672 * {@code appendValue(java.time.temporal.TemporalField, int)}. When parsing in lenient mode,
673 * the minimum width is considered to be zero and the maximum is nine.
674 * <p>
675 * If the value cannot be obtained then an exception will be thrown.
676 * If the value is negative an exception will be thrown.
677 * If the field does not have a fixed set of valid values then an
678 * exception will be thrown.
679 * If the field value in the date-time to be printed is invalid it
680 * cannot be printed and an exception will be thrown.
681 *
682 * @param field the field to append, not null
683 * @param minWidth the minimum width of the field excluding the decimal point, from 0 to 9
684 * @param maxWidth the maximum width of the field excluding the decimal point, from 1 to 9
685 * @param decimalPoint whether to output the localized decimal point symbol
686 * @return this, for chaining, not null
687 * @throws IllegalArgumentException if the field has a variable set of valid values or
688 * either width is invalid
689 */
690 public DateTimeFormatterBuilder appendFraction(
691 TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) {
692 if (minWidth == maxWidth && decimalPoint == false) {
693 // adjacent parsing
694 appendValue(new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint));
695 } else {
696 appendInternal(new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint));
697 }
698 return this;
699 }
700
701 //-----------------------------------------------------------------------
702 /**
703 * Appends the text of a date-time field to the formatter using the full
704 * text style.
705 * <p>
706 * The text of the field will be output during a format.
707 * The value must be within the valid range of the field.
708 * If the value cannot be obtained then an exception will be thrown.
709 * If the field has no textual representation, then the numeric value will be used.
710 * <p>
711 * The value will be printed as per the normal format of an integer value.
712 * Only negative numbers will be signed. No padding will be added.
713 *
714 * @param field the field to append, not null
715 * @return this, for chaining, not null
716 */
717 public DateTimeFormatterBuilder appendText(TemporalField field) {
2910 */
2911 @Override
2912 boolean isFixedWidth(DateTimeParseContext context) {
2913 if (context.isStrict() == false) {
2914 return false;
2915 }
2916 return super.isFixedWidth(context);
2917 }
2918
2919 @Override
2920 public String toString() {
2921 return "ReducedValue(" + field + "," + minWidth + "," + maxWidth +
2922 "," + Objects.requireNonNullElse(baseDate, baseValue) + ")";
2923 }
2924 }
2925
2926 //-----------------------------------------------------------------------
2927 /**
2928 * Prints and parses a numeric date-time field with optional padding.
2929 */
2930 static final class FractionPrinterParser extends NumberPrinterParser {
2931 private final boolean decimalPoint;
2932
2933 /**
2934 * Constructor.
2935 *
2936 * @param field the field to output, not null
2937 * @param minWidth the minimum width to output, from 0 to 9
2938 * @param maxWidth the maximum width to output, from 0 to 9
2939 * @param decimalPoint whether to output the localized decimal point symbol
2940 */
2941 FractionPrinterParser(TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) {
2942 this(field, minWidth, maxWidth, decimalPoint, 0);
2943 Objects.requireNonNull(field, "field");
2944 if (field.range().isFixed() == false) {
2945 throw new IllegalArgumentException("Field must have a fixed set of values: " + field);
2946 }
2947 if (minWidth < 0 || minWidth > 9) {
2948 throw new IllegalArgumentException("Minimum width must be from 0 to 9 inclusive but was " + minWidth);
2949 }
2950 if (maxWidth < 1 || maxWidth > 9) {
2951 throw new IllegalArgumentException("Maximum width must be from 1 to 9 inclusive but was " + maxWidth);
2952 }
2953 if (maxWidth < minWidth) {
2954 throw new IllegalArgumentException("Maximum width must exceed or equal the minimum width but " +
2955 maxWidth + " < " + minWidth);
2956 }
2957 }
2958
2959 /**
2960 * Constructor.
2961 *
2962 * @param field the field to output, not null
2963 * @param minWidth the minimum width to output, from 0 to 9
2964 * @param maxWidth the maximum width to output, from 0 to 9
2965 * @param decimalPoint whether to output the localized decimal point symbol
2966 * @param subsequentWidth the subsequentWidth for this instance
2967 */
2968 FractionPrinterParser(TemporalField field, int minWidth, int maxWidth, boolean decimalPoint, int subsequentWidth) {
2969 super(field, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, subsequentWidth);
2970 this.decimalPoint = decimalPoint;
2971 }
2972
2973 /**
2974 * Returns a new instance with fixed width flag set.
2975 *
2976 * @return a new updated printer-parser, not null
2977 */
2978 @Override
2979 FractionPrinterParser withFixedWidth() {
2980 if (subsequentWidth == -1) {
2981 return this;
2982 }
2983 return new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint, -1);
2984 }
2985
2986 /**
2987 * Returns a new instance with an updated subsequent width.
2988 *
2989 * @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater
2990 * @return a new updated printer-parser, not null
2991 */
2992 @Override
2993 FractionPrinterParser withSubsequentWidth(int subsequentWidth) {
2994 return new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint, this.subsequentWidth + subsequentWidth);
2995 }
2996
2997 /**
2998 *
2999 * @param context the context
3000 * @return if the field is fixed width
3001 * @see DateTimeFormatterBuilder#appendValueFraction(java.time.temporal.TemporalField, int, int, boolean)
3002 */
3003 @Override
3004 boolean isFixedWidth(DateTimeParseContext context) {
3005 if (context.isStrict() && minWidth == maxWidth && decimalPoint == false) {
3006 return true;
3007 }
3008 return false;
3009 }
3010
3011 @Override
3012 public boolean format(DateTimePrintContext context, StringBuilder buf) {
3013 Long value = context.getValue(field);
3014 if (value == null) {
3015 return false;
3016 }
3017 DecimalStyle decimalStyle = context.getDecimalStyle();
3018 BigDecimal fraction = convertToFraction(value);
3019 if (fraction.scale() == 0) { // scale is zero if value is zero
3020 if (minWidth > 0) {
3021 if (decimalPoint) {
3022 buf.append(decimalStyle.getDecimalSeparator());
3023 }
3024 for (int i = 0; i < minWidth; i++) {
3025 buf.append(decimalStyle.getZeroDigit());
3026 }
3027 }
3028 } else {
3029 int outputScale = Math.min(Math.max(fraction.scale(), minWidth), maxWidth);
3030 fraction = fraction.setScale(outputScale, RoundingMode.FLOOR);
3031 String str = fraction.toPlainString().substring(2);
3032 str = decimalStyle.convertNumberToI18N(str);
3033 if (decimalPoint) {
3034 buf.append(decimalStyle.getDecimalSeparator());
3035 }
3036 buf.append(str);
3037 }
3038 return true;
3039 }
3040
3041 @Override
3042 public int parse(DateTimeParseContext context, CharSequence text, int position) {
3043 int effectiveMin = (context.isStrict() || isFixedWidth(context) ? minWidth : 0);
3044 int effectiveMax = (context.isStrict() || isFixedWidth(context) ? maxWidth : 9);
3045 int length = text.length();
3046 if (position == length) {
3047 // valid if whole field is optional, invalid if minimum width
3048 return (effectiveMin > 0 ? ~position : position);
3049 }
3050 if (decimalPoint) {
3051 if (text.charAt(position) != context.getDecimalStyle().getDecimalSeparator()) {
3052 // valid if whole field is optional, invalid if minimum width
3053 return (effectiveMin > 0 ? ~position : position);
3054 }
3055 position++;
3056 }
3057 int minEndPos = position + effectiveMin;
3058 if (minEndPos > length) {
3059 return ~position; // need at least min width digits
3060 }
3061 int maxEndPos = Math.min(position + effectiveMax, length);
3062 int total = 0; // can use int because we are only parsing up to 9 digits
3063 int pos = position;
3064 while (pos < maxEndPos) {
|