use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum TonalpohualliSign {
Cipactli,
Ehecatl,
Calli,
Cuetzpalin,
Coatl,
Miquiztli,
Mazatl,
Tochtli,
Atl,
Itzcuintli,
Ozomatli,
Malinalli,
Acatl,
Ocelotl,
Cuauhtli,
Cozcacuauhtli,
Ollin,
Tecpatl,
Quiahuitl,
Xochitl,
}
const TONALPOHUALLI_SIGNS: [TonalpohualliSign; 20] = [
TonalpohualliSign::Cipactli,
TonalpohualliSign::Ehecatl,
TonalpohualliSign::Calli,
TonalpohualliSign::Cuetzpalin,
TonalpohualliSign::Coatl,
TonalpohualliSign::Miquiztli,
TonalpohualliSign::Mazatl,
TonalpohualliSign::Tochtli,
TonalpohualliSign::Atl,
TonalpohualliSign::Itzcuintli,
TonalpohualliSign::Ozomatli,
TonalpohualliSign::Malinalli,
TonalpohualliSign::Acatl,
TonalpohualliSign::Ocelotl,
TonalpohualliSign::Cuauhtli,
TonalpohualliSign::Cozcacuauhtli,
TonalpohualliSign::Ollin,
TonalpohualliSign::Tecpatl,
TonalpohualliSign::Quiahuitl,
TonalpohualliSign::Xochitl,
];
impl core::fmt::Display for TonalpohualliSign {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let name = match self {
Self::Cipactli => "Cipactli",
Self::Ehecatl => "Ehecatl",
Self::Calli => "Calli",
Self::Cuetzpalin => "Cuetzpalin",
Self::Coatl => "Coatl",
Self::Miquiztli => "Miquiztli",
Self::Mazatl => "Mazatl",
Self::Tochtli => "Tochtli",
Self::Atl => "Atl",
Self::Itzcuintli => "Itzcuintli",
Self::Ozomatli => "Ozomatli",
Self::Malinalli => "Malinalli",
Self::Acatl => "Acatl",
Self::Ocelotl => "Ocelotl",
Self::Cuauhtli => "Cuauhtli",
Self::Cozcacuauhtli => "Cozcacuauhtli",
Self::Ollin => "Ollin",
Self::Tecpatl => "Tecpatl",
Self::Quiahuitl => "Quiahuitl",
Self::Xochitl => "Xochitl",
};
write!(f, "{name}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Tonalpohualli {
pub number: u8,
pub sign: TonalpohualliSign,
}
impl core::fmt::Display for Tonalpohualli {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{} {}", self.number, self.sign)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum XiuhpohuallMonth {
Atlcahualo,
Tlacaxipehualiztli,
Tozoztontli,
HueyTozoztli,
Toxcatl,
Etzalcualiztli,
Tecuilhuitontli,
HueyTecuilhuitl,
Tlaxochimaco,
XocotlHuetzi,
Ochpaniztli,
Teotleco,
Tepeilhuitl,
Quecholli,
Panquetzaliztli,
Atemoztli,
Tititl,
Izcalli,
Nemontemi,
}
const XIUHPOHUALL_MONTHS: [XiuhpohuallMonth; 19] = [
XiuhpohuallMonth::Atlcahualo,
XiuhpohuallMonth::Tlacaxipehualiztli,
XiuhpohuallMonth::Tozoztontli,
XiuhpohuallMonth::HueyTozoztli,
XiuhpohuallMonth::Toxcatl,
XiuhpohuallMonth::Etzalcualiztli,
XiuhpohuallMonth::Tecuilhuitontli,
XiuhpohuallMonth::HueyTecuilhuitl,
XiuhpohuallMonth::Tlaxochimaco,
XiuhpohuallMonth::XocotlHuetzi,
XiuhpohuallMonth::Ochpaniztli,
XiuhpohuallMonth::Teotleco,
XiuhpohuallMonth::Tepeilhuitl,
XiuhpohuallMonth::Quecholli,
XiuhpohuallMonth::Panquetzaliztli,
XiuhpohuallMonth::Atemoztli,
XiuhpohuallMonth::Tititl,
XiuhpohuallMonth::Izcalli,
XiuhpohuallMonth::Nemontemi,
];
impl core::fmt::Display for XiuhpohuallMonth {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let name = match self {
Self::Atlcahualo => "Atlcahualo",
Self::Tlacaxipehualiztli => "Tlacaxipehualiztli",
Self::Tozoztontli => "Tozoztontli",
Self::HueyTozoztli => "Huey Tozoztli",
Self::Toxcatl => "Toxcatl",
Self::Etzalcualiztli => "Etzalcualiztli",
Self::Tecuilhuitontli => "Tecuilhuitontli",
Self::HueyTecuilhuitl => "Huey Tecuilhuitl",
Self::Tlaxochimaco => "Tlaxochimaco",
Self::XocotlHuetzi => "Xocotl Huetzi",
Self::Ochpaniztli => "Ochpaniztli",
Self::Teotleco => "Teotleco",
Self::Tepeilhuitl => "Tepeilhuitl",
Self::Quecholli => "Quecholli",
Self::Panquetzaliztli => "Panquetzaliztli",
Self::Atemoztli => "Atemoztli",
Self::Tititl => "Tititl",
Self::Izcalli => "Izcalli",
Self::Nemontemi => "Nemontemi",
};
write!(f, "{name}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Xiuhpohualli {
pub day: u8,
pub month: XiuhpohuallMonth,
}
impl core::fmt::Display for Xiuhpohualli {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{} {}", self.day, self.month)
}
}
const AZTEC_CORRELATION_JDN: i64 = 2_275_856;
const CORRELATION_TONAL_NUMBER: i64 = 1;
const CORRELATION_TONAL_SIGN: i64 = 4;
const CORRELATION_XIUH_DOY: i64 = 180;
pub const AZTEC_CALENDAR_ROUND_DAYS: u64 = 18_980;
#[must_use]
pub fn tonalpohualli_from_jdn(jdn: f64) -> Tonalpohualli {
let jdn_int = (jdn + 0.5).floor() as i64;
let days = jdn_int - AZTEC_CORRELATION_JDN;
let number = ((days + CORRELATION_TONAL_NUMBER - 1).rem_euclid(13) + 1) as u8;
let sign_idx = (days + CORRELATION_TONAL_SIGN).rem_euclid(20) as usize;
Tonalpohualli {
number,
sign: TONALPOHUALLI_SIGNS[sign_idx],
}
}
#[must_use]
pub fn xiuhpohualli_from_jdn(jdn: f64) -> Xiuhpohualli {
let jdn_int = (jdn + 0.5).floor() as i64;
let days = jdn_int - AZTEC_CORRELATION_JDN;
let doy = (days + CORRELATION_XIUH_DOY).rem_euclid(365);
let month_idx = (doy / 20).min(18) as usize;
let day = (doy - (month_idx as i64) * 20 + 1) as u8;
Xiuhpohualli {
day,
month: XIUHPOHUALL_MONTHS[month_idx],
}
}
#[must_use]
#[inline]
pub fn aztec_calendar_round(jdn: f64) -> (Tonalpohualli, Xiuhpohualli) {
(tonalpohualli_from_jdn(jdn), xiuhpohualli_from_jdn(jdn))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn correlation_date_tonalpohualli() {
let t = tonalpohualli_from_jdn(2_275_855.5);
assert_eq!(t.number, 1);
assert_eq!(t.sign, TonalpohualliSign::Coatl);
}
#[test]
fn correlation_date_xiuhpohualli() {
let x = xiuhpohualli_from_jdn(2_275_855.5);
assert_eq!(x.month, XiuhpohuallMonth::XocotlHuetzi);
assert_eq!(x.day, 1);
}
#[test]
fn tonalpohualli_260_day_cycle() {
let t1 = tonalpohualli_from_jdn(2_275_855.5);
let t2 = tonalpohualli_from_jdn(2_275_855.5 + 260.0);
assert_eq!(t1, t2);
}
#[test]
fn xiuhpohualli_365_day_cycle() {
let x1 = xiuhpohualli_from_jdn(2_275_855.5);
let x2 = xiuhpohualli_from_jdn(2_275_855.5 + 365.0);
assert_eq!(x1, x2);
}
#[test]
fn calendar_round_52_year_cycle() {
let (t1, x1) = aztec_calendar_round(2_275_855.5);
let (t2, x2) = aztec_calendar_round(2_275_855.5 + 18_980.0);
assert_eq!(t1, t2);
assert_eq!(x1, x2);
}
#[test]
fn tonalpohualli_all_signs_cycle() {
let mut signs = std::collections::HashSet::new();
for i in 0..20 {
let t = tonalpohualli_from_jdn(2_275_855.5 + f64::from(i));
signs.insert(format!("{:?}", t.sign));
}
assert_eq!(signs.len(), 20);
}
#[test]
fn tonalpohualli_number_range() {
for i in 0..260 {
let t = tonalpohualli_from_jdn(2_275_855.5 + f64::from(i));
assert!(
t.number >= 1 && t.number <= 13,
"number {} out of range",
t.number
);
}
}
#[test]
fn xiuhpohualli_nemontemi() {
let mut nemontemi_count = 0;
for i in 0..365 {
let x = xiuhpohualli_from_jdn(2_275_855.5 + f64::from(i));
if x.month == XiuhpohuallMonth::Nemontemi {
nemontemi_count += 1;
assert!(x.day >= 1 && x.day <= 5);
}
}
assert_eq!(nemontemi_count, 5);
}
#[test]
fn xiuhpohualli_month_days() {
let mut total = 0u32;
for i in 0..365 {
let x = xiuhpohualli_from_jdn(2_275_855.5 + f64::from(i));
if x.day == 1 {
total += 1; }
}
assert_eq!(total, 19); }
#[test]
fn display_tonalpohualli() {
let t = Tonalpohualli {
number: 1,
sign: TonalpohualliSign::Coatl,
};
assert_eq!(t.to_string(), "1 Coatl");
}
#[test]
fn display_xiuhpohualli() {
let x = Xiuhpohualli {
day: 1,
month: XiuhpohuallMonth::XocotlHuetzi,
};
assert_eq!(x.to_string(), "1 Xocotl Huetzi");
}
#[test]
fn serde_roundtrip_tonalpohualli() {
let t = tonalpohualli_from_jdn(2_275_855.5);
let json = serde_json::to_string(&t).unwrap();
let back: Tonalpohualli = serde_json::from_str(&json).unwrap();
assert_eq!(t, back);
}
#[test]
fn serde_roundtrip_xiuhpohualli() {
let x = xiuhpohualli_from_jdn(2_275_855.5);
let json = serde_json::to_string(&x).unwrap();
let back: Xiuhpohualli = serde_json::from_str(&json).unwrap();
assert_eq!(x, back);
}
#[test]
fn sign_display() {
assert_eq!(TonalpohualliSign::Cipactli.to_string(), "Cipactli");
assert_eq!(TonalpohualliSign::Xochitl.to_string(), "Xochitl");
assert_eq!(TonalpohualliSign::Ocelotl.to_string(), "Ocelotl");
}
#[test]
fn month_display() {
assert_eq!(XiuhpohuallMonth::Atlcahualo.to_string(), "Atlcahualo");
assert_eq!(XiuhpohuallMonth::Nemontemi.to_string(), "Nemontemi");
assert_eq!(
XiuhpohuallMonth::Tlacaxipehualiztli.to_string(),
"Tlacaxipehualiztli"
);
}
}