|
1 package hirondelle.web4j.model; |
|
2 |
|
3 import static hirondelle.web4j.model.DateTime.DayOverflow; |
|
4 import static hirondelle.web4j.model.DateTime.Unit; |
|
5 |
|
6 /** |
|
7 Helper class for adding intervals of time. |
|
8 The mental model of this class is similar to that of a car's odometer, except |
|
9 in reverse. |
|
10 */ |
|
11 final class DateTimeInterval { |
|
12 |
|
13 /** Constructor. */ |
|
14 DateTimeInterval(DateTime aFrom, DayOverflow aMonthOverflow){ |
|
15 fFrom = aFrom; |
|
16 checkUnits(); |
|
17 fYear = fFrom.getYear() == null ? 1 : fFrom.getYear(); |
|
18 fMonth = fFrom.getMonth() == null ? 1 : fFrom.getMonth(); |
|
19 fDay = fFrom.getDay() == null ? 1 : fFrom.getDay(); |
|
20 fHour = fFrom.getHour() == null ? 0 : fFrom.getHour(); |
|
21 fMinute = fFrom.getMinute() == null ? 0 : fFrom.getMinute(); |
|
22 fSecond = fFrom.getSecond() == null ? 0 : fFrom.getSecond(); |
|
23 fNanosecond = fFrom.getNanoseconds() == null ? 0 : fFrom.getNanoseconds(); |
|
24 fDayOverflow = aMonthOverflow; |
|
25 } |
|
26 |
|
27 DateTime plus(int aYear, int aMonth, int aDay, int aHour, int aMinute, int aSecond, int aNanosecond){ |
|
28 return plusOrMinus(PLUS, aYear, aMonth, aDay, aHour, aMinute, aSecond, aNanosecond); |
|
29 } |
|
30 |
|
31 DateTime minus(int aYear, int aMonth, int aDay, int aHour, int aMinute, int aSecond, int aNanosecond){ |
|
32 return plusOrMinus(MINUS, aYear, aMonth, aDay, aHour, aMinute, aSecond, aNanosecond); |
|
33 } |
|
34 |
|
35 // PRIVATE |
|
36 |
|
37 //the base date to which the interval is calculated |
|
38 private final DateTime fFrom; |
|
39 |
|
40 private boolean fIsPlus; |
|
41 private DateTime.DayOverflow fDayOverflow; |
|
42 |
|
43 //the various increments |
|
44 private int fYearIncr; |
|
45 private int fMonthIncr; |
|
46 private int fDayIncr; |
|
47 private int fHourIncr; |
|
48 private int fMinuteIncr; |
|
49 private int fSecondIncr; |
|
50 private int fNanosecondIncr; |
|
51 |
|
52 //work area for the final result - starts off with values from base date fFrom |
|
53 private Integer fYear; |
|
54 private Integer fMonth; |
|
55 private Integer fDay; |
|
56 private Integer fHour; |
|
57 private Integer fMinute; |
|
58 private Integer fSecond; |
|
59 private Integer fNanosecond; |
|
60 |
|
61 private static final int MIN = 0; |
|
62 private static final int MAX = 9999; |
|
63 private static final int MIN_NANOS = 0; |
|
64 private static final int MAX_NANOS = 999999999; |
|
65 private static final boolean PLUS = true; |
|
66 private static final boolean MINUS = false; |
|
67 |
|
68 private void checkUnits(){ |
|
69 boolean success = false; |
|
70 if(fFrom.unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.HOUR, Unit.MINUTE, Unit.SECOND) ){ |
|
71 success = true; |
|
72 } |
|
73 else if( fFrom.unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY) && fFrom.unitsAllAbsent(Unit.HOUR, Unit.MINUTE, Unit.SECOND) ){ |
|
74 success = true; |
|
75 } |
|
76 else if ( fFrom.unitsAllAbsent(Unit.YEAR, Unit.MONTH, Unit.DAY) && fFrom.unitsAllPresent(Unit.HOUR, Unit.MINUTE, Unit.SECOND) ){ |
|
77 success = true; |
|
78 } |
|
79 else { |
|
80 success = false; |
|
81 } |
|
82 if(! success ){ |
|
83 throw new IllegalArgumentException("For interval calculations, DateTime must have year-month-day, or hour-minute-second, or both."); |
|
84 } |
|
85 } |
|
86 |
|
87 private DateTime plusOrMinus(boolean aIsPlus, Integer aYear, Integer aMonth, Integer aDay, Integer aHour, Integer aMinute, Integer aSecond, Integer aNanosecond){ |
|
88 fIsPlus = aIsPlus; |
|
89 fYearIncr = aYear; |
|
90 fMonthIncr = aMonth; |
|
91 fDayIncr = aDay; |
|
92 fHourIncr = aHour; |
|
93 fMinuteIncr = aMinute; |
|
94 fSecondIncr = aSecond; |
|
95 fNanosecondIncr = aNanosecond; |
|
96 |
|
97 checkRange(fYearIncr, "Year"); |
|
98 checkRange(fMonthIncr, "Month"); |
|
99 checkRange(fDayIncr, "Day"); |
|
100 checkRange(fHourIncr, "Hour"); |
|
101 checkRange(fMinuteIncr, "Minute"); |
|
102 checkRange(fSecondIncr, "Second"); |
|
103 checkRangeNanos(fNanosecondIncr); |
|
104 |
|
105 changeYear(); |
|
106 changeMonth(); |
|
107 handleMonthOverflow(); |
|
108 changeDay(); |
|
109 changeHour(); |
|
110 changeMinute(); |
|
111 changeSecond(); |
|
112 changeNanosecond(); |
|
113 |
|
114 return new DateTime(fYear, fMonth, fDay, fHour, fMinute, fSecond, fNanosecond); |
|
115 } |
|
116 |
|
117 private void checkRange(Integer aValue, String aName) { |
|
118 if ( aValue < MIN || aValue > MAX ) { |
|
119 throw new IllegalArgumentException(aName + " is not in the range " + MIN + ".." + MAX); |
|
120 } |
|
121 } |
|
122 |
|
123 private void checkRangeNanos(Integer aValue) { |
|
124 if ( aValue < MIN_NANOS || aValue > MAX_NANOS ) { |
|
125 throw new IllegalArgumentException("Nanosecond interval is not in the range " + MIN_NANOS + ".." + MAX_NANOS); |
|
126 } |
|
127 } |
|
128 |
|
129 private void changeYear(){ |
|
130 if(fIsPlus){ |
|
131 fYear = fYear + fYearIncr; |
|
132 } |
|
133 else { |
|
134 fYear = fFrom.getYear() - fYearIncr; |
|
135 } |
|
136 //the DateTime ctor will check the range of the year |
|
137 } |
|
138 |
|
139 private void changeMonth(){ |
|
140 int count = 0; |
|
141 while (count < fMonthIncr){ |
|
142 stepMonth(); |
|
143 count++; |
|
144 } |
|
145 } |
|
146 |
|
147 private void changeDay(){ |
|
148 int count = 0; |
|
149 while (count < fDayIncr){ |
|
150 stepDay(); |
|
151 count++; |
|
152 } |
|
153 } |
|
154 |
|
155 private void changeHour(){ |
|
156 int count = 0; |
|
157 while (count < fHourIncr){ |
|
158 stepHour(); |
|
159 count++; |
|
160 } |
|
161 } |
|
162 |
|
163 private void changeMinute(){ |
|
164 int count = 0; |
|
165 while (count < fMinuteIncr){ |
|
166 stepMinute(); |
|
167 count++; |
|
168 } |
|
169 } |
|
170 |
|
171 private void changeSecond(){ |
|
172 int count = 0; |
|
173 while (count < fSecondIncr){ |
|
174 stepSecond(); |
|
175 count++; |
|
176 } |
|
177 } |
|
178 |
|
179 /** |
|
180 Nanos are different from other items. They don't cycle one step at a time. |
|
181 They are just added. If they under/over flow, then extra math is performed. |
|
182 They don't over/under by more than 1 second, since the size of the increment is limited. |
|
183 */ |
|
184 private void changeNanosecond(){ |
|
185 if (fIsPlus){ |
|
186 fNanosecond = fNanosecond + fNanosecondIncr; |
|
187 } |
|
188 else { |
|
189 fNanosecond = fNanosecond - fNanosecondIncr; |
|
190 } |
|
191 if(fNanosecond > MAX_NANOS){ |
|
192 stepSecond(); |
|
193 fNanosecond = fNanosecond - MAX_NANOS - 1; |
|
194 } |
|
195 else if (fNanosecond < MIN_NANOS){ |
|
196 stepSecond(); |
|
197 fNanosecond = MAX_NANOS + fNanosecond + 1; |
|
198 } |
|
199 } |
|
200 |
|
201 private void stepYear() { |
|
202 if(fIsPlus) { |
|
203 fYear = fYear + 1; |
|
204 } |
|
205 else { |
|
206 fYear = fYear - 1; |
|
207 } |
|
208 } |
|
209 |
|
210 private void stepMonth() { |
|
211 if(fIsPlus){ |
|
212 fMonth = fMonth + 1; |
|
213 } |
|
214 else { |
|
215 fMonth = fMonth - 1; |
|
216 } |
|
217 if(fMonth > 12) { |
|
218 fMonth = 1; |
|
219 stepYear(); |
|
220 } |
|
221 else if(fMonth < 1){ |
|
222 fMonth = 12; |
|
223 stepYear(); |
|
224 } |
|
225 } |
|
226 |
|
227 private void stepDay() { |
|
228 if(fIsPlus){ |
|
229 fDay = fDay + 1; |
|
230 } |
|
231 else { |
|
232 fDay = fDay - 1; |
|
233 } |
|
234 if(fDay > numDaysInMonth()){ |
|
235 fDay = 1; |
|
236 stepMonth(); |
|
237 } |
|
238 else if (fDay < 1){ |
|
239 fDay = numDaysInPreviousMonth(); |
|
240 stepMonth(); |
|
241 } |
|
242 } |
|
243 |
|
244 private int numDaysInMonth(){ |
|
245 return DateTime.getNumDaysInMonth(fYear, fMonth); |
|
246 } |
|
247 |
|
248 private int numDaysInPreviousMonth(){ |
|
249 int result = 0; |
|
250 if(fMonth > 1) { |
|
251 result = DateTime.getNumDaysInMonth(fYear, fMonth - 1); |
|
252 } |
|
253 else { |
|
254 result = DateTime.getNumDaysInMonth(fYear - 1 , 12); |
|
255 } |
|
256 return result; |
|
257 } |
|
258 |
|
259 private void stepHour() { |
|
260 if(fIsPlus){ |
|
261 fHour = fHour + 1; |
|
262 } |
|
263 else { |
|
264 fHour = fHour - 1; |
|
265 } |
|
266 if(fHour > 23){ |
|
267 fHour = 0; |
|
268 stepDay(); |
|
269 } |
|
270 else if (fHour < 0){ |
|
271 fHour = 23; |
|
272 stepDay(); |
|
273 } |
|
274 } |
|
275 |
|
276 private void stepMinute() { |
|
277 if(fIsPlus){ |
|
278 fMinute = fMinute + 1; |
|
279 } |
|
280 else { |
|
281 fMinute = fMinute - 1; |
|
282 } |
|
283 if(fMinute > 59){ |
|
284 fMinute = 0; |
|
285 stepHour(); |
|
286 } |
|
287 else if (fMinute < 0){ |
|
288 fMinute = 59; |
|
289 stepHour(); |
|
290 } |
|
291 } |
|
292 |
|
293 private void stepSecond() { |
|
294 if(fIsPlus){ |
|
295 fSecond = fSecond + 1; |
|
296 } |
|
297 else { |
|
298 fSecond = fSecond - 1; |
|
299 } |
|
300 if (fSecond > 59){ |
|
301 fSecond = 0; |
|
302 stepMinute(); |
|
303 } |
|
304 else if (fSecond < 0){ |
|
305 fSecond = 59; |
|
306 stepMinute(); |
|
307 } |
|
308 } |
|
309 |
|
310 private void handleMonthOverflow(){ |
|
311 int daysInMonth = numDaysInMonth(); |
|
312 if( fDay > daysInMonth ){ |
|
313 if(DayOverflow.Abort == fDayOverflow) { |
|
314 throw new RuntimeException( |
|
315 "Day Overflow: Year:" + fYear + " Month:" + fMonth + " has " + daysInMonth + " days, but day has value:" + fDay + |
|
316 " To avoid these exceptions, please specify a different DayOverflow policy." |
|
317 ); |
|
318 } |
|
319 else if (DayOverflow.FirstDay == fDayOverflow) { |
|
320 fDay = 1; |
|
321 stepMonth(); |
|
322 } |
|
323 else if (DayOverflow.LastDay == fDayOverflow) { |
|
324 fDay = daysInMonth; |
|
325 } |
|
326 else if (DayOverflow.Spillover == fDayOverflow) { |
|
327 int overflowAmount = fDay - daysInMonth; |
|
328 fDay = overflowAmount; |
|
329 stepMonth(); |
|
330 } |
|
331 } |
|
332 } |
|
333 |
|
334 |
|
335 } |