1
2
3
4
5
6
7
8
9
10
11 package de.jaret.util.date.holidayenumerator;
12
13 import java.util.ArrayList;
14 import java.util.Calendar;
15 import java.util.Date;
16 import java.util.GregorianCalendar;
17 import java.util.HashMap;
18 import java.util.List;
19 import java.util.Locale;
20 import java.util.Map;
21
22 /***
23 * Base implementation for HolidayEnumerators. This abstract class contains a complete base implementation and some date
24 * calculation code (easter and other). Concrete HolidayEnumertaors have to implement the method
25 * <code>fillMap(int year)</code> to fill all hollidays (and special days). To add a day the method
26 * <code>addNamedDate</code> should be used. Attention has to be taken: all month values start with 0 (january = 0)
27 *
28 * @author Peter Kliem
29 * @version $Id: HolidayEnumeratorBase.java 297 2007-03-12 21:38:00Z olk $
30 */
31 public abstract class HolidayEnumeratorBase implements HolidayEnumerator {
32 /*** map containing the holidays and special days. */
33 protected Map<Integer, Map<Integer, Map<Integer, NamedDate>>> _yearMap = new HashMap<Integer, Map<Integer, Map<Integer, NamedDate>>>();
34 /*** calendar instance for calculations. */
35 protected static final Calendar CALENDAR = new GregorianCalendar();
36 /*** locale. */
37 protected Locale _locale;
38 /*** region id. */
39 protected String _regionId;
40
41
42
43
44 /***
45 * Calculate the holidays for a given year. This method has to be implemented by concrete implementations of
46 * HolidayEnumerators.
47 *
48 * @param year year to calculate the holidays for
49 */
50 protected abstract void fillMap(int year);
51
52 /***
53 * {@inheritDoc} Default implementation returns en empty array.
54 */
55 public String[] getAvailableRegionIds() {
56 return new String[0];
57 }
58
59 /***
60 * {@inheritDoc }toString delivers the region.
61 */
62 public String toString() {
63 return _regionId != null ? _regionId : "NONE";
64 }
65
66 /***
67 * {@inheritDoc}. Two hes are qual if loale and region match.
68 */
69 public boolean equals(Object obj) {
70 if (obj == null) {
71 return false;
72 }
73 if (!(obj instanceof HolidayEnumerator)) {
74 return false;
75 }
76 HolidayEnumerator he = (HolidayEnumerator) obj;
77
78 if (!_locale.equals(he.getLocale())) {
79 return false;
80 }
81 if (_regionId == null && he.getRegionId() == null) {
82 return true;
83 }
84 if (_regionId != null) {
85 return _regionId.equals(he.getRegionId());
86 }
87 if (he.getRegionId() != null) {
88 return he.getRegionId().equals(_regionId);
89 }
90 return false;
91 }
92
93 @Override
94 public int hashCode() {
95 return _locale.hashCode() + 13 * (_regionId != null ? _regionId.hashCode() : 0);
96 }
97
98 /***
99 * Do initializations.
100 */
101 protected void init() {
102 _yearMap = new HashMap<Integer, Map<Integer, Map<Integer, NamedDate>>>();
103 }
104
105 private NamedDate getNamedDate(int year, int month, int day) {
106 if (_yearMap == null) {
107 init();
108 }
109 Map<Integer, Map<Integer, NamedDate>> yMap = _yearMap.get(year);
110 if (yMap == null) {
111 fillMap(year);
112 yMap = _yearMap.get(year);
113 }
114 Map<Integer, NamedDate> mMap = yMap.get(month);
115 if (mMap != null) {
116 return mMap.get(day);
117 }
118 return null;
119 }
120
121 /***
122 * Add a named date to the list of holidays.
123 *
124 * @param year year
125 * @param month (0= January!)
126 * @param day day
127 * @param isHoliday true for holiday, false = special day
128 * @param name name of the date
129 */
130 protected void addNamedDate(int year, int month, int day, boolean isHoliday, String name) {
131 Map<Integer, Map<Integer, NamedDate>> yMap = _yearMap.get(year);
132 if (yMap == null) {
133 yMap = new HashMap<Integer, Map<Integer, NamedDate>>();
134 _yearMap.put(year, yMap);
135 }
136 Map<Integer, NamedDate> mMap = yMap.get(month);
137 if (mMap == null) {
138 mMap = new HashMap<Integer, NamedDate>();
139 yMap.put(month, mMap);
140 }
141 Date date = getDate(year, month, day, 0);
142 NamedDate namedDate = new NamedDateImpl(date, name, isHoliday);
143 mMap.put(day, namedDate);
144 }
145
146 protected void addNamedDate(int year, int month, int day, int dayOffset, boolean isHoliday, String name) {
147 Date date = getDate(year, month, day, dayOffset);
148 EasyDate ed = new EasyDate(date);
149 year = ed.year;
150 month = ed.month;
151 day = ed.day;
152 addNamedDate(year, month, day, isHoliday, name);
153 }
154
155 protected void addNamedDate(EasyDate ed, boolean isHoliday, String name) {
156 int year = ed.year;
157 int month = ed.month;
158 int day = ed.day;
159 addNamedDate(year, month, day, isHoliday, name);
160 }
161
162 protected Date getDate(int year, int month, int day, int dayOffset) {
163 CALENDAR.set(Calendar.YEAR, year);
164 CALENDAR.set(Calendar.MONTH, month);
165 CALENDAR.set(Calendar.DAY_OF_MONTH, day);
166 CALENDAR.set(Calendar.HOUR_OF_DAY, 0);
167 CALENDAR.set(Calendar.MINUTE, 0);
168 CALENDAR.set(Calendar.SECOND, 0);
169 CALENDAR.set(Calendar.MILLISECOND, 0);
170
171 CALENDAR.add(Calendar.DAY_OF_YEAR, dayOffset);
172 return CALENDAR.getTime();
173 }
174
175 protected int getWeekday(Date date) {
176 CALENDAR.setTime(date);
177 return CALENDAR.get(Calendar.DAY_OF_WEEK);
178 }
179
180 /***
181 * {@inheritDoc}
182 */
183 public boolean isHoliday(Date date) {
184 EasyDate ed = new EasyDate(date);
185 NamedDate d = getNamedDate(ed.year, ed.month, ed.day);
186
187 if (d == null) {
188 return false;
189 }
190 return d.isHoliday();
191 }
192
193 /***
194 * {@inheritDoc}
195 */
196 public boolean isSpecialDay(Date date) {
197 EasyDate ed = new EasyDate(date);
198 NamedDate d = getNamedDate(ed.year, ed.month, ed.day);
199 if (d == null) {
200 return false;
201 }
202 return !d.isHoliday();
203 }
204
205 /***
206 * {@inheritDoc}
207 */
208 public String getDayName(Date date) {
209 EasyDate ed = new EasyDate(date);
210 NamedDate d = getNamedDate(ed.year, ed.month, ed.day);
211 if (d == null) {
212 return null;
213 }
214 return d.getName();
215 }
216
217 /***
218 * {@inheritDoc}
219 */
220 public Locale getLocale() {
221 return _locale;
222 }
223
224 /***
225 * {@inheritDoc}
226 */
227 public void setLocale(Locale locale) {
228 _locale = locale;
229 init();
230 }
231
232 /***
233 * {@inheritDoc}
234 */
235 public String getRegionId() {
236 return _regionId;
237 }
238
239 /***
240 * {@inheritDoc}
241 */
242 public void setRegionId(String regionId) {
243 _regionId = regionId;
244 init();
245 }
246
247 /***
248 * {@inheritDoc}
249 */
250 public List<NamedDate> getNamedDays(int year, boolean includeSpecialDays) {
251 List<NamedDate> result = new ArrayList<NamedDate>();
252 for (int month = 0; month < 12; month++) {
253 result.addAll(getNamedDays(year, month, includeSpecialDays));
254 }
255 return result;
256 }
257
258 /***
259 * {@inheritDoc}
260 */
261 public List<NamedDate> getNamedDays(int year, int month, boolean includeSpecialDays) {
262 List<NamedDate> result = new ArrayList<NamedDate>();
263 if (_yearMap == null) {
264 init();
265 }
266 Map<Integer, Map<Integer, NamedDate>> yMap = _yearMap.get(year);
267 if (yMap == null) {
268 fillMap(year);
269 yMap = _yearMap.get(year);
270 }
271 Map<Integer, NamedDate> mMap = yMap.get(month);
272 if (mMap != null) {
273 for (Integer day : mMap.keySet()) {
274 NamedDate d = mMap.get(day);
275 if (d.isHoliday() || includeSpecialDays) {
276 result.add(d);
277 }
278 }
279
280 }
281 return result;
282 }
283
284 /***
285 * Calculate the easter date (sunday) for a given year. This method was supplied by Marita Gierlings.
286 *
287 * @author Peter Kliem
288 *
289 * @param year year (valid input from 1583 to 2499)
290 * @return an Easydate indicating easter sunday.
291 */
292 public EasyDate calcEaster(int year) {
293 if (year < 1583 || year > 2499) {
294 throw new IllegalArgumentException("Valid input from 1583 to 2499");
295 }
296 int jahr = year;
297 int monat = -1;
298 int tag = -1;
299 int M = 0;
300 int N = 0;
301 if (1583 <= jahr && jahr <= 1699) {
302 M = 22;
303 N = 2;
304 } else if (1700 <= jahr && jahr <= 1799) {
305 M = 23;
306 N = 3;
307 } else if (1800 <= jahr && jahr <= 1899) {
308 M = 23;
309 N = 4;
310 } else if (1900 <= jahr && jahr <= 2099) {
311 M = 24;
312 N = 5;
313 } else if (2100 <= jahr && jahr <= 2199) {
314 M = 24;
315 N = 6;
316 } else if (2200 <= jahr && jahr <= 2299) {
317 M = 25;
318 N = 0;
319 } else if (2300 <= jahr && jahr <= 2499) {
320 M = 26;
321 N = 1;
322 } else if (2400 <= jahr && jahr <= 2499) {
323 M = 25;
324 N = 1;
325 }
326 int a = jahr % 19;
327 int b = jahr % 4;
328 int c = jahr % 7;
329 int d = (19 * a + M) % 30;
330 int e = (2 * b + 4 * c + 6 * d + N) % 7;
331
332 if ((d + e) <= 9) {
333 int easter = 22 + d + e;
334 monat = 2;
335 tag = easter;
336 } else if ((d + e) > 9) {
337 if (d + e - 9 == 26) {
338 int easter = 19;
339 monat = 3;
340 tag = easter;
341 } else if ((d + e - 9) == 25 && d == 28 && a > 10) {
342 int easter = 18;
343 monat = 3;
344 tag = easter;
345 } else {
346 int easter = d + e - 9;
347 monat = 3;
348 tag = easter;
349 }
350 }
351 if (monat == -1) {
352 throw new RuntimeException("??? Error in easter calculation");
353 }
354 return new EasyDate(jahr, monat, tag);
355 }
356
357 /***
358 * Calculate the nTh weekday in a month (i.e. the 3rd Monday in october)
359 *
360 * @param year year
361 * @param month month (starting with january = 0)
362 * @param weekday weekday as constant from java.util.Calendar
363 * @param nTh first=1, ... max 5
364 * @return date as EasyDate or null if not found
365 */
366 public EasyDate nThWeekdayInMonth(int year, int month, int weekday, int nTh) {
367 if (nTh > 4 || nTh < 0) {
368 throw new IllegalArgumentException("nTh must be within 1..4");
369 }
370 EasyDate result = null;
371 int i = 0;
372 int n = 0;
373 int daysInMonth = daysInMonth(year, month);
374 while (i < daysInMonth && result == null) {
375 Date d = getDate(year, month, 1, i);
376 if (getWeekday(d) == weekday) {
377 n++;
378 if (n == nTh) {
379 result = new EasyDate(d);
380 }
381 }
382 i++;
383 }
384 return result;
385 }
386
387 /***
388 * Calculate the last weekday in a given month.
389 *
390 * @param year year
391 * @param month (January=0!)
392 * @param weekday constant from java.util.Calendar
393 * @return EasyDate
394 */
395 public EasyDate lastWeekdayInMonth(int year, int month, int weekday) {
396 EasyDate result = null;
397 int i = 0;
398 int daysInMonth = daysInMonth(year, month);
399 while (i < daysInMonth) {
400 Date d = getDate(year, month, 1, i);
401 if (getWeekday(d) == weekday) {
402 result = new EasyDate(d);
403 }
404 i++;
405 }
406 return result;
407 }
408
409 /***
410 * If the given Date is a saturday return the friday before, if it is a sunday return the monday after, in all other
411 * cases return <code>null</code>. Used for correction of US holidays.
412 *
413 * @param ed date to go from
414 * @return shifted date or null
415 */
416 public EasyDate fridayOrMonday(EasyDate ed) {
417 if (getWeekday(ed.date) == Calendar.SATURDAY) {
418 return new EasyDate(getDate(ed.year, ed.month, ed.day, -1));
419 } else if (getWeekday(ed.date) == Calendar.SUNDAY) {
420 return new EasyDate(getDate(ed.year, ed.month, ed.day, 1));
421 } else {
422 return null;
423 }
424 }
425
426 protected int daysInMonth(int year, int month) {
427 Date d = getDate(year, month, 1, 0);
428 CALENDAR.setTime(d);
429 return CALENDAR.getMaximum(Calendar.DAY_OF_MONTH);
430 }
431
432 public EasyDate getEasyDate(int year, int month, int day) {
433 return new EasyDate(year, month, day);
434 }
435
436 /***
437 * Simple holder class for a split date.
438 *
439 * @author Peter Kliem
440 * @version $Id: HolidayEnumeratorBase.java 297 2007-03-12 21:38:00Z olk $
441 */
442 public class EasyDate {
443 public int year;
444 public int month;
445 public int day;
446 public Date date;
447
448 public EasyDate(int year, int month, int day) {
449 this.year = year;
450 this.month = month;
451 this.day = day;
452 this.date = getDate(year, month, day, 0);
453 }
454
455 public EasyDate(Date date) {
456 CALENDAR.setTime(date);
457 this.date = date;
458 year = CALENDAR.get(Calendar.YEAR);
459 month = CALENDAR.get(Calendar.MONTH);
460 day = CALENDAR.get(Calendar.DAY_OF_MONTH);
461 }
462
463 public boolean equals(int year, int month, int day) {
464 return year == this.year && month == this.month && day == this.day;
465 }
466
467 }
468
469 /***
470 * Implementation of the NamedDate for use with holiday enumerators.
471 *
472 * @author Peter Kliem
473 * @version $Id: HolidayEnumeratorBase.java 297 2007-03-12 21:38:00Z olk $
474 */
475 public class NamedDateImpl implements NamedDate, Comparable<NamedDate> {
476 private String _name;
477 private Date _date;
478 private boolean _holiday;
479
480 public NamedDateImpl(Date date, String name, boolean holiday) {
481 if (date == null) {
482 throw new RuntimeException("date may not be null");
483 }
484 _date = date;
485 _name = name;
486 _holiday = holiday;
487 }
488
489 /***
490 * {@inheritDoc}
491 */
492 public String getName() {
493 return _name;
494 }
495
496 /***
497 * {@inheritDoc}
498 */
499 public Date getDate() {
500 return _date;
501 }
502
503 /***
504 * {@inheritDoc}
505 */
506 public boolean isHoliday() {
507 return _holiday;
508 }
509
510 /***
511 * {@inheritDoc}
512 */
513 public boolean equals(Object obj) {
514 return _date.equals(obj);
515 }
516
517 /***
518 * {@inheritDoc}
519 */
520 public int hashCode() {
521 return _date.hashCode();
522 }
523
524 /***
525 * {@inheritDoc}
526 */
527 public String toString() {
528 return "NamedDateImpl[date=" + _date + ";name=" + _name + ";isHoliday=" + _holiday + "]";
529 }
530
531 /***
532 * {@inheritDoc}
533 */
534 public int compareTo(NamedDate date) {
535 return _date.compareTo((date.getDate()));
536 }
537 }
538 }