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