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.chrono;
17
18 import org.joda.time.DateTimeConstants;
19 import org.joda.time.DateTimeFieldType;
20 import org.joda.time.DurationField;
21 import org.joda.time.field.FieldUtils;
22 import org.joda.time.field.ImpreciseDateTimeField;
23
24 /**
25  * Provides time calculations for the week of the weekyear component of time.
26  *
27  * @author Guy Allard
28  * @author Stephen Colebourne
29  * @author Brian S O'Neill
30  * @since 1.1, refactored from GJWeekyearDateTimeField
31  */

32 final class BasicWeekyearDateTimeField extends ImpreciseDateTimeField {
33     
34     @SuppressWarnings("unused")
35     private static final long serialVersionUID = 6215066916806820644L;
36
37     private static final long WEEK_53 = (53L - 1) * DateTimeConstants.MILLIS_PER_WEEK;
38
39     private final BasicChronology iChronology;
40
41     /**
42      * Restricted constructor
43      */

44     BasicWeekyearDateTimeField(BasicChronology chronology) {
45         super(DateTimeFieldType.weekyear(), chronology.getAverageMillisPerYear());
46         iChronology = chronology;
47     }
48
49     public boolean isLenient() {
50         return false;
51     }
52
53     /**
54      * Get the Year of a week based year component of the specified time instant.
55      * 
56      * @see org.joda.time.DateTimeField#get
57      * @param instant  the time instant in millis to query.
58      * @return the year extracted from the input.
59      */

60     public int get(long instant) {
61         return iChronology.getWeekyear(instant);
62     }
63
64     /**
65      * Add the specified years to the specified time instant.
66      * 
67      * @see org.joda.time.DateTimeField#add
68      * @param instant  the time instant in millis to update.
69      * @param years  the years to add (can be negative).
70      * @return the updated time instant.
71      */

72     public long add(long instant, int years) {
73         if (years == 0) {
74             return instant;
75         }
76         return set(instant, get(instant) + years);
77     }
78
79     public long add(long instant, long value) {
80         return add(instant, FieldUtils.safeToInt(value));
81     }
82
83     /**
84      * Add to the year component of the specified time instant
85      * wrapping around within that component if necessary.
86      * 
87      * @see org.joda.time.DateTimeField#addWrapField
88      * @param instant  the time instant in millis to update.
89      * @param years  the years to add (can be negative).
90      * @return the updated time instant.
91      */

92     public long addWrapField(long instant, int years) {
93         return add(instant, years);
94     }
95
96     public long getDifferenceAsLong(long minuendInstant, long subtrahendInstant) {
97         if (minuendInstant < subtrahendInstant) {
98             return -getDifference(subtrahendInstant, minuendInstant);
99         }
100
101         int minuendWeekyear = get(minuendInstant);
102         int subtrahendWeekyear = get(subtrahendInstant);
103
104         long minuendRem = remainder(minuendInstant);
105         long subtrahendRem = remainder(subtrahendInstant);
106
107         // Balance leap weekyear differences on remainders.
108         if (subtrahendRem >= WEEK_53 && iChronology.getWeeksInYear(minuendWeekyear) <= 52) {
109             subtrahendRem -= DateTimeConstants.MILLIS_PER_WEEK;
110         }
111
112         int difference = minuendWeekyear - subtrahendWeekyear;
113         if (minuendRem < subtrahendRem) {
114             difference--;
115         }
116         return difference;
117     }
118
119     /**
120      * Set the Year of a week based year component of the specified time instant.
121      *
122      * @see org.joda.time.DateTimeField#set
123      * @param instant  the time instant in millis to update.
124      * @param year  the year (-9999,9999) to set the date to.
125      * @return the updated DateTime.
126      * @throws IllegalArgumentException  if year is invalid.
127      */

128     public long set(long instant, int year) {
129         FieldUtils.verifyValueBounds(this, Math.abs(year),
130                                      iChronology.getMinYear(), iChronology.getMaxYear());
131         //
132         // Do nothing if no real change is requested.
133         //
134         int thisWeekyear = get( instant );
135         if ( thisWeekyear == year ) {
136             return instant;
137         }
138         //
139         // Calculate the DayOfWeek (to be preserved).
140         //
141         int thisDow = iChronology.getDayOfWeek(instant);
142         //
143         // Calculate the maximum weeks in the target year.
144         //
145         int weeksInFromYear = iChronology.getWeeksInYear( thisWeekyear );
146         int weeksInToYear = iChronology.getWeeksInYear( year );
147         int maxOutWeeks = (weeksInToYear < weeksInFromYear) ?
148             weeksInToYear : weeksInFromYear;
149         //
150         // Get the current week of the year. This will be preserved in
151         // the output unless it is greater than the maximum possible
152         // for the target weekyear.  In that case it is adjusted
153         // to the maximum possible.
154         //
155         int setToWeek = iChronology.getWeekOfWeekyear(instant);
156         if ( setToWeek > maxOutWeeks ) {
157             setToWeek = maxOutWeeks;
158         }
159         //
160         // Get a wroking copy of the current date-time.
161         // This can be a convenience for debugging.
162         //
163         long workInstant = instant; // Get a copy
164         //
165         // Attempt to get close to the proper weekyear.
166         // Note - we cannot currently call ourself, so we just call
167         // set for the year.  This at least gets us close.
168         //
169         workInstant = iChronology.setYear( workInstant, year );
170         //
171         // Calculate the weekyear number for the get close to value
172         // (which might not be equal to the year just set).
173         //
174         int workWoyYear = get( workInstant );
175
176         //
177         // At most we are off by one year, which can be "fixed" by
178         // adding/subtracting a week.
179         //
180         if ( workWoyYear < year ) {
181             workInstant += DateTimeConstants.MILLIS_PER_WEEK;
182         } else if ( workWoyYear > year ) {
183             workInstant -= DateTimeConstants.MILLIS_PER_WEEK;
184         }
185         //
186         // Set the proper week in the current weekyear.
187         //
188
189         // BEGIN: possible set WeekOfWeekyear logic.
190         int currentWoyWeek = iChronology.getWeekOfWeekyear(workInstant);
191         // No range check required (we already know it is OK).
192         workInstant = workInstant + (setToWeek - currentWoyWeek)
193             * (long)DateTimeConstants.MILLIS_PER_WEEK;
194         // END: possible set WeekOfWeekyear logic.
195
196         //
197         // Reset DayOfWeek to previous value.
198         //
199         // Note: This works fine, but it ideally shouldn't invoke other
200         // fields from within a field.
201         workInstant = iChronology.dayOfWeek().set( workInstant, thisDow );
202         //
203         // Return result.
204         //
205         return workInstant;
206     }
207
208     public DurationField getRangeDurationField() {
209         return null;
210     }
211
212     public boolean isLeap(long instant) {
213         return iChronology.getWeeksInYear(iChronology.getWeekyear(instant)) > 52;
214     }
215
216     public int getLeapAmount(long instant) {
217         return iChronology.getWeeksInYear(iChronology.getWeekyear(instant)) - 52;
218     }
219
220     public DurationField getLeapDurationField() {
221         return iChronology.weeks();
222     }
223
224     public int getMinimumValue() {
225         return iChronology.getMinYear();
226     }
227
228     public int getMaximumValue() {
229         return iChronology.getMaxYear();
230     }
231
232     public long roundFloor(long instant) {
233         // Note: This works fine, but it ideally shouldn't invoke other
234         // fields from within a field.
235         instant = iChronology.weekOfWeekyear().roundFloor(instant);
236         int wow = iChronology.getWeekOfWeekyear(instant);
237         if (wow > 1) {
238             instant -= ((long) DateTimeConstants.MILLIS_PER_WEEK) * (wow - 1);
239         }
240         return instant;
241     }
242
243     public long remainder(long instant) {
244         return instant - roundFloor(instant);
245     }
246
247     /**
248      * Serialization singleton
249      */

250     private Object readResolve() {
251         return iChronology.weekyear();
252     }
253 }
254