packages/core/src/format/DateTimeParseContext.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 { assert, requireNonNull } from '../assert';
import { DateTimeBuilder } from './DateTimeBuilder';
import { EnumMap } from './EnumMap';
import { IsoChronology } from '../chrono/IsoChronology';
import { Temporal } from '../temporal/Temporal';
import { TemporalQueries } from '../temporal/TemporalQueries';
/**
* @private
*/
export class DateTimeParseContext{
constructor(){
if(arguments.length === 1){
if(arguments[0] instanceof DateTimeParseContext){
this._constructorSelf.apply(this, arguments);
return;
} else {
this._constructorFormatter.apply(this, arguments);
}
} else {
this._constructorParam.apply(this, arguments);
}
this._caseSensitive = true;
this._strict = true;
this._parsed = [new Parsed(this)];
}
_constructorParam(locale, symbols, chronology){
this._locale = locale;
this._symbols = symbols;
this._overrideChronology = chronology;
}
_constructorFormatter(formatter){
this._locale = formatter.locale();
this._symbols = formatter.decimalStyle();
this._overrideChronology = formatter.chronology();
}
_constructorSelf(other) {
this._locale = other._locale;
this._symbols = other._symbols;
this._overrideChronology = other._overrideChronology;
this._overrideZone = other._overrideZone;
this._caseSensitive = other._caseSensitive;
this._strict = other._strict;
this._parsed = [new Parsed(this)];
}
/**
* Creates a copy of this context.
*/
copy() {
return new DateTimeParseContext(this);
}
symbols(){
return this._symbols;
}
isStrict(){
return this._strict;
}
setStrict(strict){
this._strict = strict;
}
locale() {
return this._locale;
}
setLocale(locale) {
this._locale = locale;
}
//-----------------------------------------------------------------------
/**
* Starts the parsing of an optional segment of the input.
*/
startOptional() {
this._parsed.push(this.currentParsed().copy());
}
/**
* Ends the parsing of an optional segment of the input.
*
* @param {boolean} successful whether the optional segment was successfully parsed
*/
endOptional(successful) {
if (successful) {
this._parsed.splice(this._parsed.length - 2, 1);
} else {
this._parsed.splice(this._parsed.length - 1, 1);
}
}
/**
* Checks if parsing is case sensitive.
*
* @return true if parsing is case sensitive, false if case insensitive
*/
isCaseSensitive() {
return this._caseSensitive;
}
/**
* Sets whether the parsing is case sensitive or not.
*
* @param caseSensitive changes the parsing to be case sensitive or not from now on
*/
setCaseSensitive(caseSensitive) {
this._caseSensitive = caseSensitive;
}
/**
* Helper to compare two {@link CharSequence} instances.
* This uses {@link isCaseSensitive}.
*
* @param cs1 the first character sequence, not null
* @param offset1 the offset into the first sequence, valid
* @param cs2 the second character sequence, not null
* @param offset2 the offset into the second sequence, valid
* @param length the length to check, valid
* @return true if equal
*/
subSequenceEquals(cs1, offset1, cs2, offset2, length) {
if (offset1 + length > cs1.length || offset2 + length > cs2.length) {
return false;
}
if (! this.isCaseSensitive()) {
cs1 = cs1.toLowerCase();
cs2 = cs2.toLowerCase();
}
for (let i = 0; i < length; i++) {
const ch1 = cs1[offset1 + i];
const ch2 = cs2[offset2 + i];
if (ch1 !== ch2) {
return false;
}
}
return true;
}
/**
* Helper to compare two `char`.
* This uses {@link isCaseSensitive}.
*
* @param ch1 the first character
* @param ch2 the second character
* @return true if equal
*/
charEquals(ch1, ch2) {
if (this.isCaseSensitive()) {
return ch1 === ch2;
}
return this.charEqualsIgnoreCase(ch1, ch2);
}
/**
* Compares two characters ignoring case.
*
* @param c1 the first
* @param c2 the second
* @return true if equal
*/
charEqualsIgnoreCase(c1, c2) {
return c1 === c2 ||
c1.toLowerCase() === c2.toLowerCase();
}
setParsedField(field, value, errorPos, successPos){
const currentParsedFieldValues = this.currentParsed().fieldValues;
const old = currentParsedFieldValues.get(field);
currentParsedFieldValues.set(field, value);
return (old != null && old !== value) ? ~errorPos : successPos;
}
/**
* Stores the parsed zone.
*
* This stores the zone that has been parsed.
* No validation is performed other than ensuring it is not null.
*
* @param {ZoneId} zone the parsed zone, not null
*/
setParsedZone(zone) {
requireNonNull(zone, 'zone');
this.currentParsed().zone = zone;
}
getParsed(field) {
return this.currentParsed().fieldValues.get(field);
}
toParsed() {
return this.currentParsed();
}
currentParsed() {
return this._parsed[this._parsed.length - 1];
}
/**
* Stores the leap second.
*/
setParsedLeapSecond() {
this.currentParsed().leapSecond = true;
}
/**
* Gets the effective chronology during parsing.
*
* @return the effective parsing chronology, not null
*/
getEffectiveChronology() {
let chrono = this.currentParsed().chrono;
if (chrono == null) {
chrono = this._overrideChronology;
if (chrono == null) {
chrono = IsoChronology.INSTANCE;
}
}
return chrono;
}
}
class Parsed extends Temporal {
constructor(dateTimeParseContext){
super();
this.chrono = null;
this.zone = null;
this.fieldValues = new EnumMap();
this.leapSecond = false;
this.dateTimeParseContext = dateTimeParseContext;
}
copy() {
const cloned = new Parsed();
cloned.chrono = this.chrono;
cloned.zone = this.zone;
cloned.fieldValues.putAll(this.fieldValues);
cloned.leapSecond = this.leapSecond;
cloned.dateTimeParseContext = this.dateTimeParseContext;
return cloned;
}
toString() {
return `${this.fieldValues}, ${this.chrono}, ${this.zone}`;
}
isSupported(field) {
return this.fieldValues.containsKey(field);
}
get(field) {
const val = this.fieldValues.get(field);
assert(val != null);
return val;
}
query(query) {
if (query === TemporalQueries.chronology()) {
return this.chrono;
}
if (query === TemporalQueries.zoneId() || query === TemporalQueries.zone()) {
return this.zone;
}
return super.query(query);
}
toBuilder() {
const builder = new DateTimeBuilder();
builder.fieldValues.putAll(this.fieldValues);
builder.chrono = this.dateTimeParseContext.getEffectiveChronology();
if (this.zone != null) {
builder.zone = this.zone;
} else {
builder.zone = this.overrideZone;
}
builder.leapSecond = this.leapSecond;
builder.excessDays = this.excessDays;
return builder;
}
}