View Javadoc

1   /*
2    *  File: HolidayEnumeratorBase.java 
3    *  Copyright (c) 2004-2007  Peter Kliem (Peter.Kliem@jaret.de)
4    *  A commercial license is available, see http://www.jaret.de.
5    *
6    * All rights reserved. This program and the accompanying materials
7    * are made available under the terms of the Common Public License v1.0
8    * which accompanies this distribution, and is available at
9    * http://www.eclipse.org/legal/cpl-v10.html
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         // add dayOffset
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         // System.out.println("y:"+year+" m:"+month+" d:"+day+"("+d+")");
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             // Collections.sort(result);
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; // Korrektur Schaltjahr
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 }