src/format/DateTimeBuilder.js
/*
* @copyright (c) 2016, Philipp Thuerwaechter & Pattrick Hueper
* @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 {DateTimeException} from '../errors';
import {MathUtil} from '../MathUtil';
import {EnumMap} from './EnumMap';
import {ResolverStyle} from './ResolverStyle';
import {IsoChronology} from '../chrono/IsoChronology';
import {ChronoLocalDate} from '../chrono/ChronoLocalDate';
import {ChronoField} from '../temporal/ChronoField';
import {Temporal} from '../temporal/Temporal';
import {TemporalQueries} from '../temporal/TemporalQueries';
import {LocalTime} from '../LocalTime';
import {LocalDate} from '../LocalDate';
import {Period} from '../Period';
//import {ZoneOffset} from '../ZoneOffset';
/**
* Builder that can holds date and time fields and related date and time objects.
* <p>
* The builder is used to hold onto different elements of date and time.
* It is designed as two separate maps:
* <p><ul>
* <li>from {@link TemporalField} to {@code long} value, where the value may be
* outside the valid range for the field
* <li>from {@code Class} to {@link TemporalAccessor}, holding larger scale objects
* like {@code LocalDateTime}.
* </ul><p>
*
*/
export class DateTimeBuilder extends Temporal {
/**
* Creates a new instance of the builder with a single field-value.
* <p>
* This is equivalent to using {@link #addFieldValue(TemporalField, long)} on an empty builder.
*
* @param {TemporalField} field - the field to add, not null
* @param {number} value - the value to add, not null
* @return {DateTimeBuilder}
*/
static create(field, value) {
var dtb = new DateTimeBuilder();
dtb._addFieldValue(field, value);
return dtb;
}
constructor(){
super();
/**
* The map of other fields.
*/
this.fieldValues = new EnumMap();
/**
* The chronology.
*/
this.chrono = null;
/**
* The zone.
*/
this.zone = null;
/**
* The date.
*/
this.date = null;
/**
* The time.
*/
this.time = null;
/**
* The leap second flag.
*/
this.leapSecond = false;
/**
* The excess days.
*/
this.excessDays = null;
}
/**
*
* @param {TemporalField} field
* @return {Number} field value
*/
getFieldValue0(field) {
return this.fieldValues.get(field);
}
/**
* Adds a field-value pair to the builder.
* <p>
* This adds a field to the builder.
* If the field is not already present, then the field-value pair is added to the map.
* If the field is already present and it has the same value as that specified, no action occurs.
* If the field is already present and it has a different value to that specified, then
* an exception is thrown.
*
* @param {TemporalField} field - the field to add, not null
* @param {Number} value - the value to add, not null
* @return {DateTimeBuilder}, this for method chaining
* @throws DateTimeException if the field is already present with a different value
*/
_addFieldValue(field, value) {
requireNonNull(field, 'field');
var old = this.getFieldValue0(field); // check first for better error message
if (old != null && old.longValue() !== value) {
throw new DateTimeException('Conflict found: ' + field + ' ' + old + ' differs from ' + field + ' ' + value + ': ' + this);
}
return this._putFieldValue0(field, value);
}
/**
* @param {TemporalField} field
* @param {Number} value
* @return {DateTimeBuilder}, this for method chaining
*/
_putFieldValue0(field, value) {
this.fieldValues.put(field, value);
return this;
}
/**
* Resolves the builder, evaluating the date and time.
* <p>
* This examines the contents of the builder and resolves it to produce the best
* available date and time, throwing an exception if a problem occurs.
* Calling this method changes the state of the builder.
*
* @param {ResolverStyle} resolverStyle - how to resolve
* @param {Set<TemporalField>} resolverFields
* @return {DateTimeBuilder} this, for method chaining
*/
resolve(resolverStyle, resolverFields) {
if (resolverFields != null) {
this.fieldValues.retainAll(resolverFields);
}
// handle standard fields
// this._mergeInstantFields();
this._mergeDate(resolverStyle);
this._mergeTime(resolverStyle);
//if (resolveFields(resolverStyle)) {
// mergeInstantFields();
// mergeDate(resolverStyle);
// mergeTime(resolverStyle);
//}
this._resolveTimeInferZeroes(resolverStyle);
//this._crossCheck();
if (this.excessDays != null && this.excessDays.isZero() === false && this.date != null && this.time != null) {
this.date = this.date.plus(this.excessDays);
this.excessDays = Period.ZERO;
}
//resolveFractional();
//resolveInstant();
return this;
}
/**
*
* @param {ResolverStyle} resolverStyle
* @private
*/
_mergeDate(resolverStyle) {
//if (this.chrono instanceof IsoChronology) {
this._checkDate(IsoChronology.INSTANCE.resolveDate(this.fieldValues, resolverStyle));
//} else {
// if (this.fieldValues.containsKey(ChronoField.EPOCH_DAY)) {
// this._checkDate(LocalDate.ofEpochDay(this.fieldValues.remove(ChronoField.EPOCH_DAY)));
// return;
// }
//}
}
/**
*
* @param {LocalDate} date
* @private
*/
_checkDate(date) {
if (date != null) {
this._addObject(date);
for (let field in this.fieldValues.keySet()) {
if (field instanceof ChronoField) {
if (field.isDateBased()) {
var val1;
try {
val1 = date.getLong(field);
} catch (ex) {
if(ex instanceof DateTimeException){
continue;
} else {
throw ex;
}
}
var val2 = this.fieldValues.get(field);
if (val1 !== val2) {
throw new DateTimeException('Conflict found: Field ' + field + ' ' + val1 + ' differs from ' + field + ' ' + val2 + ' derived from ' + date);
}
}
}
}
}
}
/**
*
* @param {ResolverStyle} resolverStyle
* @private
*/
_mergeTime(resolverStyle) {
if (this.fieldValues.containsKey(ChronoField.CLOCK_HOUR_OF_DAY)) {
let ch = this.fieldValues.remove(ChronoField.CLOCK_HOUR_OF_DAY);
if (resolverStyle !== ResolverStyle.LENIENT) {
if (resolverStyle === ResolverStyle.SMART && ch === 0) {
// ok
} else {
ChronoField.CLOCK_HOUR_OF_DAY.checkValidValue(ch);
}
}
this._addFieldValue(ChronoField.HOUR_OF_DAY, ch === 24 ? 0 : ch);
}
if (this.fieldValues.containsKey(ChronoField.CLOCK_HOUR_OF_AMPM)) {
let ch = this.fieldValues.remove(ChronoField.CLOCK_HOUR_OF_AMPM);
if (resolverStyle !== ResolverStyle.LENIENT) {
if (resolverStyle === ResolverStyle.SMART && ch === 0) {
// ok
} else {
ChronoField.CLOCK_HOUR_OF_AMPM.checkValidValue(ch);
}
}
this._addFieldValue(ChronoField.HOUR_OF_AMPM, ch === 12 ? 0 : ch);
}
if (resolverStyle !== ResolverStyle.LENIENT) {
if (this.fieldValues.containsKey(ChronoField.AMPM_OF_DAY)) {
ChronoField.AMPM_OF_DAY.checkValidValue(this.fieldValues.get(ChronoField.AMPM_OF_DAY));
}
if (this.fieldValues.containsKey(ChronoField.HOUR_OF_AMPM)) {
ChronoField.HOUR_OF_AMPM.checkValidValue(this.fieldValues.get(ChronoField.HOUR_OF_AMPM));
}
}
if (this.fieldValues.containsKey(ChronoField.AMPM_OF_DAY) && this.fieldValues.containsKey(ChronoField.HOUR_OF_AMPM)) {
let ap = this.fieldValues.remove(ChronoField.AMPM_OF_DAY);
let hap = this.fieldValues.remove(ChronoField.HOUR_OF_AMPM);
this._addFieldValue(ChronoField.HOUR_OF_DAY, ap * 12 + hap);
}
// if (timeFields.containsKey(HOUR_OF_DAY) && timeFields.containsKey(MINUTE_OF_HOUR)) {
// let hod = timeFields.remove(HOUR_OF_DAY);
// let moh = timeFields.remove(MINUTE_OF_HOUR);
// this._addFieldValue(MINUTE_OF_DAY, hod * 60 + moh);
// }
// if (timeFields.containsKey(MINUTE_OF_DAY) && timeFields.containsKey(SECOND_OF_MINUTE)) {
// let mod = timeFields.remove(MINUTE_OF_DAY);
// let som = timeFields.remove(SECOND_OF_MINUTE);
// this._addFieldValue(SECOND_OF_DAY, mod * 60 + som);
// }
if (this.fieldValues.containsKey(ChronoField.NANO_OF_DAY)) {
let nod = this.fieldValues.remove(ChronoField.NANO_OF_DAY);
if (resolverStyle !== ResolverStyle.LENIENT) {
ChronoField.NANO_OF_DAY.checkValidValue(nod);
}
this._addFieldValue(ChronoField.SECOND_OF_DAY, MathUtil.intDiv(nod, 1000000000));
this._addFieldValue(ChronoField.NANO_OF_SECOND, MathUtil.intMod(nod, 1000000000));
}
if (this.fieldValues.containsKey(ChronoField.MICRO_OF_DAY)) {
let cod = this.fieldValues.remove(ChronoField.MICRO_OF_DAY);
if (resolverStyle !== ResolverStyle.LENIENT) {
ChronoField.MICRO_OF_DAY.checkValidValue(cod);
}
this._addFieldValue(ChronoField.SECOND_OF_DAY, MathUtil.intDiv(cod, 1000000));
this._addFieldValue(ChronoField.MICRO_OF_SECOND, MathUtil.intMod(cod, 1000000));
}
if (this.fieldValues.containsKey(ChronoField.MILLI_OF_DAY)) {
let lod = this.fieldValues.remove(ChronoField.MILLI_OF_DAY);
if (resolverStyle !== ResolverStyle.LENIENT) {
ChronoField.MILLI_OF_DAY.checkValidValue(lod);
}
this._addFieldValue(ChronoField.SECOND_OF_DAY, MathUtil.intDiv(lod, 1000));
this._addFieldValue(ChronoField.MILLI_OF_SECOND, MathUtil.intMod(lod, 1000));
}
if (this.fieldValues.containsKey(ChronoField.SECOND_OF_DAY)) {
let sod = this.fieldValues.remove(ChronoField.SECOND_OF_DAY);
if (resolverStyle !== ResolverStyle.LENIENT) {
ChronoField.SECOND_OF_DAY.checkValidValue(sod);
}
this._addFieldValue(ChronoField.HOUR_OF_DAY, MathUtil.intDiv(sod, 3600));
this._addFieldValue(ChronoField.MINUTE_OF_HOUR, MathUtil.intMod(MathUtil.intDiv(sod, 60), 60));
this._addFieldValue(ChronoField.SECOND_OF_MINUTE, MathUtil.intMod(sod, 60));
}
if (this.fieldValues.containsKey(ChronoField.MINUTE_OF_DAY)) {
let mod = this.fieldValues.remove(ChronoField.MINUTE_OF_DAY);
if (resolverStyle !== ResolverStyle.LENIENT) {
ChronoField.MINUTE_OF_DAY.checkValidValue(mod);
}
this._addFieldValue(ChronoField.HOUR_OF_DAY, MathUtil.intDiv(mod, 60));
this._addFieldValue(ChronoField.MINUTE_OF_HOUR, MathUtil.intMod(mod, 60));
}
// let sod = MathUtil.intDiv(nod, 1000000000L);
// this._addFieldValue(HOUR_OF_DAY, MathUtil.intDiv(sod, 3600));
// this._addFieldValue(MINUTE_OF_HOUR, MathUtil.intMod(MathUtil.intDiv(sod, 60), 60));
// this._addFieldValue(SECOND_OF_MINUTE, MathUtil.intMod(sod, 60));
// this._addFieldValue(NANO_OF_SECOND, MathUtil.intMod(nod, 1000000000L));
if (resolverStyle !== ResolverStyle.LENIENT) {
if (this.fieldValues.containsKey(ChronoField.MILLI_OF_SECOND)) {
ChronoField.MILLI_OF_SECOND.checkValidValue(this.fieldValues.get(ChronoField.MILLI_OF_SECOND));
}
if (this.fieldValues.containsKey(ChronoField.MICRO_OF_SECOND)) {
ChronoField.MICRO_OF_SECOND.checkValidValue(this.fieldValues.get(ChronoField.MICRO_OF_SECOND));
}
}
if (this.fieldValues.containsKey(ChronoField.MILLI_OF_SECOND) && this.fieldValues.containsKey(ChronoField.MICRO_OF_SECOND)) {
let los = this.fieldValues.remove(ChronoField.MILLI_OF_SECOND);
let cos = this.fieldValues.get(ChronoField.MICRO_OF_SECOND);
this._addFieldValue(ChronoField.MICRO_OF_SECOND, los * 1000 + (MathUtil.intMod(cos, 1000)));
}
if (this.fieldValues.containsKey(ChronoField.MICRO_OF_SECOND) && this.fieldValues.containsKey(ChronoField.NANO_OF_SECOND)) {
let nos = this.fieldValues.get(ChronoField.NANO_OF_SECOND);
this._addFieldValue(ChronoField.MICRO_OF_SECOND, MathUtil.intDiv(nos, 1000));
this.fieldValues.remove(ChronoField.MICRO_OF_SECOND);
}
if (this.fieldValues.containsKey(ChronoField.MILLI_OF_SECOND) && this.fieldValues.containsKey(ChronoField.NANO_OF_SECOND)) {
let nos = this.fieldValues.get(ChronoField.NANO_OF_SECOND);
this._addFieldValue(ChronoField.MILLI_OF_SECOND, MathUtil.intDiv(nos, 1000000));
this.fieldValues.remove(ChronoField.MILLI_OF_SECOND);
}
if (this.fieldValues.containsKey(ChronoField.MICRO_OF_SECOND)) {
let cos = this.fieldValues.remove(ChronoField.MICRO_OF_SECOND);
this._addFieldValue(ChronoField.NANO_OF_SECOND, cos * 1000);
} else if (this.fieldValues.containsKey(ChronoField.MILLI_OF_SECOND)) {
let los = this.fieldValues.remove(ChronoField.MILLI_OF_SECOND);
this._addFieldValue(ChronoField.NANO_OF_SECOND, los * 1000000);
}
}
/**
*
* @param {ResolverStyle} resolverStyle
* @private
*/
_resolveTimeInferZeroes(resolverStyle) {
let hod = this.fieldValues.get(ChronoField.HOUR_OF_DAY);
let moh = this.fieldValues.get(ChronoField.MINUTE_OF_HOUR);
let som = this.fieldValues.get(ChronoField.SECOND_OF_MINUTE);
let nos = this.fieldValues.get(ChronoField.NANO_OF_SECOND);
if (hod == null) {
return;
}
if (moh == null && (som != null || nos != null)) {
return;
}
if (moh != null && som == null && nos != null) {
return;
}
if (resolverStyle !== ResolverStyle.LENIENT) {
if (hod != null) {
if (resolverStyle === ResolverStyle.SMART &&
hod === 24 &&
(moh == null || moh === 0) &&
(som == null || som === 0) &&
(nos == null || nos === 0)) {
hod = 0;
this.excessDays = Period.ofDays(1);
}
let hodVal = ChronoField.HOUR_OF_DAY.checkValidIntValue(hod);
if (moh != null) {
let mohVal = ChronoField.MINUTE_OF_HOUR.checkValidIntValue(moh);
if (som != null) {
let somVal = ChronoField.SECOND_OF_MINUTE.checkValidIntValue(som);
if (nos != null) {
let nosVal = ChronoField.NANO_OF_SECOND.checkValidIntValue(nos);
this._addObject(LocalTime.of(hodVal, mohVal, somVal, nosVal));
} else {
this._addObject(LocalTime.of(hodVal, mohVal, somVal));
}
} else {
if (nos == null) {
this._addObject(LocalTime.of(hodVal, mohVal));
}
}
} else {
if (som == null && nos == null) {
this._addObject(LocalTime.of(hodVal, 0));
}
}
}
} else {
if (hod != null) {
let hodVal = hod;
if (moh != null) {
if (som != null) {
if (nos == null) {
nos = 0;
}
let totalNanos = MathUtil.safeMultiply(hodVal, 3600000000000);
totalNanos = MathUtil.safeAdd(totalNanos, MathUtil.safeMultiply(moh, 60000000000));
totalNanos = MathUtil.safeAdd(totalNanos, MathUtil.safeMultiply(som, 1000000000));
totalNanos = MathUtil.safeAdd(totalNanos, nos);
let excessDays = MathUtil.floorDiv(totalNanos, 86400000000000); // safe int cast
let nod = MathUtil.floorMod(totalNanos, 86400000000000);
this._addObject(LocalTime.ofNanoOfDay(nod));
this.excessDays = Period.ofDays(excessDays);
} else {
let totalSecs = MathUtil.safeMultiply(hodVal, 3600);
totalSecs = MathUtil.safeAdd(totalSecs, MathUtil.safeMultiply(moh, 60));
let excessDays = MathUtil.floorDiv(totalSecs, 86400); // safe int cast
let sod = MathUtil.floorMod(totalSecs, 86400);
this._addObject(LocalTime.ofSecondOfDay(sod));
this.excessDays = Period.ofDays(excessDays);
}
} else {
let excessDays = MathUtil.safeToInt(MathUtil.floorDiv(hodVal, 24));
hodVal = MathUtil.floorMod(hodVal, 24);
this._addObject(LocalTime.of(hodVal, 0));
this.excessDays = Period.ofDays(excessDays);
}
}
}
this.fieldValues.remove(ChronoField.HOUR_OF_DAY);
this.fieldValues.remove(ChronoField.MINUTE_OF_HOUR);
this.fieldValues.remove(ChronoField.SECOND_OF_MINUTE);
this.fieldValues.remove(ChronoField.NANO_OF_SECOND);
}
/**
*
* @param {ChronoLocalDate|LocalTime} dateOrTime
* @private
*/
_addObject(dateOrTime) {
if (dateOrTime instanceof ChronoLocalDate){
this.date = dateOrTime;
} else if (dateOrTime instanceof LocalTime){
this.time = dateOrTime;
}
}
/**
* Builds the specified type from the values in this builder.
*
* This attempts to build the specified type from this builder.
* If the builder cannot return the type, an exception is thrown.
*
* @param {!TemporalQuery} type - the type to invoke {@code from} on, not null
* @return {*} the extracted value, not null
* @throws DateTimeException if an error occurs
*/
build(type) {
return type.queryFrom(this);
}
/**
*
* @param {TemporalField} field
* @returns {number}
*/
isSupported(field) {
if (field == null) {
return false;
}
return this.fieldValues.containsKey(field) ||
(this.date != null && this.date.isSupported(field)) ||
(this.time != null && this.time.isSupported(field));
}
/**
*
* @param {TemporalField} field
* @returns {number}
*/
getLong(field) {
requireNonNull(field, 'field');
var value = this.getFieldValue0(field);
if (value == null) {
if (this.date != null && this.date.isSupported(field)) {
return this.date.getLong(field);
}
if (this.time != null && this.time.isSupported(field)) {
return this.time.getLong(field);
}
throw new DateTimeException('Field not found: ' + field);
}
return value;
}
/**
*
* @param {!TemporalQuery} query
* @returns {*}
*/
query(query) {
if (query === TemporalQueries.zoneId()) {
return this.zone;
} else if (query === TemporalQueries.chronology()) {
return this.chrono;
} else if (query === TemporalQueries.localDate()) {
return this.date != null ? LocalDate.from(this.date) : null;
} else if (query === TemporalQueries.localTime()) {
return this.time;
} else if (query === TemporalQueries.zone() || query === TemporalQueries.offset()) {
return query.queryFrom(this);
} else if (query === TemporalQueries.precision()) {
return null; // not a complete date/time
}
// inline TemporalAccessor.super.query(query) as an optimization
// non-JDK classes are not permitted to make this optimization
return query.queryFrom(this);
}
}