BREAKING CHANGE: the JSON format has deeply changed to enhance its consistencytags/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 | |||||