from datetime import datetime, timedelta from typing import List, Optional, Tuple from astral import LocationInfo, SunDirection, sun 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_times( observer: LocationInfo, date: datetime, surrounding_days: bool = False ) -> List[Tuple[datetime, datetime]]: """Get today’s Răhukăla times""" if surrounding_days: yesterday = date - timedelta(days=1) tomorrow = date + timedelta(days=1) dates = [yesterday, date, tomorrow] else: dates = [date] times: List[Tuple[datetime, datetime]] = [] for day in dates: try: daytime_rahukaala = sun.rahukaalam(observer, date=day, daytime=True) except ValueError: pass else: times.append(daytime_rahukaala) try: night_rahukaala = sun.rahukaalam(observer, date=day, daytime=False) except ValueError: pass else: times.append(night_rahukaala) times.sort(key=lambda items: items[0]) return times def get_rahukaalam( observer: LocationInfo, date: datetime ) -> Tuple[Optional[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""" times = get_rahukaalam_times(observer, date, surrounding_days=True) active: bool = False next_time: Optional[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