packages/core/src/format/parser/OffsetIdPrinterParser.js
/**
* @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';
import { ChronoField } from '../../temporal/ChronoField';
//-----------------------------------------------------------------------
const PATTERNS = [
'+HH', '+HHmm', '+HH:mm', '+HHMM', '+HH:MM', '+HHMMss', '+HH:MM:ss', '+HHMMSS', '+HH:MM:SS'
];
/**
* Prints or parses an offset ID.
* @private
*/
export class OffsetIdPrinterParser {
/**
* Constructor.
*
* @param {string} noOffsetText the text to use for UTC, not null
* @param {string} pattern the pattern
*/
constructor(noOffsetText, pattern) {
requireNonNull(noOffsetText, 'noOffsetText');
requireNonNull(pattern, 'pattern');
this.noOffsetText = noOffsetText;
this.type = this._checkPattern(pattern);
}
/**
* @param {String} pattern
* @return {number}
*/
_checkPattern(pattern) {
for (let i = 0; i < PATTERNS.length; i++) {
if (PATTERNS[i] === pattern) {
return i;
}
}
throw new IllegalArgumentException(`Invalid zone offset pattern: ${pattern}`);
}
/**
* @param {DateTimePrintContext} context
* @param {StringBuilder} buf
* @return {boolean}
*/
print(context, buf) {
const offsetSecs = context.getValue(ChronoField.OFFSET_SECONDS);
if (offsetSecs == null) {
return false;
}
const totalSecs = MathUtil.safeToInt(offsetSecs);
if (totalSecs === 0) {
buf.append(this.noOffsetText);
} else {
const absHours = Math.abs(MathUtil.intMod(MathUtil.intDiv(totalSecs, 3600), 100)); // anything larger than 99 silently dropped
const absMinutes = Math.abs(MathUtil.intMod(MathUtil.intDiv(totalSecs, 60), 60));
const absSeconds = Math.abs(MathUtil.intMod(totalSecs, 60));
const bufPos = buf.length();
let output = absHours;
buf.append(totalSecs < 0 ? '-' : '+')
.appendChar((`${MathUtil.intDiv(absHours, 10)}0`)).appendChar(`${MathUtil.intMod(absHours, 10)}0`);
if (this.type >= 3 || (this.type >= 1 && absMinutes > 0)) {
buf.append((this.type % 2) === 0 ? ':' : '')
.appendChar((`${MathUtil.intDiv(absMinutes, 10)}0`)).appendChar((`${absMinutes % 10}0`));
output += absMinutes;
if (this.type >= 7 || (this.type >= 5 && absSeconds > 0)) {
buf.append((this.type % 2) === 0 ? ':' : '')
.appendChar((`${MathUtil.intDiv(absSeconds, 10)}0`)).appendChar((`${absSeconds % 10}0`));
output += absSeconds;
}
}
if (output === 0) {
buf.setLength(bufPos);
buf.append(this.noOffsetText);
}
}
return true;
}
/**
* @param {DateTimeParseContext} context
* @param {String} text
* @param {number} position
* @return {number}
*/
parse(context, text, position) {
const length = text.length;
const noOffsetLen = this.noOffsetText.length;
if (noOffsetLen === 0) {
if (position === length) {
return context.setParsedField(ChronoField.OFFSET_SECONDS, 0, position, position);
}
} else {
if (position === length) {
return ~position;
}
if (context.subSequenceEquals(text, position, this.noOffsetText, 0, noOffsetLen)) {
return context.setParsedField(ChronoField.OFFSET_SECONDS, 0, position, position + noOffsetLen);
}
}
// parse normal plus/minus offset
const sign = text[position]; // IOOBE if invalid position
if (sign === '+' || sign === '-') {
// starts
const negative = (sign === '-' ? -1 : 1);
const array = [0,0,0,0];
array[0] = position + 1;
if ((this._parseNumber(array, 1, text, true) ||
this._parseNumber(array, 2, text, this.type >=3) ||
this._parseNumber(array, 3, text, false)) === false) {
// success
const offsetSecs = MathUtil.safeZero(negative * (array[1] * 3600 + array[2] * 60 + array[3]));
return context.setParsedField(ChronoField.OFFSET_SECONDS, offsetSecs, position, array[0]);
}
}
// handle special case of empty no offset text
if (noOffsetLen === 0) {
return context.setParsedField(ChronoField.OFFSET_SECONDS, 0, position, position + noOffsetLen);
}
return ~position;
}
/**
* Parse a two digit zero-prefixed number.
*
* @param {number[]} array the array of parsed data, 0=pos,1=hours,2=mins,3=secs, not null
* @param {number} arrayIndex the index to parse the value into
* @param {string} parseText the offset ID, not null
* @param {boolean} required whether this number is required
* @return {boolean} true if an error occurred
*/
_parseNumber(array, arrayIndex, parseText, required) {
if ((this.type + 3) / 2 < arrayIndex) {
return false; // ignore seconds/minutes
}
let pos = array[0];
if ((this.type % 2) === 0 && arrayIndex > 1) {
if (pos + 1 > parseText.length || parseText[pos] !== ':') {
return required;
}
pos++;
}
if (pos + 2 > parseText.length) {
return required;
}
const ch1 = parseText[pos++];
const ch2 = parseText[pos++];
if (ch1 < '0' || ch1 > '9' || ch2 < '0' || ch2 > '9') {
return required;
}
const value = (ch1.charCodeAt(0) - 48) * 10 + (ch2.charCodeAt(0) - 48);
if (value < 0 || value > 59) {
return required;
}
array[arrayIndex] = value;
array[0] = pos;
return false;
}
toString() {
const converted = this.noOffsetText.replace('\'', '\'\'');
return `Offset(${PATTERNS[this.type]},'${converted}')`;
}
}
OffsetIdPrinterParser.INSTANCE_ID = new OffsetIdPrinterParser('Z', '+HH:MM:ss');
OffsetIdPrinterParser.PATTERNS = PATTERNS;