/**
|
* @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 { assert } from '../../assert';
|
import { ArithmeticException, DateTimeException, IllegalArgumentException } from '../../errors';
|
import { MathUtil } from '../../MathUtil';
|
|
import { IsoChronology } from '../../chrono/IsoChronology';
|
|
import { SignStyle } from '../SignStyle';
|
|
|
const MAX_WIDTH = 15; // can't parse all numbers with more then 15 digits in javascript
|
|
const EXCEED_POINTS = [
|
0,
|
10,
|
100,
|
1000,
|
10000,
|
100000,
|
1000000,
|
10000000,
|
100000000,
|
1000000000
|
];
|
|
/**
|
* @private
|
*/
|
export class NumberPrinterParser {
|
|
/**
|
* Constructor.
|
*
|
* @param field the field to print, not null
|
* @param minWidth the minimum field width, from 1 to 19
|
* @param maxWidth the maximum field width, from minWidth to 19
|
* @param signStyle the positive/negative sign style, not null
|
* @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater,
|
* -1 if fixed width due to active adjacent parsing
|
*/
|
constructor(field, minWidth, maxWidth, signStyle, subsequentWidth=0){
|
this._field = field;
|
this._minWidth = minWidth;
|
this._maxWidth = maxWidth;
|
this._signStyle = signStyle;
|
this._subsequentWidth = subsequentWidth;
|
}
|
|
field(){ return this._field;}
|
minWidth(){ return this._minWidth;}
|
maxWidth(){ return this._maxWidth;}
|
signStyle(){ return this._signStyle;}
|
|
withFixedWidth() {
|
if (this._subsequentWidth === -1) {
|
return this;
|
}
|
return new NumberPrinterParser(this._field, this._minWidth, this._maxWidth, this._signStyle, -1);
|
}
|
|
withSubsequentWidth(subsequentWidth) {
|
return new NumberPrinterParser(this._field, this._minWidth, this._maxWidth, this._signStyle, this._subsequentWidth + subsequentWidth);
|
}
|
|
_isFixedWidth() {
|
return this._subsequentWidth === -1 ||
|
(this._subsequentWidth > 0 && this._minWidth === this._maxWidth && this._signStyle === SignStyle.NOT_NEGATIVE);
|
}
|
|
print(context, buf) {
|
const contextValue = context.getValue(this._field);
|
if (contextValue == null) {
|
return false;
|
}
|
const value = this._getValue(context, contextValue);
|
const symbols = context.symbols();
|
let str = `${Math.abs(value)}`;
|
if (str.length > this._maxWidth) {
|
throw new DateTimeException(`Field ${this._field
|
} cannot be printed as the value ${value
|
} exceeds the maximum print width of ${this._maxWidth}`);
|
}
|
str = symbols.convertNumberToI18N(str);
|
|
if (value >= 0) {
|
switch (this._signStyle) {
|
case SignStyle.EXCEEDS_PAD:
|
if (this._minWidth < MAX_WIDTH && value >= EXCEED_POINTS[this._minWidth]) {
|
buf.append(symbols.positiveSign());
|
}
|
break;
|
case SignStyle.ALWAYS:
|
buf.append(symbols.positiveSign());
|
break;
|
}
|
} else {
|
switch (this._signStyle) {
|
case SignStyle.NORMAL:
|
case SignStyle.EXCEEDS_PAD:
|
case SignStyle.ALWAYS:
|
buf.append(symbols.negativeSign());
|
break;
|
case SignStyle.NOT_NEGATIVE:
|
throw new DateTimeException(`Field ${this._field
|
} cannot be printed as the value ${value
|
} cannot be negative according to the SignStyle`);
|
}
|
}
|
for (let i = 0; i < this._minWidth - str.length; i++) {
|
buf.append(symbols.zeroDigit());
|
}
|
buf.append(str);
|
return true;
|
}
|
|
parse(context, text, position){
|
const length = text.length;
|
if (position === length) {
|
return ~position;
|
}
|
assert(position>=0 && position<length);
|
const sign = text.charAt(position); // IOOBE if invalid position
|
let negative = false;
|
let positive = false;
|
if (sign === context.symbols().positiveSign()) {
|
if (this._signStyle.parse(true, context.isStrict(), this._minWidth === this._maxWidth) === false) {
|
return ~position;
|
}
|
positive = true;
|
position++;
|
} else if (sign === context.symbols().negativeSign()) {
|
if (this._signStyle.parse(false, context.isStrict(), this._minWidth === this._maxWidth) === false) {
|
return ~position;
|
}
|
negative = true;
|
position++;
|
} else {
|
if (this._signStyle === SignStyle.ALWAYS && context.isStrict()) {
|
return ~position;
|
}
|
}
|
const effMinWidth = (context.isStrict() || this._isFixedWidth() ? this._minWidth : 1);
|
const minEndPos = position + effMinWidth;
|
if (minEndPos > length) {
|
return ~position;
|
}
|
let effMaxWidth = (context.isStrict() || this._isFixedWidth() ? this._maxWidth : 9) + Math.max(this._subsequentWidth, 0);
|
let total = 0;
|
let pos = position;
|
for (let pass = 0; pass < 2; pass++) {
|
const maxEndPos = Math.min(pos + effMaxWidth, length);
|
while (pos < maxEndPos) {
|
const ch = text.charAt(pos++);
|
const digit = context.symbols().convertToDigit(ch);
|
if (digit < 0) {
|
pos--;
|
if (pos < minEndPos) {
|
return ~position; // need at least min width digits
|
}
|
break;
|
}
|
if ((pos - position) > MAX_WIDTH) {
|
throw new ArithmeticException('number text exceeds length');
|
} else {
|
total = total * 10 + digit;
|
}
|
}
|
if (this._subsequentWidth > 0 && pass === 0) {
|
// re-parse now we know the correct width
|
const parseLen = pos - position;
|
effMaxWidth = Math.max(effMinWidth, parseLen - this._subsequentWidth);
|
pos = position;
|
total = 0;
|
} else {
|
break;
|
}
|
}
|
if (negative) {
|
if (total === 0 && context.isStrict()) {
|
return ~(position - 1); // minus zero not allowed
|
}
|
if(total !== 0) {
|
total = -total;
|
}
|
} else if (this._signStyle === SignStyle.EXCEEDS_PAD && context.isStrict()) {
|
const parseLen = pos - position;
|
if (positive) {
|
if (parseLen <= this._minWidth) {
|
return ~(position - 1); // '+' only parsed if minWidth exceeded
|
}
|
} else {
|
if (parseLen > this._minWidth) {
|
return ~position; // '+' must be parsed if minWidth exceeded
|
}
|
}
|
}
|
return this._setValue(context, total, position, pos);
|
}
|
|
/**
|
* Gets the value to output.
|
* (This is needed to allow e.g. ReducedPrinterParser to override this and change the value!
|
*
|
* @param context the context
|
* @param value the value of the field, not null
|
* @return the value
|
* @private
|
*/
|
_getValue(context, value) {
|
return value;
|
}
|
|
/**
|
* Stores the value.
|
*
|
* @param context the context to store into, not null
|
* @param value the value
|
* @param errorPos the position of the field being parsed
|
* @param successPos the position after the field being parsed
|
* @return the new position
|
*/
|
_setValue(context, value, errorPos, successPos) {
|
return context.setParsedField(this._field, value, errorPos, successPos);
|
}
|
|
toString() {
|
if (this._minWidth === 1 && this._maxWidth === MAX_WIDTH && this._signStyle === SignStyle.NORMAL) {
|
return `Value(${this._field})`;
|
}
|
if (this._minWidth === this._maxWidth && this._signStyle === SignStyle.NOT_NEGATIVE) {
|
return `Value(${this._field},${this._minWidth})`;
|
}
|
return `Value(${this._field},${this._minWidth},${this._maxWidth},${this._signStyle})`;
|
}
|
|
}
|
//-----------------------------------------------------------------------
|
/**
|
* Prints and parses a reduced numeric date-time field.
|
* @private
|
*/
|
export class ReducedPrinterParser extends NumberPrinterParser {
|
|
/**
|
* Constructor.
|
*
|
* @param {TemporalField} field the field to print, validated not null
|
* @param {number} width the field width, from 1 to 10
|
* @param {number} maxWidth the field max width, from 1 to 10
|
* @param {number} baseValue the base value
|
* @param {ChronoLocalDate} baseDate the base date
|
*/
|
constructor(field, width, maxWidth, baseValue, baseDate) {
|
super(field, width, maxWidth, SignStyle.NOT_NEGATIVE);
|
if (width < 1 || width > 10) {
|
throw new IllegalArgumentException(`The width must be from 1 to 10 inclusive but was ${width}`);
|
}
|
if (maxWidth < 1 || maxWidth > 10) {
|
throw new IllegalArgumentException(`The maxWidth must be from 1 to 10 inclusive but was ${maxWidth}`);
|
}
|
if (maxWidth < width) {
|
throw new IllegalArgumentException('The maxWidth must be greater than the width');
|
}
|
if (baseDate === null) {
|
if (field.range().isValidValue(baseValue) === false) {
|
throw new IllegalArgumentException('The base value must be within the range of the field');
|
}
|
if ((baseValue + EXCEED_POINTS[width]) > MathUtil.MAX_SAFE_INTEGER) {
|
throw new DateTimeException('Unable to add printer-parser as the range exceeds the capacity of an int');
|
}
|
}
|
this._baseValue = baseValue;
|
this._baseDate = baseDate;
|
}
|
|
/**
|
*
|
* @param {DateTimePrintContext} context
|
* @param {number} value
|
*/
|
_getValue(context, value) {
|
const absValue = Math.abs(value);
|
let baseValue = this._baseValue;
|
if (this._baseDate !== null) {
|
// TODO: in threetenbp the following line is used, but we dont have Chronology yet,
|
// let chrono = Chronology.from(context.getTemporal());
|
// so let's use IsoChronology for now
|
context.temporal();
|
const chrono = IsoChronology.INSTANCE;
|
baseValue = chrono.date(this._baseDate).get(this._field);
|
}
|
if (value >= baseValue && value < baseValue + EXCEED_POINTS[this._minWidth]) {
|
return absValue % EXCEED_POINTS[this._minWidth];
|
}
|
return absValue % EXCEED_POINTS[this._maxWidth];
|
}
|
|
/**
|
*
|
* @param {DateTimeParseContext} context
|
* @param {number} value
|
* @param {number} errorPos
|
* @param {number} successPos
|
*/
|
_setValue(context, value, errorPos, successPos) {
|
let baseValue = this._baseValue;
|
if (this._baseDate != null) {
|
const chrono = context.getEffectiveChronology();
|
baseValue = chrono.date(this._baseDate).get(this._field);
|
// TODO: not implemented??
|
// context.addChronologyChangedParser(this, value, errorPos, successPos);
|
}
|
const parseLen = successPos - errorPos;
|
if (parseLen === this._minWidth && value >= 0) {
|
const range = EXCEED_POINTS[this._minWidth];
|
const lastPart = baseValue % range;
|
const basePart = baseValue - lastPart;
|
if (baseValue > 0) {
|
value = basePart + value;
|
} else {
|
value = basePart - value;
|
}
|
if (value < baseValue) {
|
value += range;
|
}
|
}
|
return context.setParsedField(this._field, value, errorPos, successPos);
|
}
|
|
withFixedWidth() {
|
if (this._subsequentWidth === -1) {
|
return this;
|
}
|
return new ReducedPrinterParser(this._field, this._minWidth, this._maxWidth, this._baseValue, this._baseDate, -1);
|
}
|
|
/**
|
*
|
* @param {number} subsequentWidth
|
* @returns {ReducedPrinterParser}
|
*/
|
withSubsequentWidth(subsequentWidth) {
|
return new ReducedPrinterParser(this._field, this._minWidth, this._maxWidth, this._baseValue, this._baseDate,
|
this._subsequentWidth + subsequentWidth);
|
}
|
|
/**
|
*
|
* @param {DateTimeParseContext} context
|
*/
|
isFixedWidth(context) {
|
if (context.isStrict() === false) {
|
return false;
|
}
|
return super.isFixedWidth(context);
|
}
|
|
toString() {
|
return `ReducedValue(${this._field},${this._minWidth},${this._maxWidth},${this._baseDate != null ? this._baseDate : this._baseValue})`;
|
}
|
}
|