"""Main module of the Seasonal Clock""" __version__ = '0.1.0' from datetime import datetime, timedelta from typing import List, Tuple, Union from astral import Depression, LocationInfo, SunDirection, moon, sun from pytz import UTC from .config import load_config from .hours import seasonal_hours def get_moon_phase_text(moon_phase: float) -> str: """Get the name of the Moon phase""" name = 'Dark Moon' if moon_phase < 0.05: name = 'New Moon' elif moon_phase < 6.95: name = 'Waxing Crescent Moon' elif moon_phase < 7.05: name = 'Waxing Half Moon' elif moon_phase < 13.95: name = 'Waxing Gibbous Moon' elif moon_phase < 14.05: name = 'Full Moon' elif moon_phase < 20.95: name = 'Waning Gibbous Moon' elif moon_phase < 21.05: name = 'Waning Half Moon' elif moon_phase < 27.95: name = 'Waning Crescent Moon' return name def collect_day_parts( observer: LocationInfo, date: datetime ) -> List[Tuple[str, datetime, str, str]]: """Collect timestamp for all parts of the day on date""" day_parts = [] midnight = sun.midnight(observer, date=date) if midnight.date() < date.date(): midnight = sun.midnight(observer, date=date + timedelta(days=1)) elif midnight.date() > date.date(): midnight = sun.midnight(observer, date=date - timedelta(days=1)) day_parts.append(('midnight', midnight, 'Night', 'Midnight')) try: morning_blue_start = sun.blue_hour( observer, date=date, direction=SunDirection.RISING )[0] except ValueError: # At certain times and latitudes there might be no blue hour pass else: day_parts.append( ( 'morning_blue_start', morning_blue_start, 'Morning blue hour', 'Morning golden hour', ) ) try: golden_start, golden_end = sun.golden_hour( observer, date=date, direction=SunDirection.RISING ) except ValueError: # At certain times and latitudes there might be no golden hour pass else: day_parts.append( ('morning_golden_start', golden_start, 'Morning golden hour', 'Full Daytime') ) day_parts.append(('morning_golden_end', golden_end, 'Morning', 'Noon')) day_parts.append(('noon', sun.noon(observer, date=date), 'Afternoon', 'Noon')) try: evening_golden_start = sun.golden_hour(observer, date=date, direction=SunDirection.SETTING)[0] except ValueError: # At certain times and latitudes there might be no golden hour pass else: day_parts.append( ( 'evening_golden_start', evening_golden_start, 'Evening golden hour', 'Evening blue hour', ) ) try: blue_start, blue_end = sun.blue_hour( observer, date=date, direction=SunDirection.SETTING ) except ValueError: # At certain times and latitudes there might be no blue hour pass else: day_parts.append(('evening_blue_start', blue_start, 'Evening blue hour', 'Night')) day_parts.append(('evening_blue_end', blue_end, 'Night', 'Midnight')) day_parts.sort(key=lambda elem: elem[1]) return day_parts def get_rahukaalam(observer: LocationInfo, date: datetime) -> Tuple[bool, datetime]: """Get the time of the next Rāhukāla or, if we are in the middle of one, the time of its end""" yesterday = date - timedelta(days=1) tomorrow = date + timedelta(days=1) times: List[Tuple[datetime, datetime]] = [] for day in (yesterday, date, tomorrow): try: daytime_rahukaalam = sun.rahukaalam(observer, date=day, daytime=False) except ValueError: pass else: times.append(daytime_rahukaalam) try: night_rahukaalam = sun.rahukaalam(observer, date=day, daytime=True) except ValueError: pass else: times.append(night_rahukaalam) times.sort(key=lambda items: items[0]) active: bool = False next_time: datetime = None for start, end in times: if date < start: active = False next_time = start break if start < date < end: active = True next_time = end break if next_time is None: return None, None return active, next_time def main() -> None: """The Main Thing™""" config = load_config() location = LocationInfo( config['city'], config['country'], config['timezone'], config['latitude'], config['longitude'], ) local_tz = location.tzinfo utc_now = datetime.utcnow().replace(tzinfo=UTC) local_now = utc_now.astimezone(local_tz) moon_phase = moon.phase(local_now) day_parts = collect_day_parts(location.observer, local_now) final_name = 'Night' upcoming_name = None final_idx = -1 for idx, (_, time, name, next_name) in enumerate(day_parts): if utc_now > time: final_name = name upcoming_name = next_name final_idx = idx next_time = day_parts[final_idx + 1][1] if upcoming_name is None: upcoming_name = day_parts[0][2] print( f'Now: {local_now.strftime("%Y-%m-%d %H:%M:%S")} (UTC {utc_now.strftime("%Y-%m-%d %H:%M:%S")})' ) print(f'{final_name}, {next_time-local_now} until {upcoming_name}') rahukaalam, next_rahukaalam = get_rahukaalam(location.observer, local_now) if next_rahukaalam is not None: if rahukaalam: print(f'Rāhukāla (until {next_rahukaalam.strftime("%H:%M:%S")})') else: print(f'Next Rāhukāla at {next_rahukaalam.strftime("%H:%M:%S")}') print(get_moon_phase_text(moon_phase)) hour_num = utc_now.hour hour_short = seasonal_hours[utc_now.hour].short hour_long = seasonal_hours[utc_now.hour].long print(f'{hour_long} ({hour_short}, {hour_num})')