001    /*
002     *  Copyright 2001-2005 Stephen Colebourne
003     *
004     *  Licensed under the Apache License, Version 2.0 (the "License");
005     *  you may not use this file except in compliance with the License.
006     *  You may obtain a copy of the License at
007     *
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     *
010     *  Unless required by applicable law or agreed to in writing, software
011     *  distributed under the License is distributed on an "AS IS" BASIS,
012     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     *  See the License for the specific language governing permissions and
014     *  limitations under the License.
015     */
016    package org.joda.time.field;
017    
018    import org.joda.time.DateTimeFieldType;
019    import org.joda.time.DurationField;
020    
021    /**
022     * Precise datetime field, composed of two precise duration fields.
023     * <p>
024     * This DateTimeField is useful for defining DateTimeFields that are composed
025     * of precise durations, like time of day fields. If either duration field is
026     * imprecise, then an {@link ImpreciseDateTimeField} may be used instead.
027     * <p>
028     * PreciseDateTimeField is thread-safe and immutable.
029     *
030     * @author Brian S O'Neill
031     * @author Stephen Colebourne
032     * @since 1.0
033     * @see ImpreciseDateTimeField
034     */
035    public class PreciseDateTimeField extends PreciseDurationDateTimeField {
036    
037        private static final long serialVersionUID = -5586801265774496376L;
038    
039        /** The maximum range in the correct units */
040        private final int iRange;
041    
042        private final DurationField iRangeField;
043    
044        /**
045         * Constructor.
046         * 
047         * @param type  the field type this field uses
048         * @param unit  precise unit duration, like "seconds()".
049         * @param range precise range duration, preferably a multiple of the unit,
050         * like "minutes()".
051         * @throws IllegalArgumentException if either duration field is imprecise
052         * @throws IllegalArgumentException if unit milliseconds is less than one
053         * or effective value range is less than two.
054         */
055        public PreciseDateTimeField(DateTimeFieldType type,
056                                    DurationField unit, DurationField range) {
057            super(type, unit);
058    
059            if (!range.isPrecise()) {
060                throw new IllegalArgumentException("Range duration field must be precise");
061            }
062    
063            long rangeMillis = range.getUnitMillis();
064            iRange = (int)(rangeMillis / getUnitMillis());
065            if (iRange < 2) {
066                throw new IllegalArgumentException("The effective range must be at least 2");
067            }
068    
069            iRangeField = range;
070        }
071    
072        /**
073         * Get the amount of fractional units from the specified time instant.
074         * 
075         * @param instant  the milliseconds from 1970-01-01T00:00:00Z to query
076         * @return the amount of fractional units extracted from the input.
077         */
078        public int get(long instant) {
079            if (instant >= 0) {
080                return (int) ((instant / getUnitMillis()) % iRange);
081            } else {
082                return iRange - 1 + (int) (((instant + 1) / getUnitMillis()) % iRange);
083            }
084        }
085    
086        /**
087         * Add to the component of the specified time instant, wrapping around
088         * within that component if necessary.
089         * 
090         * @param instant  the milliseconds from 1970-01-01T00:00:00Z to add to
091         * @param amount  the amount of units to add (can be negative).
092         * @return the updated time instant.
093         */
094        public long addWrapField(long instant, int amount) {
095            int thisValue = get(instant);
096            int wrappedValue = FieldUtils.getWrappedValue
097                (thisValue, amount, getMinimumValue(), getMaximumValue());
098            // copy code from set() to avoid repeat call to get()
099            return instant + (wrappedValue - thisValue) * getUnitMillis();
100        }
101    
102        /**
103         * Set the specified amount of units to the specified time instant.
104         * 
105         * @param instant  the milliseconds from 1970-01-01T00:00:00Z to set in
106         * @param value  value of units to set.
107         * @return the updated time instant.
108         * @throws IllegalArgumentException if value is too large or too small.
109         */
110        public long set(long instant, int value) {
111            FieldUtils.verifyValueBounds(this, value, getMinimumValue(), getMaximumValue());
112            return instant + (value - get(instant)) * iUnitMillis;
113        }
114    
115        /**
116         * Returns the range duration of this field. For example, if this field
117         * represents "minute of hour", then the range duration field is an hours.
118         *
119         * @return the range duration of this field, or null if field has no range
120         */
121        public DurationField getRangeDurationField() {
122            return iRangeField;
123        }
124    
125        /**
126         * Get the maximum value for the field.
127         * 
128         * @return the maximum value
129         */
130        public int getMaximumValue() {
131            return iRange - 1;
132        }
133        
134        /**
135         * Returns the range of the field in the field's units.
136         * <p>
137         * For example, 60 for seconds per minute. The field is allowed values
138         * from 0 to range - 1.
139         * 
140         * @return unit range
141         */
142        public int getRange() {
143            return iRange;
144        }
145    
146    }