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 */
016package org.joda.time.field;
017
018import org.joda.time.DateTimeField;
019import org.joda.time.DateTimeFieldType;
020import org.joda.time.DurationField;
021
022/**
023 * Divides a DateTimeField such that the retrieved values are reduced by a
024 * fixed divisor. The field's unit duration is scaled accordingly, but the
025 * range duration is unchanged.
026 * <p>
027 * DividedDateTimeField is thread-safe and immutable.
028 *
029 * @see RemainderDateTimeField
030 * 
031 * @author Stephen Colebourne
032 * @author Brian S O'Neill
033 * @since 1.0
034 */
035public class DividedDateTimeField extends DecoratedDateTimeField {
036
037    private static final long serialVersionUID = 8318475124230605365L;
038
039    // Shared with RemainderDateTimeField.
040    final int iDivisor;
041    final DurationField iDurationField;
042
043    private final int iMin;
044    private final int iMax;
045
046    /**
047     * Constructor.
048     * 
049     * @param field  the field to wrap, like "year()".
050     * @param type  the field type this field will actually use
051     * @param divisor  divisor, such as 100 years in a century
052     * @throws IllegalArgumentException if divisor is less than two
053     */
054    public DividedDateTimeField(DateTimeField field,
055                                DateTimeFieldType type, int divisor) {
056        super(field, type);
057                
058        if (divisor < 2) {
059            throw new IllegalArgumentException("The divisor must be at least 2");
060        }
061
062        DurationField unitField = field.getDurationField();
063        if (unitField == null) {
064            iDurationField = null;
065        } else {
066            iDurationField = new ScaledDurationField(
067                unitField, type.getDurationType(), divisor);
068        }
069
070        iDivisor = divisor;
071
072        int i = field.getMinimumValue();
073        int min = (i >= 0) ? i / divisor : ((i + 1) / divisor - 1);
074
075        int j = field.getMaximumValue();
076        int max = (j >= 0) ? j / divisor : ((j + 1) / divisor - 1);
077
078        iMin = min;
079        iMax = max;
080    }
081
082    /**
083     * Construct a DividedDateTimeField that compliments the given
084     * RemainderDateTimeField.
085     *
086     * @param remainderField  complimentary remainder field, like "yearOfCentury()".
087     * @param type  the field type this field will actually use
088     */
089    public DividedDateTimeField(RemainderDateTimeField remainderField, DateTimeFieldType type) {
090        super(remainderField.getWrappedField(), type);
091        int divisor = iDivisor = remainderField.iDivisor;
092        iDurationField = remainderField.iRangeField;
093
094        DateTimeField field = getWrappedField();
095        int i = field.getMinimumValue();
096        int min = (i >= 0) ? i / divisor : ((i + 1) / divisor - 1);
097
098        int j = field.getMaximumValue();
099        int max = (j >= 0) ? j / divisor : ((j + 1) / divisor - 1);
100
101        iMin = min;
102        iMax = max;
103    }
104
105    /**
106     * Get the amount of scaled units from the specified time instant.
107     * 
108     * @param instant  the time instant in millis to query.
109     * @return the amount of scaled units extracted from the input.
110     */
111    public int get(long instant) {
112        int value = getWrappedField().get(instant);
113        if (value >= 0) {
114            return value / iDivisor;
115        } else {
116            return ((value + 1) / iDivisor) - 1;
117        }
118    }
119
120    /**
121     * Add the specified amount of scaled units to the specified time
122     * instant. The amount added may be negative.
123     * 
124     * @param instant  the time instant in millis to update.
125     * @param amount  the amount of scaled units to add (can be negative).
126     * @return the updated time instant.
127     */
128    public long add(long instant, int amount) {
129        return getWrappedField().add(instant, amount * iDivisor);
130    }
131
132    /**
133     * Add the specified amount of scaled units to the specified time
134     * instant. The amount added may be negative.
135     * 
136     * @param instant  the time instant in millis to update.
137     * @param amount  the amount of scaled units to add (can be negative).
138     * @return the updated time instant.
139     */
140    public long add(long instant, long amount) {
141        return getWrappedField().add(instant, amount * iDivisor);
142    }
143
144    /**
145     * Add to the scaled component of the specified time instant,
146     * wrapping around within that component if necessary.
147     * 
148     * @param instant  the time instant in millis to update.
149     * @param amount  the amount of scaled units to add (can be negative).
150     * @return the updated time instant.
151     */
152    public long addWrapField(long instant, int amount) {
153        return set(instant, FieldUtils.getWrappedValue(get(instant), amount, iMin, iMax));
154    }
155
156    public int getDifference(long minuendInstant, long subtrahendInstant) {
157        return getWrappedField().getDifference(minuendInstant, subtrahendInstant) / iDivisor;
158    }
159
160    public long getDifferenceAsLong(long minuendInstant, long subtrahendInstant) {
161        return getWrappedField().getDifferenceAsLong(minuendInstant, subtrahendInstant) / iDivisor;
162    }
163
164    /**
165     * Set the specified amount of scaled units to the specified time instant.
166     * 
167     * @param instant  the time instant in millis to update.
168     * @param value  value of scaled units to set.
169     * @return the updated time instant.
170     * @throws IllegalArgumentException if value is too large or too small.
171     */
172    public long set(long instant, int value) {
173        FieldUtils.verifyValueBounds(this, value, iMin, iMax);
174        int remainder = getRemainder(getWrappedField().get(instant));
175        return getWrappedField().set(instant, value * iDivisor + remainder);
176    }
177
178    /**
179     * Returns a scaled version of the wrapped field's unit duration field.
180     */
181    public DurationField getDurationField() {
182        return iDurationField;
183    }
184
185    /**
186     * Get the minimum value for the field.
187     * 
188     * @return the minimum value
189     */
190    public int getMinimumValue() {
191        return iMin;
192    }
193
194    /**
195     * Get the maximum value for the field.
196     * 
197     * @return the maximum value
198     */
199    public int getMaximumValue() {
200        return iMax;
201    }
202
203    public long roundFloor(long instant) {
204        DateTimeField field = getWrappedField();
205        return field.roundFloor(field.set(instant, get(instant) * iDivisor));
206    }
207
208    public long remainder(long instant) {
209        return set(instant, get(getWrappedField().remainder(instant)));
210    }
211
212    /**
213     * Returns the divisor applied, in the field's units.
214     * 
215     * @return the divisor
216     */
217    public int getDivisor() {
218        return iDivisor;
219    }
220
221    private int getRemainder(int value) {
222        if (value >= 0) {
223            return value % iDivisor;
224        } else {
225            return (iDivisor - 1) + ((value + 1) % iDivisor);
226        }
227    }
228
229}