# -*- coding: utf-8 -*- # This file is part of convertdate. # http://github.com/fitnr/convertdate # Licensed under the MIT license: # http://opensource.org/licenses/MIT # Copyright (c) 2016, fitnr from math import trunc import itertools from .utils import amod from . import gregorian EPOCH = 584282.5 HAAB_MONTHS = ["Pop", "Wo'", "Zip", "Sotz'", "Sek", "Xul", "Yaxk'in'", "Mol", "Ch'en", "Yax", "Sak'", "Keh", "Mak", "K'ank'in", "Muwan'", "Pax", "K'ayab", "Kumk'u", "Wayeb'"] HAAB_TRANSLATIONS = [ "Mat", "Frog", "Red", "Bat", "Bee", "Dog", "First Sun", "Water", "Cave", "Green", "White", "Red", "Encloser", "Yellow Sun", "Screech Owl", "Planting Time", "Turtle", "Ripe Corn", "Nameless"] TZOLKIN_NAMES = ["Imix'", "Ik'", "Ak'b'al", "K'an", "Chikchan", "Kimi", "Manik'", "Lamat", "Muluk", "Ok", "Chuwen", "Eb'", "B'en", "Ix", "Men", "K'ib'", "Kab'an", "Etz'nab'", "Kawak", "Ajaw"] TZOLKIN_TRANSLATIONS = ['Water', 'Wind', 'Darkness', 'Net', 'Feathered Serpent', 'Death', 'Deer', 'Seed', 'Jade', 'Dog', 'Thread', 'Path', 'Maize', 'Tiger', 'Bird', 'Will', 'Wisdom', 'Obsidian Knife', 'Thunder', 'Sun'] def to_jd(baktun, katun, tun, uinal, kin): '''Determine Julian day from Mayan long count''' return EPOCH + (baktun * 144000) + (katun * 7200) + (tun * 360) + (uinal * 20) + kin def from_jd(jd): '''Calculate Mayan long count from Julian day''' d = jd - EPOCH baktun = trunc(d / 144000) d = (d % 144000) katun = trunc(d / 7200) d = (d % 7200) tun = trunc(d / 360) d = (d % 360) uinal = trunc(d / 20) kin = int((d % 20)) return (baktun, katun, tun, uinal, kin) def to_gregorian(baktun, katun, tun, uinal, kin): jd = to_jd(baktun, katun, tun, uinal, kin) return gregorian.from_jd(jd) def from_gregorian(year, month, day): jd = gregorian.to_jd(year, month, day) return from_jd(jd) def to_haab(jd): '''Determine Mayan Haab "month" and day from Julian day''' # Number of days since the start of the long count lcount = trunc(jd) + 0.5 - EPOCH # Long Count begins 348 days after the start of the cycle day = (lcount + 348) % 365 count = day % 20 month = trunc(day / 20) return int(count), HAAB_MONTHS[month] def to_tzolkin(jd): '''Determine Mayan Tzolkin "month" and day from Julian day''' lcount = trunc(jd) + 0.5 - EPOCH day = amod(lcount + 4, 13) name = amod(lcount + 20, 20) return int(day), TZOLKIN_NAMES[int(name) - 1] def lc_to_haab(baktun, katun, tun, uinal, kin): jd = to_jd(baktun, katun, tun, uinal, kin) return to_haab(jd) def lc_to_tzolkin(baktun, katun, tun, uinal, kin): jd = to_jd(baktun, katun, tun, uinal, kin) return to_tzolkin(jd) def lc_to_haab_tzolkin(baktun, katun, tun, uinal, kin): jd = to_jd(baktun, katun, tun, uinal, kin) dates = to_tzolkin(jd) + to_haab(jd) return "{0} {1} {2} {3}".format(*dates) def translate_haab(h): return dict(list(zip(HAAB_MONTHS, HAAB_TRANSLATIONS))).get(h) def translate_tzolkin(tz): return dict(list(zip(TZOLKIN_NAMES, TZOLKIN_TRANSLATIONS))).get(tz) def _haab_count(day, month): '''Return the count of the given haab in the cycle. e.g. 0 Pop == 1, 5 Wayeb' == 365''' if day < 0 or day > 19: raise IndexError("Invalid day number") try: i = HAAB_MONTHS.index(month) except ValueError: raise ValueError("'{0}' is not a valid Haab' month".format(month)) return min(i * 20, 360) + day def _tzolkin_from_count(count): number = amod(count, 13) name = TZOLKIN_NAMES[count % 20 - 1] return number, name def _tzolkin_count(day, name): if day < 1 or day > 13: raise IndexError("Invalid day number") days = set(x + day for x in range(0, 260, 13)) try: n = 1 + TZOLKIN_NAMES.index(name) except ValueError: raise ValueError("'{0}' is not a valid Tzolk'in day name".format(name)) names = set(y + n for y in range(0, 260, 20)) return days.intersection(names).pop() def tzolkin_generator(number=None, name=None): '''For a given tzolkin name/number combination, return a generator that gives cycle, starting with the input''' # By default, it will start at the beginning number = number or 13 name = name or "Ajaw" if number > 13: raise ValueError("Invalid day number") if name not in TZOLKIN_NAMES: raise ValueError("Invalid day name") count = _tzolkin_count(number, name) ranged = itertools.chain(list(range(count, 260)), list(range(1, count))) for i in ranged: yield _tzolkin_from_count(i) def longcount_generator(baktun, katun, tun, uinal, kin): '''Generate long counts, starting with input''' j = to_jd(baktun, katun, tun, uinal, kin) while True: yield from_jd(j) j = j + 1 def next_haab(month, jd): '''For a given haab month and a julian day count, find the next start of that month on or after the JDC''' if jd < EPOCH: raise IndexError("Input day is before Mayan epoch.") hday, hmonth = to_haab(jd) if hmonth == month: days = 1 - hday else: count1 = _haab_count(hday, hmonth) count2 = _haab_count(1, month) # Find number of days between haab of given jd and desired haab days = (count2 - count1) % 365 # add in the number of days and return new jd return jd + days def next_tzolkin(tzolkin, jd): '''For a given tzolk'in day, and a julian day count, find the next occurrance of that tzolk'in after the date''' if jd < EPOCH: raise IndexError("Input day is before Mayan epoch.") count1 = _tzolkin_count(*to_tzolkin(jd)) count2 = _tzolkin_count(*tzolkin) add_days = (count2 - count1) % 260 return jd + add_days def next_tzolkin_haab(tzolkin, haab, jd): '''For a given haab-tzolk'in combination, and a Julian day count, find the next occurrance of the combination after the date''' # get H & T of input jd, and their place in the 18,980 day cycle haabcount = _haab_count(*to_haab(jd)) haab_desired_count = _haab_count(*haab) # How many days between the input day and the desired day? haab_days = (haab_desired_count - haabcount) % 365 possible_haab = set(h + haab_days for h in range(0, 18980, 365)) tzcount = _tzolkin_count(*to_tzolkin(jd)) tz_desired_count = _tzolkin_count(*tzolkin) # How many days between the input day and the desired day? tzolkin_days = (tz_desired_count - tzcount) % 260 possible_tz = set(t + tzolkin_days for t in range(0, 18980, 260)) try: return possible_tz.intersection(possible_haab).pop() + jd except KeyError: raise IndexError("That Haab'-Tzolk'in combination isn't possible") def month_length(month): """Not the actual length of the month, but accounts for the 5 unlucky/nameless days""" if month == "Wayeb'": return 5 else: return 20 def haab_monthcalendar(baktun=None, katun=None, tun=None, uinal=None, kin=None, jdc=None): '''For a given long count, return a calender of the current haab month, divided into tzolkin "weeks"''' if not jdc: jdc = to_jd(baktun, katun, tun, uinal, kin) haab_number, haab_month = to_haab(jdc) first_j = jdc - haab_number + 1 tzolkin_start_number, tzolkin_start_name = to_tzolkin(first_j) gen_longcount = longcount_generator(*from_jd(first_j)) gen_tzolkin = tzolkin_generator(tzolkin_start_number, tzolkin_start_name) # 13 day long tzolkin 'weeks' lpad = tzolkin_start_number - 1 rpad = 13 - (tzolkin_start_number + 19 % 13) monlen = month_length(haab_month) days = [None] * lpad + list(range(1, monlen + 1)) + rpad * [None] def g(x, generate): if x is None: return None return next(generate) return [[(k, g(k, gen_tzolkin), g(k, gen_longcount)) for k in days[i:i + 13]] for i in range(0, len(days), 13)] def haab_monthcalendar_prospective(haabmonth, jdc): '''Give the monthcalendar for the next occurance of given haab month after jdc''' return haab_monthcalendar(jdc=next_haab(haabmonth, jdc))