# -*- coding: utf-8 -*-
#
# badidatetime/badi_calendar.py
#
__docformat__ = "restructuredtext en"
import math
from badidatetime.base_calendar import BaseCalendar
from badidatetime.gregorian_calendar import GregorianCalendar
from badidatetime._coefficients import Coefficients
__all__ = ('BahaiCalendar',)
_LEAP_CACHE = None
[docs]
class BahaiCalendar(BaseCalendar, Coefficients):
"""
Implementation of the Baha'i (Badí') Calendar.
| WGS84--https://coordinates-converter.com/
| https://whatismyelevation.com/location/35.63735,51.72569/Tehran--Iran-
| https://en-us.topographic-map.com/map-g9q1h/Tehran/?center=35.69244%2C51.19492
| https://www.google.com/maps/place/Tehran,+Tehran+Province,+Iran/@35.9098957,51.51371,9.49z/data=!4m6!3m5!1s0x3f8e02c69b919039:0x17c26479772c5928!8m2!3d35.6891975!4d51.3889736!16s%2Fm%2F025zk75?entry=ttu
| https://gml.noaa.gov/grad/solcalc/ Sunset data
"""
# latitude longitude zone IANA name
_BAHAI_LOCATION = (35.69435, 51.288701, 3.5, 'Asia/Tehran')
"""
tuple: Represents the location coordinates latitude, longitude, political
time zone, and IANA time zone name of the Badí' orientation point in
Tehran.
"""
_GMT_LOCATION = (51.477928, -0.001545, 0.0)
"""
tuple: Represents the location coordinates latitude, longitude, and
political time zone of the GMT meridian.
"""
# 2394645.11511552 using Meeus' algorithm
_BADI_EPOCH = 2394643.11511551 # 2394643.11511552
"""
float: The Badí' epoch represented by the astronomical proleptic JD
algorithm. It represents UT time or GMT.
"""
KULLISHAY_MIN = -5
"""
int: Constant indicating the minimum Kull-i-shay the API supports.
"""
KULLISHAY_MAX = 4
"""
int: Constant indicating the maximum Kull-i-shay the API supports.
"""
MINYEAR = -1842
"""
int: Constant indicating the minimum year this API can represent.
"""
MAXYEAR = 1161
"""
int: Constant indicating the maximum year this API can represent.
"""
PROLEPTIC_GREG_1ST_DAY = 1721423.5
"""
float: Constant indicating the proleptic Gregorian first day or year 1.
"""
_RD_START = -287
"""
int: Constant indicating the minimum Rata Die.
"""
_RD_END = None
"""
int: Constant indicating the maximum Rata Die. Auto-generated constant
used to find ordinals.
:meta hide-value:
"""
_YEAR_START = None
"""
dict: Auto-generated constant used to find ordinals.
:meta hide-value:
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
"""
kull_i_shay: 361-year (19^2) vahid (integer)
vahid: (integer) 19-year vahid
year: (integer) 1 - 19
month: (integer) 1 - 19 plus 0 for Ayyām-i-Hā
day: (integer) 1 - 19
Baha'i long form date: [kull_i_shay, vahid, year, month, day]
"""
self._bahai_date = None
self._gc = GregorianCalendar()
[docs]
def utc_sunset(self, date: tuple, lat: float=None, lon: float=None,
zone: float=None) -> tuple:
"""
Return the time of sunset in UTC time for the given Badí' Day.
:param tuple date: A Badí' date.
:param float lat: The latitude.
:param float lon: The longitude.
:param float zone: The time zone.
:returns: The hour, minute, and second of sunset based on the
provided coordinates.
:rtype: tuple
"""
if any([True if l is None else False for l in (lat, lon, zone)]):
lat, lon, zone = self._BAHAI_LOCATION[:3]
jd = self.jd_from_badi_date(date[:3], lat, lon, zone)
jd += self._HR(zone)
return self._hms_from_decimal_day(jd + 0.5)
[docs]
def naw_ruz_g_date(self, year: int, lat: float=None, lon: float=None,
zone: float=None, *, hms: bool=False) -> tuple:
"""
Return the Badí' date for Naw-Ruz from the given Badí' year.
:param int year: A Badí' year.
:param float lat: The latitude.
:param float lon: The longitude.
:param float zone: The time zone.
:param bool hms: If True the output returns the hours, minutes, and
seconds as separate fields. If False the day has a
decimal value indicating the hours, minutes, and
seconds.
:returns: A Gregorian date.
:rtype: tuple
"""
if any([True if l is None else False for l in (lat, lon, zone)]):
lat, lon, zone = self._BAHAI_LOCATION[:3]
jd = self.jd_from_badi_date((year, 1, 1), lat, lon, zone)
jd += self._HR(zone)
return self._gc.gregorian_date_from_jd(jd, hms=hms, exact=True)
[docs]
def first_day_of_ridvan_g_date(self, year: int, lat: float=None,
lon: float=None, zone: float=None, *,
hms: bool=False) -> tuple:
"""
Find the first day of Riḍván either with or without hours, minutes,
and seconds. If the latitude, longitude, and time zone are not given
Riḍván time of day is determined for the city of Nur in Iran.
:param int year: A Badí' year.
:param float lat: The latitude.
:param float lon: The longitude.
:param float zone: The time zone.
:param bool hms: If True the output returns the hours, minutes, and
second as separate fields. If False the day has a
decimal value indicating the hours, minutes, and
seconds.
:returns: A Gregorian date.
:rtype: tuple
"""
if any([True if l is None else False for l in (lat, lon, zone)]):
lat, lon, zone = self._BAHAI_LOCATION[:3]
jd = self.jd_from_badi_date((year, 2, 13), lat, lon, zone)
jd += self._HR(zone)
return self._gc.gregorian_date_from_jd(jd, hms=hms, exact=True)
[docs]
def jd_from_badi_date(self, b_date: tuple, lat: float=None,
lon: float=None, zone: float=None) -> float:
"""
Convert a Badí' short form date to Julian period day.
.. note::
The JD must be interoperable with different calendar code. This
method returns a standard UT time not Badí' time. To convert to
Badí' time you will need to add the time zone divided by 24 to
the returned JD.
:param tuple b_date: A short form Badí' date.
:param float lat: The latitude.
:param float lon: The longitude.
:param float zone: The time zone.
:returns: The Julian Period day.
:rtype: float
"""
self._check_valid_badi_date(b_date, short_in=True)
year, month, day = b_date[:3]
hh, mm, ss, us = self._get_hms(b_date, short_in=True)
if month == 0: # Ayyam-i-Ha
days = 18 * 19
elif month < 19: # month 1 - 18
days = (month - 1) * 19
else: # month 19
days = 18 * 19 + 4 + self._is_leap_year(year)
td = self._days_in_years(year - 1)
jd = td + math.floor(self._BADI_EPOCH + 1) + days + day
if any([True if l is None else False for l in (lat, lon, zone)]):
lat, lon, zone = self._BAHAI_LOCATION[:3]
# The day may have a decimal component. ex. 1.5 = (1 day and 12 hours)
# This day is relative to UTC time, so we need to compensate for Badí'
# time since a Badí' day starts at sunset, not at midnight.
jd0 = self._meeus_from_exact(jd)
coeff = self._get_day_coeff(year)
jd0 += coeff
jd_ss = self._sun_setting(jd0, lat, lon)
a_ss = self._exact_from_meeus(jd_ss)
day_frac = self._decimal_day_from_hms(hh, mm, ss, us)
return round(a_ss + day_frac, self._ROUNDING_PLACES)
[docs]
def badi_date_from_jd(self, jd: float, lat: float=None, lon: float=None,
zone: float=None, *, us: bool=False,
short: bool=False, fraction: bool=False,
trim: bool=False, rtd: bool=False) -> tuple:
"""
Convert a Julian Period day to a Badí' date.
.. note::
Only pass a UTC JD not a zone shifted JD.
:param float jd: Julian Period day in the Astronomically correct
method and in UT time.
:param float lat: The latitude.
:param float lon: The longitude.
:param float zone: The standard time zone.
:param bool us: If True the seconds are split to seconds and
microseconds else if False the seconds has a partial
day as a decimal.
:param bool short: If True then parse for a short date else if False
(default) parse for a long date.
:param bool fraction: This will return a short date with a possible
fraction on the day.
:param bool trim: Trim the us, ss, mm, and hh in that order.
:param bool rtd: Round to day.
:returns: The Badí' date from an Astronomically correct Julian
Period day.
:rtype: tuple
"""
assert self._xor_boolean((fraction, us, rtd)), (
"Cannot set more than one of fraction, us, or rtd to True.")
if any([True if l is None else False for l in (lat, lon, zone)]):
lat, lon, zone = self._BAHAI_LOCATION[:3]
astro_ss, frac = self._utc_to_badi_time(jd, lat, lon)
# We need to slowly adjust the RD based on the longitude so the day
# stays within the local time frame.
local_civil_day = math.floor(astro_ss + lon / 360.0 + 0.5)
civil_epoch = math.floor(self.PROLEPTIC_GREG_1ST_DAY + 0.5)
rd = local_civil_day - civil_epoch + 1
year = self._badi_year_from_rd(rd)
year_start_rd = self._YEAR_START[year]
doy = rd - year_start_rd + 1
ayyamiha = 4 + self._is_leap_year(year)
if doy <= 342: # Months 1 - 18
month = (doy - 1) // 19 + 1
day = (doy - 1) % 19 + 1
elif doy <= 342 + ayyamiha: # Month 0
month = 0
day = doy - 342
else: # Month 19
month = 19
day = doy - (342 + ayyamiha)
if fraction:
b_date = year, month, round(day + frac, 6)
elif rtd:
day = round(day + frac)
b_date = year, month, day
if not short:
b_date = self.long_date_from_short_date(b_date, trim=trim)
else:
trim = trim if us else True
date = year, month, day, *self._hms_from_decimal_day(frac)
l_date = self.long_date_from_short_date(date, trim=True)
b_date = self.kvymdhms_from_b_date(l_date, us=us, short=short,
trim=trim)
return b_date
[docs]
def _badi_year_from_rd(self, rd: int) -> int:
assert self._RD_START <= rd <= self._RD_END, (
f"Invalid Rata Die value {rd} it must be between "
f"[{self._RD_START}, {self._RD_END}].")
# Approximate year
y = int((rd - self._RD_START) / 365.2425) + self.MINYEAR
# Clamp
if y < self.MINYEAR: # pragma: no cover
y = self.MINYEAR
elif y > self.MAXYEAR:
y = self.MAXYEAR
# Correct if necessary
if rd < self._YEAR_START[y]:
y -= 1
elif rd >= self._YEAR_START.get(y + 1, self._RD_END + 1):
y += 1 # pragma: no cover
return y
[docs]
def short_date_from_long_date(self, b_date: tuple, *, trim: bool=False,
) -> tuple:
"""
Convert a long date (kvymdhms) to a short date (ymdhms). In either
case microseconds could also be provided.
:param tuple b_date: A long form date with or without microseconds.
:param bool trim: Trim the us, ss, mm, and hh in that order.
:returns: The short form Badí' date.
:rtype: tuple
"""
kull_i_shay, vahid, year, month, day = b_date[:5]
hh, mm, ss, us = self._get_hms(b_date)
y = (kull_i_shay - 1) * 361 + (vahid - 1) * 19 + year
dm = 19 if month in range(1, 20) else 4 + self._is_leap_year(y)
# The ShortFormStruct and LongFormStruct accept seconds up to 61.
if ss >= 60:
mm += 1
ss -= 1
if mm >= 60:
hh += 1
mm -= 1
if hh >= 24:
day += 1
hh -= 1
if day > dm:
if dm in (4, 5):
month = 19
day = 1
else:
month += 1
day -= 1
if month > 19:
month = 1
y += 1
hmsms = self._trim_hms((hh, mm, ss, us)) if trim else (hh, mm, ss, us)
date = (y, month, day) + hmsms
self._check_valid_badi_date(date, short_in=True)
return date
[docs]
def long_date_from_short_date(self, date: tuple, *, trim: bool=False,
) -> tuple:
"""
Convert a date to a short date (ymdhms) to a long date (kvymdhms).
:param tuple b_date: A short form date with or without microseconds.
:param bool trim: Trim the us, ss, mm, and hh in that order.
:returns: The long form Badí' date.
:rtype: tuple
"""
y, month, day = date[:3]
hh, mm, ss, us = self._get_hms(date, short_in=True)
k = y / 361
kull_i_shay = 0 if y == 0 else math.ceil(k)
k0 = self._truncate_decimal(k % 1, self._ROUNDING_PLACES)
v = k0 / 19 * 361
if v == 0: # If there is no fraction in v
vahid = 19
year = 19
else:
vahid = math.ceil(v)
year = math.ceil(v % 1 * 19)
hmsms = self._trim_hms((hh, mm, ss, us)) if trim else (hh, mm, ss, us)
b_date = (kull_i_shay, vahid, year, month, day) + hmsms
self._check_valid_badi_date(b_date)
return b_date
[docs]
def date_from_kvymdhms(self, b_date: tuple, *, short: bool=False) -> tuple:
"""
Convert (Kull-i-Shay, Váḥid, year, month, day, hour, minute, second,
us) into a (Kull-i-Shay, Váḥid, year, month, day.fraction) or
(year, month, day.fraction) date.
:param tuple b_date: The Badí' date in long form.
:param bool short: If True then parse for a short date else if False
(default) parse for a long date.
:returns: The long or short form Badí' date with hours, minutes,
seconds, and microseconds if set.
:rtype: tuple
"""
self._check_valid_badi_date(b_date)
kull_i_shay, vahid, year, month, day = b_date[:5]
hour, minute, second, us = self._get_hms(b_date)
day += round(self._HR(hour) + self._MN(minute) + self._SEC(second) +
self._US(us), 6) # Round to 6 decimal places
date = (kull_i_shay, vahid, year, month, day)
return (self.short_date_from_long_date(
date, trim=True) if short else date)
[docs]
def kvymdhms_from_b_date(self, b_date: tuple, *, us: bool=False,
short: bool=False, trim: bool=False) -> tuple:
"""
Convert (Kull-i-Shay, Váḥid, year, month, day.fraction) into
(Kull-i-Shay, Váḥid, year, month, day, hour, minute, second) or if
short is True (year, month, day, hour, minute, second). If us is
True the seconds are split to second and microsecond.
:param tuple b_date: The Badí' date in long form.
:param bool us: If True the seconds are split to seconds and
microseconds else if False the seconds has a partial
day as a decimal.
:param bool short: If True then parse for a short date else if False
(default) parse for a long date.
:param bool trim: Trim the us, ss, mm, and hh in that order.
:returns: The long or short form Badí' date with hours, minutes,
seconds, and microseconds if set.
:rtype: tuple
"""
dlen = len(b_date)
# We need to trim any zero hh, mm, ss, us so partial days below
# work correctly.
if dlen > 5:
hms = self._trim_hms(b_date[5:dlen])
b_date = b_date[:5] + hms
dlen = len(b_date)
self._check_valid_badi_date(b_date)
kull_i_shay, vahid, year, month, day = b_date[:5]
if dlen == 5:
hd = self._PARTIAL_DAY_TO_HOURS(day)
hour = math.floor(hd)
md = self._PARTIAL_HOUR_TO_MINUTE(hd)
minute = math.floor(md)
# Round to 6 decimal places
second = round(self._PARTIAL_MINUTE_TO_SECOND(md), 6)
else:
hour = b_date[5] if dlen > 5 else 0
minute = b_date[6] if dlen > 6 else 0
second = b_date[7] if dlen > 7 else 0
date = (kull_i_shay, vahid, year, month, math.floor(day))
if us:
hmsms = (hour, minute, *self._sec_microsec_from_seconds(second))
else:
hmsms = (hour, minute, second)
date += self._trim_hms(hmsms) if trim else hmsms
return (self.short_date_from_long_date(date, trim=trim)
if short else date)
[docs]
def badi_date_from_gregorian_date(self, g_date: tuple, lat: float=None,
lon: float=None, zone: float=None, *,
us: bool=False, short: bool=False,
trim: bool=False, rtd: bool=False,
_exact: bool=True) -> tuple:
"""
Get the Badí' date from the Gregorian date.
.. note::
The date that is passed in is in GMT date and time not the date
and time of the time zone you want.
:param tuple g_date: A Gregorian date.
:param float lat: The latitude.
:param float lon: The longitude.
:param float zone: The standard time zone.
:param bool us: If True the seconds are split to seconds amd
microseconds else if False the seconds has a partial
day as a decimal.
:param bool short: If True then parse for a short date else if False
(default) parse for a long date.
:param bool trim: Trim the us, ss, mm, and hh in that order.
:param bool rtd: Round to day.
:param bool _exact: Use the more exact Julian Period algorithm.
Default is True. This should generally be set to
True, a False value will give inaccurate results
and is used for testing only.
:returns: A Badí' date long or short form.
:rtype: tuple
"""
jd = self._gc.jd_from_gregorian_date(g_date, exact=_exact)
return self.badi_date_from_jd(jd, lat, lon, zone, us=us, short=short,
trim=trim, rtd=rtd)
[docs]
def gregorian_date_from_badi_date(self, b_date: tuple, lat: float=None,
lon: float=None, zone: float=None, *,
us: bool=False, _exact: bool=True,
) -> tuple:
"""
Get the Gregorian date from the Badí' date.
:param tuple b_date: A Badí' date short form.
:param float lat: The latitude.
:param float lon: The longitude.
:param float zone: The standard time zone.
:param bool us: If True the seconds are split to seconds amd
microseconds else if False the seconds has a partial
day as a decimal.
:param bool _exact: Use the more exact Julian Period algorithm.
Default is True. This should generally be set to
True, a False value, will give inaccurate results
and is used for testing only.
:returns: The Gregorian date.
:rtype: tuple
"""
if any([True if l is None else False for l in (lat, lon, zone)]):
lat, lon, zone = self._BAHAI_LOCATION[:3]
jd = self.jd_from_badi_date(b_date, lat, lon, zone)
jd += self._HR(zone)
return self._gc.gregorian_date_from_jd(jd, hms=True, us=us,
exact=_exact)
[docs]
def badi_date_from_timestamp(self, t: float, lat: float=None,
lon: float=None, zone: float=None, *,
us: bool=False, short: bool=False,
trim: bool=False, rtd: bool=False) -> tuple:
"""
Get the Badí' date from a POSIX timestamp.
:param float t: Timestamp
:param float lat: The latitude.
:param float lon: The longitude.
:param float zone: The time zone.
:param bool us: If True the seconds are split to seconds and
microseconds else if False the seconds has a partial
day as a decimal.
:param bool short: If True then parse for a short date else if False
(default) parse for a long date.
:param bool trim: Trim the us, ss, mm, and hh in that order.
:param bool rtd: Round to day.
:returns: A Badí' date long or short form.
:rtype: tuple
"""
jd = t / self._SECONDS_PER_DAY + self._POSIX_EPOCH
return self.badi_date_from_jd(jd, lat, lon, zone, us=us, short=short,
trim=trim, rtd=rtd)
[docs]
def timestamp_from_badi_date(self, date: tuple, lat: float=None,
lon: float=None, zone: float=None) -> float:
"""
Convert a Badí' date to a timestamp.
:param tuple date: The Badí' date.
:param float lat: The latitude.
:param float lon: The longitude.
:param float zone: The time zone.
:returns: The timestamp corrected for the time zone.
:rtype: float
"""
jd = self.jd_from_badi_date(date, lat, lon, zone)
return round((jd - self._POSIX_EPOCH) * self._SECONDS_PER_DAY,
self._ROUNDING_PLACES)
[docs]
def midday(self, date: tuple, *, hms: bool=False, _short: bool) -> tuple:
"""
Find the midday time in hours, minutes, and seconds with fraction.
All calculations are done in GMT.
:param tuple date: Badí' date short or long.
:param bool hms: If True return the hours, minutes, and seconds else
if False return the decimal value.
:param bool _short: Indicates the incoming date format.
:returns: Midday in hours, minutes, and seconds.
:rtype: tuple
"""
if not _short:
b_date = self.short_date_from_long_date(date, trim=True)
else:
b_date = date
jd = self.jd_from_badi_date(b_date)
jd1 = self._meeus_from_exact(jd)
ss0 = self._sun_setting(jd1, *self._GMT_LOCATION[:2])
jd2 = self._meeus_from_exact(jd + 1)
ss1 = self._sun_setting(jd2, *self._GMT_LOCATION[:2])
ut_mid = (ss1 - ss0) / 2
local_mid = self._local_zone_correction(ut_mid, self._GMT_LOCATION[2])
return self._hms_from_decimal_day(local_mid) if hms else local_mid
[docs]
def _trim_hms(self, hms: tuple) -> tuple:
"""
Trim the hours, minutes, seconds or microseconds off the date if
zero unless a lower value was not zero.
.. list-table:: Examples
:widths: 18 16 66
:header-rows: 1
* - Examples
- Results
- Description
* - (12, 30, 6, 0)
- (12, 30, 6)
- The zero microseconds would be trimmed.
* - (12, 0, 6, 0)
- (12, 0, 6)
- The zero microseconds would be trimmed but the zero minutes
would be left untouched.
:param tuple hms: An hour, minute, and second object.
:returns: An object with the lower order parts stripped off if
they have a zero value.
:rtype: tuple
"""
items = []
has = False
for v in reversed(hms):
if v == 0 and not has:
continue
else:
items.append(v)
has = True
return tuple(reversed(items))
[docs]
def _check_valid_badi_date(self, b_date: tuple, short_in: bool=False
) -> None:
"""
Check that the Kull-i-Shay, Váḥids, year, month, day, hour, minute,
second, and microsecond values are valid.
:param tuple b_date: A long form Badí' date.
:param bool short_in: If True then parse for a short date else if
False parse for a long date. This is for
incoming dates not outgoing dates as in most
other uses of 'short'.
:returns: Nothing
:rtype: None
:raises AssertionError: When a date Váḥid, year, month, day, hour,
minute, second, or microsecond are out of
range.
"""
cycle = 20
if not short_in: # Long Badí' date
kull_i_shay, vahid, year, month, day = b_date[:5]
hour, minute, second, us = self._get_hms(b_date)
assert (self.KULLISHAY_MIN-1 <= kull_i_shay
<= self.KULLISHAY_MAX+1), (
f"Invalid kull-i-shay {kull_i_shay}, it must be in the range "
f"of [{self.KULLISHAY_MIN}, {self.KULLISHAY_MAX}].")
assert 1 <= vahid < cycle, (
f"Invalid Váḥids '{vahid}' in a Kull-i-Shay’, it must be in "
"the range of [1, 19].")
assert 1 <= year < cycle, (
f"Invalid year '{year}' in a Váḥid, it must be in the "
"range of [1, 19].")
ly = (kull_i_shay - 1) * 361 + (vahid - 1) * 19 + year
else: # Short Badí' date
year, month, day = b_date[:3]
hour, minute, second, us = self._get_hms(b_date, short_in=True)
assert self.MINYEAR-1 <= year <= self.MAXYEAR+1, (
f"Invalid year '{year}' it must be in the range of ["
f"{self.MINYEAR}, {self.MAXYEAR}].")
ly = year
assert 0 <= month < cycle, (
f"Invalid month '{month}', it must be in the range of [0, 19].")
# This is Ayyām-i-Hā and could be 4 or 5 days depending on leap year.
cycle = (5 + self._is_leap_year(ly)) if month == 0 else cycle
assert 1 <= day < (cycle), (
f"Invalid day '{day}' for month '{month}', it must be in the "
f"range of [1, {cycle-1}].")
self._check_valid_badi_time(hour, minute, second, us)
# Check if there are any fractions that invalidate other values.
if any((hour, minute, second)):
assert not day % 1, (
"If there is a part day then there can be no hours, minutes, "
"or seconds.")
if any((minute, second)):
assert not hour % 1, ("If there is a part hour then there can "
"be no minutes or seconds.")
if second:
assert not minute % 1, (
"If there is a part minute then there can be no seconds.")
[docs]
def _check_valid_badi_time(self, hour: float, minute: float, second: float,
us: int, maxsec: int=60) -> None:
"""
Check that the hour, minute, second, and microsecond values are valid.
:param float hour: Hours
:param float minute: Minutes
:param float second: Seconds
:param float us: Microseconds
:returns: Nothing
:rtype: None
:raises AssertionError: When an hour, minute, second, or microsecond
are out of range.
"""
assert 0 <= hour < 25, (
f"Invalid hour '{hour}', it must be in the range of [0, 24].")
assert 0 <= minute < 60, (
f"Invalid minute '{minute}', it must be in the range of [0, 59].")
assert 0 <= second < maxsec, (
f"Invalid second '{second}', it must be in the range of "
f"[0, {maxsec}].")
assert 0 <= us < 1000000, (
f"Invalid microseconds '{us}', it must be in the range of "
"[0, 999999].")
[docs]
def _is_leap_year(self, year: int) -> bool:
"""
Return a Boolean True if a Badí' leap year, False if not.
:param int year: This value must be a Badí' short form year.
:returns: A Boolean indicating if a leap year or not.
:rtype: bool
"""
return self._YEAR_START[year + 1] - self._YEAR_START[year] == 366
[docs]
def _get_hms(self, date: tuple, *, short_in: bool=False) -> tuple:
"""
Parse the hours, minutes, seconds, and microseconds, if they exist
for either the short or long form Badí' date.
:param tuple date: A long or short form Badí' date.
:param bool short_in: If True then parse for a short date else if False
parse for a long date. This is for incoming dates
not outgoing dates as in most other uses of
'short'.
:returns: The relevant hours, minutes, and seconds.
:rtype: tuple
"""
t_len = len(date)
s = 3 if short_in else 5
hour = date[s] if t_len > s and date[s] is not None else 0
minute = date[s+1] if t_len > s+1 and date[s+1] is not None else 0
second = date[s+2] if t_len > s+2 and date[s+2] is not None else 0
us = date[s+3] if t_len > s+3 and date[s+3] is not None else 0
return hour, minute, second, us
[docs]
def _day_length(self, jd: float, lat: float, lon: float, *,
decimal: bool=False) -> tuple:
"""
The hour, minute, and seconds of the day's offset either less than
or more than 24 hours.
:param float jd: The astronomically exact Julian Period day.
:param float lat: The latitude.
:param float lon: The longitude.
:param bool decimal: If `False` (default) return HH:MM:SS else if
`True` return a decimal number.
:returns: The hour, minute, and second or a decimal number.
:rtype: tuple or float
"""
jd0 = math.floor(jd)
jd1 = jd0 + 1
# The next day
jd1 = self._meeus_from_exact(jd1)
ss1 = self._sun_setting(jd1, lat, lon)
# The first day
jd0 = self._meeus_from_exact(jd0)
ss0 = self._sun_setting(jd0, lat, lon)
# Subtract the first day from the next day given the total
# hours, minutes, and seconds between them.
ut_ss = ss1 - ss0
if decimal:
ret = round(ut_ss, self._ROUNDING_PLACES)
else:
value = list(self._hms_from_decimal_day(ut_ss))
value[0] = 24 if value[0] == 0 else value[0]
ret = tuple(value)
return ret
[docs]
def _utc_to_badi_time(self, jd: float, lat: float, lon: float) -> float:
"""
Convert UTC time to Badí' time. The JD must be in UT time. The
resultant date and time are now authoritative.
:param float jd: An Astronomically correct JD.
:param float lat: The latitude.
:param float lon: The longitude.
:returns: The JD with the correct Badí' time.
:rtype: float
"""
def get_sunset(hist_jd, inc):
ss = self._sun_setting(hist_jd + inc, lat, lon)
return self._exact_from_meeus(ss)
hist_jd = self._meeus_from_exact(jd)
ss_prev = get_sunset(hist_jd, -1)
ss_curr = get_sunset(hist_jd, 0)
astro_ss = ss_curr if jd >= ss_curr else ss_prev
delta = jd - astro_ss
if delta < 0: # pragma: no cover
astro_ss -= 1
elif delta >= 1:
astro_ss += 1
# Elapsed fraction since sunset
frac = jd - astro_ss
return astro_ss, frac
[docs]
def _build_badi_year_start(self):
year_start = {}
lon_div = self._BAHAI_LOCATION[1] / 360.0
for year in range(self.MINYEAR - 1, self.MAXYEAR + 2):
jd = self.jd_from_badi_date((year, 1, 1))
jd_int = int(jd)
frac = jd - jd_int
rd = math.floor(jd_int - self.PROLEPTIC_GREG_1ST_DAY +
(frac + lon_div + 0.5 + 1e-12))
year_start[year] = rd
return year_start