wangzhibo
2026-05-09 53418655c061ac26f66058253f39200aced1ab1d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
 * @copyright (c) 2016, Philipp Thürwächter & Pattrick Hüper
 * @copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
 * @license BSD-3-Clause (see LICENSE in the root directory of this source tree)
 */
 
import { requireNonNull } from '../../assert';
import { IllegalArgumentException } from '../../errors';
import { MathUtil } from '../../MathUtil';
 
/**
 * TODO optimize FractionPrinterParser, fix documentation
 *
 * Prints and parses a numeric date-time field with optional padding.
 * @private
 */
export class FractionPrinterParser {
 
    /**
     * Constructor.
     *
     * @param {TemporalField} field  the field to output, not null
     * @param {Number} minWidth  the minimum width to output, from 0 to 9
     * @param {Number} maxWidth  the maximum width to output, from 0 to 9
     * @param {boolean} decimalPoint  whether to output the localized decimal point symbol
     */
    constructor(field, minWidth, maxWidth, decimalPoint) {
        requireNonNull(field, 'field');
        if (field.range().isFixed() === false) {
            throw new IllegalArgumentException(`Field must have a fixed set of values: ${field}`);
        }
        if (minWidth < 0 || minWidth > 9) {
            throw new IllegalArgumentException(`Minimum width must be from 0 to 9 inclusive but was ${minWidth}`);
        }
        if (maxWidth < 1 || maxWidth > 9) {
            throw new IllegalArgumentException(`Maximum width must be from 1 to 9 inclusive but was ${maxWidth}`);
        }
        if (maxWidth < minWidth) {
            throw new IllegalArgumentException(`Maximum width must exceed or equal the minimum width but ${ 
                maxWidth} < ${minWidth}`);
        }
        this.field = field;
        this.minWidth = minWidth;
        this.maxWidth = maxWidth;
        this.decimalPoint = decimalPoint;
    }
 
    print(context, buf) {
        const value = context.getValue(this.field);
        if (value === null) {
            return false;
        }
        const symbols = context.symbols();
        if (value === 0) {  // scale is zero if value is zero
            if (this.minWidth > 0) {
                if (this.decimalPoint) {
                    buf.append(symbols.decimalSeparator());
                }
                for (let i = 0; i < this.minWidth; i++) {
                    buf.append(symbols.zeroDigit());
                }
            }
        } else {
            let fraction = this.convertToFraction(value, symbols.zeroDigit());
            const outputScale = Math.min(Math.max(fraction.length, this.minWidth), this.maxWidth);
            fraction = fraction.substr(0, outputScale);
            if(fraction * 1 > 0 ) {
                while (fraction.length > this.minWidth && fraction[fraction.length - 1] === '0') {
                    fraction = fraction.substr(0, fraction.length - 1);
                }
            }
            let str = fraction;
            str = symbols.convertNumberToI18N(str);
            if (this.decimalPoint) {
                buf.append(symbols.decimalSeparator());
            }
            buf.append(str);
        }
        return true;
    }
 
    parse(context, text, position) {
        const effectiveMin = (context.isStrict() ? this.minWidth : 0);
        const effectiveMax = (context.isStrict() ? this.maxWidth : 9);
        const length = text.length;
        if (position === length) {
            // valid if whole field is optional, invalid if minimum width
            return (effectiveMin > 0 ? ~position : position);
        }
        if (this.decimalPoint) {
            if (text[position] !== context.symbols().decimalSeparator()) {
                // valid if whole field is optional, invalid if minimum width
                return (effectiveMin > 0 ? ~position : position);
            }
            position++;
        }
        const minEndPos = position + effectiveMin;
        if (minEndPos > length) {
            return ~position;  // need at least min width digits
        }
        const maxEndPos = Math.min(position + effectiveMax, length);
        let total = 0;  // can use int because we are only parsing up to 9 digits
        let pos = position;
        while (pos < maxEndPos) {
            const ch = text.charAt(pos++);
            const digit = context.symbols().convertToDigit(ch);
            if (digit < 0) {
                if (pos < minEndPos) {
                    return ~position;  // need at least min width digits
                }
                pos--;
                break;
            }
            total = total * 10 + digit;
        }
        const moveLeft = pos - position;
        const scale = Math.pow(10, moveLeft);
        const value = this.convertFromFraction(total, scale);
        return context.setParsedField(this.field, value, position, pos);
    }
 
    /**
     *
     * @param {Number} value  the value to convert, must be valid for this rule
     * @param {String} zeroDigit  the character for zero
     * @return {String} the value as a fraction within the range, from 0 to 1, not null
     */
    convertToFraction(value, zeroDigit) {
        const range = this.field.range();
        range.checkValidValue(value, this.field);
        const _min = range.minimum();
        const _range = range.maximum() - _min + 1;
        const _value = value - _min;
        const _scaled = MathUtil.intDiv((_value * 1000000000),  _range);
        let fraction = `${_scaled}`;
        while(fraction.length < 9){
            fraction = zeroDigit + fraction;
        }
        return fraction;
    }
 
    /**
     *
     * @param {Number} total  the fraction to convert, not null
     * @param {Number} scale  the scale, not null
     * @return {Number} the value of the field, valid for this rule
     * @throws DateTimeException if the value cannot be converted
     */
    convertFromFraction(total, scale) {
        const range = this.field.range();
        const _min = range.minimum();
        const _range = range.maximum() - _min + 1;
        const _value = MathUtil.intDiv((total * _range), scale);
        return _value;
    }
 
    toString() {
        const decimal = (this.decimalPoint ? ',DecimalPoint' : '');
        return `Fraction(${this.field},${this.minWidth},${this.maxWidth}${decimal})`;
    }
}