jpayne@68: # -*- coding: utf-8 -*- jpayne@68: """ jpayne@68: This module offers a generic Easter computing method for any given year, using jpayne@68: Western, Orthodox or Julian algorithms. jpayne@68: """ jpayne@68: jpayne@68: import datetime jpayne@68: jpayne@68: __all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"] jpayne@68: jpayne@68: EASTER_JULIAN = 1 jpayne@68: EASTER_ORTHODOX = 2 jpayne@68: EASTER_WESTERN = 3 jpayne@68: jpayne@68: jpayne@68: def easter(year, method=EASTER_WESTERN): jpayne@68: """ jpayne@68: This method was ported from the work done by GM Arts, jpayne@68: on top of the algorithm by Claus Tondering, which was jpayne@68: based in part on the algorithm of Ouding (1940), as jpayne@68: quoted in "Explanatory Supplement to the Astronomical jpayne@68: Almanac", P. Kenneth Seidelmann, editor. jpayne@68: jpayne@68: This algorithm implements three different Easter jpayne@68: calculation methods: jpayne@68: jpayne@68: 1. Original calculation in Julian calendar, valid in jpayne@68: dates after 326 AD jpayne@68: 2. Original method, with date converted to Gregorian jpayne@68: calendar, valid in years 1583 to 4099 jpayne@68: 3. Revised method, in Gregorian calendar, valid in jpayne@68: years 1583 to 4099 as well jpayne@68: jpayne@68: These methods are represented by the constants: jpayne@68: jpayne@68: * ``EASTER_JULIAN = 1`` jpayne@68: * ``EASTER_ORTHODOX = 2`` jpayne@68: * ``EASTER_WESTERN = 3`` jpayne@68: jpayne@68: The default method is method 3. jpayne@68: jpayne@68: More about the algorithm may be found at: jpayne@68: jpayne@68: `GM Arts: Easter Algorithms `_ jpayne@68: jpayne@68: and jpayne@68: jpayne@68: `The Calendar FAQ: Easter `_ jpayne@68: jpayne@68: """ jpayne@68: jpayne@68: if not (1 <= method <= 3): jpayne@68: raise ValueError("invalid method") jpayne@68: jpayne@68: # g - Golden year - 1 jpayne@68: # c - Century jpayne@68: # h - (23 - Epact) mod 30 jpayne@68: # i - Number of days from March 21 to Paschal Full Moon jpayne@68: # j - Weekday for PFM (0=Sunday, etc) jpayne@68: # p - Number of days from March 21 to Sunday on or before PFM jpayne@68: # (-6 to 28 methods 1 & 3, to 56 for method 2) jpayne@68: # e - Extra days to add for method 2 (converting Julian jpayne@68: # date to Gregorian date) jpayne@68: jpayne@68: y = year jpayne@68: g = y % 19 jpayne@68: e = 0 jpayne@68: if method < 3: jpayne@68: # Old method jpayne@68: i = (19*g + 15) % 30 jpayne@68: j = (y + y//4 + i) % 7 jpayne@68: if method == 2: jpayne@68: # Extra dates to convert Julian to Gregorian date jpayne@68: e = 10 jpayne@68: if y > 1600: jpayne@68: e = e + y//100 - 16 - (y//100 - 16)//4 jpayne@68: else: jpayne@68: # New method jpayne@68: c = y//100 jpayne@68: h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30 jpayne@68: i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11)) jpayne@68: j = (y + y//4 + i + 2 - c + c//4) % 7 jpayne@68: jpayne@68: # p can be from -6 to 56 corresponding to dates 22 March to 23 May jpayne@68: # (later dates apply to method 2, although 23 May never actually occurs) jpayne@68: p = i - j + e jpayne@68: d = 1 + (p + 27 + (p + 6)//40) % 31 jpayne@68: m = 3 + (p + 26)//30 jpayne@68: return datetime.date(int(y), int(m), int(d))