1 /*
2  *  Copyright 2001-2013 Stephen Colebourne
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License");
5  *  you may not use this file except in compliance with the License.
6  *  You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *  Unless required by applicable law or agreed to in writing, software
11  *  distributed under the License is distributed on an "AS IS" BASIS,
12  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *  See the License for the specific language governing permissions and
14  *  limitations under the License.
15  */

16 package org.joda.time.field;
17
18 import org.joda.time.DateTimeField;
19 import org.joda.time.DateTimeFieldType;
20 import org.joda.time.DurationField;
21
22 /**
23  * Divides a DateTimeField such that the retrieved values are reduced by a
24  * fixed divisor. The field's unit duration is scaled accordingly, but the
25  * range duration is unchanged.
26  * <p>
27  * DividedDateTimeField is thread-safe and immutable.
28  *
29  * @see RemainderDateTimeField
30  * 
31  * @author Stephen Colebourne
32  * @author Brian S O'Neill
33  * @since 1.0
34  */

35 public class DividedDateTimeField extends DecoratedDateTimeField {
36
37     @SuppressWarnings("unused")
38     private static final long serialVersionUID = 8318475124230605365L;
39
40     // Shared with RemainderDateTimeField.
41     final int iDivisor;
42     final DurationField iDurationField;
43     final DurationField iRangeDurationField;
44
45     private final int iMin;
46     private final int iMax;
47
48     /**
49      * Constructor.
50      * 
51      * @param field  the field to wrap, like "year()".
52      * @param type  the field type this field will actually use
53      * @param divisor  divisor, such as 100 years in a century
54      * @throws IllegalArgumentException if divisor is less than two
55      */

56     public DividedDateTimeField(DateTimeField field,
57                                 DateTimeFieldType type, int divisor) {
58         this(field, field.getRangeDurationField(), type, divisor);
59     }
60
61     /**
62      * Constructor.
63      * 
64      * @param field  the field to wrap, like "year()".
65      * @param rangeField  the range field, null to derive
66      * @param type  the field type this field will actually use
67      * @param divisor  divisor, such as 100 years in a century
68      * @throws IllegalArgumentException if divisor is less than two
69      */

70     public DividedDateTimeField(DateTimeField field, DurationField rangeField,
71                                 DateTimeFieldType type, int divisor) {
72         super(field, type);
73         if (divisor < 2) {
74             throw new IllegalArgumentException("The divisor must be at least 2");
75         }
76         DurationField unitField = field.getDurationField();
77         if (unitField == null) {
78             iDurationField = null;
79         } else {
80             iDurationField = new ScaledDurationField(
81                 unitField, type.getDurationType(), divisor);
82         }
83         iRangeDurationField = rangeField;
84         iDivisor = divisor;
85         int i = field.getMinimumValue();
86         int min = (i >= 0) ? i / divisor : ((i + 1) / divisor - 1);
87         int j = field.getMaximumValue();
88         int max = (j >= 0) ? j / divisor : ((j + 1) / divisor - 1);
89         iMin = min;
90         iMax = max;
91     }
92
93     /**
94      * Construct a DividedDateTimeField that compliments the given
95      * RemainderDateTimeField.
96      *
97      * @param remainderField  complimentary remainder field, like "yearOfCentury()".
98      * @param type  the field type this field will actually use
99      */

100     public DividedDateTimeField(RemainderDateTimeField remainderField, DateTimeFieldType type) {
101         this(remainderField, null, type);
102     }
103
104     /**
105      * Construct a DividedDateTimeField that compliments the given
106      * RemainderDateTimeField.
107      *
108      * @param remainderField  complimentary remainder field, like "yearOfCentury()".
109      * @param rangeField  the range field, null to derive
110      * @param type  the field type this field will actually use
111      */

112     public DividedDateTimeField(RemainderDateTimeField remainderField, DurationField rangeField, DateTimeFieldType type) {
113         super(remainderField.getWrappedField(), type);
114         int divisor = iDivisor = remainderField.iDivisor;
115         iDurationField = remainderField.iRangeField;
116         iRangeDurationField = rangeField;
117         DateTimeField field = getWrappedField();
118         int i = field.getMinimumValue();
119         int min = (i >= 0) ? i / divisor : ((i + 1) / divisor - 1);
120         int j = field.getMaximumValue();
121         int max = (j >= 0) ? j / divisor : ((j + 1) / divisor - 1);
122         iMin = min;
123         iMax = max;
124     }
125
126     @Override
127     public DurationField getRangeDurationField() {
128         if (iRangeDurationField != null) {
129             return iRangeDurationField;
130         }
131         return super.getRangeDurationField();
132     }
133
134     /**
135      * Get the amount of scaled units from the specified time instant.
136      * 
137      * @param instant  the time instant in millis to query.
138      * @return the amount of scaled units extracted from the input.
139      */

140     public int get(long instant) {
141         int value = getWrappedField().get(instant);
142         if (value >= 0) {
143             return value / iDivisor;
144         } else {
145             return ((value + 1) / iDivisor) - 1;
146         }
147     }
148
149     /**
150      * Add the specified amount of scaled units to the specified time
151      * instant. The amount added may be negative.
152      * 
153      * @param instant  the time instant in millis to update.
154      * @param amount  the amount of scaled units to add (can be negative).
155      * @return the updated time instant.
156      */

157     public long add(long instant, int amount) {
158         return getWrappedField().add(instant, amount * iDivisor);
159     }
160
161     /**
162      * Add the specified amount of scaled units to the specified time
163      * instant. The amount added may be negative.
164      * 
165      * @param instant  the time instant in millis to update.
166      * @param amount  the amount of scaled units to add (can be negative).
167      * @return the updated time instant.
168      */

169     public long add(long instant, long amount) {
170         return getWrappedField().add(instant, amount * iDivisor);
171     }
172
173     /**
174      * Add to the scaled component of the specified time instant,
175      * wrapping around within that component if necessary.
176      * 
177      * @param instant  the time instant in millis to update.
178      * @param amount  the amount of scaled units to add (can be negative).
179      * @return the updated time instant.
180      */

181     public long addWrapField(long instant, int amount) {
182         return set(instant, FieldUtils.getWrappedValue(get(instant), amount, iMin, iMax));
183     }
184
185     public int getDifference(long minuendInstant, long subtrahendInstant) {
186         return getWrappedField().getDifference(minuendInstant, subtrahendInstant) / iDivisor;
187     }
188
189     public long getDifferenceAsLong(long minuendInstant, long subtrahendInstant) {
190         return getWrappedField().getDifferenceAsLong(minuendInstant, subtrahendInstant) / iDivisor;
191     }
192
193     /**
194      * Set the specified amount of scaled units to the specified time instant.
195      * 
196      * @param instant  the time instant in millis to update.
197      * @param value  value of scaled units to set.
198      * @return the updated time instant.
199      * @throws IllegalArgumentException if value is too large or too small.
200      */

201     public long set(long instant, int value) {
202         FieldUtils.verifyValueBounds(this, value, iMin, iMax);
203         int remainder = getRemainder(getWrappedField().get(instant));
204         return getWrappedField().set(instant, value * iDivisor + remainder);
205     }
206
207     /**
208      * Returns a scaled version of the wrapped field's unit duration field.
209      */

210     public DurationField getDurationField() {
211         return iDurationField;
212     }
213
214     /**
215      * Get the minimum value for the field.
216      * 
217      * @return the minimum value
218      */

219     public int getMinimumValue() {
220         return iMin;
221     }
222
223     /**
224      * Get the maximum value for the field.
225      * 
226      * @return the maximum value
227      */

228     public int getMaximumValue() {
229         return iMax;
230     }
231
232     public long roundFloor(long instant) {
233         DateTimeField field = getWrappedField();
234         return field.roundFloor(field.set(instant, get(instant) * iDivisor));
235     }
236
237     public long remainder(long instant) {
238         return set(instant, get(getWrappedField().remainder(instant)));
239     }
240
241     /**
242      * Returns the divisor applied, in the field's units.
243      * 
244      * @return the divisor
245      */

246     public int getDivisor() {
247         return iDivisor;
248     }
249
250     private int getRemainder(int value) {
251         if (value >= 0) {
252             return value % iDivisor;
253         } else {
254             return (iDivisor - 1) + ((value + 1) % iDivisor);
255         }
256     }
257
258 }
259