View Javadoc

1   /*
2    *  Copyright 2001-2005 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.chrono;
17  
18  import org.joda.time.DateTimeConstants;
19  import org.joda.time.DateTimeFieldType;
20  import org.joda.time.DateTimeUtils;
21  import org.joda.time.DurationField;
22  import org.joda.time.ReadablePartial;
23  import org.joda.time.field.FieldUtils;
24  import org.joda.time.field.ImpreciseDateTimeField;
25  
26  /**
27   * Provides time calculations for the month of the year component of time.
28   *
29   * @author Guy Allard
30   * @author Stephen Colebourne
31   * @author Brian S O'Neill
32   * @since 1.2, refactored from GJMonthOfYearDateTimeField
33   */
34  class BasicMonthOfYearDateTimeField extends ImpreciseDateTimeField {
35  
36      /** Serialization version */
37      private static final long serialVersionUID = -8258715387168736L;
38  
39      private static final int MIN = DateTimeConstants.JANUARY;
40  
41      private final BasicChronology iChronology;
42      private final int iMax;
43      private final int iLeapMonth;
44  
45      /**
46       * Restricted constructor.
47       * 
48       * @param leapMonth the month of year that leaps
49       */
50      BasicMonthOfYearDateTimeField(BasicChronology chronology, int leapMonth) {
51          super(DateTimeFieldType.monthOfYear(), chronology.getAverageMillisPerMonth());
52          iChronology = chronology;
53          iMax = iChronology.getMaxMonth();
54          iLeapMonth = leapMonth;
55      }
56  
57      //-----------------------------------------------------------------------
58      public boolean isLenient() {
59          return false;
60      }
61  
62      //-----------------------------------------------------------------------
63      /**
64       * Get the Month component of the specified time instant.
65       *
66       * @see org.joda.time.DateTimeField#get(long)
67       * @see org.joda.time.ReadableDateTime#getMonthOfYear()
68       * @param instant  the time instant in millis to query.
69       * @return the month extracted from the input.
70       */
71      public int get(long instant) {
72          return iChronology.getMonthOfYear(instant);
73      }
74  
75      //-----------------------------------------------------------------------
76      /**
77       * Add the specified month to the specified time instant.
78       * The amount added may be negative.<p>
79       * If the new month has less total days than the specified
80       * day of the month, this value is coerced to the nearest
81       * sane value. e.g.<p>
82       * 07-31 - (1 month) = 06-30<p>
83       * 03-31 - (1 month) = 02-28 or 02-29 depending<p>
84       * 
85       * @see org.joda.time.DateTimeField#add
86       * @see org.joda.time.ReadWritableDateTime#addMonths(int)
87       * @param instant  the time instant in millis to update.
88       * @param months  the months to add (can be negative).
89       * @return the updated time instant.
90       */
91      public long add(long instant, int months) {
92          if (months == 0) {
93              return instant; // the easy case
94          }
95          //
96          // Save time part first.
97          //
98          long timePart = iChronology.getMillisOfDay(instant);
99          //
100         //
101         // Get this year and month.
102         //
103         int thisYear = iChronology.getYear(instant);
104         int thisMonth = iChronology.getMonthOfYear(instant, thisYear);
105         // ----------------------------------------------------------
106         //
107         // Do not refactor without careful consideration.
108         // Order of calculation is important.
109         //
110         int yearToUse;
111         // Initially, monthToUse is zero-based
112         int monthToUse = thisMonth - 1 + months;
113         if (monthToUse >= 0) {
114             yearToUse = thisYear + (monthToUse / iMax);
115             monthToUse = (monthToUse % iMax) + 1;
116         } else {
117             yearToUse = thisYear + (monthToUse / iMax) - 1;
118             monthToUse = Math.abs(monthToUse);
119             int remMonthToUse = monthToUse % iMax;
120             // Take care of the boundary condition
121             if (remMonthToUse == 0) {
122                 remMonthToUse = iMax;
123             }
124             monthToUse = iMax - remMonthToUse + 1;
125             // Take care of the boundary condition
126             if (monthToUse == 1) {
127                 yearToUse += 1;
128             }
129         }
130         // End of do not refactor.
131         // ----------------------------------------------------------
132 
133         //
134         // Quietly force DOM to nearest sane value.
135         //
136         int dayToUse = iChronology.getDayOfMonth(instant, thisYear, thisMonth);
137         int maxDay = iChronology.getDaysInYearMonth(yearToUse, monthToUse);
138         if (dayToUse > maxDay) {
139             dayToUse = maxDay;
140         }
141         //
142         // get proper date part, and return result
143         //
144         long datePart =
145             iChronology.getYearMonthDayMillis(yearToUse, monthToUse, dayToUse);
146         return datePart + timePart;
147     }
148 
149     //-----------------------------------------------------------------------
150     public long add(long instant, long months) {
151         int i_months = (int)months;
152         if (i_months == months) {
153             return add(instant, i_months);
154         }
155 
156         // Copied from add(long, int) and modified slightly:
157 
158         long timePart = iChronology.getMillisOfDay(instant);
159 
160         int thisYear = iChronology.getYear(instant);
161         int thisMonth = iChronology.getMonthOfYear(instant, thisYear);
162 
163         long yearToUse;
164         long monthToUse = thisMonth - 1 + months;
165         if (monthToUse >= 0) {
166             yearToUse = thisYear + (monthToUse / iMax);
167             monthToUse = (monthToUse % iMax) + 1;
168         } else {
169             yearToUse = thisYear + (monthToUse / iMax) - 1;
170             monthToUse = Math.abs(monthToUse);
171             int remMonthToUse = (int)(monthToUse % iMax);
172             if (remMonthToUse == 0) {
173                 remMonthToUse = iMax;
174             }
175             monthToUse = iMax - remMonthToUse + 1;
176             if (monthToUse == 1) {
177                 yearToUse += 1;
178             }
179         }
180 
181         if (yearToUse < iChronology.getMinYear() ||
182             yearToUse > iChronology.getMaxYear()) {
183 
184             throw new IllegalArgumentException
185                 ("Magnitude of add amount is too large: " + months);
186         }
187 
188         int i_yearToUse = (int)yearToUse;
189         int i_monthToUse = (int)monthToUse;
190 
191         int dayToUse = iChronology.getDayOfMonth(instant, thisYear, thisMonth);
192         int maxDay = iChronology.getDaysInYearMonth(i_yearToUse, i_monthToUse);
193         if (dayToUse > maxDay) {
194             dayToUse = maxDay;
195         }
196 
197         long datePart =
198             iChronology.getYearMonthDayMillis(i_yearToUse, i_monthToUse, dayToUse);
199         return datePart + timePart;
200     }
201 
202     //-----------------------------------------------------------------------
203     public int[] add(ReadablePartial partial, int fieldIndex, int[] values, int valueToAdd) {
204         // overridden as superclass algorithm can't handle
205         // 2004-02-29 + 48 months -> 2008-02-29 type dates
206         if (valueToAdd == 0) {
207             return values;
208         }
209         if (partial.size() > 0 && partial.getFieldType(0).equals(DateTimeFieldType.monthOfYear()) && fieldIndex == 0) {
210             // month is largest field and being added to, such as month-day
211             int curMonth0 = partial.getValue(0) - 1;
212             int newMonth = ((curMonth0 + (valueToAdd % 12) + 12) % 12) + 1;
213             return set(partial, 0, values, newMonth);
214         }
215         if (DateTimeUtils.isContiguous(partial)) {
216             long instant = 0L;
217             for (int i = 0, isize = partial.size(); i < isize; i++) {
218                 instant = partial.getFieldType(i).getField(iChronology).set(instant, values[i]);
219             }
220             instant = add(instant, valueToAdd);
221             return iChronology.get(partial, instant);
222         } else {
223             return super.add(partial, fieldIndex, values, valueToAdd);
224         }
225     }
226 
227     //-----------------------------------------------------------------------
228     /**
229      * Add to the Month component of the specified time instant
230      * wrapping around within that component if necessary.
231      * 
232      * @see org.joda.time.DateTimeField#addWrapField
233      * @param instant  the time instant in millis to update.
234      * @param months  the months to add (can be negative).
235      * @return the updated time instant.
236      */
237     public long addWrapField(long instant, int months) {
238         return set(instant, FieldUtils.getWrappedValue(get(instant), months, MIN, iMax));
239     }
240 
241     //-----------------------------------------------------------------------
242     public long getDifferenceAsLong(long minuendInstant, long subtrahendInstant) {
243         if (minuendInstant < subtrahendInstant) {
244             return -getDifference(subtrahendInstant, minuendInstant);
245         }
246 
247         int minuendYear = iChronology.getYear(minuendInstant);
248         int minuendMonth = iChronology.getMonthOfYear(minuendInstant, minuendYear);
249         int subtrahendYear = iChronology.getYear(subtrahendInstant);
250         int subtrahendMonth = iChronology.getMonthOfYear(subtrahendInstant, subtrahendYear);
251 
252         long difference = (minuendYear - subtrahendYear) * ((long) iMax) + minuendMonth - subtrahendMonth;
253 
254         // Before adjusting for remainder, account for special case of add
255         // where the day-of-month is forced to the nearest sane value.
256         int minuendDom = iChronology.getDayOfMonth
257             (minuendInstant, minuendYear, minuendMonth);
258         if (minuendDom == iChronology.getDaysInYearMonth(minuendYear, minuendMonth)) {
259             // Last day of the minuend month...
260             int subtrahendDom = iChronology.getDayOfMonth
261                 (subtrahendInstant, subtrahendYear, subtrahendMonth);
262             if (subtrahendDom > minuendDom) {
263                 // ...and day of subtrahend month is larger.
264                 // Note: This works fine, but it ideally shouldn't invoke other
265                 // fields from within a field.
266                 subtrahendInstant = iChronology.dayOfMonth().set(subtrahendInstant, minuendDom);
267             }
268         }
269 
270         // Inlined remainder method to avoid duplicate calls.
271         long minuendRem = minuendInstant
272             - iChronology.getYearMonthMillis(minuendYear, minuendMonth);
273         long subtrahendRem = subtrahendInstant
274             - iChronology.getYearMonthMillis(subtrahendYear, subtrahendMonth);
275 
276         if (minuendRem < subtrahendRem) {
277             difference--;
278         }
279 
280         return difference;
281     }
282 
283     //-----------------------------------------------------------------------
284     /**
285      * Set the Month component of the specified time instant.<p>
286      * If the new month has less total days than the specified
287      * day of the month, this value is coerced to the nearest
288      * sane value. e.g.<p>
289      * 07-31 to month 6 = 06-30<p>
290      * 03-31 to month 2 = 02-28 or 02-29 depending<p>
291      * 
292      * @param instant  the time instant in millis to update.
293      * @param month  the month (1,12) to update the time to.
294      * @return the updated time instant.
295      * @throws IllegalArgumentException  if month is invalid
296      */
297     public long set(long instant, int month) {
298         FieldUtils.verifyValueBounds(this, month, MIN, iMax);
299         //
300         int thisYear = iChronology.getYear(instant);
301         //
302         int thisDom = iChronology.getDayOfMonth(instant, thisYear);
303         int maxDom = iChronology.getDaysInYearMonth(thisYear, month);
304         if (thisDom > maxDom) {
305             // Quietly force DOM to nearest sane value.
306             thisDom = maxDom;
307         }
308         // Return newly calculated millis value
309         return iChronology.getYearMonthDayMillis(thisYear, month, thisDom) +
310             iChronology.getMillisOfDay(instant);
311     }
312 
313     //-----------------------------------------------------------------------
314     public DurationField getRangeDurationField() {
315         return iChronology.years();
316     }
317 
318     //-----------------------------------------------------------------------
319     public boolean isLeap(long instant) {
320         int thisYear = iChronology.getYear(instant);
321         if (iChronology.isLeapYear(thisYear)) {
322             return (iChronology.getMonthOfYear(instant, thisYear) == iLeapMonth);
323         }
324         return false;
325     }
326 
327     //-----------------------------------------------------------------------
328     public int getLeapAmount(long instant) {
329         return isLeap(instant) ? 1 : 0;
330     }
331 
332     //-----------------------------------------------------------------------
333     public DurationField getLeapDurationField() {
334         return iChronology.days();
335     }
336 
337     //-----------------------------------------------------------------------
338     public int getMinimumValue() {
339         return MIN;
340     }
341 
342     //-----------------------------------------------------------------------
343     public int getMaximumValue() {
344         return iMax;
345     }
346 
347     //-----------------------------------------------------------------------
348     public long roundFloor(long instant) {
349         int year = iChronology.getYear(instant);
350         int month = iChronology.getMonthOfYear(instant, year);
351         return iChronology.getYearMonthMillis(year, month);
352     }
353 
354     //-----------------------------------------------------------------------
355     public long remainder(long instant) {
356         return instant - roundFloor(instant);
357     }
358 
359     //-----------------------------------------------------------------------
360     /**
361      * Serialization singleton
362      */
363     private Object readResolve() {
364         return iChronology.monthOfYear();
365     }
366 }