1/* 2 ******************************************************************************* 3 * Copyright (C) 1996-2014, International Business Machines Corporation and * 4 * others. All Rights Reserved. * 5 ******************************************************************************* 6 */ 7 8package com.ibm.icu.util; 9 10import java.util.Date; 11 12/** 13 * <b>Note:</b> The Holiday framework is a technology preview. 14 * Despite its age, is still draft API, and clients should treat it as such. 15 * 16 * A Holiday subclass which represents holidays that occur 17 * a fixed number of days before or after Easter. Supports both the 18 * Western and Orthodox methods for calculating Easter. 19 * @draft ICU 2.8 (retainAll) 20 * @provisional This API might change or be removed in a future release. 21 */ 22public class EasterHoliday extends Holiday 23{ 24 /** 25 * Construct a holiday that falls on Easter Sunday every year 26 * 27 * @param name The name of the holiday 28 * @draft ICU 2.8 29 * @provisional This API might change or be removed in a future release. 30 */ 31 public EasterHoliday(String name) 32 { 33 super(name, new EasterRule(0, false)); 34 } 35 36 /** 37 * Construct a holiday that falls a specified number of days before 38 * or after Easter Sunday each year. 39 * 40 * @param daysAfter The number of days before (-) or after (+) Easter 41 * @param name The name of the holiday 42 * @draft ICU 2.8 43 * @provisional This API might change or be removed in a future release. 44 */ 45 public EasterHoliday(int daysAfter, String name) 46 { 47 super(name, new EasterRule(daysAfter, false)); 48 } 49 50 /** 51 * Construct a holiday that falls a specified number of days before 52 * or after Easter Sunday each year, using either the Western 53 * or Orthodox calendar. 54 * 55 * @param daysAfter The number of days before (-) or after (+) Easter 56 * @param orthodox Use the Orthodox calendar? 57 * @param name The name of the holiday 58 * @draft ICU 2.8 59 * @provisional This API might change or be removed in a future release. 60 */ 61 public EasterHoliday(int daysAfter, boolean orthodox, String name) 62 { 63 super(name, new EasterRule(daysAfter, orthodox)); 64 } 65 66 /** 67 * Shrove Tuesday, aka Mardi Gras, 48 days before Easter 68 * @draft ICU 2.8 69 * @provisional This API might change or be removed in a future release. 70 */ 71 static public final EasterHoliday SHROVE_TUESDAY = new EasterHoliday(-48, "Shrove Tuesday"); 72 73 /** 74 * Ash Wednesday, start of Lent, 47 days before Easter 75 * @draft ICU 2.8 76 * @provisional This API might change or be removed in a future release. 77 */ 78 static public final EasterHoliday ASH_WEDNESDAY = new EasterHoliday(-47, "Ash Wednesday"); 79 80 /** 81 * Palm Sunday, 7 days before Easter 82 * @draft ICU 2.8 83 * @provisional This API might change or be removed in a future release. 84 */ 85 static public final EasterHoliday PALM_SUNDAY = new EasterHoliday( -7, "Palm Sunday"); 86 87 /** 88 * Maundy Thursday, 3 days before Easter 89 * @draft ICU 2.8 90 * @provisional This API might change or be removed in a future release. 91 */ 92 static public final EasterHoliday MAUNDY_THURSDAY = new EasterHoliday( -3, "Maundy Thursday"); 93 94 /** 95 * Good Friday, 2 days before Easter 96 * @draft ICU 2.8 97 * @provisional This API might change or be removed in a future release. 98 */ 99 static public final EasterHoliday GOOD_FRIDAY = new EasterHoliday( -2, "Good Friday"); 100 101 /** 102 * Easter Sunday 103 * @draft ICU 2.8 104 * @provisional This API might change or be removed in a future release. 105 */ 106 static public final EasterHoliday EASTER_SUNDAY = new EasterHoliday( 0, "Easter Sunday"); 107 108 /** 109 * Easter Monday, 1 day after Easter 110 * @draft ICU 2.8 111 * @provisional This API might change or be removed in a future release. 112 */ 113 static public final EasterHoliday EASTER_MONDAY = new EasterHoliday( 1, "Easter Monday"); 114 115 /** 116 * Ascension, 39 days after Easter 117 * @draft ICU 2.8 118 * @provisional This API might change or be removed in a future release. 119 */ 120 static public final EasterHoliday ASCENSION = new EasterHoliday( 39, "Ascension"); 121 122 /** 123 * Pentecost (aka Whit Sunday), 49 days after Easter 124 * @draft ICU 2.8 125 * @provisional This API might change or be removed in a future release. 126 */ 127 static public final EasterHoliday PENTECOST = new EasterHoliday( 49, "Pentecost"); 128 129 /** 130 * Whit Sunday (aka Pentecost), 49 days after Easter 131 * @draft ICU 2.8 132 * @provisional This API might change or be removed in a future release. 133 */ 134 static public final EasterHoliday WHIT_SUNDAY = new EasterHoliday( 49, "Whit Sunday"); 135 136 /** 137 * Whit Monday, 50 days after Easter 138 * @draft ICU 2.8 139 * @provisional This API might change or be removed in a future release. 140 */ 141 static public final EasterHoliday WHIT_MONDAY = new EasterHoliday( 50, "Whit Monday"); 142 143 /** 144 * Corpus Christi, 60 days after Easter 145 * @draft ICU 2.8 146 * @provisional This API might change or be removed in a future release. 147 */ 148 static public final EasterHoliday CORPUS_CHRISTI = new EasterHoliday( 60, "Corpus Christi"); 149} 150 151class EasterRule implements DateRule { 152 public EasterRule(int daysAfterEaster, boolean isOrthodox) { 153 this.daysAfterEaster = daysAfterEaster; 154 if (isOrthodox) { 155 orthodox.setGregorianChange(new Date(Long.MAX_VALUE)); 156 calendar = orthodox; 157 } 158 } 159 160 /** 161 * Return the first occurrance of this rule on or after the given date 162 */ 163 public Date firstAfter(Date start) 164 { 165 return doFirstBetween(start, null); 166 } 167 168 /** 169 * Return the first occurrance of this rule on or after 170 * the given start date and before the given end date. 171 */ 172 public Date firstBetween(Date start, Date end) 173 { 174 return doFirstBetween(start, end); 175 } 176 177 /** 178 * Return true if the given Date is on the same day as Easter 179 */ 180 public boolean isOn(Date date) 181 { 182 synchronized(calendar) { 183 calendar.setTime(date); 184 int dayOfYear = calendar.get(Calendar.DAY_OF_YEAR); 185 186 calendar.setTime(computeInYear(calendar.getTime(), calendar)); 187 188 return calendar.get(Calendar.DAY_OF_YEAR) == dayOfYear; 189 } 190 } 191 192 /** 193 * Return true if Easter occurs between the two dates given 194 */ 195 public boolean isBetween(Date start, Date end) 196 { 197 return firstBetween(start, end) != null; // TODO: optimize? 198 } 199 200 private Date doFirstBetween(Date start, Date end) 201 { 202 //System.out.println("doFirstBetween: start = " + start.toString()); 203 //System.out.println("doFirstBetween: end = " + end.toString()); 204 205 synchronized(calendar) { 206 // Figure out when this holiday lands in the given year 207 Date result = computeInYear(start, calendar); 208 209 //System.out.println(" result = " + result.toString()); 210 211 // We might have gotten a date that's in the same year as "start", but 212 // earlier in the year. If so, go to next year 213 if (result.before(start)) 214 { 215 calendar.setTime(start); 216 calendar.get(Calendar.YEAR); // JDK 1.1.2 bug workaround 217 calendar.add(Calendar.YEAR, 1); 218 219 //System.out.println(" Result before start, going to next year: " 220 // + calendar.getTime().toString()); 221 222 result = computeInYear(calendar.getTime(), calendar); 223 //System.out.println(" result = " + result.toString()); 224 } 225 226 if (end != null && !result.before(end)) { 227 //System.out.println("Result after end, returning null"); 228 return null; 229 } 230 return result; 231 } 232 } 233 234 /** 235 * Compute the month and date on which this holiday falls in the year 236 * containing the date "date". First figure out which date Easter 237 * lands on in this year, and then add the offset for this holiday to get 238 * the right date. 239 * <p> 240 * The algorithm here is taken from the 241 * <a href="http://www.faqs.org/faqs/calendars/faq/">Calendar FAQ</a>. 242 */ 243 private Date computeInYear(Date date, GregorianCalendar cal) 244 { 245 if (cal == null) cal = calendar; 246 247 synchronized(cal) { 248 cal.setTime(date); 249 250 int year = cal.get(Calendar.YEAR); 251 int g = year % 19; // "Golden Number" of year - 1 252 int i = 0; // # of days from 3/21 to the Paschal full moon 253 int j = 0; // Weekday (0-based) of Paschal full moon 254 255 if (cal.getTime().after( cal.getGregorianChange())) 256 { 257 // We're past the Gregorian switchover, so use the Gregorian rules. 258 int c = year / 100; 259 int h = (c - c/4 - (8*c+13)/25 + 19*g + 15) % 30; 260 i = h - (h/28)*(1 - (h/28)*(29/(h+1))*((21-g)/11)); 261 j = (year + year/4 + i + 2 - c + c/4) % 7; 262 } 263 else 264 { 265 // Use the old Julian rules. 266 i = (19*g + 15) % 30; 267 j = (year + year/4 + i) % 7; 268 } 269 int l = i - j; 270 int m = 3 + (l+40)/44; // 1-based month in which Easter falls 271 int d = l + 28 - 31*(m/4); // Date of Easter within that month 272 273 cal.clear(); 274 cal.set(Calendar.ERA, GregorianCalendar.AD); 275 cal.set(Calendar.YEAR, year); 276 cal.set(Calendar.MONTH, m-1); // 0-based 277 cal.set(Calendar.DATE, d); 278 cal.getTime(); // JDK 1.1.2 bug workaround 279 cal.add(Calendar.DATE, daysAfterEaster); 280 281 return cal.getTime(); 282 } 283 } 284 285 private static GregorianCalendar gregorian = new GregorianCalendar(/* new SimpleTimeZone(0, "UTC") */); 286 private static GregorianCalendar orthodox = new GregorianCalendar(/* new SimpleTimeZone(0, "UTC") */); 287 288 private int daysAfterEaster; 289 private GregorianCalendar calendar = gregorian; 290} 291