def obliquity(tee): """Return (mean) obliquity of ecliptic at moment tee.""" c = julian_centuries(tee) return (angle(23, 26, mpf(21.448)) + poly(c, [ mpf(0), angle(0, 0, mpf(-46.8150)), angle(0, 0, mpf(-0.00059)), angle(0, 0, mpf(0.001813)) ]))
def location(cls, tee): """Return location of Beijing; time zone varies with time, tee.""" year = GregorianDate.to_year(int(math.floor(tee))) if (year < 1929): return Location(angle(39, 55, 0), angle(116, 25, 0), 43.5, Clock.days_from_hours(1397 / 180)) else: return Location(angle(39, 55, 0), angle(116, 25, 0), 43.5, Clock.days_from_hours(8))
def vietnamese_location(tee): """Return the location for Vietnamese calendar is Hanoi; varies with moment, tee. Time zone has changed over the years.""" if (tee < GregorianDate.new_year(1968)): z = 8 else: z = 7 return Location(angle(21, 2, 0), angle(105, 51, 0), 12, Clock.days_from_hours(z))
def korean_location(tee): """Return the location for Korean calendar; varies with moment, tee.""" # Seoul city hall at a varying time zone. if (tee < GregorianDate(1908, MonthOfYear.APRIL, 1).toordinal()): #local mean time for longitude 126 deg 58 min z = 3809 / 450 elif (tee < GregorianDate(1912, MonthOfYear.JANUARY, 1).toordinal()): z = 8.5 elif (tee < GregorianDate(1954, MonthOfYear.MARCH, 21).toordinal()): z = 9 elif (tee < GregorianDate(1961, MonthOfYear.AUGUST, 10).toordinal()): z = 8.5 else: z = 9 return Location(angle(37, 34, 0), angle(126, 58, 0), 0, Clock.days_from_hours(z))
def equation_of_time(cls, date): """Return the time from true to mean midnight of date, date.""" offset = cls.sine(cls.mean_position(date, cls.ANOMALISTIC_YEAR)) equation_sun = (offset * angle(57, 18, 0) * (14 / 360 - (abs(offset) / 1080))) return ((cls.daily_motion(date) / 360) * (equation_sun / 360) * cls.SIDEREAL_YEAR)
def daily_motion(cls, date): """Return the sidereal daily motion of sun on date, date.""" mean_motion = 360 / cls.SIDEREAL_YEAR anomaly = cls.mean_position(date, cls.ANOMALISTIC_YEAR) epicycle = 14 / 360 - abs(cls.sine(anomaly)) / 1080 entry = int(math.floor(float(anomaly), angle(0, 225, 0))) sine_table_step = cls.sine_table(entry + 1) - cls.sine_table(entry) factor = -3438 / 225 * sine_table_step * epicycle return mean_motion * (factor + 1)
def arcsin(cls, amp): """Return the inverse of Hindu sine function of amp.""" if (amp < 0): return -cls.arcsin(-amp) else: pos = next_int(0, lambda k: amp <= cls.sine_table(k)) below = cls.sine_table(pos - 1) return (angle(0, 225, 0) * (pos - 1 + ((amp - below) / (cls.sine_table(pos) - below))))
def japanese_location(tee): """Return the location for Japanese calendar; varies with moment, tee.""" year = GregorianDate.to_year(int(math.floor(tee))) if (year < 1888): # Tokyo (139 deg 46 min east) local time loc = Location(mpf(35.7), angle(139, 46, 0), 24, Clock.days_from_hours(9 + 143 / 450)) else: # Longitude 135 time zone loc = Location(35, 135, 0, Clock.days_from_hours(9)) return loc
def sine(cls, theta): """Return the linear interpolation for angle, theta, in Hindu table.""" entry = theta / angle(0, 225, 0) fraction = entry % 1 return ((fraction * cls.sine_table(int(math.ceil(entry)))) + ((1 - fraction) * cls.sine_table(int(math.floor(entry)))))
def sine_table(cls, entry): """Return the value for entry in the Hindu sine table. Entry, entry, is an angle given as a multiplier of 225'.""" exact = 3438 * sin_degrees(entry * angle(0, 225, 0)) error = 0.215 * signum(exact) * signum(abs(exact) - 1716) return int(round(exact + error)) / 3438
class HinduDate(object): SIDEREAL_YEAR = 365 + 279457 / 1080000 ANOMALISTIC_YEAR = 1577917828000 / (4320000000 - 387) CREATION = OldHindu.EPOCH - 1955880000 * SIDEREAL_YEAR UJJAIN = Location(angle(23, 9, 0), angle(75, 46, 6), 0, Clock.days_from_hours(5 + 461 / 9000)) LOCATION = UJJAIN def __init__(self, year, month, leap_month, day, leap_day): self.year = year self.month = month self.leap_month = leap_month self.day = day self.leap_day = leap_day def to_tuple(self): return (self.year, self.month, self.leap_month, self.day, self.leap_day) def __eq__(self, other): return isinstance(other, HinduDate) and all( map(lambda (x, y): x == y, zip(self.to_tuple(), other.to_tuple()))) def __ne__(self, other): return not self.__eq__(other) def __lt__(self, other): return isinstance(other, HinduDate) and reduce_cond( lambda _, (x, y): x < y, lambda r, (x, y): not r and x == y, zip(self.to_tuple(), other.to_tuple()), False) def __le__(self, other): return isinstance(other, HinduDate) and reduce_cond( lambda _, (x, y): x <= y, lambda r, (x, y): not r and x == y, zip(self.to_tuple(), other.to_tuple()), False) def __gt__(self, other): return isinstance(other, HinduDate) and reduce_cond( lambda _, (x, y): x > y, lambda r, (x, y): not r and x == y, zip(self.to_tuple(), other.to_tuple()), False) def __ge__(self, other): return isinstance(other, HinduDate) and reduce_cond( lambda _, (x, y): x >= y, lambda r, (x, y): not r and x == y, zip(self.to_tuple(), other.to_tuple()), False) @classmethod def sine_table(cls, entry): """Return the value for entry in the Hindu sine table. Entry, entry, is an angle given as a multiplier of 225'.""" exact = 3438 * sin_degrees(entry * angle(0, 225, 0)) error = 0.215 * signum(exact) * signum(abs(exact) - 1716) return int(round(exact + error)) / 3438 @classmethod def sine(cls, theta): """Return the linear interpolation for angle, theta, in Hindu table.""" entry = theta / angle(0, 225, 0) fraction = entry % 1 return ((fraction * cls.sine_table(int(math.ceil(entry)))) + ((1 - fraction) * cls.sine_table(int(math.floor(entry))))) @classmethod def arcsin(cls, amp): """Return the inverse of Hindu sine function of amp.""" if (amp < 0): return -cls.arcsin(-amp) else: pos = next_int(0, lambda k: amp <= cls.sine_table(k)) below = cls.sine_table(pos - 1) return (angle(0, 225, 0) * (pos - 1 + ((amp - below) / (cls.sine_table(pos) - below)))) @classmethod def mean_position(cls, tee, period): """Return the position in degrees at moment, tee, in uniform circular orbit of period days.""" return 360 * (((tee - cls.CREATION) / period) % 1) @classmethod def true_position(cls, tee, period, size, anomalistic, change): """Return the longitudinal position at moment, tee. period is the period of mean motion in days. size is ratio of radii of epicycle and deferent. anomalistic is the period of retrograde revolution about epicycle. change is maximum decrease in epicycle size.""" lam = cls.mean_position(tee, period) offset = cls.sine(cls.mean_position(tee, anomalistic)) contraction = abs(offset) * change * size equation = cls.arcsin(offset * (size - contraction)) return (lam - equation) % 360 @classmethod def solar_longitude(cls, tee): """Return the solar longitude at moment, tee.""" return cls.true_position(tee, cls.SIDEREAL_YEAR, 14 / 360, cls.ANOMALISTIC_YEAR, 1 / 42) @classmethod def zodiac(cls, tee): """Return the zodiacal sign of the sun, as integer in range 1..12, at moment tee.""" return int(math.floor(float(cls.solar_longitude(tee)), 30)) + 1 @classmethod def equation_of_time(cls, date): """Return the time from true to mean midnight of date, date.""" offset = cls.sine(cls.mean_position(date, cls.ANOMALISTIC_YEAR)) equation_sun = (offset * angle(57, 18, 0) * (14 / 360 - (abs(offset) / 1080))) return ((cls.daily_motion(date) / 360) * (equation_sun / 360) * cls.SIDEREAL_YEAR) @classmethod def ascensional_difference(cls, date, location): """Return the difference between right and oblique ascension of sun on date, date, at loacel, location.""" sin_delta = (1397 / 3438) * cls.sine(cls.tropical_longitude(date)) phi = location.latitude diurnal_radius = cls.sine(90 + cls.arcsin(sin_delta)) tan_phi = cls.sine(phi) / cls.sine(90 + phi) earth_sine = sin_delta * tan_phi return cls.arcsin(-earth_sine / diurnal_radius) @classmethod def daily_motion(cls, date): """Return the sidereal daily motion of sun on date, date.""" mean_motion = 360 / cls.SIDEREAL_YEAR anomaly = cls.mean_position(date, cls.ANOMALISTIC_YEAR) epicycle = 14 / 360 - abs(cls.sine(anomaly)) / 1080 entry = int(math.floor(float(anomaly), angle(0, 225, 0))) sine_table_step = cls.sine_table(entry + 1) - cls.sine_table(entry) factor = -3438 / 225 * sine_table_step * epicycle return mean_motion * (factor + 1) @classmethod def solar_sidereal_difference(cls, date): """Return the difference between solar and sidereal day on date, date.""" return cls.daily_motion(date) * cls.rising_sign(date) @classmethod def sunrise(cls, date): """Return the sunrise at hindu_location on date, date.""" return (date + Clock.days_from_hours(6) + ((cls.UJJAIN.longitude - cls.LOCATION.longitude) / 360) - cls.equation_of_time(date) + ((1577917828 / 1582237828 / 360) * (cls.ascensional_difference(date, cls.LOCATION) + (1 / 4 * cls.solar_sidereal_difference(date))))) @classmethod def sunset(cls, date): """Return sunset at HINDU_LOCATION on date, date.""" return (date + Clock.days_from_hours(18) + ((cls.UJJAIN.longitude - cls.LOCATION.longitude) / 360) - cls.equation_of_time(date) + (((1577917828 / 1582237828) / 360) * (-cls.ascensional_difference(date, cls.LOCATION) + (3 / 4 * cls.solar_sidereal_difference(date))))) @classmethod def alt_sunrise(cls, date): """Return the astronomical sunrise at Hindu location on date, date, per Lahiri, rounded to nearest minute, as a rational number.""" rise = cls.UJJAIN.dawn(date, angle(0, 47, 0)) return 1 / 24 * 1 / 60 * int(round(rise * 24 * 60))
def refraction(self, tee): """Return refraction angle at location 'location' and time 'tee'.""" h = max(0, self.elevation) cap_R = 6.372E6 dip = arccos_degrees(cap_R / (cap_R + h)) return angle(0, 50, 0) + dip + secs(19) * math.sqrt(h)
def hindu_lunar_station(date): """Return the Hindu lunar station (nakshatra) at sunrise on date, date.""" critical = HinduDate.sunrise(date) return int( math.floor(HinduLunarDate.longitude(critical) / angle(0, 800, 0))) + 1
def precise_obliquity(tee): """Return precise (mean) obliquity of ecliptic at moment tee.""" u = julian_centuries(tee) / 100 #assert(abs(u) < 1, # 'Error! This formula is valid for +/-10000 years around J2000.0') return (poly(u, [ angle(23, 26, mpf(21.448)), angle(0, 0, mpf(-4680.93)), angle(0, 0, mpf(-1.55)), angle(0, 0, mpf(+1999.25)), angle(0, 0, mpf(-51.38)), angle(0, 0, mpf(-249.67)), angle(0, 0, mpf(-39.05)), angle(0, 0, mpf(+7.12)), angle(0, 0, mpf(+27.87)), angle(0, 0, mpf(+5.79)), angle(0, 0, mpf(+2.45)) ]))
class FrenchDate(YearMonthDay): #"""Date of start of the French Revolutionary calendar.""" EPOCH = GregorianDate(1792, MonthOfYear.SEPTEMBER, 22).toordinal() PARIS = Location(angle(48, 50, 11), angle(2, 20, 15), 27, Clock.days_from_hours(1)) def __init__(self, year, month, day): super().__init__(year, month, day) def toordinal(self): """Return ordinal date of French Revolutionary date, f_date""" new_year = self.new_year_on_or_before(ifloor(self.EPOCH + 180 + MEAN_TROPICAL_YEAR * (self.year - 1))) return new_year - 1 + 30 * (self.month - 1) + self.day @classmethod def fromordinal(cls, ordinal): """Return French Revolutionary date of ordinal date, 'ordinal'.""" new_year = cls.new_year_on_or_before(ordinal) year = iround((new_year - cls.EPOCH) / MEAN_TROPICAL_YEAR) + 1 month = quotient(ordinal - new_year, 30) + 1 day = ((ordinal - new_year) % 30) + 1 return FrenchDate(year, month, day) @classmethod def midnight_in_paris(cls, ordinal): """Return Universal Time of true midnight at the end of ordinal date, ordinal.""" # tricky bug: I was using midDAY!!! So French Revolutionary was failing... return cls.PARIS.universal_from_standard(cls.PARIS.midnight(ordinal + 1)) @classmethod def new_year_on_or_before(cls, ordinal): """Return ordinal date of French Revolutionary New Year on or before ordinal, ordinal.""" approx = estimate_prior_solar_longitude(Season.AUTUMN, cls.midnight_in_paris(ordinal)) return next_int(ifloor(approx) - 1, lambda day: Season.AUTUMN <= solar_longitude(cls.midnight_in_paris(day))) @classmethod def is_arithmetic_leap_year(cls, f_year): """Return True if year, f_year, is a leap year on the French Revolutionary calendar.""" return f_year % 4 == 0 and f_year % 400 not in [100, 200, 300] and f_year % 4000 != 0 def toordinal_arithmetic(self): """Return ordinal date of French Revolutionary date, f_date.""" return (self.EPOCH - 1 + 365 * (self.year - 1) + quotient(self.year - 1, 4) - quotient(self.year - 1, 100) + quotient(self.year - 1, 400) - quotient(self.year - 1, 4000) + 30 * (self.month - 1) + self.day) @classmethod def fromordinal_arithmetic(cls, ordinal): """Return French Revolutionary date [year, month, day] of ordinal ordinal, ordinal.""" approx = quotient(ordinal - cls.EPOCH + 2, 1460969/4000) + 1 year = ((approx - 1) if (ordinal < FrenchDate(approx, 1, 1).toordinal_arithmetic()) else approx) month = 1 + quotient(ordinal - FrenchDate(year, 1, 1).toordinal_arithmetic(), 30) day = ordinal - FrenchDate(year, month, 1).toordinal_arithmetic() + 1 return FrenchDate(year, month, day)
def jewish_dusk(date, location): """Return standard time of Jewish dusk on fixed date, date, at location, location, (as per Vilna Gaon).""" return location.dusk(date, angle(4, 40, 0))
def jewish_sabbath_ends(date, location): """Return standard time of end of Jewish sabbath on fixed date, date, at location, location, (as per Berthold Cohn).""" return location.dusk(date, angle(7, 5, 0))
def alt_sunrise(cls, date): """Return the astronomical sunrise at Hindu location on date, date, per Lahiri, rounded to nearest minute, as a rational number.""" rise = cls.UJJAIN.dawn(date, angle(0, 47, 0)) return 1 / 24 * 1 / 60 * int(round(rise * 24 * 60))
at location, location, as a small positive/negative angle in degrees, ignoring parallax and refraction. Adapted from 'Astronomical Algorithms' by Jean Meeus, Willmann_Bell, Inc., 1998.""" lamb = lunar_longitude(tee) beta = lunar_latitude(tee) alpha =right_ascension(tee, beta, lamb) delta = declination(tee, beta, lamb) theta0 = sidereal_from_moment(tee) cap_H = mod(theta0 + self.longitude - alpha, 360) altitude = arcsin_degrees( (sin_degrees(self.latitude) * sin_degrees(delta)) + (cos_degrees(self.latitude) * cos_degrees(delta) * cos_degrees(cap_H))) return mod(altitude + 180, 360) - 180 def visible_crescent(self, date): """Return S. K. Shaukat's criterion for likely visibility of crescent moon on eve of date 'date', at location 'location'.""" tee = self.universal_from_standard(self.dusk(date - 1, mpf(4.5))) phase = lunar_phase(tee) altitude = self.lunar_altitude(tee) arc_of_light = arccos_degrees(cos_degrees(lunar_latitude(tee)) * cos_degrees(phase)) return ((MoonPhase.NEW < phase < MoonPhase.FIRST_QUARTER) and (mpf(10.6) <= arc_of_light <= 90) and (altitude > mpf(4.1))) MECCA = Location(angle(21, 25, 24), angle(39, 49, 24), 298, Clock.days_from_hours(3)) JERUSALEM = Location(31.8, 35.2, 800, Clock.days_from_hours(2)) BRUXELLES = Location(angle(4, 21, 17), angle(50, 50, 47), 800, Clock.days_from_hours(1)) URBANA = Location(40.1, -88.2, 225, Clock.days_from_hours(-6)) GREENWHICH = Location(51.4777815, 0, 46.9, Clock.days_from_hours(0))
def yoga(date): """Return the Hindu yoga on date, date.""" return int( math.floor((( (HinduSolarDate.longitude(date) + HinduLunarDate.longitude(date)) / angle(0, 800, 0)) % 27))) + 1
elif month == HebrewMonth.KISLEV and day < 30: extra = [DayOfWeek.MONDAY, DayOfWeek.WEDNESDAY, DayOfWeek.FRIDAY] elif month == HebrewMonth.KISLEV and day == 30: extra = [DayOfWeek.MONDAY] elif month in [HebrewMonth.TEVET, HebrewMonth.SHEVAT]: extra = [DayOfWeek.SUNDAY, DayOfWeek.MONDAY] elif month == HebrewMonth.ADAR and day < 30: extra = [DayOfWeek.SUNDAY, DayOfWeek.MONDAY] else: extra = [DayOfWeek.SUNDAY] basic.extend(extra) return map(lambda x: weekday_fromordinal(x + n), basic) JAFFA = Location(angle(32, 1, 60), angle(34, 45, 0), 0, Clock.days_from_hours(2)) class HebrewObservationalDate(YearMonthDay): def __init__(self, year, month, day): YearMonthDay.__init__(self, year, month, day) def toordinal(self): """Return ordinal date equivalent to Observational Hebrew date.""" year1 = self.year - 1 if self.month >= HebrewMonth.TISHRI else self.year start = HebrewDate(year1, HebrewMonth.NISAN, 1).toordinal() g_year = GregorianDate.to_year(start + 60) new_year = self.new_year(g_year) midmonth = new_year + int(round(29.5 * (self.month - 1))) + 15 return JAFFA.phasis_on_or_before(midmonth) + self.day - 1