Files
2026-03-31 09:44:27 +02:00

137 lines
4.2 KiB
Python

"""Various conveniences.
Higher-level libraries like Skyfield that use this one usually have
their own date and time handling. But for folks using this library by
itself, native Python datetime handling could be convenient.
"""
import datetime as dt
import sgp4
from math import pi
from .functions import days2mdhms, jday
class _UTC(dt.tzinfo):
'UTC'
zero = dt.timedelta(0)
def __repr__(self):
return 'UTC'
def dst(self, datetime):
return self.zero
def tzname(self, datetime):
return 'UTC'
def utcoffset(self, datetime):
return self.zero
UTC = _UTC()
def jday_datetime(datetime):
"""Return two floats that, when added, produce the specified Julian date.
The first float returned gives the date, while the second float
provides an additional offset for the particular hour, minute, and
second of that date. Because the second float is much smaller in
magnitude it can, unlike the first float, be accurate down to very
small fractions of a second.
>>> jd, fr = jday(2020, 2, 11, 13, 57, 0)
>>> jd
2458890.5
>>> fr
0.58125
Note that the first float, which gives the moment of midnight that
commences the given calendar date, always carries the fraction
``.5`` because Julian dates begin and end at noon. This made Julian
dates more convenient for astronomers in Europe, by making the whole
night belong to a single Julian date.
The input is a native `datetime` object. Timezone of the input is
converted internally to UTC.
"""
u = datetime.astimezone(UTC)
year = u.year
mon = u.month
day = u.day
hr = u.hour
minute = u.minute
sec = u.second + u.microsecond * 1e-6
return jday(year, mon, day, hr, minute, sec)
def sat_epoch_datetime(sat):
"""Return the epoch of the given satellite as a Python datetime."""
year = sat.epochyr
year += 1900 + (year < 57) * 100
days = sat.epochdays
month, day, hour, minute, second = days2mdhms(year, days)
if month == 12 and day > 31: # for that time the ISS epoch was "Dec 32"
year += 1
month = 1
day -= 31
second, fraction = divmod(second, 1.0)
second = int(second)
micro = int(fraction * 1e6)
return dt.datetime(year, month, day, hour, minute, second, micro, UTC)
_ATTRIBUTES = []
_ATTR_MAXES = {}
_MAX_VALUES = {'2pi': 2*pi, 'pi': pi}
def _load_attributes():
for line in sgp4.__doc__.splitlines():
if line.endswith('*'):
title = line.strip('*')
_ATTRIBUTES.append(title)
elif line.startswith('| ``'):
pieces = line.split('``')
name = pieces[1]
_ATTRIBUTES.append(name)
i = 2
while pieces[i] == ', ':
another_name = pieces[i+1]
_ATTRIBUTES.append(another_name)
i += 2
if '<' in line:
_ATTR_MAXES[name] = '2pi' if ('2pi' in line) else 'pi'
def check_satrec(sat):
"""Check whether satellite orbital elements are within range."""
if not _ATTRIBUTES:
_load_attributes()
e = []
for name, max_name in sorted(_ATTR_MAXES.items()):
value = getattr(sat, name)
if 0.0 <= value < _MAX_VALUES[max_name]:
continue
e.append(' {0} = {1:f} is outside the range 0 <= {0} < {2}\n'
.format(name, value, max_name))
if e:
raise ValueError('satellite parameters out of range:\n' + '\n'.join(e))
def dump_satrec(sat, sat2=None):
"""Yield lines that list the attributes of one or two satellites."""
if not _ATTRIBUTES:
_load_attributes()
for item in _ATTRIBUTES:
if item[0].isupper():
title = item
yield '\n'
yield '# -------- {0} --------\n'.format(title)
else:
name = item
value = getattr(sat, item, '(not set)')
line = '{0} = {1!r}\n'.format(item, value)
if sat2 is not None:
value2 = getattr(sat2, name, '(not set)')
verdict = '==' if (value == value2) else '!='
line = '{0:39} {1} {2!r}\n'.format(line[:-1], verdict, value2)
yield line