k
This commit is contained in:
215
thelema_calendar.py
Normal file
215
thelema_calendar.py
Normal file
@@ -0,0 +1,215 @@
|
||||
"""
|
||||
Thelema Calendar calculation function.
|
||||
Converts Gregorian calendar to Thelema Calendar (Year of the Beast).
|
||||
|
||||
References:
|
||||
- Year 1 of Thelema = 1904 CE (Spring Equinox)
|
||||
- 22-year cycles (The Great Year)
|
||||
- Weekday planetary rulers
|
||||
"""
|
||||
|
||||
from datetime import datetime, date, timedelta, timezone
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
|
||||
# Planetary symbols for weekdays (Sun=0, Mon=1, ..., Sat=6)
|
||||
WEEKDAY_PLANETS = ["☉︎", "☽︎", "♂︎", "☿︎", "♃︎", "♀︎", "♄︎"]
|
||||
|
||||
# Luminary symbols
|
||||
LUMINARY_SYMBOLS = {
|
||||
"Moon": "☾︎",
|
||||
"Mercury": "☿︎",
|
||||
"Mars": "♂︎",
|
||||
"Venus": "♀︎",
|
||||
"Sun": "☉︎",
|
||||
}
|
||||
|
||||
# Luminary order
|
||||
LUMINARIES = ["Moon", "Mercury", "Mars", "Venus", "Sun"]
|
||||
|
||||
|
||||
def to_roman(num: int) -> str:
|
||||
"""Convert an integer to Roman numerals.
|
||||
|
||||
Args:
|
||||
num: Integer to convert (1-3999)
|
||||
|
||||
Returns:
|
||||
Roman numeral string
|
||||
"""
|
||||
val = [
|
||||
1000, 900, 500, 400,
|
||||
100, 90, 50, 40,
|
||||
10, 9, 5, 4,
|
||||
1
|
||||
]
|
||||
syms = [
|
||||
"M", "CM", "D", "CD",
|
||||
"C", "XC", "L", "XL",
|
||||
"X", "IX", "V", "IV",
|
||||
"I"
|
||||
]
|
||||
roman_num = ''
|
||||
i = 0
|
||||
while num > 0:
|
||||
for _ in range(num // val[i]):
|
||||
roman_num += syms[i]
|
||||
num -= val[i]
|
||||
i += 1
|
||||
return roman_num
|
||||
|
||||
|
||||
def _apply_gmt_offset(moment: datetime, gmt_offset: str) -> datetime:
|
||||
"""Apply GMT offset to a datetime object.
|
||||
|
||||
Args:
|
||||
moment: datetime object to convert
|
||||
gmt_offset: Offset as string (e.g., "+3:00", "-8:00", "+5:30")
|
||||
|
||||
Returns:
|
||||
datetime object adjusted to the specified timezone
|
||||
|
||||
Example:
|
||||
>>> utc_time = datetime(2025, 11, 16, 12, 0, 0)
|
||||
>>> pst_time = _apply_gmt_offset(utc_time, "-8:00")
|
||||
>>> pst_time.hour
|
||||
4 # 12:00 UTC - 8 hours = 04:00 PST
|
||||
"""
|
||||
# Parse GMT offset (e.g., "+3:00" or "-8:00")
|
||||
gmt_offset = gmt_offset.strip()
|
||||
|
||||
# Handle format like "+3:00", "-8:00", "+5:30"
|
||||
if ":" in gmt_offset:
|
||||
sign = 1 if gmt_offset[0] == "+" else -1
|
||||
parts = gmt_offset[1:].split(":")
|
||||
hours = int(parts[0])
|
||||
minutes = int(parts[1]) if len(parts) > 1 else 0
|
||||
offset_seconds = sign * (hours * 3600 + minutes * 60)
|
||||
else:
|
||||
# Handle format like "+3" or "-8"
|
||||
sign = 1 if gmt_offset[0] == "+" else -1
|
||||
hours = int(gmt_offset[1:])
|
||||
offset_seconds = sign * hours * 3600
|
||||
|
||||
# Create timezone with the offset
|
||||
tz = timezone(timedelta(seconds=offset_seconds))
|
||||
|
||||
# Convert the naive datetime to aware datetime in UTC, then convert to target timezone
|
||||
utc_dt = moment.replace(tzinfo=timezone.utc)
|
||||
local_dt = utc_dt.astimezone(tz)
|
||||
|
||||
# Return as naive datetime (without timezone info) for calculation
|
||||
return local_dt.replace(tzinfo=None)
|
||||
|
||||
|
||||
def get_astro_data() -> Dict[str, str]:
|
||||
"""Get current astrological data for luminaries.
|
||||
|
||||
This is a placeholder that returns mock data.
|
||||
In a real implementation, you would scrape astro.com or use an astrology library.
|
||||
|
||||
Returns:
|
||||
Dictionary mapping luminary names to their astrological positions
|
||||
Format: "26°Taurus", "19°Sagittarius", etc.
|
||||
"""
|
||||
# Mock data - replace with actual astrological calculations
|
||||
astro_data = {
|
||||
"Moon": "24°Sagittarius",
|
||||
"Mercury": "7°Aquarius",
|
||||
"Mars": "4°Virgo",
|
||||
"Venus": "30°Scorpio",
|
||||
"Sun": "26°Taurus",
|
||||
}
|
||||
return astro_data
|
||||
|
||||
|
||||
def thelema_calendar(moment: Optional[datetime] = None, gmt_offset: Optional[str] = None) -> str:
|
||||
"""Calculate and return the Thelema Calendar date.
|
||||
|
||||
The Thelema Calendar counts from the Spring Equinox of 1904 CE (Year 1 of the Beast).
|
||||
It divides time into 22-year cycles and displays planetary correspondences.
|
||||
|
||||
Args:
|
||||
moment: datetime object (defaults to current date/time)
|
||||
gmt_offset: GMT offset as string (e.g., "+3:00", "-8:00", "+5:30")
|
||||
If provided, converts the given moment to this timezone before calculating.
|
||||
Example: "+3:00" for UTC+3, "-8:00" for PST (UTC-8), "+5:30" for IST
|
||||
|
||||
Returns:
|
||||
Formatted Thelema Calendar string with format:
|
||||
"[Luminary positions] : [Weekday planet] : [Epoch][Year]"
|
||||
|
||||
Example:
|
||||
>>> # Current time in UTC
|
||||
>>> Tarot.thelema_calendar()
|
||||
|
||||
>>> # Specific time in PST (UTC-8)
|
||||
>>> from datetime import datetime
|
||||
>>> moment = datetime(2025, 11, 16, 14, 30, 0)
|
||||
>>> Tarot.thelema_calendar(moment, "-8:00")
|
||||
|
||||
>>> # IST (UTC+5:30)
|
||||
>>> Tarot.thelema_calendar(gmt_offset="+5:30")
|
||||
"""
|
||||
if moment is None:
|
||||
moment = datetime.now()
|
||||
|
||||
# Apply timezone offset if provided
|
||||
if gmt_offset is not None:
|
||||
moment = _apply_gmt_offset(moment, gmt_offset)
|
||||
|
||||
today = moment.date()
|
||||
current_year = today.year
|
||||
|
||||
# Calculate year difference from Thelema epoch (1904)
|
||||
year_diff = current_year - 1904
|
||||
|
||||
# Check if we've passed the spring equinox (March 22)
|
||||
# If yes, increment the year count
|
||||
equinox_passed = (today.month > 3) or (today.month == 3 and today.day >= 22)
|
||||
if equinox_passed:
|
||||
year_diff += 1
|
||||
|
||||
# Calculate epoch (22-year cycles)
|
||||
epoch_count = year_diff // 22
|
||||
epoch_roman = to_roman(epoch_count)
|
||||
|
||||
# Calculate year within current 22-year cycle
|
||||
thelema_year = (current_year - 12) % 22
|
||||
thelema_year_roman = to_roman(thelema_year).lower()
|
||||
|
||||
# Get weekday (0=Monday, 6=Sunday in Python; we want 0=Sunday for alignment)
|
||||
# Python's weekday(): Monday=0, Sunday=6
|
||||
# We want: Sunday=0, Monday=1, ..., Saturday=6
|
||||
python_weekday = today.weekday() # 0=Mon, 6=Sun
|
||||
weekday_index = (python_weekday + 1) % 7 # Convert to Sun=0, Mon=1, etc.
|
||||
|
||||
# Determine weekday based on UTC hour (or provided moment hour)
|
||||
utc_hour = moment.hour # Note: this is local hour, not UTC in this simplified version
|
||||
if utc_hour >= 0 and utc_hour < 14:
|
||||
day_weekday = weekday_index % 7
|
||||
else:
|
||||
day_weekday = (weekday_index + 1) % 7
|
||||
|
||||
weekday_symbol = WEEKDAY_PLANETS[day_weekday]
|
||||
|
||||
# Get astrological data
|
||||
astro_data = get_astro_data()
|
||||
|
||||
# Build luminary string
|
||||
luminary_strings = []
|
||||
for luminary in LUMINARIES:
|
||||
symbol = LUMINARY_SYMBOLS[luminary]
|
||||
position = astro_data.get(luminary, "unknown")
|
||||
luminary_strings.append(f"{symbol} {position}")
|
||||
|
||||
# Combine all cycles
|
||||
cycles = [
|
||||
" : ".join(luminary_strings),
|
||||
weekday_symbol,
|
||||
f"{epoch_roman}{thelema_year_roman}",
|
||||
]
|
||||
|
||||
return " : ".join(cycles)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user