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.DateTimeField;
019    import org.joda.time.DateTimeFieldType;
020    import 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     */
035    public 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    }