Package javax.time.calendar

Source Code of javax.time.calendar.Calendrical$FieldMap

/*
* Copyright (c) 2008-2009, Stephen Colebourne & Michael Nascimento Santos
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*  * Redistributions of source code must retain the above copyright notice,
*    this list of conditions and the following disclaimer.
*
*  * Redistributions in binary form must reproduce the above copyright notice,
*    this list of conditions and the following disclaimer in the documentation
*    and/or other materials provided with the distribution.
*
*  * Neither the name of JSR-310 nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package javax.time.calendar;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;

import javax.time.CalendricalException;

/**
* A flexible representation of calendrical information which may or may not be valid.
* <p>
* Dates and times often need to be processed even when they are not fully valid.
* <code>Calendrical</code> permits that processing to occur, however the cost is
* a complicated state model. <b>Use this class with care.</b>
* <p>
* Instances of calendrical hold any combination of date and time information.
* The five pieces of state are date, time, offset, zone and a field-value map.
* Each piece of state is optional, allowing the calendrical to contain incomplete information.
* <p>
* The state of a calendrical can conflict itself.
* For example, the date might be '2007-12-03' while the field-value map holds the
* month as February. This can be checked using {@link #checkConsistent()}.
* Values that can be derived from other values, such as MonthOfYear from date, can be
* removed using {@link #removeDerivable()}.
* <p>
* The state in the field-value map can also be merged according to rules.
* For example, parsing might read in 'HourOfAmPm=9', 'AmPm=PM' and 'MinuteOfHour=25'.
* Using the {@link #mergeStrict()} and related methods these different fields can
* be merged into a <code>LocalTime</code>.
* <p>
* Virtually any two parts of the state can conflict.
* For example, the offset might be '+01:00' while the time zone is 'Asia/Tokyo'.
* This is invalid as the offset '+01:00' is never valid for Tokyo.
* This can be checked manually if desired before calling {@link #toZonedDateTime()}.
* <p>
* Finally, the field-value map may contain values that are outside the
* normal range for the field. For example, a day of month of -3 or an hour of 1000.
* The {@link #mergeLenient()} method can be used to interpret these values.
* <p>
* Each method is documented to explain how it handles the possible conflicting cases.
* A key method is {@link #merge(CalendricalContext)} which takes all the available
* data and attempts to merge it together into meaningful information.
* <p>
* Calendrical is mutable and cannot be shared safely between threads without
* external synchronization.
*
* @author Michael Nascimento Santos
* @author Stephen Colebourne
*/
public final class Calendrical
        implements CalendricalProvider, Cloneable, Serializable {

    /** Serialization version. */
    private static final long serialVersionUID = 273575873876986L;

    /**
     * The field map, never null.
     */
    private FieldMap fieldMap;
    /**
     * The date, may be null.
     */
    private LocalDate date;
    /**
     * The time, may be null.
     */
    private LocalTime time;
    /**
     * The offset, may be null.
     */
    private ZoneOffset offset;
    /**
     * The zone, may be null.
     */
    private TimeZone zone;

    /**
     * Constructor creating an empty instance which places no restrictions
     * on the date-time.
     */
    public Calendrical() {
        fieldMap = new FieldMap();
    }

    /**
     * Constructor creating a calendrical from a field-value pair.
     * <p>
     * A calendrical can hold state that is not a valid date-time.
     * Thus, this constructor does not check to see if the value is valid for the field.
     * For example, you could setup a calendrical with a day of month of 75.
     * <p>
     * On completion, the created calendrical will contain a field-value map
     * with one mapping. The date, time, offset and zone will be null.
     *
     * @param fieldRule  the rule, not null
     * @param value  the field value, may be invalid
     * @throws NullPointerException if the field is null
     */
    public Calendrical(DateTimeFieldRule fieldRule, int value) {
        this();
        fieldMap.put(fieldRule, value);
    }

    /**
     * Constructor creating a calendrical from two field-value pairs.
     * <p>
     * A calendrical can hold state that is not a valid date-time.
     * Thus, this constructor does not check to see if the value is valid for the field.
     * For example, you could setup a calendrical with a day of month of 75.
     * <p>
     * On completion, the created calendrical will contain a field-value map
     * with two mappings. The date, time, offset and zone will be null.
     *
     * @param fieldRule1  the first rule, not null
     * @param value1  the first field value
     * @param fieldRule2  the second rule, not null
     * @param value2  the second field value
     * @throws NullPointerException if either field is null
     */
    public Calendrical(DateTimeFieldRule fieldRule1, int value1, DateTimeFieldRule fieldRule2, int value2) {
        this();
        fieldMap.put(fieldRule1, value1);
        fieldMap.put(fieldRule2, value2);
    }

    /**
     * Constructor creating a calendrical from the four main date-time objects.
     * <p>
     * A calendrical can hold state that is not a valid date-time.
     * Thus, this constructor does not cross reference the date or time with the offset or zone.
     * For example, the zone could be set to 'America/New_York' and the offset could be
     * set to '+01:00', even though that is never a valid offset for the New York zone.
     * <p>
     * On completion, the created calendrical will contain an empty field-value map.
     * The date, time, offset and zone will be populated based on the parameters specified.
     *
     * @param date  the optional local date, such as '2007-12-03', may be null
     * @param time  the optional local time, such as '10:15:30', may be null
     * @param offset  the optional time zone offset, such as '+01:00', may be null
     * @param zone  the optional time zone, such as 'Europe/Paris', may be null
     */
    public Calendrical(LocalDate date, LocalTime time, ZoneOffset offset, TimeZone zone) {
        this();
        this.date = date;
        this.time = time;
        this.offset = offset;
        this.zone = zone;
    }

    /**
     * Constructor creating a calendrical from a set of fields.
     * <p>
     * On completion, the created calendrical will contain a field-value map with
     * mapping copied from the input object. The date, time, offset and zone will be null.
     *
     * @param fields  the fields to copy, not null
     */
    public Calendrical(DateTimeFields fields) {
        fieldMap = new FieldMap(fields);
    }

    //-----------------------------------------------------------------------
    /**
     * Gets the field-value map.
     * <p>
     * The field map is part of the state of this calendrical.
     * Changing the returned map changes the state of this calendrical but does
     * not affect any other state
     *
     * @return the connected field value map, never null
     */
    public FieldMap getFieldMap() {
        return fieldMap;
    }

    //-----------------------------------------------------------------------
    /**
     * Gets the date, such as '2007-12-03'.
     * <p>
     * The date is part of the state of this calendrical.
     * The date and the field-value map may have conflicting values.
     *
     * @return the date, may be null
     */
    public LocalDate getDate() {
        return date;
    }

    /**
     * Sets the date, such as '2007-12-03'.
     * <p>
     * The date is part of the state of this calendrical.
     * Changing the date does not affect any other part of the state.
     * The date and the field-value map may have conflicting values.
     *
     * @param date  the date, may be null
     */
    public void setDate(LocalDate date) {
        this.date = date;
    }

    //-----------------------------------------------------------------------
    /**
     * Gets the time, such as '10:15:30'.
     * <p>
     * The time is part of the state of this calendrical.
     * The time and the field-value map may have conflicting values.
     *
     * @return the time, may be null
     */
    public LocalTime getTime() {
        return time;
    }

    /**
     * Sets the time, such as '10:15:30'.
     * <p>
     * The time is part of the state of this calendrical.
     * Changing the time does not affect any other part of the state.
     * The time and the field-value map may have conflicting values.
     *
     * @param time  the time, may be null
     */
    public void setTime(LocalTime time) {
        this.time = time;
    }

    //-----------------------------------------------------------------------
    /**
     * Gets the time zone offset, such as '+01:00'.
     * <p>
     * The offset is part of the state of this calendrical.
     * The zone offset and time zone may have conflicting values.
     *
     * @return the offset, may be null
     */
    public ZoneOffset getOffset() {
        return offset;
    }

    /**
     * Sets the time zone offset, such as '+01:00'.
     * <p>
     * The time is part of the state of this calendrical.
     * Changing the offset does not affect any other part of the state.
     * The zone offset and time zone may have conflicting values.
     *
     * @param offset  the offset, may be null
     */
    public void setOffset(ZoneOffset offset) {
        this.offset = offset;
    }

    //-----------------------------------------------------------------------
    /**
     * Gets the time zone, such as 'Europe/Paris'.
     * <p>
     * The zone is part of the state of this calendrical.
     * The time zone and zone offset may have conflicting values.
     *
     * @return the zone, may be null
     */
    public TimeZone getZone() {
        return zone;
    }

    /**
     * Sets the time zone, such as 'Europe/Paris'.
     * <p>
     * The zone is part of the state of this calendrical.
     * Changing the zone does not affect any other part of the state.
     * The time zone and zone offset may have conflicting values.
     *
     * @param zone  the zone, may be null
     */
    public void setZone(TimeZone zone) {
        this.zone = zone;
    }

    //-----------------------------------------------------------------------
    /**
     * Checks if the specified field can be derived.
     * <p>
     * This method checks if the field can be derived from the date, time or
     * field-value map. No check is performed on the validity of the value.
     *
     * @param fieldRule  the rule to query from the map, null returns false
     * @return true if a value can be derived for the field
     */
    public boolean isDerivable(DateTimeFieldRule fieldRule) {
        return (deriveValueQuiet(fieldRule) != null);
    }

    /**
     * Derives the value for the specified field from the date, time or field-value
     * map throwing an exception if the field is not present or is invalid.
     * <p>
     * The value will be derived first from the date and/or time.
     * If that does not succeed, then the field-value map will be queried using
     * {@link FieldMap#deriveValue}.
     * Thus, if the date/time and field-value map are inconsistent, the value
     * from the date/time takes precedence.
     * <p>
     * A calendrical can hold invalid values, such as a day of month of -3 or an hour of 1000.
     * This method performs no validation on the returned value.
     *
     * @param fieldRule  the rule to query from the map, not null
     * @return the value mapped to the specified field
     * @throws UnsupportedCalendarFieldException if the field is not in the map
     */
    public int deriveValue(DateTimeFieldRule fieldRule) {
        ISOChronology.checkNotNull(fieldRule, "DateTimeFieldRule must not be null");
        Integer value = deriveValueQuiet(fieldRule);
        if (value == null) {
            throw new UnsupportedCalendarFieldException(fieldRule, "Calendrical");
        }
        return value;
    }

    /**
     * Derives the value for the specified field from the date, time or field-value
     * map quietly returning null if the field is not present.
     * <p>
     * The value will be derived first from the date and/or time.
     * If that does not succeed, then the field-value map will be queried using
     * {@link FieldMap#deriveValue}.
     * Thus, if the date/time and field-value map are inconsistent, the value
     * from the date/time takes precedence.
     * <p>
     * A calendrical can hold invalid values, such as a day of month of -3 or an hour of 1000.
     * This method performs no validation on the returned value.
     *
     * @param fieldRule  the rule to query from the map, null returns null
     * @return the value for the specified field, null if value not present
     */
    public Integer deriveValueQuiet(DateTimeFieldRule fieldRule) {
        if (fieldRule == null) {
            return null;
        }
        Integer value = fieldRule.getValueQuiet(date, time);
        return (value == null ? fieldMap.deriveValueQuiet(fieldRule) : value);
    }

    //-----------------------------------------------------------------------
    /**
     * Checks this calendrical for consistency.
     * <p>
     * This method ensures that the each value in the field-value map does not
     * conflict with the date, time or any other value in field-value map.
     * <p>
     * For example, if the date was '2007-12-03' and the field-value map contained
     * a mapping of 'MonthOfYear=November', then the state would be inconsistent
     * and an exception would be thrown. If the mapping was 'MonthOfYear=December'
     * then the state would be consistent and the method would return normally.
     *
     * @throws InvalidCalendarFieldException if any field is inconsistent
     */
    public void checkConsistent() {
        if (date != null || time != null) {
            Object errorValue = null;
            String errorText = null;
            if (date != null & time != null) {
                errorValue = LocalDateTime.dateTime(date, time);
                errorText = "date-time ";
            } else if (date != null) {
                errorValue = date;
                errorText = "date ";
            } else {
                errorValue = time;
                errorText = "time ";
            }
            Iterator<Entry<DateTimeFieldRule, Integer>> it = fieldMap.fieldValueMap.entrySet().iterator();
            while (it.hasNext()) {
                Entry<DateTimeFieldRule, Integer> entry = it.next();
                DateTimeFieldRule fieldRule = entry.getKey();
                Integer derivedValue = fieldRule.getValueQuiet(date, time);
                if (derivedValue != null) {
                    Integer mapValue = entry.getValue();
                    if (derivedValue.equals(mapValue) == false) {
                        throw new InvalidCalendarFieldException("Calendrical contains field map value " +
                                fieldRule.getID() + "=" + mapValue + " that is inconsistent with " +
                                errorText + errorValue, fieldRule);

//                        throw new InvalidCalendarFieldException("Calendrical contains a " + errorText +
//                                errorValue + " that is inconsistent with the value " + mapValue +
//                                " for " + fieldRule.getID(), fieldRule);
                    }
                }
            }
        }
        fieldMap.checkConsistent();
    }

    /**
     * Removes any field from the map that can be derived from the date, time
     * or from another field without checking if the removed value matches.
     * <p>
     * For example, if the field-value map contains 'MonthOfYear' and the date is
     * non-null, then the month will be removed from the map as it can be derived
     * from the date.
     * <p>
     * No check is performed to see if the derived value would be the same as the
     * removed value. See {@link #checkConsistent()}.
     */
    public void removeDerivable() {
        if (date != null || time != null) {
            Iterator<DateTimeFieldRule> it = fieldMap.fieldValueMap.keySet().iterator();
            while (it.hasNext()) {
                Integer derivedValue = it.next().getValueQuiet(date, time);
                if (derivedValue != null) {
                    it.remove();
                }
            }
        }
        fieldMap.removeDerivable();
    }

//    /**
//     * Checks this calendrical for consistency, removing any field that can be
//     * derived from date, time or field-value map.
//     */
//    public void checkAndRemoveDerivable() {
//        // date-time
//        Object errorValue = null;
//        String errorText = null;
//        if (date != null & time != null) {
//            errorValue = LocalDateTime.dateTime(date, time);
//            errorText = "date-time ";
//        } else if (date != null) {
//            errorValue = date;
//            errorText = "date ";
//        } else if (time != null) {
//            errorValue = time;
//            errorText = "time ";
//        }
//        if (errorValue != null) {
//            Iterator<Entry<DateTimeFieldRule, Integer>> it = fieldMap.fieldValueMap.entrySet().iterator();
//            while (it.hasNext()) {
//                Entry<DateTimeFieldRule, Integer> entry = it.next();
//                DateTimeFieldRule fieldRule = entry.getKey();
//                Integer mapValue = fieldRule.getValueQuiet(date, time);
//                if (mapValue != null) {
//                    Integer originalValue = entry.getValue();
//                    if (mapValue.equals(originalValue)) {
//                        it.remove();
//                    } else {
//                        throw new InvalidCalendarFieldException("Calendrical contained a " + errorText +
//                                errorValue + " that is inconsistent with the value " + originalValue +
//                                " for " + fieldRule.getID(), fieldRule);
//                    }
//                }
//            }
//        }
//        // fields
//        Iterator<Entry<DateTimeFieldRule, Integer>> it = fieldMap.fieldValueMap.entrySet().iterator();
//        while (it.hasNext()) {
//            Entry<DateTimeFieldRule, Integer> entry = it.next();
//            DateTimeFieldRule fieldRule = entry.getKey();
//            Integer mergedValue = fieldRule.deriveValue(fieldMap);
//            if (mergedValue != null) {
//                Integer originalValue = entry.getValue();
//                if (mergedValue.equals(originalValue)) {
//                    it.remove();
//                } else {
//                    throw new InvalidCalendarFieldException("Calendrical contained a value " +
//                            mergedValue + " that is inconsistent with the input value " + originalValue +
//                            " for " + fieldRule.getID(), fieldRule);
//                }
//            }
//        }
//    }

    //-----------------------------------------------------------------------
    /**
     * Merges the fields in this map to form a calendrical.
     * <p>
     * The merge process aims to extract the maximum amount of information
     * possible from this set of fields. Ideally the outcome will be a date, time
     * or both, however there may be insufficient information to achieve this.
     * <p>
     * The process repeatedly calls the field rule {@link DateTimeFieldRule#mergeFields merge fields}
     * and {@link DateTimeFieldRule#mergeDateTime merge date time} methods to perform
     * the merge on each individual field.
     * Sometimes two or more fields will combine to form a more significant field.
     * Sometimes they will combine to form a date or time.
     * The process stops when there no more merges can occur.
     * <p>
     * The process is based around hierarchies that can be combined.
     * For example, QuarterOfYear and MonthOfQuarter can be combined to form MonthOfYear.
     * Then, MonthOfYear can be combined with DayOfMonth and Year to form a date.
     * Any fields which take part in a merge will be removed from the result as their
     * values can be derived from the merged field.
     * <p>
     * The exact definition of which fields combine with which is chronology dependent.
     * For example, see {@link ISOChronology}.
     * <p>
     * This method uses strict merging. This means that each value in the field-value map
     * must be completely valid. For example, field-value mappings representing
     * 'MonthOfYear=June' and 'DayOfMonth=32' are invalid in combination and will throw an
     * exception. See {@link #mergeLenient()} for alternate behavior.
     * <p>
     * The merge must result in consistent values for each field, date and time.
     * If two different values are produced an exception is thrown.
     * For example, both Year/MonthOfYear/DayOfMonth and Year/DayOfYear will merge to form a date.
     * If both sets of fields do not produce the same date then an exception will be thrown.
     *
     * @return the new instance, with merged fields, never null
     * @throws CalendricalException if the fields cannot be merged
     */
    public Calendrical mergeStrict() {
        return merge(new CalendricalContext(true, true));
    }

    //-----------------------------------------------------------------------
    /**
     * Merges the fields in this map to form a calendrical.
     * <p>
     * The merge process aims to extract the maximum amount of information
     * possible from this set of fields. Ideally the outcome will be a date, time
     * or both, however there may be insufficient information to achieve this.
     * <p>
     * The process repeatedly calls the field rule {@link DateTimeFieldRule#mergeFields merge fields}
     * and {@link DateTimeFieldRule#mergeDateTime merge date time} methods to perform
     * the merge on each individual field.
     * Sometimes two or more fields will combine to form a more significant field.
     * Sometimes they will combine to form a date or time.
     * The process stops when there no more merges can occur.
     * <p>
     * The process is based around hierarchies that can be combined.
     * For example, QuarterOfYear and MonthOfQuarter can be combined to form MonthOfYear.
     * Then, MonthOfYear can be combined with DayOfMonth and Year to form a date.
     * Any fields which take part in a merge will be removed from the result as their
     * values can be derived from the merged field.
     * <p>
     * The exact definition of which fields combine with which is chronology dependent.
     * For example, see {@link ISOChronology}.
     * <p>
     * This method uses lenient merging. This approach allows invalid combinations to be
     * interpreted. For example, field-value mappings representing 'MonthOfYear=June' and
     * 'DayOfMonth=32' would normally be invalid in combination, however lenient merging
     * will adjust this to be 'July 1st'. See {@link #mergeStrict()} for alternate behavior.
     * <p>
     * The merge must result in consistent values for each field, date and time.
     * If two different values are produced an exception is thrown.
     * For example, both Year/MonthOfYear/DayOfMonth and Year/DayOfYear will merge to form a date.
     * If both sets of fields do not produce the same date then an exception will be thrown.
     *
     * @return the new instance, with merged fields, never null
     * @throws CalendricalException if the fields cannot be merged
     */
    public Calendrical mergeLenient() {
        return merge(new CalendricalContext(false, true));
    }

    /**
     * Merges the fields in this map to form a calendrical using the specified context.
     * <p>
     * The merge process aims to extract the maximum amount of information
     * possible from this set of fields. Ideally the outcome will be a date, time
     * or both, however there may be insufficient information to achieve this.
     * <p>
     * The process repeatedly calls the field rule {@link DateTimeFieldRule#mergeFields merge fields}
     * and {@link DateTimeFieldRule#mergeDateTime merge date time} methods to perform
     * the merge on each individual field.
     * Sometimes two or more fields will combine to form a more significant field.
     * Sometimes they will combine to form a date or time.
     * The process stops when there no more merges can occur.
     * <p>
     * The process is based around hierarchies that can be combined.
     * For example, QuarterOfYear and MonthOfQuarter can be combined to form MonthOfYear.
     * Then, MonthOfYear can be combined with DayOfMonth and Year to form a date.
     * Any fields which take part in a merge will be removed from the result as their
     * values can be derived from the merged field.
     * <p>
     * The exact definition of which fields combine with which is chronology dependent.
     * For example, see {@link ISOChronology}.
     * <p>
     * The details of the process are controlled by the merge context.
     * This includes strict/lenient behavior.
     * <p>
     * The merge must result in consistent values for each field, date and time.
     * If two different values are produced an exception is thrown.
     * For example, both Year/MonthOfYear/DayOfMonth and Year/DayOfYear will merge to form a date.
     * If both sets of fields do not produce the same date then an exception will be thrown.
     *
     * @param context  the context to use for merging, not null
     * @return the new instance, with merged fields, never null
     * @throws CalendricalException if the fields cannot be merged
     */
    public Calendrical merge(CalendricalContext context) {
        ISOChronology.checkNotNull(context, "CalendricalContext must not be null");
        if (fieldMap.size() > 0) {
            Merger merger = new Merger(this, context);
            merger.merge();
        }
        return this;
    }

    //-----------------------------------------------------------------------
    /**
     * Converts this calendrical to a LocalDate.
     * <p>
     * A calendrical contains five pieces of state - date, time, offset, zone and field-value map.
     * The conversion to a <code>LocalDate</code> simply returns the date from the state.
     * If the date is null then an exception is thrown.
     * <p>
     * Any date information held in the field-value map is ignored.
     * Thus, it is standard practice to call one of the <code>merge</code> methods
     * before calling this method:
     * <pre>
     * LocalDate date = calendrical.mergeStrict().toLocalDate();
     * </pre>
     *
     * @return the LocalDate, never null
     * @throws CalendarConversionException if the date cannot be converted
     */
    public LocalDate toLocalDate() {
        if (date == null) {
            throw new CalendarConversionException(
                "Cannot convert Calendrical to LocalDate, insufficient infomation to create a date");
        }
        return date;
    }

    /**
     * Converts this calendrical to a LocalTime.
     * <p>
     * A calendrical contains five pieces of state - date, time, offset, zone and field-value map.
     * The conversion to a <code>LocalTime</code> simply returns the time from the state.
     * If the time is null then an exception is thrown.
     * <p>
     * Any time information held in the field-value map is ignored.
     * Thus, it is standard practice to call one of the <code>merge</code> methods
     * before calling this method:
     * <pre>
     * LocalTime time = calendrical.mergeStrict().toLocalTime();
     * </pre>
     *
     * @return the LocalTime, never null
     * @throws CalendarConversionException if the time cannot be converted
     */
    public LocalTime toLocalTime() {
        if (time == null) {
            throw new CalendarConversionException(
                "Cannot convert Calendrical to LocalTime, insufficient infomation to create a time");
        }
        return time;
    }

    /**
     * Converts this calendrical to a LocalDateTime.
     * <p>
     * A calendrical contains five pieces of state - date, time, offset, zone and field-value map.
     * The conversion to a <code>LocalDateTime</code> simply combines the date and time from the state.
     * If either the date or time is null then an exception is thrown.
     * <p>
     * Any date-time information held in the field-value map is ignored.
     * Thus, it is standard practice to call one of the <code>merge</code> methods
     * before calling this method:
     * <pre>
     * LocalDateTime dateTime = calendrical.mergeStrict().toLocalDateTime();
     * </pre>
     *
     * @return the LocalDateTime, never null
     * @throws CalendarConversionException if the date or time cannot be converted
     */
    public LocalDateTime toLocalDateTime() {
        if (date == null || time == null) {
            throw new CalendarConversionException(
                "Cannot convert Calendrical to LocalTime, insufficient infomation available");
        }
        return LocalDateTime.dateTime(date, time);
    }

    //-----------------------------------------------------------------------
    /**
     * Converts this object to an OffsetDate.
     * <p>
     * A calendrical contains five pieces of state - date, time, offset, zone and field-value map.
     * The conversion to a <code>OffsetDate</code> simply combines the date and offset from the state.
     * If either the date or offset is null then an exception is thrown.
     * <p>
     * Any date information held in the field-value map is ignored.
     * Thus, it is standard practice to call one of the <code>merge</code> methods
     * before calling this method:
     * <pre>
     * OffsetDate date = calendrical.mergeStrict().toOffsetDate();
     * </pre>
     *
     * @return the OffsetDate, never null
     * @throws CalendarConversionException if the date cannot be converted, or the offset is null
     */
    public OffsetDate toOffsetDate() {
        if (offset == null) {
            throw new CalendarConversionException("Cannot convert Calendrical to OffsetDate because the offset is null");
        }
        return OffsetDate.date(toLocalDate(), offset);
    }

    /**
     * Converts this object to an OffsetTime.
     * <p>
     * A calendrical contains five pieces of state - date, time, offset, zone and field-value map.
     * The conversion to a <code>OffsetTime</code> simply combines the time and offset from the state.
     * If either the time or offset is null then an exception is thrown.
     * <p>
     * Any time information held in the field-value map is ignored.
     * Thus, it is standard practice to call one of the <code>merge</code> methods
     * before calling this method:
     * <pre>
     * OffsetTime time = calendrical.mergeStrict().toOffsetTime();
     * </pre>
     *
     * @return the OffsetTime, never null
     * @throws CalendarConversionException if the time cannot be converted, or the offset is null
     */
    public OffsetTime toOffsetTime() {
        if (offset == null) {
            throw new CalendarConversionException("Cannot convert Calendrical to OffsetTime because the offset is null");
        }
        return OffsetTime.time(toLocalTime(), offset);
    }

    /**
     * Converts this object to an OffsetDateTime.
     * <p>
     * A calendrical contains five pieces of state - date, time, offset, zone and field-value map.
     * The conversion to a <code>OffsetDateTime</code> simply combines the date, time and offset from the state.
     * If either the date, time or offset is null then an exception is thrown.
     * <p>
     * Any date-time information held in the field-value map is ignored.
     * Thus, it is standard practice to call one of the <code>merge</code> methods
     * before calling this method:
     * <pre>
     * OffsetDateTime dateTime = calendrical.mergeStrict().toOffsetDateTime();
     * </pre>
     *
     * @return the OffsetDateTime, never null
     * @throws CalendarConversionException if the date or time cannot be converted, or the offset is null
     */
    public OffsetDateTime toOffsetDateTime() {
        if (offset == null) {
            throw new CalendarConversionException("Cannot convert Calendrical to OffsetDateTime because the offset is null");
        }
        return OffsetDateTime.dateTime(toLocalDateTime(), offset);
    }

    //-----------------------------------------------------------------------
    /**
     * Converts this object to a ZonedDateTime.
     * <p>
     * A calendrical contains five pieces of state - date, time, offset, zone and field-value map.
     * The conversion to a <code>OffsetDateTime</code> combines the date, time, offset and zone from the state.
     * If any of the date, time, offset or zone is null then an exception is thrown.
     * <p>
     * Any date-time information held in the field-value map is ignored.
     * Thus, it is standard practice to call one of the <code>merge</code> methods
     * before calling this method:
     * <pre>
     * ZonedDateTime dateTime = calendrical.mergeStrict().toZonedDateTime();
     * </pre>
     *
     * @return the ZonedDateTime, never null
     * @throws CalendarConversionException if the date or time cannot be converted, or the offset or zone is null
     */
    public ZonedDateTime toZonedDateTime() {
        OffsetDateTime dateTime = toOffsetDateTime();
        if (zone == null) {
            throw new CalendarConversionException("Cannot convert Calendrical to ZonedDateTime because the zone is null");
        }
        return ZonedDateTime.dateTime(dateTime, zone);
    }

    //-----------------------------------------------------------------------
    /**
     * Converts this object to a Calendrical, returning a clone.
     * <p>
     * The returned instance is a clone of this object, with the same state.
     *
     * @return a clone of this instance, never null
     */
    public Calendrical toCalendrical() {
        return clone();
    }

    //-----------------------------------------------------------------------
    /**
     * Clones this Calendrical.
     * <p>
     * The returned instance is a clone of this object, with the same state.
     *
     * @return a clone of this instance, never null
     */
    @Override
    public Calendrical clone() {
        Calendrical cloned = new Calendrical(date, time, offset, zone);
        cloned.getFieldMap().putAll(fieldMap.fieldValueMap);
        return cloned;
    }

    //-----------------------------------------------------------------------
    /**
     * Is this calendrical equal to the specified calendrical.
     * <p>
     * The comparison is based on the five pieces of state - date, time, offset
     * zone and field-value map.
     *
     * @param obj  the other Calendrical to compare to, null returns false
     * @return true if this instance is equal to the specified Calendrical
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj instanceof Calendrical == false) {
            return false;
        }
        final Calendrical other = (Calendrical) obj;
        if (this.date != other.date && (this.date == null || !this.date.equals(other.date))) {
            return false;
        }
        if (this.time != other.time && (this.time == null || !this.time.equals(other.time))) {
            return false;
        }
        if (this.offset != other.offset && (this.offset == null || !this.offset.equals(other.offset))) {
            return false;
        }
        if (this.zone != other.zone && (this.zone == null || !this.zone.equals(other.zone))) {
            return false;
        }
        return this.fieldMap.equals(other.fieldMap);
    }

    /**
     * A hash code for this calendrical.
     * <p>
     * The hash code is based on the five pieces of state - date, time, offset
     * zone and field-value map.
     *
     * @return a suitable hash code
     */
    @Override
    public int hashCode() {
        int hash = 7;
        hash = 59 * hash + (date != null ? date.hashCode() : 0);
        hash = 59 * hash + (time != null ? time.hashCode() : 0);
        hash = 59 * hash + (offset != null ? offset.hashCode() : 0);
        hash = 59 * hash + (zone != null ? zone.hashCode() : 0);
        hash = 59 * hash + fieldMap.hashCode();
        return hash;
    }

    //-----------------------------------------------------------------------
    /**
     * Outputs the calendrical as a string.
     *
     * @return the formatted date-time string, never null
     */
    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder();
        if (fieldMap.size() > 0) {
            buf.append(fieldMap).append(' ');
        }
        if (date != null) {
            buf.append(date).append(' ');
        }
        if (time != null) {
            buf.append(time).append(' ');
        }
        if (offset != null) {
            buf.append(offset).append(' ');
        }
        if (zone != null) {
            buf.append(zone).append(' ');
        }
        if (buf.length() > 0) {
            buf.setLength(buf.length() - 1);
        }
        return buf.toString();
    }

    //-----------------------------------------------------------------------
    /**
     * Mutable map of date-time fields that may be invalid.
     * <p>
     * A calendrical contains five pieces of state - date, time, offset, zone and field-value map.
     * This object is the field-value map, and changes made to this object directly
     * affect the associated calendrical.
     * <p>
     * Changes made to this object do not affect the date, time, offset or zone
     * of the associated calendrical.
     * <p>
     * Calendrical.FieldMap is mutable and not thread-safe.
     * It must only be used from a single thread and must not be passed between threads.
     */
    public static final class FieldMap implements Iterable<DateTimeFieldRule>, Serializable {

        /** Serialization version. */
        private static final long serialVersionUID = 1L;
        /**
         * The date time field map, may be null.
         */
        private final Map<DateTimeFieldRule, Integer> fieldValueMap =
            new TreeMap<DateTimeFieldRule, Integer>(Collections.reverseOrder());

        /**
         * Constructor.
         */
        private FieldMap() {
        }

        /**
         * Constructor.
         *
         * @param fields  the fields to copy, not null
         */
        private FieldMap(DateTimeFields fields) {
            fields.copyInto(fieldValueMap);
        }

        //-----------------------------------------------------------------------
        /**
         * Returns the size of the map of fields to values.
         * <p>
         * This method returns the number of field-value pairs stored.
         *
         * @return number of field-value pairs, zero or greater
         */
        public int size() {
            return fieldValueMap.size();
        }

        /**
         * Iterates through all the fields.
         * <p>
         * This method fulfills the {@link Iterable} interface and allows looping
         * around the fields using the for-each loop. The values can be obtained using
         * {@link #get(DateTimeFieldRule)} or {@link #getValidated(DateTimeFieldRule)}.
         *
         * @return an iterator over the fields in this object, never null
         */
        public Iterator<DateTimeFieldRule> iterator() {
            return fieldValueMap.keySet().iterator();
        }

        //-----------------------------------------------------------------------
        /**
         * Checks if the field-value map directly contains the specified field.
         * <p>
         * This method does not check if the value returned would be valid.
         * <p>
         * Calling this method checks whether {@link #getIntValidated} will
         * throw an exception and whether {@link #get} will return null.
         *
         * @param fieldRule  the field to query, null returns false
         * @return true if the field is supported, false otherwise
         */
        public boolean contains(DateTimeFieldRule fieldRule) {
            return fieldRule != null && fieldValueMap.containsKey(fieldRule);
        }

        /**
         * Checks if the field-value map directly contains the specified field
         * and that its value is valid.
         * <p>
         * Calling this method checks whether {@link #getIntValidated} will
         * throw an exception or return a valid value.
         *
         * @param fieldRule  the field to query, null returns false
         * @return true if the field is supported, false otherwise
         */
        public boolean containsValid(DateTimeFieldRule fieldRule) {
            if (fieldRule == null) {
                return false;
            }
            Integer value = fieldValueMap.get(fieldRule);
            return value != null && fieldRule.isValidValue(value);
        }

        //-----------------------------------------------------------------------
        /**
         * Gets the value directly from the field-value map throwing an exception
         * if the field is not present.
         * <p>
         * This method only finds the value for the field if it is actually held in the map.
         * If the value is not held directly, it may be {@link #deriveValue able to be derived}.
         * <p>
         * A calendrical can hold invalid values, such as a day of month of -3 or an hour of 1000.
         * This method performs no validation on the returned value.
         *
         * @param fieldRule  the rule to query from the map, not null
         * @return the value mapped to the specified field
         * @throws UnsupportedCalendarFieldException if the field is not in the map
         */
        public int get(DateTimeFieldRule fieldRule) {
            ISOChronology.checkNotNull(fieldRule, "DateTimeFieldRule must not be null");
            Integer value = fieldValueMap.get(fieldRule);
            if (value == null) {
                throw new UnsupportedCalendarFieldException(fieldRule, "Calendrical");
            }
            return value;
        }

        /**
         * Gets the value directly from the field-value map throwing an exception
         * if the field is not present or is invalid.
         * <p>
         * This method only finds the value for the field if it is actually held in the map.
         * If the value is not held directly, it may be {@link #deriveValue able to be derived}.
         * <p>
         * A calendrical can hold invalid values, such as a day of month of -3 or an hour of 1000.
         * This method ensures that the result is within the valid range for the field.
         * No cross-validation between fields is performed.
         *
         * @param fieldRule  the rule to query from the map, not null
         * @return the value mapped to the specified field
         * @throws UnsupportedCalendarFieldException if the field is not in the map
         * @throws IllegalCalendarFieldValueException if the value is invalid
         */
        public int getValidated(DateTimeFieldRule fieldRule) {
            int value = get(fieldRule);
            fieldRule.checkValue(value);
            return value;
        }

        /**
         * Gets the value directly from the field-value map quietly returning null
         * if the field is not present.
         * <p>
         * This method only finds the value for the field if it is actually held in the map.
         * If the value is not held directly, it may be {@link #deriveValue able to be derived}.
         * <p>
         * The value is not validated and might be out of range for the rule.
         * <p>
         * A calendrical can hold invalid values, such as a day of month of -3 or an hour of 1000.
         * This method performs no validation on the returned value.
         *
         * @param fieldRule  the rule to query from the map, null returns null
         * @return the value mapped to the specified field, null if not present
         */
        public Integer getQuiet(DateTimeFieldRule fieldRule) {
            return fieldRule == null ? null : fieldValueMap.get(fieldRule);
        }

        //-----------------------------------------------------------------------
        /**
         * Puts a field-value pair directly into this map replacing any previous value.
         * <p>
         * This method adds the specified field-value pair to the map.
         * If this instance already has a value for a field then the value is replaced.
         * Otherwise the value is added.
         *
         * @param fieldRule  the field to store, not null
         * @param value  the value to store
         * @return this, for method chaining, never null
         */
        public FieldMap put(DateTimeFieldRule fieldRule, int value) {
            ISOChronology.checkNotNull(fieldRule, "DateTimeFieldRule must not be null");
            fieldValueMap.put(fieldRule, value);
            return this;
        }

        /**
         * Puts a map of field-value pairs directly into this map.
         * <p>
         * This method adds the specified field-value pairs to the map.
         * If this instance already has a value for a field then the value is replaced.
         * Otherwise the value is added.
         *
         * @param fieldValueMap  the map of field-value pairs to store, not null
         * @return this, for method chaining, never null
         * @throws IllegalArgumentException if the map contains null keys or values
         */
        public FieldMap putAll(Map<DateTimeFieldRule, Integer> fieldValueMap) {
            ISOChronology.checkNotNull(fieldValueMap, "Field-value map must not be null");
            if (fieldValueMap.size() > 0) {
                // don't use contains() as tree map and others can throw NPE
                for (Entry<DateTimeFieldRule, Integer> entry : fieldValueMap.entrySet()) {
                    DateTimeFieldRule key = entry.getKey();
                    Integer value = entry.getValue();
                    ISOChronology.checkNotNull(key, "Null keys are not permitted in field-value map");
                    ISOChronology.checkNotNull(value, "Null values are not permitted in field-value map");
                }
                this.fieldValueMap.putAll(fieldValueMap);
            }
            return this;
        }

        /**
         * Puts a map of field-value pairs into the field-value map.
         * <p>
         * This method adds the specified field-value pairs to the map.
         * If this instance already has a value for a field then the value is replaced.
         * Otherwise the value is added.
         *
         * @param fields  the map of field-value pairs to store, not null
         * @return this, for method chaining, never null
         */
        public FieldMap putAll(DateTimeFields fields) {
            ISOChronology.checkNotNull(fields, "DateTimeFields must not be null");
            fields.copyInto(fieldValueMap);
            return this;
        }

        /**
         * Removes the specified field rule from the field-value map.
         * <p>
         * If this object holds a mapping for the specified rule then the mapping
         * will be removed.
         *
         * @param fieldRule  the field to remove, not null
         * @return this, for method chaining, never null
         */
        public FieldMap remove(DateTimeFieldRule fieldRule) {
            ISOChronology.checkNotNull(fieldRule, "DateTimeFieldRule must not be null");
            fieldValueMap.remove(fieldRule);
            return this;
        }

        /**
         * Removes the specified field rules directly from the field-value map.
         * <p>
         * This will remove the mapping for each of the specified rules from this object.
         *
         * @param fieldRules  the fields to remove, not null
         * @return this, for method chaining, never null
         */
        public FieldMap removeAll(Iterable<DateTimeFieldRule> fieldRules) {
            ISOChronology.checkNotNull(fieldRules, "DateTimeFieldRule iterable must not be null");
            for (DateTimeFieldRule fieldRule : fieldRules) {
                remove(fieldRule);
            }
            return this;
        }

        /**
         * Clears the field-value map.
         *
         * @return this, for method chaining, never null
         */
        public FieldMap clear() {
            fieldValueMap.clear();
            return this;
        }

        //-----------------------------------------------------------------------
        /**
         * Checks if the value of each field is within its valid range.
         * <p>
         * The validation simply checks that each value in the field map is within the
         * normal range for the field as defined by {@link DateTimeFieldRule#checkValue(int)}.
         * No cross-validation between fields is performed, thus the field map could
         * contain an invalid date such as February 31st.
         *
         * @return true if all the fields are with in their valid range
         * @throws IllegalCalendarFieldValueException if any field is invalid
         */
        public boolean isValid() {
            for (Entry<DateTimeFieldRule, Integer> entry : fieldValueMap.entrySet()) {
                if (entry.getKey().isValidValue(entry.getValue()) == false) {
                    return false;
                }
            }
            return true;
        }

        /**
         * Validates that the value of each field in the map is within its valid range
         * throwing an exception if not.
         * <p>
         * The validation simply checks that each value in the field map is within the
         * normal range for the field as defined by {@link DateTimeFieldRule#checkValue(int)}.
         * No cross-validation between fields is performed, thus the field map could
         * contain an invalid date such as February 31st.
         *
         * @return this, for method chaining, never null
         * @throws IllegalCalendarFieldValueException if any field is invalid
         */
        public FieldMap validate() {
            for (Entry<DateTimeFieldRule, Integer> entry : fieldValueMap.entrySet()) {
                entry.getKey().checkValue(entry.getValue());
            }
            return this;
        }

        //-----------------------------------------------------------------------
        /**
         * Derives the value of the requested field from the information in the
         * field-value map quietly returning null if the value cannot be derived.
         * <p>
         * For example, if this map contains the ISO Hour of Day field, then it
         * is possible to derive the Hour of AM/PM and the AM/PM values.
         * <p>
         * The value is not validated and might be out of range for the rule.
         * <p>
         * A calendrical can hold invalid values, such as a day of month of -3 or an hour of 1000.
         * This method performs no validation on the returned value.
         *
         * @param fieldRule  the rule to query from the map, null returns null
         * @return the value of the specified field derived from this map, null if cannot be derived
         */
        public Integer deriveValueQuiet(DateTimeFieldRule fieldRule) {
            return fieldRule == null ? null : fieldRule.getValueQuiet(this);
        }

        //-----------------------------------------------------------------------
        /**
         * Checks the calendrical fields for consistency.
         * <p>
         * This method ensures that each field in the map is consistent with each other field.
         * For example, if the map contains 'HourOfDay' and 'AmPm' then the AM/PM value must
         * be correct for the hour of day.
         *
         * @throws InvalidCalendarFieldException if any field is inconsistent
         */
        public void checkConsistent() {
            Iterator<Entry<DateTimeFieldRule, Integer>> it = fieldValueMap.entrySet().iterator();
            while (it.hasNext()) {
                Entry<DateTimeFieldRule, Integer> entry = it.next();
                DateTimeFieldRule fieldRule = entry.getKey();
                Integer derivedValue = fieldRule.deriveValue(this);
                if (derivedValue != null) {
                    Integer mapValue = entry.getValue();
                    if (derivedValue.equals(mapValue) == false) {
                        throw new InvalidCalendarFieldException("Calendrical contains field map value " +
                                fieldRule.getID() + "=" + mapValue + " that is inconsistent with value " +
                                derivedValue + " derived from another field", fieldRule);
                    }
                }
            }
        }

        /**
         * Removes any field from the map that can be derived from another field.
         * No check is performed that the removed field was consistent with the
         * remaining value.
         * <p>
         * For example, if the field map contains 'MonthOfYear' and the date is
         * non-null, then the month will be removed from the map.
         */
        public void removeDerivable() {
            Iterator<DateTimeFieldRule> it = fieldValueMap.keySet().iterator();
            while (it.hasNext()) {
                Integer derivedValue = it.next().deriveValue(this);
                if (derivedValue != null) {
                    it.remove();
                }
            }
        }

        //-----------------------------------------------------------------------
        /**
         * Converts this object to a DateTimeFields.
         * <p>
         * The returned <code>DateTimeFields</code> will contain all the mappings
         * from this object. Other related information from the calendrical,
         * such as the date or time, is not used.
         *
         * @return the DateTimeFields, never null
         */
        public DateTimeFields toDateTimeFields() {
            return DateTimeFields.fields(toFieldValueMap())// optimize
        }

        /**
         * Converts this object to a map of fields to values.
         * <p>
         * The returned map will never be null, however it may be empty.
         * It is independent of this object - changes will not be reflected back.
         *
         * @return an independent, modifiable copy of the field-value map, never null
         */
        public Map<DateTimeFieldRule, Integer> toFieldValueMap() {
            Map<DateTimeFieldRule, Integer> cloned =
                new TreeMap<DateTimeFieldRule, Integer>(Collections.reverseOrder());
            cloned.putAll(fieldValueMap);
            return cloned;
        }

        //-----------------------------------------------------------------------
        /**
         * Is this field map equal to the specified map.
         *
         * @param obj  the other field map to compare to, null returns false
         * @return true if this instance is equal to the specified field map
         */
        @Override
        public boolean equals(Object obj) {
            if (obj == this) {
                return true;
            }
            if (obj instanceof FieldMap) {
                FieldMap other = (FieldMap) obj;
                return fieldValueMap.equals(other.fieldValueMap);
            }
            return false;
        }

        /**
         * A hash code for this set of fields.
         *
         * @return a suitable hash code
         */
        @Override
        public int hashCode() {
            return fieldValueMap.hashCode();
        }

        //-----------------------------------------------------------------------
        /**
         * Outputs the set of fields as a <code>String</code>.
         * <p>
         * The output will consist of the field-value map in standard map format.
         *
         * @return the formatted date-time string, never null
         */
        @Override
        public String toString() {
            return fieldValueMap.toString();
        }
    }

    //-----------------------------------------------------------------------
    /**
     * Stateful helper used during the calendrical merge process.
     * <p>
     * Instances of this class are used internally when merging a field-value map
     * into a calendrical. The class is public as it is possible for additional
     * date/time fields to be created and used by subclassing {@link DateTimeFieldRule}.
     * <p>
     * The merger is a view of the underlying calendrical.
     * Changes to this instance affect the calendrical which created it.
     * <p>
     * Calendrical.Merger is mutable and not thread-safe.
     * It must only be used from a single thread and must not be passed between threads.
     *
     * @author Michael Nascimento Santos
     * @author Stephen Colebourne
     */
    public static final class Merger {

        /**
         * The calendrical being merged, never null.
         */
        private final Calendrical calendrical;
        /**
         * The merge context to use.
         */
        private final CalendricalContext context;
        /**
         * The time including number of days overflow.
         */
        private LocalTime.Overflow mergedTime;
        /**
         * The fields that have been processed so far.
         */
        private final Set<DateTimeFieldRule> processedFieldSet = new HashSet<DateTimeFieldRule>();
        /**
         * Current iterator, updated when the state of the map is changed.
         */
        private Iterator<DateTimeFieldRule> iterator;

        /**
         * Constructs an instance using a specific context.
         *
         * @param calendrical  the calendrical to merge, validated not null
         * @param context  the context to use, validated not null
         */
        private Merger(Calendrical calendrical, CalendricalContext context) {
            this.calendrical = calendrical;
            this.context = context;
        }

        //-----------------------------------------------------------------------
//        /**
//         * Gets the underlying calendrical that is being merged.
//         * <p>
//         * This should not normally be changed during the merge process, as this
//         * will have unexpected side effects.
//         *
//         * @return the original field-value map being merged, never null
//         */
//        public Calendrical getCalendrical() {
//            return calendrical;
//        }

        /**
         * Gets the calendrical context in use for the merge.
         *
         * @return the calendrical context, never null
         */
        public CalendricalContext getContext() {
            return context;
        }

        /**
         * Checks if the merge is strict.
         *
         * @return true if the merge is strict
         */
        public boolean isStrict() {
            return context.isStrict();
        }

//        /**
//         * Gets the set of fields that have been processed so far.
//         * <p>
//         * These are the fields that have been merged into a date, time or other fields.
//         * The set may contain fields that are not in the original field-value map.
//         *
//         * @return the processed field set, modifiable copy, not null
//         */
//        public Set<DateTimeFieldRule> getProcessedFieldSet() {
//            return new HashSet<DateTimeFieldRule>(processedFieldSet);
//        }

        //-----------------------------------------------------------------------
        /**
         * Gets the value for the specified field, throwing an exception if the field is not present.
         * <p>
         * This obtains the value for the field as defined in the underlying field-value map.
         * If this object does not define the field an exception is thrown.
         * <p>
         * The underlying field-value map is unvalidated and can contain out of range
         * values, such as a day of month of -3 or an hour of 1000.
         * If the context is strict, then the result of this method will be validated
         * before it is returned. This ensures that the value will be between the
         * minimum and maximum values for that field.
         *
         * @param fieldRule  the rule to query from the map, not null
         * @return the value mapped to the specified field
         * @throws UnsupportedCalendarFieldException if the field is not supported
         */
        public int getValue(DateTimeFieldRule fieldRule) {
            return calendrical.getFieldMap().get(fieldRule);
        }

        /**
         * Gets the value for the specified field, quietly returning null if the field is not present.
         * <p>
         * This obtains the value for the field as defined in the underlying field-value map.
         * If the map does not define the field, null is returned.
         * <p>
         * The underlying field-value map is unvalidated and can contain out of range
         * values, such as a day of month of -3 or an hour of 1000.
         * If the context is strict, then the result of this method will be validated
         * before it is returned. This ensures that the value will be between the
         * minimum and maximum values for that field.
         *
         * @param fieldRule  the rule to query from the map, null returns null
         * @return the value mapped to the specified field, null if not present
         * @throws IllegalCalendarFieldValueException if the merge is strict and the value is invalid
         */
        public Integer getValueQuiet(DateTimeFieldRule fieldRule) {
            return calendrical.getFieldMap().getQuiet(fieldRule);
        }

        //-----------------------------------------------------------------------
        /**
         * Marks a field that has been processed to the list.
         * <p>
         * The merge process needs to keep track of those fields that are merged
         * at each stage. This is done when the field rule calls this method.
         * For example, if fields A and B are merged to produce C, then this
         * method must be called twice, passing in the values A and B.
         *
         * @param fieldRule  the field to mark as processed, not null
         */
        public void markFieldAsProcessed(DateTimeFieldRule fieldRule) {
            ISOChronology.checkNotNull(fieldRule, "Field rule must not be null");
            processedFieldSet.add(fieldRule);
        }

        /**
         * Stores the merged date checking that it matches any previously stored date.
         * <p>
         * The ultimate aim of the merge process for date fields is to produce a date.
         * When the merge results in a date then this method must be called.
         * For example, when Year and DayOfYear are merged, the result is a date
         * and that is stored by calling this method.
         * <p>
         * It is possible that the field-value map contains multiple hierarchies that
         * can produce a date. In this case, all the hierarchies must produce the same
         * date, something which is validated by this method.
         *
         * @param date  the date to set, not null
         * @throws CalendricalException if the input date does not match a previously stored date
         */
        public void storeMergedDate(LocalDate date) {
            ISOChronology.checkNotNull(date, "Date must not be null");
            LocalDate storedDate = calendrical.getDate();
            if (storedDate != null && storedDate.equals(date) == false) {
                throw new CalendricalException("Merge resulted in two different dates, " + storedDate + " and " + date);
            }
            calendrical.setDate(date);
            // no need to reset iterator, as store of date should not enable any more calculations
        }

        /**
         * Stores the merged time checking that it matches any previously stored
         * time ignoring the number of days overflow.
         *
         * @param time  the time to set, may be null
         * @throws CalendricalException if the input time does not match a previously stored time
         */
        public void storeMergedTime(LocalTime time) {
            ISOChronology.checkNotNull(time, "Time must not be null");
            LocalTime storedTime = calendrical.getTime();
            if (storedTime != null && storedTime.equals(time) == false) {
                throw new CalendricalException("Merge resulted in two different times, " + storedTime + " and " + time);
            }
            calendrical.setTime(time);
            // no need to reset iterator, as store of date should not enable any more calculations
        }

        /**
         * Stores the merged time checking that it matches any previously stored
         * time including the number of days overflow.
         *
         * @param time  the time to set, may be null
         * @throws CalendricalException if the input time does not match a previously stored time
         */
        public void storeMergedTime(LocalTime.Overflow time) {
            ISOChronology.checkNotNull(time, "Time must not be null");
            if (mergedTime != null && mergedTime.equals(time) == false) {
                throw new CalendricalException("Merge resulted in two different times, " + mergedTime + " and " + time);
            }
            storeMergedTime(time.getResultTime());
            mergedTime = time;
            // no need to reset iterator, as store of date should not enable any more calculations
        }

        /**
         * Stores a field-value pair into this map ensuring that it does not clash
         * with any previous value defined for that field.
         * <p>
         * This method adds the specified field-value pair to the map.
         * If this instance already has a value for the field then the value is checked
         * to see if it is the same with an exception being thrown if it is not.
         * If this instance does not hold the field already, then the value is simply added.
         * <p>
         * DateTimeFieldMap is an unvalidated map of field to value.
         * The value specified may be outside the normal valid range for the field.
         * For example, you could setup a map with a day of month of -3.
         *
         * @param fieldRule  the field to store, not null
         * @param value  the value to store
         * @throws CalendricalException if the input field does not match a previously stored field
         */
        public void storeMergedField(DateTimeFieldRule fieldRule, int value) {
            ISOChronology.checkNotNull(fieldRule, "Field rule must not be null");
            Integer oldValue = calendrical.getFieldMap().getQuiet(fieldRule);
            if (oldValue != null) {
                if (oldValue.intValue() != value) {
                    throw new InvalidCalendarFieldException("Merge resulted in two different values, " + value +
                            " and " + oldValue + ", for " + fieldRule.getID() + " within fields " +
                            calendrical.getFieldMap(), fieldRule);
                } else {
                    return// no change
                }
            }
            calendrical.getFieldMap().put(fieldRule, value);
            iterator = calendrical.getFieldMap().iterator()// restart the iterator
        }

        //-----------------------------------------------------------------------
        /**
         * Merges the fields to extract the maximum possible date, time and offset information.
         * <p>
         * The merge process aims to extract the maximum amount of information
         * possible from this set of fields. Ideally the outcome will be a date, time
         * or both, however there may be insufficient information to achieve this.
         * <p>
         * The process repeatedly calls the field rule {@link DateTimeFieldRule#merge merge}
         * method to perform the merge on each individual field. Sometimes two or
         * more fields will combine to form a more significant field. Sometimes they
         * will combine to form a date or time. The process stops when there no more
         * merges can occur.
         * <p>
         * The process is based around hierarchies that can be combined.
         * For example, QuarterOfYear and MonthOfQuarter can be combined to form MonthOfYear.
         * Then, MonthOfYear can be combined with DayOfMonth and Year to form a date.
         * Any fields which take part in a merge will be removed from the result as their
         * values can be derived from the merged field.
         * <p>
         * The exact definition of which fields combine with which is chronology dependent.
         * For example, see {@link ISOChronology}.
         * <p>
         * The details of the process are controlled by the merge context.
         * This includes strict/lenient behavior.
         * <p>
         * The merge must result in consistent values for each field, date and time.
         * If two different values are produced an exception is thrown.
         * For example, both Year/MonthOfYear/DayOfMonth and Year/DayOfYear will merge to form a date.
         * If both sets of fields do not produce the same date then an exception will be thrown.
         *
         * @throws CalendricalException if the merge cannot be completed successfully
         */
        void merge() {
            // it is essential to validate for consistency, as there is no way to
            // reliably determine which is the more significant or original value
           
            // exit quick in simple case
            if (calendrical.getFieldMap().size() > 0) {
                // strict must pre-validate fields for out of range problems
                if (isStrict()) {
                    calendrical.getFieldMap().validate();
                }
               
                // we keep all the fields in the map during the merge and only remove at the end
                // once merged, the initial fields can be derived from the merged fields
                mergeFieldsLoop();
                mergeDateTime();
               
                // remove the fields that have been merged into more significant fields
                calendrical.getFieldMap().removeAll(processedFieldSet);
               
                // check and remove any remaining less significant that can be derived from
                // the new set of more significant fields
                if (getContext().isCheckUnusedFields()) {
                    calendrical.checkConsistent();
                }
               
                // add days overflow
                if (calendrical.getDate() != null && mergedTime != null && mergedTime.getOverflowDays() > 0) {
                    calendrical.setDate(calendrical.getDate().plusDays(mergedTime.getOverflowDays()));
                }
               
                // remove derivable fields
                calendrical.removeDerivable();
            }
        }

        /**
         * Performs the loop to merge the fields.
         *
         * @throws CalendricalException if the merge cannot be completed successfully
         */
        private void mergeFieldsLoop() {
            iterator = calendrical.getFieldMap().iterator();
            int protect = 0// avoid infinite looping
            while (iterator.hasNext() && protect < 100) {
                iterator.next().mergeFields(this);
                protect++;
            }
            if (iterator.hasNext()) {
                throw new CalendricalException("Merge fields failed, infinite loop blocked, " +
                        "probably caused by an incorrectly implemented field rule");
            }
        }

        /**
         * Performs the loop to merge the fields to date/time.
         *
         * @throws CalendricalException if the merge cannot be completed successfully
         */
        private void mergeDateTime() {
            iterator = calendrical.getFieldMap().iterator();
            while (iterator.hasNext()) {
                iterator.next().mergeDateTime(this);
            }
        }
    }
}
TOP

Related Classes of javax.time.calendar.Calendrical$FieldMap

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.