refactor: simplify ephemerides, remove dead codetags/v0.8.0
@@ -47,7 +47,13 @@ EVENTS = { | |||||
} | } | ||||
class MoonPhase: | |||||
class Serializable(ABC): | |||||
@abstractmethod | |||||
def serialize(self) -> dict: | |||||
pass | |||||
class MoonPhase(Serializable): | |||||
def __init__(self, identifier: str, time: Union[datetime, None], next_phase_date: Union[datetime, None]): | def __init__(self, identifier: str, time: Union[datetime, None], next_phase_date: Union[datetime, None]): | ||||
if identifier not in MOON_PHASES.keys(): | if identifier not in MOON_PHASES.keys(): | ||||
raise ValueError('identifier parameter must be one of %s (got %s)' % (', '.join(MOON_PHASES.keys()), | raise ValueError('identifier parameter must be one of %s (got %s)' % (', '.join(MOON_PHASES.keys()), | ||||
@@ -60,6 +66,11 @@ class MoonPhase: | |||||
def get_phase(self): | def get_phase(self): | ||||
return MOON_PHASES[self.identifier] | return MOON_PHASES[self.identifier] | ||||
def get_next_phase_name(self): | |||||
next_identifier = self.get_next_phase() | |||||
return MOON_PHASES[next_identifier] | |||||
def get_next_phase(self): | def get_next_phase(self): | ||||
if self.identifier == 'NEW_MOON' or self.identifier == 'WAXING_CRESCENT': | if self.identifier == 'NEW_MOON' or self.identifier == 'WAXING_CRESCENT': | ||||
next_identifier = 'FIRST_QUARTER' | next_identifier = 'FIRST_QUARTER' | ||||
@@ -69,39 +80,20 @@ class MoonPhase: | |||||
next_identifier = 'LAST_QUARTER' | next_identifier = 'LAST_QUARTER' | ||||
else: | else: | ||||
next_identifier = 'NEW_MOON' | next_identifier = 'NEW_MOON' | ||||
return next_identifier | |||||
return MOON_PHASES[next_identifier] | |||||
class Position: | |||||
def __init__(self, latitude: float, longitude: float): | |||||
self.latitude = latitude | |||||
self.longitude = longitude | |||||
self.observation_planet = None | |||||
self._topos = None | |||||
def get_planet_topos(self) -> Topos: | |||||
if self.observation_planet is None: | |||||
raise TypeError('Observation planet must be set.') | |||||
if self._topos is None: | |||||
self._topos = self.observation_planet + Topos(latitude_degrees=self.latitude, | |||||
longitude_degrees=self.longitude) | |||||
return self._topos | |||||
class AsterEphemerides: | |||||
def __init__(self, | |||||
rise_time: Union[datetime, None], | |||||
culmination_time: Union[datetime, None], | |||||
set_time: Union[datetime, None]): | |||||
self.rise_time = rise_time | |||||
self.culmination_time = culmination_time | |||||
self.set_time = set_time | |||||
def serialize(self) -> dict: | |||||
return { | |||||
'phase': self.identifier, | |||||
'time': self.time.isoformat() if self.time is not None else None, | |||||
'next': { | |||||
'phase': self.get_next_phase(), | |||||
'time': self.next_phase_date.isoformat() | |||||
} | |||||
} | |||||
class Object(ABC): | |||||
class Object(Serializable): | |||||
""" | """ | ||||
An astronomical object. | An astronomical object. | ||||
""" | """ | ||||
@@ -109,7 +101,6 @@ class Object(ABC): | |||||
def __init__(self, | def __init__(self, | ||||
name: str, | name: str, | ||||
skyfield_name: str, | skyfield_name: str, | ||||
ephemerides: AsterEphemerides or None = None, | |||||
radius: float = None): | radius: float = None): | ||||
""" | """ | ||||
Initialize an astronomical object | Initialize an astronomical object | ||||
@@ -122,7 +113,6 @@ class Object(ABC): | |||||
self.name = name | self.name = name | ||||
self.skyfield_name = skyfield_name | self.skyfield_name = skyfield_name | ||||
self.radius = radius | self.radius = radius | ||||
self.ephemerides = ephemerides | |||||
def get_skyfield_object(self) -> SkfPlanet: | def get_skyfield_object(self) -> SkfPlanet: | ||||
return get_skf_objects()[self.skyfield_name] | return get_skf_objects()[self.skyfield_name] | ||||
@@ -143,6 +133,13 @@ class Object(ABC): | |||||
return 360 / pi * arcsin(self.radius / from_place.at(time).observe(self.get_skyfield_object()).distance().km) | return 360 / pi * arcsin(self.radius / from_place.at(time).observe(self.get_skyfield_object()).distance().km) | ||||
def serialize(self) -> dict: | |||||
return { | |||||
'name': self.name, | |||||
'type': self.get_type(), | |||||
'radius': self.radius, | |||||
} | |||||
class Star(Object): | class Star(Object): | ||||
def get_type(self) -> str: | def get_type(self) -> str: | ||||
@@ -164,7 +161,7 @@ class Satellite(Object): | |||||
return 'satellite' | return 'satellite' | ||||
class Event: | |||||
class Event(Serializable): | |||||
def __init__(self, event_type: str, objects: [Object], start_time: datetime, | def __init__(self, event_type: str, objects: [Object], start_time: datetime, | ||||
end_time: Union[datetime, None] = None, details: str = None): | end_time: Union[datetime, None] = None, details: str = None): | ||||
if event_type not in EVENTS.keys(): | if event_type not in EVENTS.keys(): | ||||
@@ -190,6 +187,15 @@ class Event: | |||||
return tuple(object.name for object in self.objects) | return tuple(object.name for object in self.objects) | ||||
def serialize(self) -> dict: | |||||
return { | |||||
'objects': [object.serialize() for object in self.objects], | |||||
'event': self.event_type, | |||||
'starts_at': self.start_time.isoformat(), | |||||
'ends_at': self.end_time.isoformat() if self.end_time is not None else None, | |||||
'details': self.details | |||||
} | |||||
def skyfield_to_moon_phase(times: [Time], vals: [int], now: Time) -> Union[MoonPhase, None]: | def skyfield_to_moon_phase(times: [Time], vals: [int], now: Time) -> Union[MoonPhase, None]: | ||||
tomorrow = get_timescale().utc(now.utc_datetime().year, now.utc_datetime().month, now.utc_datetime().day + 1) | tomorrow = get_timescale().utc(now.utc_datetime().year, now.utc_datetime().month, now.utc_datetime().day + 1) | ||||
@@ -228,8 +234,30 @@ def skyfield_to_moon_phase(times: [Time], vals: [int], now: Time) -> Union[MoonP | |||||
next_phase_time.utc_datetime() if next_phase_time is not None else None) | next_phase_time.utc_datetime() if next_phase_time is not None else None) | ||||
class AsterEphemerides(Serializable): | |||||
def __init__(self, | |||||
rise_time: Union[datetime, None], | |||||
culmination_time: Union[datetime, None], | |||||
set_time: Union[datetime, None], | |||||
aster: Object): | |||||
self.rise_time = rise_time | |||||
self.culmination_time = culmination_time | |||||
self.set_time = set_time | |||||
self.object = aster | |||||
def serialize(self) -> dict: | |||||
return { | |||||
'object': self.object.serialize(), | |||||
'rise_time': self.rise_time.isoformat() if self.rise_time is not None else None, | |||||
'culmination_time': self.culmination_time.isoformat() if self.culmination_time is not None else None, | |||||
'set_time': self.set_time.isoformat() if self.set_time is not None else None | |||||
} | |||||
MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] | MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] | ||||
EARTH = Planet('Earth', 'EARTH') | |||||
ASTERS = [Star(_('Sun'), 'SUN', radius=696342), | ASTERS = [Star(_('Sun'), 'SUN', radius=696342), | ||||
Satellite(_('Moon'), 'MOON', radius=1737.4), | Satellite(_('Moon'), 'MOON', radius=1737.4), | ||||
Planet(_('Mercury'), 'MERCURY', radius=2439.7), | Planet(_('Mercury'), 'MERCURY', radius=2439.7), | ||||
@@ -240,3 +268,21 @@ ASTERS = [Star(_('Sun'), 'SUN', radius=696342), | |||||
Planet(_('Uranus'), 'URANUS BARYCENTER', radius=25559), | Planet(_('Uranus'), 'URANUS BARYCENTER', radius=25559), | ||||
Planet(_('Neptune'), 'NEPTUNE BARYCENTER', radius=24764), | Planet(_('Neptune'), 'NEPTUNE BARYCENTER', radius=24764), | ||||
Planet(_('Pluto'), 'PLUTO BARYCENTER', radius=1185)] | Planet(_('Pluto'), 'PLUTO BARYCENTER', radius=1185)] | ||||
class Position: | |||||
def __init__(self, latitude: float, longitude: float, aster: Object): | |||||
self.latitude = latitude | |||||
self.longitude = longitude | |||||
self.aster = aster | |||||
self._topos = None | |||||
def get_planet_topos(self) -> Topos: | |||||
if self.aster is None: | |||||
raise TypeError('Observation planet must be set.') | |||||
if self._topos is None: | |||||
self._topos = self.aster.get_skyfield_object() + Topos(latitude_degrees=self.latitude, | |||||
longitude_degrees=self.longitude) | |||||
return self._topos |
@@ -40,9 +40,10 @@ TIME_FORMAT = _('{hours}:{minutes}').format(hours='%H', minutes='%M') | |||||
class Dumper(ABC): | class Dumper(ABC): | ||||
def __init__(self, ephemeris: dict, events: [Event], date: datetime.date = datetime.date.today(), timezone: int = 0, | |||||
with_colors: bool = True): | |||||
self.ephemeris = ephemeris | |||||
def __init__(self, ephemerides: [AsterEphemerides] = None, moon_phase: MoonPhase = None, events: [Event] = None, | |||||
date: datetime.date = datetime.date.today(), timezone: int = 0, with_colors: bool = True): | |||||
self.ephemerides = ephemerides | |||||
self.moon_phase = moon_phase | |||||
self.events = events | self.events = events | ||||
self.date = date | self.date = date | ||||
self.timezone = timezone | self.timezone = timezone | ||||
@@ -52,19 +53,20 @@ class Dumper(ABC): | |||||
self._convert_dates_to_timezones() | self._convert_dates_to_timezones() | ||||
def _convert_dates_to_timezones(self): | def _convert_dates_to_timezones(self): | ||||
if self.ephemeris['moon_phase'].time is not None: | |||||
self.ephemeris['moon_phase'].time = self._datetime_to_timezone(self.ephemeris['moon_phase'].time) | |||||
if self.ephemeris['moon_phase'].next_phase_date is not None: | |||||
self.ephemeris['moon_phase'].next_phase_date = self._datetime_to_timezone( | |||||
self.ephemeris['moon_phase'].next_phase_date) | |||||
for aster in self.ephemeris['details']: | |||||
if aster.ephemerides.rise_time is not None: | |||||
aster.ephemerides.rise_time = self._datetime_to_timezone(aster.ephemerides.rise_time) | |||||
if aster.ephemerides.culmination_time is not None: | |||||
aster.ephemerides.culmination_time = self._datetime_to_timezone(aster.ephemerides.culmination_time) | |||||
if aster.ephemerides.set_time is not None: | |||||
aster.ephemerides.set_time = self._datetime_to_timezone(aster.ephemerides.set_time) | |||||
if self.moon_phase.time is not None: | |||||
self.moon_phase.time = self._datetime_to_timezone(self.moon_phase.time) | |||||
if self.moon_phase.next_phase_date is not None: | |||||
self.moon_phase.next_phase_date = self._datetime_to_timezone( | |||||
self.moon_phase.next_phase_date) | |||||
if self.ephemerides is not None: | |||||
for ephemeris in self.ephemerides: | |||||
if ephemeris.rise_time is not None: | |||||
ephemeris.rise_time = self._datetime_to_timezone(ephemeris.rise_time) | |||||
if ephemeris.culmination_time is not None: | |||||
ephemeris.culmination_time = self._datetime_to_timezone(ephemeris.culmination_time) | |||||
if ephemeris.set_time is not None: | |||||
ephemeris.set_time = self._datetime_to_timezone(ephemeris.set_time) | |||||
for event in self.events: | for event in self.events: | ||||
event.start_time = self._datetime_to_timezone(event.start_time) | event.start_time = self._datetime_to_timezone(event.start_time) | ||||
@@ -99,11 +101,11 @@ class Dumper(ABC): | |||||
class JsonDumper(Dumper): | class JsonDumper(Dumper): | ||||
def to_string(self): | def to_string(self): | ||||
self.ephemeris['events'] = self.events | |||||
self.ephemeris['ephemerides'] = self.ephemeris.pop('details') | |||||
return json.dumps(self.ephemeris, | |||||
default=self._json_default, | |||||
indent=4) | |||||
return json.dumps({ | |||||
'ephemerides': [ephemeris.serialize() for ephemeris in self.ephemerides], | |||||
'moon_phase': self.moon_phase.serialize(), | |||||
'events': [event.serialize() for event in self.events] | |||||
}, indent=4) | |||||
@staticmethod | @staticmethod | ||||
def _json_default(obj): | def _json_default(obj): | ||||
@@ -139,10 +141,10 @@ class TextDumper(Dumper): | |||||
def to_string(self): | def to_string(self): | ||||
text = [self.style(self.get_date_as_string(capitalized=True), 'h1')] | text = [self.style(self.get_date_as_string(capitalized=True), 'h1')] | ||||
if len(self.ephemeris['details']) > 0: | |||||
text.append(self.get_asters(self.ephemeris['details'])) | |||||
if self.ephemerides is not None: | |||||
text.append(self.stringify_ephemerides()) | |||||
text.append(self.get_moon(self.ephemeris['moon_phase'])) | |||||
text.append(self.get_moon(self.moon_phase)) | |||||
if len(self.events) > 0: | if len(self.events) > 0: | ||||
text.append('\n'.join([self.style(_('Expected events:'), 'h2'), | text.append('\n'.join([self.style(_('Expected events:'), 'h2'), | ||||
@@ -173,28 +175,28 @@ class TextDumper(Dumper): | |||||
return styles[tag](text) | return styles[tag](text) | ||||
def get_asters(self, asters: [Object]) -> str: | |||||
def stringify_ephemerides(self) -> str: | |||||
data = [] | data = [] | ||||
for aster in asters: | |||||
name = self.style(aster.name, 'th') | |||||
for ephemeris in self.ephemerides: | |||||
name = self.style(ephemeris.object.name, 'th') | |||||
if aster.ephemerides.rise_time is not None: | |||||
time_fmt = TIME_FORMAT if aster.ephemerides.rise_time.day == self.date.day else SHORT_DATETIME_FORMAT | |||||
planet_rise = aster.ephemerides.rise_time.strftime(time_fmt) | |||||
if ephemeris.rise_time is not None: | |||||
time_fmt = TIME_FORMAT if ephemeris.rise_time.day == self.date.day else SHORT_DATETIME_FORMAT | |||||
planet_rise = ephemeris.rise_time.strftime(time_fmt) | |||||
else: | else: | ||||
planet_rise = '-' | planet_rise = '-' | ||||
if aster.ephemerides.culmination_time is not None: | |||||
time_fmt = TIME_FORMAT if aster.ephemerides.culmination_time.day == self.date.day \ | |||||
if ephemeris.culmination_time is not None: | |||||
time_fmt = TIME_FORMAT if ephemeris.culmination_time.day == self.date.day \ | |||||
else SHORT_DATETIME_FORMAT | else SHORT_DATETIME_FORMAT | ||||
planet_culmination = aster.ephemerides.culmination_time.strftime(time_fmt) | |||||
planet_culmination = ephemeris.culmination_time.strftime(time_fmt) | |||||
else: | else: | ||||
planet_culmination = '-' | planet_culmination = '-' | ||||
if aster.ephemerides.set_time is not None: | |||||
time_fmt = TIME_FORMAT if aster.ephemerides.set_time.day == self.date.day else SHORT_DATETIME_FORMAT | |||||
planet_set = aster.ephemerides.set_time.strftime(time_fmt) | |||||
if ephemeris.set_time is not None: | |||||
time_fmt = TIME_FORMAT if ephemeris.set_time.day == self.date.day else SHORT_DATETIME_FORMAT | |||||
planet_set = ephemeris.set_time.strftime(time_fmt) | |||||
else: | else: | ||||
planet_set = '-' | planet_set = '-' | ||||
@@ -219,7 +221,7 @@ class TextDumper(Dumper): | |||||
def get_moon(self, moon_phase: MoonPhase) -> str: | def get_moon(self, moon_phase: MoonPhase) -> str: | ||||
current_moon_phase = ' '.join([self.style(_('Moon phase:'), 'strong'), moon_phase.get_phase()]) | current_moon_phase = ' '.join([self.style(_('Moon phase:'), 'strong'), moon_phase.get_phase()]) | ||||
new_moon_phase = _('{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}').format( | new_moon_phase = _('{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}').format( | ||||
next_moon_phase=moon_phase.get_next_phase(), | |||||
next_moon_phase=moon_phase.get_next_phase_name(), | |||||
next_moon_phase_date=moon_phase.next_phase_date.strftime(FULL_DATE_FORMAT), | next_moon_phase_date=moon_phase.next_phase_date.strftime(FULL_DATE_FORMAT), | ||||
next_moon_phase_time=moon_phase.next_phase_date.strftime(TIME_FORMAT) | next_moon_phase_time=moon_phase.next_phase_date.strftime(TIME_FORMAT) | ||||
) | ) | ||||
@@ -242,12 +244,12 @@ class _LatexDumper(Dumper): | |||||
'assets', 'png', 'kosmorro-logo.png') | 'assets', 'png', 'kosmorro-logo.png') | ||||
moon_phase_graphics = os.path.join(os.path.abspath(os.path.dirname(__file__)), | moon_phase_graphics = os.path.join(os.path.abspath(os.path.dirname(__file__)), | ||||
'assets', 'moonphases', 'png', | 'assets', 'moonphases', 'png', | ||||
'.'.join([self.ephemeris['moon_phase'].identifier.lower().replace('_', '-'), | |||||
'.'.join([self.moon_phase.identifier.lower().replace('_', '-'), | |||||
'png'])) | 'png'])) | ||||
document = template | document = template | ||||
if len(self.ephemeris['details']) == 0: | |||||
if self.ephemerides is None: | |||||
document = self._remove_section(document, 'ephemerides') | document = self._remove_section(document, 'ephemerides') | ||||
if len(self.events) == 0: | if len(self.events) == 0: | ||||
@@ -276,7 +278,7 @@ class _LatexDumper(Dumper): | |||||
.replace('+++EPHEMERIDES+++', self._make_ephemerides()) \ | .replace('+++EPHEMERIDES+++', self._make_ephemerides()) \ | ||||
.replace('+++MOON-PHASE-GRAPHICS+++', moon_phase_graphics) \ | .replace('+++MOON-PHASE-GRAPHICS+++', moon_phase_graphics) \ | ||||
.replace('+++CURRENT-MOON-PHASE-TITLE+++', _('Moon phase:')) \ | .replace('+++CURRENT-MOON-PHASE-TITLE+++', _('Moon phase:')) \ | ||||
.replace('+++CURRENT-MOON-PHASE+++', self.ephemeris['moon_phase'].get_phase()) \ | |||||
.replace('+++CURRENT-MOON-PHASE+++', self.moon_phase.get_phase()) \ | |||||
.replace('+++SECTION-EVENTS+++', _('Expected events')) \ | .replace('+++SECTION-EVENTS+++', _('Expected events')) \ | ||||
.replace('+++EVENTS+++', self._make_events()) | .replace('+++EVENTS+++', self._make_events()) | ||||
@@ -285,30 +287,31 @@ class _LatexDumper(Dumper): | |||||
def _make_ephemerides(self) -> str: | def _make_ephemerides(self) -> str: | ||||
latex = [] | latex = [] | ||||
for aster in self.ephemeris['details']: | |||||
if aster.ephemerides.rise_time is not None: | |||||
time_fmt = TIME_FORMAT if aster.ephemerides.rise_time.day == self.date.day else SHORT_DATETIME_FORMAT | |||||
aster_rise = aster.ephemerides.rise_time.strftime(time_fmt) | |||||
else: | |||||
aster_rise = '-' | |||||
if aster.ephemerides.culmination_time is not None: | |||||
time_fmt = TIME_FORMAT if aster.ephemerides.culmination_time.day == self.date.day\ | |||||
else SHORT_DATETIME_FORMAT | |||||
aster_culmination = aster.ephemerides.culmination_time.strftime(time_fmt) | |||||
else: | |||||
aster_culmination = '-' | |||||
if aster.ephemerides.set_time is not None: | |||||
time_fmt = TIME_FORMAT if aster.ephemerides.set_time.day == self.date.day else SHORT_DATETIME_FORMAT | |||||
aster_set = aster.ephemerides.set_time.strftime(time_fmt) | |||||
else: | |||||
aster_set = '-' | |||||
latex.append(r'\object{%s}{%s}{%s}{%s}' % (aster.name, | |||||
aster_rise, | |||||
aster_culmination, | |||||
aster_set)) | |||||
if self.ephemerides is not None: | |||||
for ephemeris in self.ephemerides: | |||||
if ephemeris.rise_time is not None: | |||||
time_fmt = TIME_FORMAT if ephemeris.rise_time.day == self.date.day else SHORT_DATETIME_FORMAT | |||||
aster_rise = ephemeris.rise_time.strftime(time_fmt) | |||||
else: | |||||
aster_rise = '-' | |||||
if ephemeris.culmination_time is not None: | |||||
time_fmt = TIME_FORMAT if ephemeris.culmination_time.day == self.date.day\ | |||||
else SHORT_DATETIME_FORMAT | |||||
aster_culmination = ephemeris.culmination_time.strftime(time_fmt) | |||||
else: | |||||
aster_culmination = '-' | |||||
if ephemeris.set_time is not None: | |||||
time_fmt = TIME_FORMAT if ephemeris.set_time.day == self.date.day else SHORT_DATETIME_FORMAT | |||||
aster_set = ephemeris.set_time.strftime(time_fmt) | |||||
else: | |||||
aster_set = '-' | |||||
latex.append(r'\object{%s}{%s}{%s}{%s}' % (ephemeris.object.name, | |||||
aster_rise, | |||||
aster_culmination, | |||||
aster_set)) | |||||
return ''.join(latex) | return ''.join(latex) | ||||
@@ -342,13 +345,9 @@ class _LatexDumper(Dumper): | |||||
class PdfDumper(Dumper): | class PdfDumper(Dumper): | ||||
def __init__(self, ephemerides, events, date=datetime.datetime.now(), timezone=0, with_colors=True): | |||||
super(PdfDumper, self).__init__(ephemerides, events, date=date, timezone=0, with_colors=with_colors) | |||||
self.timezone = timezone | |||||
def to_string(self): | def to_string(self): | ||||
try: | try: | ||||
latex_dumper = _LatexDumper(self.ephemeris, self.events, | |||||
latex_dumper = _LatexDumper(self.ephemerides, self.moon_phase, self.events, | |||||
date=self.date, timezone=self.timezone, with_colors=self.with_colors) | date=self.date, timezone=self.timezone, with_colors=self.with_colors) | ||||
return self._compile(latex_dumper.to_string()) | return self._compile(latex_dumper.to_string()) | ||||
except RuntimeError: | except RuntimeError: | ||||
@@ -17,77 +17,63 @@ | |||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. | # along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
import datetime | import datetime | ||||
from typing import Union | |||||
from skyfield import almanac | |||||
from skyfield.searchlib import find_discrete, find_maxima | from skyfield.searchlib import find_discrete, find_maxima | ||||
from skyfield.timelib import Time | from skyfield.timelib import Time | ||||
from skyfield.constants import tau | from skyfield.constants import tau | ||||
from .data import Object, Position, AsterEphemerides, MoonPhase, ASTERS, skyfield_to_moon_phase | |||||
from .data import Position, AsterEphemerides, MoonPhase, Object, ASTERS, skyfield_to_moon_phase | |||||
from .core import get_skf_objects, get_timescale, get_iau2000b | from .core import get_skf_objects, get_timescale, get_iau2000b | ||||
RISEN_ANGLE = -0.8333 | RISEN_ANGLE = -0.8333 | ||||
class EphemeridesComputer: | |||||
def __init__(self, position: Union[Position, None]): | |||||
if position is not None: | |||||
position.observation_planet = get_skf_objects()['earth'] | |||||
self.position = position | |||||
def get_moon_phase(compute_date: datetime.date) -> MoonPhase: | |||||
earth = get_skf_objects()['earth'] | |||||
moon = get_skf_objects()['moon'] | |||||
sun = get_skf_objects()['sun'] | |||||
def get_sun(self, start_time, end_time) -> dict: | |||||
times, is_risen = find_discrete(start_time, | |||||
end_time, | |||||
almanac.sunrise_sunset(get_skf_objects(), self.position)) | |||||
def moon_phase_at(time: Time): | |||||
time._nutation_angles = get_iau2000b(time) | |||||
current_earth = earth.at(time) | |||||
_, mlon, _ = current_earth.observe(moon).apparent().ecliptic_latlon('date') | |||||
_, slon, _ = current_earth.observe(sun).apparent().ecliptic_latlon('date') | |||||
return (((mlon.radians - slon.radians) // (tau / 8)) % 8).astype(int) | |||||
sunrise = times[0] if is_risen[0] else times[1] | |||||
sunset = times[1] if not is_risen[1] else times[0] | |||||
moon_phase_at.rough_period = 7.0 # one lunar phase per week | |||||
return {'rise': sunrise, 'set': sunset} | |||||
today = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day) | |||||
time1 = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day - 10) | |||||
time2 = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day + 10) | |||||
@staticmethod | |||||
def get_moon_phase(compute_date: datetime.date) -> MoonPhase: | |||||
earth = get_skf_objects()['earth'] | |||||
moon = get_skf_objects()['moon'] | |||||
sun = get_skf_objects()['sun'] | |||||
times, phase = find_discrete(time1, time2, moon_phase_at) | |||||
def moon_phase_at(time: Time): | |||||
time._nutation_angles = get_iau2000b(time) | |||||
current_earth = earth.at(time) | |||||
_, mlon, _ = current_earth.observe(moon).apparent().ecliptic_latlon('date') | |||||
_, slon, _ = current_earth.observe(sun).apparent().ecliptic_latlon('date') | |||||
return (((mlon.radians - slon.radians) // (tau / 8)) % 8).astype(int) | |||||
return skyfield_to_moon_phase(times, phase, today) | |||||
moon_phase_at.rough_period = 7.0 # one lunar phase per week | |||||
today = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day) | |||||
time1 = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day - 10) | |||||
time2 = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day + 10) | |||||
def get_ephemerides(date: datetime.date, position: Position) -> [AsterEphemerides]: | |||||
ephemerides = [] | |||||
times, phase = find_discrete(time1, time2, moon_phase_at) | |||||
def get_angle(for_aster: Object): | |||||
def fun(time: Time) -> float: | |||||
return position.get_planet_topos().at(time).observe(for_aster.get_skyfield_object()).apparent().altaz()[0]\ | |||||
.degrees | |||||
fun.rough_period = 1.0 | |||||
return fun | |||||
return skyfield_to_moon_phase(times, phase, today) | |||||
def is_risen(for_aster: Object): | |||||
def fun(time: Time) -> bool: | |||||
return get_angle(for_aster)(time) > RISEN_ANGLE | |||||
fun.rough_period = 0.5 | |||||
return fun | |||||
@staticmethod | |||||
def get_asters_ephemerides_for_aster(aster, date: datetime.date, position: Position) -> Object: | |||||
skyfield_aster = get_skf_objects()[aster.skyfield_name] | |||||
start_time = get_timescale().utc(date.year, date.month, date.day) | |||||
end_time = get_timescale().utc(date.year, date.month, date.day, 23, 59, 59) | |||||
def get_angle(time: Time) -> float: | |||||
return position.get_planet_topos().at(time).observe(skyfield_aster).apparent().altaz()[0].degrees | |||||
def is_risen(time: Time) -> bool: | |||||
return get_angle(time) > RISEN_ANGLE | |||||
get_angle.rough_period = 1.0 | |||||
is_risen.rough_period = 0.5 | |||||
start_time = get_timescale().utc(date.year, date.month, date.day) | |||||
end_time = get_timescale().utc(date.year, date.month, date.day, 23, 59, 59) | |||||
rise_times, arr = find_discrete(start_time, end_time, is_risen) | |||||
for aster in ASTERS: | |||||
rise_times, arr = find_discrete(start_time, end_time, is_risen(aster)) | |||||
try: | try: | ||||
culmination_time, _ = find_maxima(start_time, end_time, f=get_angle, epsilon=1./3600/24, num=12) | |||||
culmination_time, _ = find_maxima(start_time, end_time, f=get_angle(aster), epsilon=1./3600/24, num=12) | |||||
culmination_time = culmination_time[0] if len(culmination_time) > 0 else None | culmination_time = culmination_time[0] if len(culmination_time) > 0 else None | ||||
except ValueError: | except ValueError: | ||||
culmination_time = None | culmination_time = None | ||||
@@ -109,37 +95,6 @@ class EphemeridesComputer: | |||||
if set_time is not None: | if set_time is not None: | ||||
set_time = set_time.utc_datetime().replace(microsecond=0) if set_time is not None else None | set_time = set_time.utc_datetime().replace(microsecond=0) if set_time is not None else None | ||||
aster.ephemerides = AsterEphemerides(rise_time, culmination_time, set_time) | |||||
return aster | |||||
@staticmethod | |||||
def is_leap_year(year: int) -> bool: | |||||
return (year % 4 == 0 and year % 100 > 0) or (year % 400 == 0) | |||||
def compute_ephemerides(self, compute_date: datetime.date) -> dict: | |||||
return {'moon_phase': self.get_moon_phase(compute_date), | |||||
'details': [self.get_asters_ephemerides_for_aster(aster, compute_date, self.position) | |||||
for aster in ASTERS] if self.position is not None else []} | |||||
@staticmethod | |||||
def get_seasons(year: int) -> dict: | |||||
start_time = get_timescale().utc(year, 1, 1) | |||||
end_time = get_timescale().utc(year, 12, 31) | |||||
times, almanac_seasons = find_discrete(start_time, end_time, almanac.seasons(get_skf_objects())) | |||||
seasons = {} | |||||
for time, almanac_season in zip(times, almanac_seasons): | |||||
if almanac_season == 0: | |||||
season = 'MARCH' | |||||
elif almanac_season == 1: | |||||
season = 'JUNE' | |||||
elif almanac_season == 2: | |||||
season = 'SEPTEMBER' | |||||
elif almanac_season == 3: | |||||
season = 'DECEMBER' | |||||
else: | |||||
raise AssertionError | |||||
seasons[season] = time.utc_iso() | |||||
return seasons | |||||
ephemerides.append(AsterEphemerides(rise_time, culmination_time, set_time, aster=aster)) | |||||
return ephemerides |
@@ -8,7 +8,7 @@ msgid "" | |||||
msgstr "" | msgstr "" | ||||
"Project-Id-Version: kosmorro 0.7.0\n" | "Project-Id-Version: kosmorro 0.7.0\n" | ||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | ||||
"POT-Creation-Date: 2020-04-16 17:57+0200\n" | |||||
"POT-Creation-Date: 2020-04-18 15:51+0200\n" | |||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
"Language-Team: LANGUAGE <LL@li.org>\n" | "Language-Team: LANGUAGE <LL@li.org>\n" | ||||
@@ -79,43 +79,43 @@ msgstr "" | |||||
msgid "%s's largest elongation" | msgid "%s's largest elongation" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/data.py:233 | |||||
#: kosmorrolib/data.py:261 | |||||
msgid "Sun" | msgid "Sun" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/data.py:234 | |||||
#: kosmorrolib/data.py:262 | |||||
msgid "Moon" | msgid "Moon" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/data.py:235 | |||||
#: kosmorrolib/data.py:263 | |||||
msgid "Mercury" | msgid "Mercury" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/data.py:236 | |||||
#: kosmorrolib/data.py:264 | |||||
msgid "Venus" | msgid "Venus" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/data.py:237 | |||||
#: kosmorrolib/data.py:265 | |||||
msgid "Mars" | msgid "Mars" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/data.py:238 | |||||
#: kosmorrolib/data.py:266 | |||||
msgid "Jupiter" | msgid "Jupiter" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/data.py:239 | |||||
#: kosmorrolib/data.py:267 | |||||
msgid "Saturn" | msgid "Saturn" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/data.py:240 | |||||
#: kosmorrolib/data.py:268 | |||||
msgid "Uranus" | msgid "Uranus" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/data.py:241 | |||||
#: kosmorrolib/data.py:269 | |||||
msgid "Neptune" | msgid "Neptune" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/data.py:242 | |||||
#: kosmorrolib/data.py:270 | |||||
msgid "Pluto" | msgid "Pluto" | ||||
msgstr "" | msgstr "" | ||||
@@ -131,68 +131,68 @@ msgstr "" | |||||
msgid "{hours}:{minutes}" | msgid "{hours}:{minutes}" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/dumper.py:148 | |||||
#: kosmorrolib/dumper.py:150 | |||||
msgid "Expected events:" | msgid "Expected events:" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/dumper.py:152 | |||||
#: kosmorrolib/dumper.py:154 | |||||
msgid "Note: All the hours are given in UTC." | msgid "Note: All the hours are given in UTC." | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/dumper.py:157 | |||||
#: kosmorrolib/dumper.py:159 | |||||
msgid "Note: All the hours are given in the UTC{offset} timezone." | msgid "Note: All the hours are given in the UTC{offset} timezone." | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/dumper.py:203 kosmorrolib/dumper.py:272 | |||||
#: kosmorrolib/dumper.py:205 kosmorrolib/dumper.py:274 | |||||
msgid "Object" | msgid "Object" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/dumper.py:204 kosmorrolib/dumper.py:273 | |||||
#: kosmorrolib/dumper.py:206 kosmorrolib/dumper.py:275 | |||||
msgid "Rise time" | msgid "Rise time" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/dumper.py:205 kosmorrolib/dumper.py:274 | |||||
#: kosmorrolib/dumper.py:207 kosmorrolib/dumper.py:276 | |||||
msgid "Culmination time" | msgid "Culmination time" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/dumper.py:206 kosmorrolib/dumper.py:275 | |||||
#: kosmorrolib/dumper.py:208 kosmorrolib/dumper.py:277 | |||||
msgid "Set time" | msgid "Set time" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/dumper.py:220 kosmorrolib/dumper.py:278 | |||||
#: kosmorrolib/dumper.py:222 kosmorrolib/dumper.py:280 | |||||
msgid "Moon phase:" | msgid "Moon phase:" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/dumper.py:221 | |||||
#: kosmorrolib/dumper.py:223 | |||||
msgid "{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}" | msgid "{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/dumper.py:259 | |||||
#: kosmorrolib/dumper.py:261 | |||||
msgid "A Summary of your Sky" | msgid "A Summary of your Sky" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/dumper.py:263 | |||||
#: kosmorrolib/dumper.py:265 | |||||
msgid "" | msgid "" | ||||
"This document summarizes the ephemerides and the events of {date}. It " | "This document summarizes the ephemerides and the events of {date}. It " | ||||
"aims to help you to prepare your observation session. All the hours are " | "aims to help you to prepare your observation session. All the hours are " | ||||
"given in {timezone}." | "given in {timezone}." | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/dumper.py:269 | |||||
#: kosmorrolib/dumper.py:271 | |||||
msgid "" | msgid "" | ||||
"Don't forget to check the weather forecast before you go out with your " | "Don't forget to check the weather forecast before you go out with your " | ||||
"equipment." | "equipment." | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/dumper.py:271 | |||||
#: kosmorrolib/dumper.py:273 | |||||
msgid "Ephemerides of the day" | msgid "Ephemerides of the day" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/dumper.py:280 | |||||
#: kosmorrolib/dumper.py:282 | |||||
msgid "Expected events" | msgid "Expected events" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/dumper.py:355 | |||||
#: kosmorrolib/dumper.py:354 | |||||
msgid "" | msgid "" | ||||
"Building PDFs was not possible, because some dependencies are not " | "Building PDFs was not possible, because some dependencies are not " | ||||
"installed.\n" | "installed.\n" | ||||
@@ -200,93 +200,93 @@ msgid "" | |||||
"information." | "information." | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:58 | |||||
#: kosmorrolib/main.py:61 | |||||
msgid "" | msgid "" | ||||
"Save the planet and paper!\n" | "Save the planet and paper!\n" | ||||
"Consider printing you PDF document only if really necessary, and use the " | "Consider printing you PDF document only if really necessary, and use the " | ||||
"other side of the sheet." | "other side of the sheet." | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:62 | |||||
#: kosmorrolib/main.py:65 | |||||
msgid "" | msgid "" | ||||
"PDF output will not contain the ephemerides, because you didn't provide " | "PDF output will not contain the ephemerides, because you didn't provide " | ||||
"the observation coordinate." | "the observation coordinate." | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:91 | |||||
#: kosmorrolib/main.py:93 | |||||
msgid "Could not save the output in \"{path}\": {error}" | msgid "Could not save the output in \"{path}\": {error}" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:96 | |||||
#: kosmorrolib/main.py:98 | |||||
msgid "Selected output format needs an output file (--output)." | msgid "Selected output format needs an output file (--output)." | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:115 | |||||
#: kosmorrolib/main.py:117 | |||||
msgid "Running on Python {python_version}" | msgid "Running on Python {python_version}" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:121 | |||||
#: kosmorrolib/main.py:123 | |||||
msgid "Do you really want to clear Kosmorro's cache? [yN] " | msgid "Do you really want to clear Kosmorro's cache? [yN] " | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:128 | |||||
#: kosmorrolib/main.py:130 | |||||
msgid "Answer did not match expected options, cache not cleared." | msgid "Answer did not match expected options, cache not cleared." | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:137 | |||||
#: kosmorrolib/main.py:139 | |||||
msgid "" | msgid "" | ||||
"Compute the ephemerides and the events for a given date, at a given " | "Compute the ephemerides and the events for a given date, at a given " | ||||
"position on Earth." | "position on Earth." | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:139 | |||||
#: kosmorrolib/main.py:141 | |||||
msgid "" | msgid "" | ||||
"By default, only the events will be computed for today ({date}).\n" | "By default, only the events will be computed for today ({date}).\n" | ||||
"To compute also the ephemerides, latitude and longitude arguments are " | "To compute also the ephemerides, latitude and longitude arguments are " | ||||
"needed." | "needed." | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:144 | |||||
#: kosmorrolib/main.py:146 | |||||
msgid "Show the program version" | msgid "Show the program version" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:146 | |||||
#: kosmorrolib/main.py:148 | |||||
msgid "Delete all the files Kosmorro stored in the cache." | msgid "Delete all the files Kosmorro stored in the cache." | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:148 | |||||
#: kosmorrolib/main.py:150 | |||||
msgid "The format under which the information have to be output" | msgid "The format under which the information have to be output" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:150 | |||||
#: kosmorrolib/main.py:152 | |||||
msgid "" | msgid "" | ||||
"The observer's latitude on Earth. Can also be set in the " | "The observer's latitude on Earth. Can also be set in the " | ||||
"KOSMORRO_LATITUDE environment variable." | "KOSMORRO_LATITUDE environment variable." | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:153 | |||||
#: kosmorrolib/main.py:155 | |||||
msgid "" | msgid "" | ||||
"The observer's longitude on Earth. Can also be set in the " | "The observer's longitude on Earth. Can also be set in the " | ||||
"KOSMORRO_LONGITUDE environment variable." | "KOSMORRO_LONGITUDE environment variable." | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:156 | |||||
#: kosmorrolib/main.py:158 | |||||
msgid "" | msgid "" | ||||
"The date for which the ephemerides must be computed (in the YYYY-MM-DD " | "The date for which the ephemerides must be computed (in the YYYY-MM-DD " | ||||
"format). Defaults to the current date ({default_date})" | "format). Defaults to the current date ({default_date})" | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:160 | |||||
#: kosmorrolib/main.py:162 | |||||
msgid "" | msgid "" | ||||
"The timezone to display the hours in (e.g. 2 for UTC+2 or -3 for UTC-3). " | "The timezone to display the hours in (e.g. 2 for UTC+2 or -3 for UTC-3). " | ||||
"Can also be set in the KOSMORRO_TIMEZONE environment variable." | "Can also be set in the KOSMORRO_TIMEZONE environment variable." | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:163 | |||||
#: kosmorrolib/main.py:165 | |||||
msgid "Disable the colors in the console." | msgid "Disable the colors in the console." | ||||
msgstr "" | msgstr "" | ||||
#: kosmorrolib/main.py:165 | |||||
#: kosmorrolib/main.py:167 | |||||
msgid "" | msgid "" | ||||
"A file to export the output to. If not given, the standard output is " | "A file to export the output to. If not given, the standard output is " | ||||
"used. This argument is needed for PDF format." | "used. This argument is needed for PDF format." | ||||
@@ -24,19 +24,22 @@ import sys | |||||
from datetime import date | from datetime import date | ||||
from termcolor import colored | from termcolor import colored | ||||
from kosmorrolib.version import VERSION | |||||
from kosmorrolib import dumper | |||||
from kosmorrolib import core | |||||
from kosmorrolib import events | |||||
from kosmorrolib.i18n import _ | |||||
from .ephemerides import EphemeridesComputer, Position | |||||
from . import dumper | |||||
from . import core | |||||
from . import events | |||||
from .data import Position, EARTH | |||||
from .exceptions import UnavailableFeatureError | from .exceptions import UnavailableFeatureError | ||||
from .i18n import _ | |||||
from . import ephemerides | |||||
from .version import VERSION | |||||
def main(): | def main(): | ||||
environment = core.get_env() | environment = core.get_env() | ||||
output_formats = get_dumpers() | output_formats = get_dumpers() | ||||
args = get_args(list(output_formats.keys())) | args = get_args(list(output_formats.keys())) | ||||
output_format = args.format | |||||
if args.special_action is not None: | if args.special_action is not None: | ||||
return 0 if args.special_action() else 1 | return 0 if args.special_action() else 1 | ||||
@@ -50,11 +53,11 @@ def main(): | |||||
position = None | position = None | ||||
if args.latitude is not None or args.longitude is not None: | if args.latitude is not None or args.longitude is not None: | ||||
position = Position(args.latitude, args.longitude) | |||||
position = Position(args.latitude, args.longitude, EARTH) | |||||
elif environment.latitude is not None and environment.longitude is not None: | elif environment.latitude is not None and environment.longitude is not None: | ||||
position = Position(float(environment.latitude), float(environment.longitude)) | |||||
position = Position(float(environment.latitude), float(environment.longitude), EARTH) | |||||
if args.format == 'pdf': | |||||
if output_format == 'pdf': | |||||
print(_('Save the planet and paper!\n' | print(_('Save the planet and paper!\n' | ||||
'Consider printing you PDF document only if really necessary, and use the other side of the sheet.')) | 'Consider printing you PDF document only if really necessary, and use the other side of the sheet.')) | ||||
if position is None: | if position is None: | ||||
@@ -63,8 +66,8 @@ def main(): | |||||
"coordinate."), 'yellow')) | "coordinate."), 'yellow')) | ||||
try: | try: | ||||
ephemeris = EphemeridesComputer(position) | |||||
ephemerides = ephemeris.compute_ephemerides(compute_date) | |||||
eph = ephemerides.get_ephemerides(date=compute_date, position=position) if position is not None else None | |||||
moon_phase = ephemerides.get_moon_phase(compute_date) | |||||
events_list = events.search_events(compute_date) | events_list = events.search_events(compute_date) | ||||
@@ -75,10 +78,9 @@ def main(): | |||||
elif timezone is None: | elif timezone is None: | ||||
timezone = 0 | timezone = 0 | ||||
selected_dumper = output_formats[args.format](ephemerides, events_list, | |||||
date=compute_date, timezone=timezone, | |||||
with_colors=args.colors) | |||||
output = selected_dumper.to_string() | |||||
format_dumper = output_formats[output_format](ephemerides=eph, moon_phase=moon_phase, events=events_list, | |||||
date=compute_date, timezone=timezone, with_colors=args.colors) | |||||
output = format_dumper.to_string() | |||||
except UnavailableFeatureError as error: | except UnavailableFeatureError as error: | ||||
print(colored(error.msg, 'red')) | print(colored(error.msg, 'red')) | ||||
return 2 | return 2 | ||||
@@ -90,7 +92,7 @@ def main(): | |||||
except OSError as error: | except OSError as error: | ||||
print(_('Could not save the output in "{path}": {error}').format(path=args.output, | print(_('Could not save the output in "{path}": {error}').format(path=args.output, | ||||
error=error.strerror)) | error=error.strerror)) | ||||
elif not selected_dumper.is_file_output_needed(): | |||||
elif not format_dumper.is_file_output_needed(): | |||||
print(output) | print(output) | ||||
else: | else: | ||||
print(colored(_('Selected output format needs an output file (--output).'), color='red')) | print(colored(_('Selected output format needs an output file (--output).'), color='red')) | ||||
@@ -3,3 +3,4 @@ from .data import * | |||||
from .dumper import * | from .dumper import * | ||||
from .ephemerides import * | from .ephemerides import * | ||||
from .events import * | from .events import * | ||||
from .testutils import * |
@@ -11,86 +11,110 @@ class DumperTestCase(unittest.TestCase): | |||||
def test_json_dumper_returns_correct_json(self): | def test_json_dumper_returns_correct_json(self): | ||||
self.assertEqual('{\n' | self.assertEqual('{\n' | ||||
' "ephemerides": [\n' | |||||
' {\n' | |||||
' "object": {\n' | |||||
' "name": "Mars",\n' | |||||
' "type": "planet",\n' | |||||
' "radius": null\n' | |||||
' },\n' | |||||
' "rise_time": null,\n' | |||||
' "culmination_time": null,\n' | |||||
' "set_time": null\n' | |||||
' }\n' | |||||
' ],\n' | |||||
' "moon_phase": {\n' | ' "moon_phase": {\n' | ||||
' "next_phase_date": "2019-10-21T00:00:00",\n' | |||||
' "phase": "FULL_MOON",\n' | ' "phase": "FULL_MOON",\n' | ||||
' "date": "2019-10-14T00:00:00"\n' | |||||
' "time": "2019-10-14T00:00:00",\n' | |||||
' "next": {\n' | |||||
' "phase": "LAST_QUARTER",\n' | |||||
' "time": "2019-10-21T00:00:00"\n' | |||||
' }\n' | |||||
' },\n' | ' },\n' | ||||
' "events": [\n' | ' "events": [\n' | ||||
' {\n' | ' {\n' | ||||
' "event_type": "OPPOSITION",\n' | |||||
' "objects": [\n' | ' "objects": [\n' | ||||
' "Mars"\n' | |||||
' {\n' | |||||
' "name": "Mars",\n' | |||||
' "type": "planet",\n' | |||||
' "radius": null\n' | |||||
' }\n' | |||||
' ],\n' | ' ],\n' | ||||
' "start_time": "2019-10-14T23:00:00",\n' | |||||
' "end_time": null,\n' | |||||
' "event": "OPPOSITION",\n' | |||||
' "starts_at": "2019-10-14T23:00:00",\n' | |||||
' "ends_at": null,\n' | |||||
' "details": null\n' | ' "details": null\n' | ||||
' },\n' | ' },\n' | ||||
' {\n' | ' {\n' | ||||
' "event_type": "MAXIMAL_ELONGATION",\n' | |||||
' "objects": [\n' | ' "objects": [\n' | ||||
' "Venus"\n' | |||||
' {\n' | |||||
' "name": "Venus",\n' | |||||
' "type": "planet",\n' | |||||
' "radius": null\n' | |||||
' }\n' | |||||
' ],\n' | ' ],\n' | ||||
' "start_time": "2019-10-14T12:00:00",\n' | |||||
' "end_time": null,\n' | |||||
' "event": "MAXIMAL_ELONGATION",\n' | |||||
' "starts_at": "2019-10-14T12:00:00",\n' | |||||
' "ends_at": null,\n' | |||||
' "details": "42.0\\u00b0"\n' | ' "details": "42.0\\u00b0"\n' | ||||
' }\n' | ' }\n' | ||||
' ],\n' | |||||
' "ephemerides": [\n' | |||||
' {\n' | |||||
' "object": "Mars",\n' | |||||
' "details": {\n' | |||||
' "rise_time": null,\n' | |||||
' "culmination_time": null,\n' | |||||
' "set_time": null\n' | |||||
' }\n' | |||||
' }\n' | |||||
' ]\n' | ' ]\n' | ||||
'}', JsonDumper(self._get_data(), self._get_events()).to_string()) | |||||
'}', JsonDumper(self._get_ephemerides(), self._get_moon_phase(), self._get_events()).to_string()) | |||||
data = self._get_data(aster_rise_set=True) | |||||
self.assertEqual('{\n' | self.assertEqual('{\n' | ||||
' "ephemerides": [\n' | |||||
' {\n' | |||||
' "object": {\n' | |||||
' "name": "Mars",\n' | |||||
' "type": "planet",\n' | |||||
' "radius": null\n' | |||||
' },\n' | |||||
' "rise_time": "2019-10-14T08:00:00",\n' | |||||
' "culmination_time": "2019-10-14T13:00:00",\n' | |||||
' "set_time": "2019-10-14T23:00:00"\n' | |||||
' }\n' | |||||
' ],\n' | |||||
' "moon_phase": {\n' | ' "moon_phase": {\n' | ||||
' "next_phase_date": "2019-10-21T00:00:00",\n' | |||||
' "phase": "FULL_MOON",\n' | ' "phase": "FULL_MOON",\n' | ||||
' "date": "2019-10-14T00:00:00"\n' | |||||
' "time": "2019-10-14T00:00:00",\n' | |||||
' "next": {\n' | |||||
' "phase": "LAST_QUARTER",\n' | |||||
' "time": "2019-10-21T00:00:00"\n' | |||||
' }\n' | |||||
' },\n' | ' },\n' | ||||
' "events": [\n' | ' "events": [\n' | ||||
' {\n' | ' {\n' | ||||
' "event_type": "OPPOSITION",\n' | |||||
' "objects": [\n' | ' "objects": [\n' | ||||
' "Mars"\n' | |||||
' {\n' | |||||
' "name": "Mars",\n' | |||||
' "type": "planet",\n' | |||||
' "radius": null\n' | |||||
' }\n' | |||||
' ],\n' | ' ],\n' | ||||
' "start_time": "2019-10-14T23:00:00",\n' | |||||
' "end_time": null,\n' | |||||
' "event": "OPPOSITION",\n' | |||||
' "starts_at": "2019-10-14T23:00:00",\n' | |||||
' "ends_at": null,\n' | |||||
' "details": null\n' | ' "details": null\n' | ||||
' },\n' | ' },\n' | ||||
' {\n' | ' {\n' | ||||
' "event_type": "MAXIMAL_ELONGATION",\n' | |||||
' "objects": [\n' | ' "objects": [\n' | ||||
' "Venus"\n' | |||||
' {\n' | |||||
' "name": "Venus",\n' | |||||
' "type": "planet",\n' | |||||
' "radius": null\n' | |||||
' }\n' | |||||
' ],\n' | ' ],\n' | ||||
' "start_time": "2019-10-14T12:00:00",\n' | |||||
' "end_time": null,\n' | |||||
' "event": "MAXIMAL_ELONGATION",\n' | |||||
' "starts_at": "2019-10-14T12:00:00",\n' | |||||
' "ends_at": null,\n' | |||||
' "details": "42.0\\u00b0"\n' | ' "details": "42.0\\u00b0"\n' | ||||
' }\n' | ' }\n' | ||||
' ],\n' | |||||
' "ephemerides": [\n' | |||||
' {\n' | |||||
' "object": "Mars",\n' | |||||
' "details": {\n' | |||||
' "rise_time": "2019-10-14T08:00:00",\n' | |||||
' "culmination_time": "2019-10-14T13:00:00",\n' | |||||
' "set_time": "2019-10-14T23:00:00"\n' | |||||
' }\n' | |||||
' }\n' | |||||
' ]\n' | ' ]\n' | ||||
'}', JsonDumper(data, | |||||
self._get_events() | |||||
).to_string()) | |||||
'}', JsonDumper(self._get_ephemerides(aster_rise_set=True), self._get_moon_phase(), | |||||
self._get_events()).to_string()) | |||||
def test_text_dumper_without_events(self): | def test_text_dumper_without_events(self): | ||||
ephemerides = self._get_data() | |||||
ephemerides = self._get_ephemerides() | |||||
self.assertEqual('Monday October 14, 2019\n\n' | self.assertEqual('Monday October 14, 2019\n\n' | ||||
'Object Rise time Culmination time Set time\n' | 'Object Rise time Culmination time Set time\n' | ||||
'-------- ----------- ------------------ ----------\n' | '-------- ----------- ------------------ ----------\n' | ||||
@@ -98,9 +122,9 @@ class DumperTestCase(unittest.TestCase): | |||||
'Moon phase: Full Moon\n' | 'Moon phase: Full Moon\n' | ||||
'Last Quarter on Monday October 21, 2019 at 00:00\n\n' | 'Last Quarter on Monday October 21, 2019 at 00:00\n\n' | ||||
'Note: All the hours are given in UTC.', | 'Note: All the hours are given in UTC.', | ||||
TextDumper(ephemerides, [], date=date(2019, 10, 14), with_colors=False).to_string()) | |||||
TextDumper(ephemerides, self._get_moon_phase(), [], date=date(2019, 10, 14), with_colors=False).to_string()) | |||||
ephemerides = self._get_data(aster_rise_set=True) | |||||
ephemerides = self._get_ephemerides(aster_rise_set=True) | |||||
self.assertEqual('Monday October 14, 2019\n\n' | self.assertEqual('Monday October 14, 2019\n\n' | ||||
'Object Rise time Culmination time Set time\n' | 'Object Rise time Culmination time Set time\n' | ||||
'-------- ----------- ------------------ ----------\n' | '-------- ----------- ------------------ ----------\n' | ||||
@@ -108,10 +132,10 @@ class DumperTestCase(unittest.TestCase): | |||||
'Moon phase: Full Moon\n' | 'Moon phase: Full Moon\n' | ||||
'Last Quarter on Monday October 21, 2019 at 00:00\n\n' | 'Last Quarter on Monday October 21, 2019 at 00:00\n\n' | ||||
'Note: All the hours are given in UTC.', | 'Note: All the hours are given in UTC.', | ||||
TextDumper(ephemerides, [], date=date(2019, 10, 14), with_colors=False).to_string()) | |||||
TextDumper(ephemerides, self._get_moon_phase(), [], date=date(2019, 10, 14), with_colors=False).to_string()) | |||||
def test_text_dumper_with_events(self): | def test_text_dumper_with_events(self): | ||||
ephemerides = self._get_data() | |||||
ephemerides = self._get_ephemerides() | |||||
self.assertEqual("Monday October 14, 2019\n\n" | self.assertEqual("Monday October 14, 2019\n\n" | ||||
"Object Rise time Culmination time Set time\n" | "Object Rise time Culmination time Set time\n" | ||||
"-------- ----------- ------------------ ----------\n" | "-------- ----------- ------------------ ----------\n" | ||||
@@ -122,10 +146,9 @@ class DumperTestCase(unittest.TestCase): | |||||
"23:00 Mars is in opposition\n" | "23:00 Mars is in opposition\n" | ||||
"12:00 Venus's largest elongation (42.0°)\n\n" | "12:00 Venus's largest elongation (42.0°)\n\n" | ||||
"Note: All the hours are given in UTC.", | "Note: All the hours are given in UTC.", | ||||
TextDumper(ephemerides, self._get_events(), date=date(2019, 10, 14), with_colors=False).to_string()) | |||||
TextDumper(ephemerides, self._get_moon_phase(), self._get_events(), date=date(2019, 10, 14), with_colors=False).to_string()) | |||||
def test_text_dumper_without_ephemerides_and_with_events(self): | def test_text_dumper_without_ephemerides_and_with_events(self): | ||||
ephemerides = self._get_data(False) | |||||
self.assertEqual('Monday October 14, 2019\n\n' | self.assertEqual('Monday October 14, 2019\n\n' | ||||
'Moon phase: Full Moon\n' | 'Moon phase: Full Moon\n' | ||||
'Last Quarter on Monday October 21, 2019 at 00:00\n\n' | 'Last Quarter on Monday October 21, 2019 at 00:00\n\n' | ||||
@@ -133,10 +156,12 @@ class DumperTestCase(unittest.TestCase): | |||||
'23:00 Mars is in opposition\n' | '23:00 Mars is in opposition\n' | ||||
"12:00 Venus's largest elongation (42.0°)\n\n" | "12:00 Venus's largest elongation (42.0°)\n\n" | ||||
'Note: All the hours are given in UTC.', | 'Note: All the hours are given in UTC.', | ||||
TextDumper(ephemerides, self._get_events(), date=date(2019, 10, 14), with_colors=False).to_string()) | |||||
TextDumper(None, self._get_moon_phase(), self._get_events(), | |||||
date=date(2019, 10, 14), with_colors=False).to_string()) | |||||
def test_timezone_is_taken_in_account(self): | def test_timezone_is_taken_in_account(self): | ||||
ephemerides = self._get_data(aster_rise_set=True) | |||||
ephemerides = self._get_ephemerides(aster_rise_set=True) | |||||
self.assertEqual('Monday October 14, 2019\n\n' | self.assertEqual('Monday October 14, 2019\n\n' | ||||
'Object Rise time Culmination time Set time\n' | 'Object Rise time Culmination time Set time\n' | ||||
'-------- ----------- ------------------ -------------\n' | '-------- ----------- ------------------ -------------\n' | ||||
@@ -147,9 +172,11 @@ class DumperTestCase(unittest.TestCase): | |||||
'Oct 15, 00:00 Mars is in opposition\n' | 'Oct 15, 00:00 Mars is in opposition\n' | ||||
"13:00 Venus's largest elongation (42.0°)\n\n" | "13:00 Venus's largest elongation (42.0°)\n\n" | ||||
'Note: All the hours are given in the UTC+1 timezone.', | 'Note: All the hours are given in the UTC+1 timezone.', | ||||
TextDumper(ephemerides, self._get_events(), date=date(2019, 10, 14), with_colors=False, timezone=1).to_string()) | |||||
TextDumper(ephemerides, self._get_moon_phase(), self._get_events(), date=date(2019, 10, 14), | |||||
with_colors=False, timezone=1).to_string()) | |||||
ephemerides = self._get_ephemerides(aster_rise_set=True) | |||||
ephemerides = self._get_data(aster_rise_set=True) | |||||
self.assertEqual('Monday October 14, 2019\n\n' | self.assertEqual('Monday October 14, 2019\n\n' | ||||
'Object Rise time Culmination time Set time\n' | 'Object Rise time Culmination time Set time\n' | ||||
'-------- ----------- ------------------ ----------\n' | '-------- ----------- ------------------ ----------\n' | ||||
@@ -160,10 +187,13 @@ class DumperTestCase(unittest.TestCase): | |||||
'22:00 Mars is in opposition\n' | '22:00 Mars is in opposition\n' | ||||
"11:00 Venus's largest elongation (42.0°)\n\n" | "11:00 Venus's largest elongation (42.0°)\n\n" | ||||
'Note: All the hours are given in the UTC-1 timezone.', | 'Note: All the hours are given in the UTC-1 timezone.', | ||||
TextDumper(ephemerides, self._get_events(), date=date(2019, 10, 14), with_colors=False, timezone=-1).to_string()) | |||||
TextDumper(ephemerides, self._get_moon_phase(), self._get_events(), date=date(2019, 10, 14), | |||||
with_colors=False, timezone=-1).to_string()) | |||||
def test_latex_dumper(self): | def test_latex_dumper(self): | ||||
latex = _LatexDumper(self._get_data(), self._get_events(), date=date(2019, 10, 14)).to_string() | |||||
latex = _LatexDumper(self._get_ephemerides(), self._get_moon_phase(), self._get_events(), | |||||
date=date(2019, 10, 14)).to_string() | |||||
self.assertRegex(latex, 'Monday October 14, 2019') | self.assertRegex(latex, 'Monday October 14, 2019') | ||||
self.assertRegex(latex, 'Full Moon') | self.assertRegex(latex, 'Full Moon') | ||||
self.assertRegex(latex, r'\\section{\\sffamily Expected events}') | self.assertRegex(latex, r'\\section{\\sffamily Expected events}') | ||||
@@ -172,12 +202,14 @@ class DumperTestCase(unittest.TestCase): | |||||
self.assertRegex(latex, r'\\event\{23:00\}\{Mars is in opposition\}') | self.assertRegex(latex, r'\\event\{23:00\}\{Mars is in opposition\}') | ||||
self.assertRegex(latex, r"\\event\{12:00\}\{Venus's largest elongation \(42.0°\)\}") | self.assertRegex(latex, r"\\event\{12:00\}\{Venus's largest elongation \(42.0°\)\}") | ||||
latex = _LatexDumper(self._get_data(aster_rise_set=True), | |||||
latex = _LatexDumper(self._get_ephemerides(aster_rise_set=True), self._get_moon_phase(), | |||||
self._get_events(), date=date(2019, 10, 14)).to_string() | self._get_events(), date=date(2019, 10, 14)).to_string() | ||||
self.assertRegex(latex, r'\\object\{Mars\}\{08:00\}\{13:00\}\{23:00\}') | self.assertRegex(latex, r'\\object\{Mars\}\{08:00\}\{13:00\}\{23:00\}') | ||||
def test_latex_dumper_without_ephemerides(self): | def test_latex_dumper_without_ephemerides(self): | ||||
latex = _LatexDumper(self._get_data(False), self._get_events(), date=date(2019, 10, 14)).to_string() | |||||
latex = _LatexDumper(None, self._get_moon_phase(), self._get_events(), | |||||
date=date(2019, 10, 14)).to_string() | |||||
self.assertRegex(latex, 'Monday October 14, 2019') | self.assertRegex(latex, 'Monday October 14, 2019') | ||||
self.assertRegex(latex, 'Full Moon') | self.assertRegex(latex, 'Full Moon') | ||||
self.assertRegex(latex, r'\\section{\\sffamily Expected events}') | self.assertRegex(latex, r'\\section{\\sffamily Expected events}') | ||||
@@ -188,7 +220,8 @@ class DumperTestCase(unittest.TestCase): | |||||
self.assertNotRegex(latex, r'\\section{\\sffamily Ephemerides of the day}') | self.assertNotRegex(latex, r'\\section{\\sffamily Ephemerides of the day}') | ||||
def test_latex_dumper_without_events(self): | def test_latex_dumper_without_events(self): | ||||
latex = _LatexDumper(self._get_data(), [], date=date(2019, 10, 14)).to_string() | |||||
latex = _LatexDumper(self._get_ephemerides(), self._get_moon_phase(), [], date=date(2019, 10, 14)).to_string() | |||||
self.assertRegex(latex, 'Monday October 14, 2019') | self.assertRegex(latex, 'Monday October 14, 2019') | ||||
self.assertRegex(latex, 'Full Moon') | self.assertRegex(latex, 'Full Moon') | ||||
self.assertRegex(latex, r'\\object\{Mars\}\{-\}\{-\}\{-\}') | self.assertRegex(latex, r'\\object\{Mars\}\{-\}\{-\}\{-\}') | ||||
@@ -197,16 +230,16 @@ class DumperTestCase(unittest.TestCase): | |||||
self.assertNotRegex(latex, r'\\section{\\sffamily Expected events}') | self.assertNotRegex(latex, r'\\section{\\sffamily Expected events}') | ||||
@staticmethod | @staticmethod | ||||
def _get_data(has_ephemerides: bool = True, aster_rise_set=False): | |||||
def _get_ephemerides(aster_rise_set=False) -> [AsterEphemerides]: | |||||
rise_time = datetime(2019, 10, 14, 8) if aster_rise_set else None | rise_time = datetime(2019, 10, 14, 8) if aster_rise_set else None | ||||
culmination_time = datetime(2019, 10, 14, 13) if aster_rise_set else None | culmination_time = datetime(2019, 10, 14, 13) if aster_rise_set else None | ||||
set_time = datetime(2019, 10, 14, 23) if aster_rise_set else None | set_time = datetime(2019, 10, 14, 23) if aster_rise_set else None | ||||
return { | |||||
'moon_phase': MoonPhase('FULL_MOON', datetime(2019, 10, 14), datetime(2019, 10, 21)), | |||||
'details': [Planet('Mars', 'MARS', | |||||
AsterEphemerides(rise_time, culmination_time, set_time))] if has_ephemerides else [] | |||||
} | |||||
return [AsterEphemerides(rise_time, culmination_time, set_time, Planet('Mars', 'MARS'))] | |||||
@staticmethod | |||||
def _get_moon_phase(): | |||||
return MoonPhase('FULL_MOON', datetime(2019, 10, 14), datetime(2019, 10, 21)) | |||||
@staticmethod | @staticmethod | ||||
def _get_events(): | def _get_events(): | ||||
@@ -1,106 +1,111 @@ | |||||
import unittest | import unittest | ||||
from kosmorrolib.ephemerides import EphemeridesComputer | |||||
from kosmorrolib.core import get_skf_objects | |||||
from kosmorrolib.data import Star, Position, MoonPhase | |||||
from .testutils import expect_assertions | |||||
from kosmorrolib import ephemerides | |||||
from kosmorrolib.data import EARTH, Position, MoonPhase | |||||
from datetime import date | from datetime import date | ||||
class EphemeridesComputerTestCase(unittest.TestCase): | |||||
class EphemeridesTestCase(unittest.TestCase): | |||||
def test_get_ephemerides_for_aster_returns_correct_hours(self): | def test_get_ephemerides_for_aster_returns_correct_hours(self): | ||||
position = Position(0, 0) | |||||
position.observation_planet = get_skf_objects()['earth'] | |||||
star = EphemeridesComputer.get_asters_ephemerides_for_aster(Star('Sun', skyfield_name='sun'), | |||||
date=date(2019, 11, 18), | |||||
position=position) | |||||
position = Position(0, 0, EARTH) | |||||
eph = ephemerides.get_ephemerides(date=date(2019, 11, 18), | |||||
position=position) | |||||
self.assertRegex(star.ephemerides.rise_time.isoformat(), '^2019-11-18T05:41:') | |||||
self.assertRegex(star.ephemerides.culmination_time.isoformat(), '^2019-11-18T11:45:') | |||||
self.assertRegex(star.ephemerides.set_time.isoformat(), '^2019-11-18T17:48:') | |||||
@expect_assertions(self.assertRegex, num=3) | |||||
def do_assertions(assert_regex): | |||||
for ephemeris in eph: | |||||
if ephemeris.object.skyfield_name == 'SUN': | |||||
assert_regex(ephemeris.rise_time.isoformat(), '^2019-11-18T05:41:') | |||||
assert_regex(ephemeris.culmination_time.isoformat(), '^2019-11-18T11:45:') | |||||
assert_regex(ephemeris.set_time.isoformat(), '^2019-11-18T17:48:') | |||||
break | |||||
do_assertions() | |||||
################################################################################################################### | ################################################################################################################### | ||||
### MOON PHASE TESTS ### | ### MOON PHASE TESTS ### | ||||
################################################################################################################### | ################################################################################################################### | ||||
def test_moon_phase_new_moon(self): | def test_moon_phase_new_moon(self): | ||||
phase = EphemeridesComputer.get_moon_phase(date(2019, 11, 25)) | |||||
phase = ephemerides.get_moon_phase(date(2019, 11, 25)) | |||||
self.assertEqual('WANING_CRESCENT', phase.identifier) | self.assertEqual('WANING_CRESCENT', phase.identifier) | ||||
self.assertIsNone(phase.time) | self.assertIsNone(phase.time) | ||||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') | self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') | ||||
phase = EphemeridesComputer.get_moon_phase(date(2019, 11, 26)) | |||||
phase = ephemerides.get_moon_phase(date(2019, 11, 26)) | |||||
self.assertEqual('NEW_MOON', phase.identifier) | self.assertEqual('NEW_MOON', phase.identifier) | ||||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-12-04T') | self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-12-04T') | ||||
phase = EphemeridesComputer.get_moon_phase(date(2019, 11, 27)) | |||||
phase = ephemerides.get_moon_phase(date(2019, 11, 27)) | |||||
self.assertEqual('WAXING_CRESCENT', phase.identifier) | self.assertEqual('WAXING_CRESCENT', phase.identifier) | ||||
self.assertIsNone(phase.time) | self.assertIsNone(phase.time) | ||||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-12-04T') | self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-12-04T') | ||||
def test_moon_phase_first_crescent(self): | def test_moon_phase_first_crescent(self): | ||||
phase = EphemeridesComputer.get_moon_phase(date(2019, 11, 3)) | |||||
phase = ephemerides.get_moon_phase(date(2019, 11, 3)) | |||||
self.assertEqual('WAXING_CRESCENT', phase.identifier) | self.assertEqual('WAXING_CRESCENT', phase.identifier) | ||||
self.assertIsNone(phase.time) | self.assertIsNone(phase.time) | ||||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-04T') | self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-04T') | ||||
phase = EphemeridesComputer.get_moon_phase(date(2019, 11, 4)) | |||||
phase = ephemerides.get_moon_phase(date(2019, 11, 4)) | |||||
self.assertEqual('FIRST_QUARTER', phase.identifier) | self.assertEqual('FIRST_QUARTER', phase.identifier) | ||||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') | self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') | ||||
phase = EphemeridesComputer.get_moon_phase(date(2019, 11, 5)) | |||||
phase = ephemerides.get_moon_phase(date(2019, 11, 5)) | |||||
self.assertEqual('WAXING_GIBBOUS', phase.identifier) | self.assertEqual('WAXING_GIBBOUS', phase.identifier) | ||||
self.assertIsNone(phase.time) | self.assertIsNone(phase.time) | ||||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') | self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') | ||||
def test_moon_phase_full_moon(self): | def test_moon_phase_full_moon(self): | ||||
phase = EphemeridesComputer.get_moon_phase(date(2019, 11, 11)) | |||||
phase = ephemerides.get_moon_phase(date(2019, 11, 11)) | |||||
self.assertEqual('WAXING_GIBBOUS', phase.identifier) | self.assertEqual('WAXING_GIBBOUS', phase.identifier) | ||||
self.assertIsNone(phase.time) | self.assertIsNone(phase.time) | ||||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') | self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') | ||||
phase = EphemeridesComputer.get_moon_phase(date(2019, 11, 12)) | |||||
phase = ephemerides.get_moon_phase(date(2019, 11, 12)) | |||||
self.assertEqual('FULL_MOON', phase.identifier) | self.assertEqual('FULL_MOON', phase.identifier) | ||||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') | self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') | ||||
phase = EphemeridesComputer.get_moon_phase(date(2019, 11, 13)) | |||||
phase = ephemerides.get_moon_phase(date(2019, 11, 13)) | |||||
self.assertEqual('WANING_GIBBOUS', phase.identifier) | self.assertEqual('WANING_GIBBOUS', phase.identifier) | ||||
self.assertIsNone(phase.time) | self.assertIsNone(phase.time) | ||||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') | self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') | ||||
def test_moon_phase_last_quarter(self): | def test_moon_phase_last_quarter(self): | ||||
phase = EphemeridesComputer.get_moon_phase(date(2019, 11, 18)) | |||||
phase = ephemerides.get_moon_phase(date(2019, 11, 18)) | |||||
self.assertEqual('WANING_GIBBOUS', phase.identifier) | self.assertEqual('WANING_GIBBOUS', phase.identifier) | ||||
self.assertIsNone(phase.time) | self.assertIsNone(phase.time) | ||||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') | self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') | ||||
phase = EphemeridesComputer.get_moon_phase(date(2019, 11, 19)) | |||||
phase = ephemerides.get_moon_phase(date(2019, 11, 19)) | |||||
self.assertEqual('LAST_QUARTER', phase.identifier) | self.assertEqual('LAST_QUARTER', phase.identifier) | ||||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') | self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') | ||||
phase = EphemeridesComputer.get_moon_phase(date(2019, 11, 20)) | |||||
phase = ephemerides.get_moon_phase(date(2019, 11, 20)) | |||||
self.assertEqual('WANING_CRESCENT', phase.identifier) | self.assertEqual('WANING_CRESCENT', phase.identifier) | ||||
self.assertIsNone(phase.time) | self.assertIsNone(phase.time) | ||||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') | self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') | ||||
def test_moon_phase_prediction(self): | def test_moon_phase_prediction(self): | ||||
phase = MoonPhase('NEW_MOON', None, None) | phase = MoonPhase('NEW_MOON', None, None) | ||||
self.assertEqual('First Quarter', phase.get_next_phase()) | |||||
self.assertEqual('First Quarter', phase.get_next_phase_name()) | |||||
phase = MoonPhase('WAXING_CRESCENT', None, None) | phase = MoonPhase('WAXING_CRESCENT', None, None) | ||||
self.assertEqual('First Quarter', phase.get_next_phase()) | |||||
self.assertEqual('First Quarter', phase.get_next_phase_name()) | |||||
phase = MoonPhase('FIRST_QUARTER', None, None) | phase = MoonPhase('FIRST_QUARTER', None, None) | ||||
self.assertEqual('Full Moon', phase.get_next_phase()) | |||||
self.assertEqual('Full Moon', phase.get_next_phase_name()) | |||||
phase = MoonPhase('WAXING_GIBBOUS', None, None) | phase = MoonPhase('WAXING_GIBBOUS', None, None) | ||||
self.assertEqual('Full Moon', phase.get_next_phase()) | |||||
self.assertEqual('Full Moon', phase.get_next_phase_name()) | |||||
phase = MoonPhase('FULL_MOON', None, None) | phase = MoonPhase('FULL_MOON', None, None) | ||||
self.assertEqual('Last Quarter', phase.get_next_phase()) | |||||
self.assertEqual('Last Quarter', phase.get_next_phase_name()) | |||||
phase = MoonPhase('WANING_GIBBOUS', None, None) | phase = MoonPhase('WANING_GIBBOUS', None, None) | ||||
self.assertEqual('Last Quarter', phase.get_next_phase()) | |||||
self.assertEqual('Last Quarter', phase.get_next_phase_name()) | |||||
phase = MoonPhase('LAST_QUARTER', None, None) | phase = MoonPhase('LAST_QUARTER', None, None) | ||||
self.assertEqual('New Moon', phase.get_next_phase()) | |||||
self.assertEqual('New Moon', phase.get_next_phase_name()) | |||||
phase = MoonPhase('WANING_CRESCENT', None, None) | phase = MoonPhase('WANING_CRESCENT', None, None) | ||||
self.assertEqual('New Moon', phase.get_next_phase()) | |||||
self.assertEqual('New Moon', phase.get_next_phase_name()) | |||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
@@ -0,0 +1,48 @@ | |||||
import functools | |||||
from unittest import mock | |||||
def expect_assertions(assert_fun, num=1): | |||||
"""Asserts that an assertion function is called as expected. | |||||
This is very useful when the assertions are in loops. | |||||
To use it, create a nested function in the the test function. | |||||
The nested function will receive as parameter the mocked assertion function to use in place of the original one. | |||||
Finally, run the nested function. | |||||
Example of use: | |||||
>>> # the function we test: | |||||
>>> def my_sum_function(n, m): | |||||
>>> # some code here | |||||
>>> pass | |||||
>>> # The unit test: | |||||
>>> def test_sum(self): | |||||
>>> @expect_assertions(self.assertEqual, num=10): | |||||
>>> def make_assertions(assert_equal): | |||||
>>> for i in range (-5, 5): | |||||
>>> for j in range(-5, 5): | |||||
>>> assert_equal(i + j, my_sum_function(i, j) | |||||
>>> | |||||
>>> make_assertions() # You don't need to give any parameter, the decorator does it for you. | |||||
:param assert_fun: the assertion function to test | |||||
:param num: the number of times the assertion function is expected to be called | |||||
""" | |||||
assert_fun_mock = mock.Mock(side_effect=assert_fun) | |||||
def fun_decorator(fun): | |||||
@functools.wraps(fun) | |||||
def sniff_function(): | |||||
fun(assert_fun_mock) | |||||
count = assert_fun_mock.call_count | |||||
if count != num: | |||||
raise AssertionError('Expected %d call(s) to function %s but called %d time(s).' % (num, | |||||
assert_fun.__name__, | |||||
count)) | |||||
return sniff_function | |||||
return fun_decorator |