BREAKING CHANGE: some methodes in Event and MoonPhase have been dropped in favor of `enum.Enum`'s `name` and `value` properties.tags/v0.9.0
@@ -19,13 +19,26 @@ jobs: | |||
- name: Check i18n | |||
run: | | |||
pipenv run python setup.py extract_messages --output-file=/tmp/kosmorro-messages.pot > /dev/null | |||
n=$(diff -y --suppress-common-lines kosmorrolib/locales/messages.pot /tmp/kosmorro-messages.pot | grep -v -E '^"POT-Creation-Date: ' | wc -l) | |||
diff=$(diff kosmorrolib/locales/messages.pot /tmp/kosmorro-messages.pot | grep '^>') | |||
n=$(echo "$diff" | grep -v '> "POT-Creation-Date: ' | wc -l) | |||
if [ "$(echo "$diff" | grep -E '^"Generated-By: Babel' | wc -l)" -eq "1" ]; then | |||
echo "❌ You dependencies may be out of date!" | |||
echo " Please run the following command to fix this:" | |||
echo | |||
echo " pipenv sync --dev" | |||
echo | |||
echo " Then update the messages file:" | |||
echo | |||
echo " make messages" | |||
exit 2 | |||
fi | |||
if [ "$n" -ne "0" ]; then | |||
echo "❌ The messages file is not up-to-date!" | |||
echo " Please run the following command to fix this:" | |||
echo | |||
echo " pipenv run python setup.py extract_messages --output-file=kosmorrolib/locales/messages.pot" | |||
echo " make messages" | |||
exit 1 | |||
fi | |||
@@ -6,13 +6,17 @@ test: | |||
unset KOSMORRO_TIMEZONE; \ | |||
LANG=C pipenv run python3 -m coverage run -m unittest test | |||
build: i18n | |||
build: i18n manpages | |||
python3 setup.py sdist bdist_wheel | |||
i18n: | |||
messages: | |||
pipenv run python setup.py extract_messages --output-file=kosmorrolib/locales/messages.pot | |||
manpages: | |||
ronn --roff manpage/kosmorro.1.md | |||
ronn --roff manpage/kosmorro.7.md | |||
i18n: | |||
if [ "$$POEDITOR_API_ACCESS" != "" ]; then \ | |||
python3 .scripts/build/getlangs.py; \ | |||
python3 setup.py compile_catalog; \ | |||
@@ -25,30 +25,10 @@ from numpy import pi, arcsin | |||
from skyfield.api import Topos, Time | |||
from skyfield.vectorlib import VectorSum as SkfPlanet | |||
from .core import get_skf_objects, get_timescale | |||
from .core import get_skf_objects | |||
from .enum import MoonPhaseType, EventType | |||
from .i18n import _ | |||
MOON_PHASES = { | |||
'NEW_MOON': _('New Moon'), | |||
'WAXING_CRESCENT': _('Waxing crescent'), | |||
'FIRST_QUARTER': _('First Quarter'), | |||
'WAXING_GIBBOUS': _('Waxing gibbous'), | |||
'FULL_MOON': _('Full Moon'), | |||
'WANING_GIBBOUS': _('Waning gibbous'), | |||
'LAST_QUARTER': _('Last Quarter'), | |||
'WANING_CRESCENT': _('Waning crescent'), | |||
'UNKNOWN': _('Unavailable') | |||
} | |||
EVENTS = { | |||
'OPPOSITION': {'message': _('%s is in opposition')}, | |||
'CONJUNCTION': {'message': _('%s and %s are in conjunction')}, | |||
'OCCULTATION': {'message': _('%s occults %s')}, | |||
'MAXIMAL_ELONGATION': {'message': _("%s's largest elongation")}, | |||
'MOON_PERIGEE': {'message': _("%s is at its perigee")}, | |||
'MOON_APOGEE': {'message': _("%s is at its apogee")}, | |||
} | |||
class Serializable(ABC): | |||
@abstractmethod | |||
@@ -57,40 +37,27 @@ class Serializable(ABC): | |||
class MoonPhase(Serializable): | |||
def __init__(self, identifier: str, time: datetime = None, next_phase_date: datetime = None): | |||
if identifier not in MOON_PHASES.keys(): | |||
raise ValueError('identifier parameter must be one of %s (got %s)' % (', '.join(MOON_PHASES.keys()), | |||
identifier)) | |||
self.identifier = identifier | |||
def __init__(self, phase_type: MoonPhaseType, time: datetime = None, next_phase_date: datetime = None): | |||
self.phase_type = phase_type | |||
self.time = time | |||
self.next_phase_date = next_phase_date | |||
def get_phase(self): | |||
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): | |||
if self.identifier == 'NEW_MOON' or self.identifier == 'WAXING_CRESCENT': | |||
next_identifier = 'FIRST_QUARTER' | |||
elif self.identifier == 'FIRST_QUARTER' or self.identifier == 'WAXING_GIBBOUS': | |||
next_identifier = 'FULL_MOON' | |||
elif self.identifier == 'FULL_MOON' or self.identifier == 'WANING_GIBBOUS': | |||
next_identifier = 'LAST_QUARTER' | |||
else: | |||
next_identifier = 'NEW_MOON' | |||
return next_identifier | |||
if self.phase_type in [MoonPhaseType.NEW_MOON, MoonPhaseType.WAXING_CRESCENT]: | |||
return MoonPhaseType.FIRST_QUARTER | |||
if self.phase_type in [MoonPhaseType.FIRST_QUARTER, MoonPhaseType.WAXING_GIBBOUS]: | |||
return MoonPhaseType.FULL_MOON | |||
if self.phase_type in [MoonPhaseType.FULL_MOON, MoonPhaseType.WANING_GIBBOUS]: | |||
return MoonPhaseType.LAST_QUARTER | |||
return MoonPhaseType.NEW_MOON | |||
def serialize(self) -> dict: | |||
return { | |||
'phase': self.identifier, | |||
'phase': self.phase_type.name, | |||
'time': self.time.isoformat() if self.time is not None else None, | |||
'next': { | |||
'phase': self.get_next_phase(), | |||
'phase': self.get_next_phase().name, | |||
'time': self.next_phase_date.isoformat() | |||
} | |||
} | |||
@@ -168,13 +135,8 @@ class Satellite(Object): | |||
class Event(Serializable): | |||
def __init__(self, event_type: str, objects: [Object], start_time: datetime, | |||
def __init__(self, event_type: EventType, objects: [Object], start_time: datetime, | |||
end_time: Union[datetime, None] = None, details: str = None): | |||
if event_type not in EVENTS.keys(): | |||
accepted_types = ', '.join(EVENTS.keys()) | |||
raise ValueError('event_type parameter must be one of the following: %s (got %s)' % (accepted_types, | |||
event_type)) | |||
self.event_type = event_type | |||
self.objects = objects | |||
self.start_time = start_time | |||
@@ -189,7 +151,7 @@ class Event(Serializable): | |||
self.details) | |||
def get_description(self, show_details: bool = True) -> str: | |||
description = EVENTS[self.event_type]['message'] % self._get_objects_name() | |||
description = self.event_type.value % self._get_objects_name() | |||
if show_details and self.details is not None: | |||
description += ' ({:s})'.format(self.details) | |||
return description | |||
@@ -203,50 +165,13 @@ class Event(Serializable): | |||
def serialize(self) -> dict: | |||
return { | |||
'objects': [object.serialize() for object in self.objects], | |||
'event': self.event_type, | |||
'event': self.event_type.name, | |||
'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]: | |||
tomorrow = get_timescale().utc(now.utc_datetime().year, now.utc_datetime().month, now.utc_datetime().day + 1) | |||
phases = list(MOON_PHASES.keys()) | |||
current_phase = None | |||
current_phase_time = None | |||
next_phase_time = None | |||
i = 0 | |||
if len(times) == 0: | |||
return None | |||
for i, time in enumerate(times): | |||
if now.utc_iso() <= time.utc_iso(): | |||
if vals[i] in [0, 2, 4, 6]: | |||
if time.utc_datetime() < tomorrow.utc_datetime(): | |||
current_phase_time = time | |||
current_phase = phases[vals[i]] | |||
else: | |||
i -= 1 | |||
current_phase_time = None | |||
current_phase = phases[vals[i]] | |||
else: | |||
current_phase = phases[vals[i]] | |||
break | |||
for j in range(i + 1, len(times)): | |||
if vals[j] in [0, 2, 4, 6]: | |||
next_phase_time = times[j] | |||
break | |||
return MoonPhase(current_phase, | |||
current_phase_time.utc_datetime() if current_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], | |||
@@ -267,8 +192,6 @@ class AsterEphemerides(Serializable): | |||
} | |||
MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] | |||
EARTH = Planet('Earth', 'EARTH') | |||
ASTERS = [Star(_('Sun'), 'SUN', radius=696342), | |||
@@ -23,6 +23,7 @@ import os | |||
from pathlib import Path | |||
from tabulate import tabulate | |||
from termcolor import colored | |||
from .data import ASTERS, AsterEphemerides, MoonPhase, Event | |||
from .i18n import _, FULL_DATE_FORMAT, SHORT_DATETIME_FORMAT, TIME_FORMAT | |||
from .version import VERSION | |||
@@ -159,9 +160,9 @@ class TextDumper(Dumper): | |||
if moon_phase is None: | |||
return _('Moon phase is unavailable for this date.') | |||
current_moon_phase = ' '.join([self.style(_('Moon phase:'), 'strong'), moon_phase.get_phase()]) | |||
current_moon_phase = ' '.join([self.style(_('Moon phase:'), 'strong'), moon_phase.phase_type.value]) | |||
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_name(), | |||
next_moon_phase=moon_phase.get_next_phase().value, | |||
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) | |||
) | |||
@@ -183,12 +184,9 @@ class _LatexDumper(Dumper): | |||
kosmorro_logo_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), | |||
'assets', 'png', 'kosmorro-logo.png') | |||
if self.moon_phase is None: | |||
self.moon_phase = MoonPhase('UNKNOWN') | |||
moon_phase_graphics = os.path.join(os.path.abspath(os.path.dirname(__file__)), | |||
'assets', 'moonphases', 'png', | |||
'.'.join([self.moon_phase.identifier.lower().replace('_', '-'), | |||
'.'.join([self.moon_phase.phase_type.name.lower().replace('_', '-'), | |||
'png'])) | |||
document = template | |||
@@ -234,7 +232,7 @@ class _LatexDumper(Dumper): | |||
.replace('+++GRAPH_LABEL_HOURS+++', _('hours')) \ | |||
.replace('+++MOON-PHASE-GRAPHICS+++', moon_phase_graphics) \ | |||
.replace('+++CURRENT-MOON-PHASE-TITLE+++', _('Moon phase:')) \ | |||
.replace('+++CURRENT-MOON-PHASE+++', self.moon_phase.get_phase()) \ | |||
.replace('+++CURRENT-MOON-PHASE+++', self.moon_phase.phase_type.value) \ | |||
.replace('+++SECTION-EVENTS+++', _('Expected events')) \ | |||
.replace('+++EVENTS+++', self._make_events()) | |||
@@ -0,0 +1,40 @@ | |||
#!/usr/bin/env python3 | |||
# Kosmorro - Compute The Next Ephemerides | |||
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr> | |||
# | |||
# This program is free software: you can redistribute it and/or modify | |||
# it under the terms of the GNU Affero General Public License as | |||
# published by the Free Software Foundation, either version 3 of the | |||
# License, or (at your option) any later version. | |||
# | |||
# This program is distributed in the hope that it will be useful, | |||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
# GNU Affero General Public License for more details. | |||
# | |||
# You should have received a copy of the GNU Affero General Public License | |||
# along with this program. If not, see <https://www.gnu.org/licenses/>. | |||
from enum import Enum | |||
from .i18n import _ | |||
class MoonPhaseType(Enum): | |||
NEW_MOON = _('New Moon') | |||
WAXING_CRESCENT = _('Waxing crescent') | |||
FIRST_QUARTER = _('First Quarter') | |||
WAXING_GIBBOUS = _('Waxing gibbous') | |||
FULL_MOON = _('Full Moon') | |||
WANING_GIBBOUS = _('Waning gibbous') | |||
LAST_QUARTER = _('Last Quarter') | |||
WANING_CRESCENT = _('Waning crescent') | |||
class EventType(Enum): | |||
OPPOSITION = _('%s is in opposition') | |||
CONJUNCTION = _('%s and %s are in conjunction') | |||
OCCULTATION = _('%s occults %s') | |||
MAXIMAL_ELONGATION = _("%s's largest elongation") | |||
MOON_PERIGEE = _("%s is at its perigee") | |||
MOON_APOGEE = _("%s is at its apogee") |
@@ -17,20 +17,59 @@ | |||
# along with this program. If not, see <https://www.gnu.org/licenses/>. | |||
import datetime | |||
from typing import Union | |||
from skyfield.searchlib import find_discrete, find_maxima | |||
from skyfield.timelib import Time | |||
from skyfield.constants import tau | |||
from skyfield.errors import EphemerisRangeError | |||
from .data import Position, AsterEphemerides, MoonPhase, Object, ASTERS, skyfield_to_moon_phase | |||
from .data import Position, AsterEphemerides, MoonPhase, Object, ASTERS | |||
from .dateutil import translate_to_timezone | |||
from .core import get_skf_objects, get_timescale, get_iau2000b | |||
from .enum import MoonPhaseType | |||
from .exceptions import OutOfRangeDateError | |||
RISEN_ANGLE = -0.8333 | |||
def _get_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) | |||
phases = list(MoonPhaseType) | |||
current_phase = None | |||
current_phase_time = None | |||
next_phase_time = None | |||
i = 0 | |||
if len(times) == 0: | |||
return None | |||
for i, time in enumerate(times): | |||
if now.utc_iso() <= time.utc_iso(): | |||
if vals[i] in [0, 2, 4, 6]: | |||
if time.utc_datetime() < tomorrow.utc_datetime(): | |||
current_phase_time = time | |||
current_phase = phases[vals[i]] | |||
else: | |||
i -= 1 | |||
current_phase_time = None | |||
current_phase = phases[vals[i]] | |||
else: | |||
current_phase = phases[vals[i]] | |||
break | |||
for j in range(i + 1, len(times)): | |||
if vals[j] in [0, 2, 4, 6]: | |||
next_phase_time = times[j] | |||
break | |||
return MoonPhase(current_phase, | |||
current_phase_time.utc_datetime() if current_phase_time is not None else None, | |||
next_phase_time.utc_datetime() if next_phase_time is not None else None) | |||
def get_moon_phase(compute_date: datetime.date, timezone: int = 0) -> MoonPhase: | |||
earth = get_skf_objects()['earth'] | |||
moon = get_skf_objects()['moon'] | |||
@@ -60,7 +99,7 @@ def get_moon_phase(compute_date: datetime.date, timezone: int = 0) -> MoonPhase: | |||
raise OutOfRangeDateError(start, end) | |||
return skyfield_to_moon_phase(times, phase, today) | |||
return _get_skyfield_to_moon_phase(times, phase, today) | |||
def get_ephemerides(date: datetime.date, position: Position, timezone: int = 0) -> [AsterEphemerides]: | |||
@@ -25,6 +25,7 @@ from numpy import pi | |||
from .data import Event, Star, Planet, ASTERS | |||
from .dateutil import translate_to_timezone | |||
from .enum import EventType | |||
from .exceptions import OutOfRangeDateError | |||
from .core import get_timescale, get_skf_objects, flatten_list | |||
@@ -68,10 +69,10 @@ def _search_conjunction(start_time: Time, end_time: Time, timezone: int) -> [Eve | |||
aster2] if aster1_pos.distance().km < aster2_pos.distance().km else [aster2, | |||
aster1] | |||
conjunctions.append(Event('OCCULTATION', occulting_aster, | |||
conjunctions.append(Event(EventType.OCCULTATION, occulting_aster, | |||
translate_to_timezone(time.utc_datetime(), timezone))) | |||
else: | |||
conjunctions.append(Event('CONJUNCTION', [aster1, aster2], | |||
conjunctions.append(Event(EventType.CONJUNCTION, [aster1, aster2], | |||
translate_to_timezone(time.utc_datetime(), timezone))) | |||
computed.append(aster1) | |||
@@ -101,7 +102,7 @@ def _search_oppositions(start_time: Time, end_time: Time, timezone: int) -> [Eve | |||
times, _ = find_discrete(start_time, end_time, is_oppositing) | |||
for time in times: | |||
events.append(Event('OPPOSITION', [aster], translate_to_timezone(time.utc_datetime(), timezone))) | |||
events.append(Event(EventType.OPPOSITION, [aster], translate_to_timezone(time.utc_datetime(), timezone))) | |||
return events | |||
@@ -129,7 +130,9 @@ def _search_maximal_elongations(start_time: Time, end_time: Time, timezone: int) | |||
for i, time in enumerate(times): | |||
elongation = elongations[i] | |||
events.append(Event('MAXIMAL_ELONGATION', [aster], translate_to_timezone(time.utc_datetime(), timezone), | |||
events.append(Event(EventType.MAXIMAL_ELONGATION, | |||
[aster], | |||
translate_to_timezone(time.utc_datetime(), timezone), | |||
details='{:.3n}°'.format(elongation))) | |||
return events | |||
@@ -157,7 +160,7 @@ def _search_moon_apogee(start_time: Time, end_time: Time, timezone: int) -> [Eve | |||
times, _ = find_maxima(start_time, end_time, f=_get_moon_distance(), epsilon=1./24/60) | |||
for time in times: | |||
events.append(Event('MOON_APOGEE', [moon], translate_to_timezone(time.utc_datetime(), timezone))) | |||
events.append(Event(EventType.MOON_APOGEE, [moon], translate_to_timezone(time.utc_datetime(), timezone))) | |||
return events | |||
@@ -169,7 +172,7 @@ def _search_moon_perigee(start_time: Time, end_time: Time, timezone: int) -> [Ev | |||
times, _ = find_minima(start_time, end_time, f=_get_moon_distance(), epsilon=1./24/60) | |||
for time in times: | |||
events.append(Event('MOON_PERIGEE', [moon], translate_to_timezone(time.utc_datetime(), timezone))) | |||
events.append(Event(EventType.MOON_PERIGEE, [moon], translate_to_timezone(time.utc_datetime(), timezone))) | |||
return events | |||
@@ -8,7 +8,7 @@ msgid "" | |||
msgstr "" | |||
"Project-Id-Version: kosmorro 0.8.1\n" | |||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | |||
"POT-Creation-Date: 2020-12-01 12:01+0100\n" | |||
"POT-Creation-Date: 2020-12-02 10:22+0100\n" | |||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | |||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | |||
"Language-Team: LANGUAGE <LL@li.org>\n" | |||
@@ -27,182 +27,116 @@ msgid "" | |||
"offset format." | |||
msgstr "" | |||
#: kosmorrolib/data.py:32 | |||
msgid "New Moon" | |||
msgstr "" | |||
#: kosmorrolib/data.py:33 | |||
msgid "Waxing crescent" | |||
msgstr "" | |||
#: kosmorrolib/data.py:34 | |||
msgid "First Quarter" | |||
msgstr "" | |||
#: kosmorrolib/data.py:35 | |||
msgid "Waxing gibbous" | |||
msgstr "" | |||
#: kosmorrolib/data.py:36 | |||
msgid "Full Moon" | |||
msgstr "" | |||
#: kosmorrolib/data.py:37 | |||
msgid "Waning gibbous" | |||
msgstr "" | |||
#: kosmorrolib/data.py:38 | |||
msgid "Last Quarter" | |||
msgstr "" | |||
#: kosmorrolib/data.py:39 | |||
msgid "Waning crescent" | |||
msgstr "" | |||
#: kosmorrolib/data.py:40 | |||
msgid "Unavailable" | |||
msgstr "" | |||
#: kosmorrolib/data.py:44 | |||
#, python-format | |||
msgid "%s is in opposition" | |||
msgstr "" | |||
#: kosmorrolib/data.py:45 | |||
#, python-format | |||
msgid "%s and %s are in conjunction" | |||
msgstr "" | |||
#: kosmorrolib/data.py:46 | |||
#, python-format | |||
msgid "%s occults %s" | |||
msgstr "" | |||
#: kosmorrolib/data.py:47 | |||
#, python-format | |||
msgid "%s's largest elongation" | |||
msgstr "" | |||
#: kosmorrolib/data.py:48 | |||
#, python-format | |||
msgid "%s is at its perigee" | |||
msgstr "" | |||
#: kosmorrolib/data.py:49 | |||
#, python-format | |||
msgid "%s is at its apogee" | |||
msgstr "" | |||
#: kosmorrolib/data.py:274 | |||
#: kosmorrolib/data.py:197 | |||
msgid "Sun" | |||
msgstr "" | |||
#: kosmorrolib/data.py:275 | |||
#: kosmorrolib/data.py:198 | |||
msgid "Moon" | |||
msgstr "" | |||
#: kosmorrolib/data.py:276 | |||
#: kosmorrolib/data.py:199 | |||
msgid "Mercury" | |||
msgstr "" | |||
#: kosmorrolib/data.py:277 | |||
#: kosmorrolib/data.py:200 | |||
msgid "Venus" | |||
msgstr "" | |||
#: kosmorrolib/data.py:278 | |||
#: kosmorrolib/data.py:201 | |||
msgid "Mars" | |||
msgstr "" | |||
#: kosmorrolib/data.py:279 | |||
#: kosmorrolib/data.py:202 | |||
msgid "Jupiter" | |||
msgstr "" | |||
#: kosmorrolib/data.py:280 | |||
#: kosmorrolib/data.py:203 | |||
msgid "Saturn" | |||
msgstr "" | |||
#: kosmorrolib/data.py:281 | |||
#: kosmorrolib/data.py:204 | |||
msgid "Uranus" | |||
msgstr "" | |||
#: kosmorrolib/data.py:282 | |||
#: kosmorrolib/data.py:205 | |||
msgid "Neptune" | |||
msgstr "" | |||
#: kosmorrolib/data.py:283 | |||
#: kosmorrolib/data.py:206 | |||
msgid "Pluto" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:87 | |||
#: kosmorrolib/dumper.py:88 | |||
msgid "Expected events:" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:91 | |||
#: kosmorrolib/dumper.py:92 | |||
msgid "Note: All the hours are given in UTC." | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:96 | |||
#: kosmorrolib/dumper.py:97 | |||
msgid "Note: All the hours are given in the UTC{offset} timezone." | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:142 kosmorrolib/dumper.py:229 | |||
#: kosmorrolib/dumper.py:143 kosmorrolib/dumper.py:227 | |||
msgid "Object" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:143 kosmorrolib/dumper.py:230 | |||
#: kosmorrolib/dumper.py:144 kosmorrolib/dumper.py:228 | |||
msgid "Rise time" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:144 kosmorrolib/dumper.py:231 | |||
#: kosmorrolib/dumper.py:145 kosmorrolib/dumper.py:229 | |||
msgid "Culmination time" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:145 kosmorrolib/dumper.py:232 | |||
#: kosmorrolib/dumper.py:146 kosmorrolib/dumper.py:230 | |||
msgid "Set time" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:160 | |||
#: kosmorrolib/dumper.py:161 | |||
msgid "Moon phase is unavailable for this date." | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:162 kosmorrolib/dumper.py:236 | |||
#: kosmorrolib/dumper.py:163 kosmorrolib/dumper.py:234 | |||
msgid "Moon phase:" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:163 | |||
#: kosmorrolib/dumper.py:164 | |||
msgid "{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:216 | |||
#: kosmorrolib/dumper.py:214 | |||
msgid "A Summary of your Sky" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:220 | |||
#: kosmorrolib/dumper.py:218 | |||
msgid "" | |||
"This document summarizes the ephemerides and the events of {date}. It " | |||
"aims to help you to prepare your observation session. All the hours are " | |||
"given in {timezone}." | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:226 | |||
#: kosmorrolib/dumper.py:224 | |||
msgid "" | |||
"Don't forget to check the weather forecast before you go out with your " | |||
"equipment." | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:228 | |||
#: kosmorrolib/dumper.py:226 | |||
msgid "Ephemerides of the day" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:234 | |||
#: kosmorrolib/dumper.py:232 | |||
msgid "hours" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:238 | |||
#: kosmorrolib/dumper.py:236 | |||
msgid "Expected events" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:352 | |||
#: kosmorrolib/dumper.py:350 | |||
msgid "" | |||
"Building PDFs was not possible, because some dependencies are not " | |||
"installed.\n" | |||
@@ -210,6 +144,68 @@ msgid "" | |||
"information." | |||
msgstr "" | |||
#: kosmorrolib/enum.py:24 | |||
msgid "New Moon" | |||
msgstr "" | |||
#: kosmorrolib/enum.py:25 | |||
msgid "Waxing crescent" | |||
msgstr "" | |||
#: kosmorrolib/enum.py:26 | |||
msgid "First Quarter" | |||
msgstr "" | |||
#: kosmorrolib/enum.py:27 | |||
msgid "Waxing gibbous" | |||
msgstr "" | |||
#: kosmorrolib/enum.py:28 | |||
msgid "Full Moon" | |||
msgstr "" | |||
#: kosmorrolib/enum.py:29 | |||
msgid "Waning gibbous" | |||
msgstr "" | |||
#: kosmorrolib/enum.py:30 | |||
msgid "Last Quarter" | |||
msgstr "" | |||
#: kosmorrolib/enum.py:31 | |||
msgid "Waning crescent" | |||
msgstr "" | |||
#: kosmorrolib/enum.py:35 | |||
#, python-format | |||
msgid "%s is in opposition" | |||
msgstr "" | |||
#: kosmorrolib/enum.py:36 | |||
#, python-format | |||
msgid "%s and %s are in conjunction" | |||
msgstr "" | |||
#: kosmorrolib/enum.py:37 | |||
#, python-format | |||
msgid "%s occults %s" | |||
msgstr "" | |||
#: kosmorrolib/enum.py:38 | |||
#, python-format | |||
msgid "%s's largest elongation" | |||
msgstr "" | |||
#: kosmorrolib/enum.py:39 | |||
#, python-format | |||
msgid "%s is at its perigee" | |||
msgstr "" | |||
#: kosmorrolib/enum.py:40 | |||
#, python-format | |||
msgid "%s is at its apogee" | |||
msgstr "" | |||
#: kosmorrolib/exceptions.py:34 | |||
msgid "The date must be between {minimum_date} and {maximum_date}" | |||
msgstr "" | |||
@@ -3,6 +3,7 @@ from datetime import date, datetime | |||
from kosmorrolib.data import AsterEphemerides, Planet, MoonPhase, Event | |||
from kosmorrolib.dumper import JsonDumper, TextDumper, _LatexDumper | |||
from kosmorrolib.enum import MoonPhaseType, EventType | |||
class DumperTestCase(unittest.TestCase): | |||
@@ -283,14 +284,14 @@ class DumperTestCase(unittest.TestCase): | |||
@staticmethod | |||
def _get_moon_phase(): | |||
return MoonPhase('FULL_MOON', datetime(2019, 10, 14), datetime(2019, 10, 21)) | |||
return MoonPhase(MoonPhaseType.FULL_MOON, datetime(2019, 10, 14), datetime(2019, 10, 21)) | |||
@staticmethod | |||
def _get_events(): | |||
return [Event('OPPOSITION', | |||
return [Event(EventType.OPPOSITION, | |||
[Planet('Mars', 'MARS')], | |||
datetime(2019, 10, 14, 23, 00)), | |||
Event('MAXIMAL_ELONGATION', | |||
Event(EventType.MAXIMAL_ELONGATION, | |||
[Planet('Venus', 'VENUS')], | |||
datetime(2019, 10, 14, 12, 00), details='42.0°'), | |||
] | |||
@@ -1,7 +1,10 @@ | |||
import unittest | |||
from .testutils import expect_assertions | |||
from kosmorrolib import ephemerides | |||
from kosmorrolib.data import EARTH, Position, MoonPhase | |||
from kosmorrolib.enum import MoonPhaseType | |||
from datetime import date | |||
from kosmorrolib.exceptions import OutOfRangeDateError | |||
@@ -29,84 +32,84 @@ class EphemeridesTestCase(unittest.TestCase): | |||
def test_moon_phase_new_moon(self): | |||
phase = ephemerides.get_moon_phase(date(2019, 11, 25)) | |||
self.assertEqual('WANING_CRESCENT', phase.identifier) | |||
self.assertEqual(MoonPhaseType.WANING_CRESCENT, phase.phase_type) | |||
self.assertIsNone(phase.time) | |||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') | |||
phase = ephemerides.get_moon_phase(date(2019, 11, 26)) | |||
self.assertEqual('NEW_MOON', phase.identifier) | |||
self.assertEqual(MoonPhaseType.NEW_MOON, phase.phase_type) | |||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-12-04T') | |||
phase = ephemerides.get_moon_phase(date(2019, 11, 27)) | |||
self.assertEqual('WAXING_CRESCENT', phase.identifier) | |||
self.assertEqual(MoonPhaseType.WAXING_CRESCENT, phase.phase_type) | |||
self.assertIsNone(phase.time) | |||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-12-04T') | |||
def test_moon_phase_first_crescent(self): | |||
phase = ephemerides.get_moon_phase(date(2019, 11, 3)) | |||
self.assertEqual('WAXING_CRESCENT', phase.identifier) | |||
self.assertEqual(MoonPhaseType.WAXING_CRESCENT, phase.phase_type) | |||
self.assertIsNone(phase.time) | |||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-04T') | |||
phase = ephemerides.get_moon_phase(date(2019, 11, 4)) | |||
self.assertEqual('FIRST_QUARTER', phase.identifier) | |||
self.assertEqual(MoonPhaseType.FIRST_QUARTER, phase.phase_type) | |||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') | |||
phase = ephemerides.get_moon_phase(date(2019, 11, 5)) | |||
self.assertEqual('WAXING_GIBBOUS', phase.identifier) | |||
self.assertEqual(MoonPhaseType.WAXING_GIBBOUS, phase.phase_type) | |||
self.assertIsNone(phase.time) | |||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') | |||
def test_moon_phase_full_moon(self): | |||
phase = ephemerides.get_moon_phase(date(2019, 11, 11)) | |||
self.assertEqual('WAXING_GIBBOUS', phase.identifier) | |||
self.assertEqual(MoonPhaseType.WAXING_GIBBOUS, phase.phase_type) | |||
self.assertIsNone(phase.time) | |||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') | |||
phase = ephemerides.get_moon_phase(date(2019, 11, 12)) | |||
self.assertEqual('FULL_MOON', phase.identifier) | |||
self.assertEqual(MoonPhaseType.FULL_MOON, phase.phase_type) | |||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') | |||
phase = ephemerides.get_moon_phase(date(2019, 11, 13)) | |||
self.assertEqual('WANING_GIBBOUS', phase.identifier) | |||
self.assertEqual(MoonPhaseType.WANING_GIBBOUS, phase.phase_type) | |||
self.assertIsNone(phase.time) | |||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') | |||
def test_moon_phase_last_quarter(self): | |||
phase = ephemerides.get_moon_phase(date(2019, 11, 18)) | |||
self.assertEqual('WANING_GIBBOUS', phase.identifier) | |||
self.assertEqual(MoonPhaseType.WANING_GIBBOUS, phase.phase_type) | |||
self.assertIsNone(phase.time) | |||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') | |||
phase = ephemerides.get_moon_phase(date(2019, 11, 19)) | |||
self.assertEqual('LAST_QUARTER', phase.identifier) | |||
self.assertEqual(MoonPhaseType.LAST_QUARTER, phase.phase_type) | |||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') | |||
phase = ephemerides.get_moon_phase(date(2019, 11, 20)) | |||
self.assertEqual('WANING_CRESCENT', phase.identifier) | |||
self.assertEqual(MoonPhaseType.WANING_CRESCENT, phase.phase_type) | |||
self.assertIsNone(phase.time) | |||
self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') | |||
def test_moon_phase_prediction(self): | |||
phase = MoonPhase('NEW_MOON', None, None) | |||
self.assertEqual('First Quarter', phase.get_next_phase_name()) | |||
phase = MoonPhase('WAXING_CRESCENT', None, None) | |||
self.assertEqual('First Quarter', phase.get_next_phase_name()) | |||
phase = MoonPhase('FIRST_QUARTER', None, None) | |||
self.assertEqual('Full Moon', phase.get_next_phase_name()) | |||
phase = MoonPhase('WAXING_GIBBOUS', None, None) | |||
self.assertEqual('Full Moon', phase.get_next_phase_name()) | |||
phase = MoonPhase('FULL_MOON', None, None) | |||
self.assertEqual('Last Quarter', phase.get_next_phase_name()) | |||
phase = MoonPhase('WANING_GIBBOUS', None, None) | |||
self.assertEqual('Last Quarter', phase.get_next_phase_name()) | |||
phase = MoonPhase('LAST_QUARTER', None, None) | |||
self.assertEqual('New Moon', phase.get_next_phase_name()) | |||
phase = MoonPhase('WANING_CRESCENT', None, None) | |||
self.assertEqual('New Moon', phase.get_next_phase_name()) | |||
phase = MoonPhase(MoonPhaseType.NEW_MOON, None, None) | |||
self.assertEqual(MoonPhaseType.FIRST_QUARTER, phase.get_next_phase()) | |||
phase = MoonPhase(MoonPhaseType.WAXING_CRESCENT, None, None) | |||
self.assertEqual(MoonPhaseType.FIRST_QUARTER, phase.get_next_phase()) | |||
phase = MoonPhase(MoonPhaseType.FIRST_QUARTER, None, None) | |||
self.assertEqual(MoonPhaseType.FULL_MOON, phase.get_next_phase()) | |||
phase = MoonPhase(MoonPhaseType.WAXING_GIBBOUS, None, None) | |||
self.assertEqual(MoonPhaseType.FULL_MOON, phase.get_next_phase()) | |||
phase = MoonPhase(MoonPhaseType.FULL_MOON, None, None) | |||
self.assertEqual(MoonPhaseType.LAST_QUARTER, phase.get_next_phase()) | |||
phase = MoonPhase(MoonPhaseType.WANING_GIBBOUS, None, None) | |||
self.assertEqual(MoonPhaseType.LAST_QUARTER, phase.get_next_phase()) | |||
phase = MoonPhase(MoonPhaseType.LAST_QUARTER, None, None) | |||
self.assertEqual(MoonPhaseType.NEW_MOON, phase.get_next_phase()) | |||
phase = MoonPhase(MoonPhaseType.WANING_CRESCENT, None, None) | |||
self.assertEqual(MoonPhaseType.NEW_MOON, phase.get_next_phase()) | |||
def test_get_ephemerides_raises_exception_on_out_of_date_range(self): | |||
with self.assertRaises(OutOfRangeDateError): | |||
@@ -1,12 +1,11 @@ | |||
import unittest | |||
from datetime import date, datetime | |||
from json import dumps | |||
from unittest_data_provider import data_provider | |||
from kosmorrolib import events | |||
from kosmorrolib.data import Event, ASTERS | |||
from kosmorrolib.core import get_timescale | |||
from unittest_data_provider import data_provider | |||
from kosmorrolib.enum import EventType | |||
from kosmorrolib.exceptions import OutOfRangeDateError | |||
@@ -14,42 +13,38 @@ class EventTestCase(unittest.TestCase): | |||
def setUp(self) -> None: | |||
self.maxDiff = None | |||
def test_event_only_accepts_valid_values(self): | |||
with self.assertRaises(ValueError): | |||
Event('SUPERNOVA', None, get_timescale().now()) | |||
expected_events_provider = lambda: ( | |||
(date(2020, 2, 7), []), | |||
(date(2020, 10, 13), [Event('OPPOSITION', [ASTERS[4]], datetime(2020, 10, 13, 23, 25))]), | |||
(date(2020, 10, 13), [Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2020, 10, 13, 23, 25))]), | |||
(date(2022, 12, 8), [Event('CONJUNCTION', [ASTERS[1], ASTERS[4]], datetime(2022, 12, 8, 4, 18)), | |||
Event('OPPOSITION', [ASTERS[4]], datetime(2022, 12, 8, 5, 41))]), | |||
(date(2022, 12, 8), [Event(EventType.CONJUNCTION, [ASTERS[1], ASTERS[4]], datetime(2022, 12, 8, 4, 18)), | |||
Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2022, 12, 8, 5, 41))]), | |||
(date(2025, 1, 16), [Event('OPPOSITION', [ASTERS[4]], datetime(2025, 1, 16, 2, 38))]), | |||
(date(2025, 1, 16), [Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2025, 1, 16, 2, 38))]), | |||
(date(2027, 2, 19), [Event('MOON_PERIGEE', [ASTERS[1]], datetime(2027, 2, 19, 7, 38)), | |||
Event('OPPOSITION', [ASTERS[4]], datetime(2027, 2, 19, 15, 50))]), | |||
(date(2027, 2, 19), [Event(EventType.MOON_PERIGEE, [ASTERS[1]], datetime(2027, 2, 19, 7, 38)), | |||
Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2027, 2, 19, 15, 50))]), | |||
(date(2020, 1, 2), [Event('MOON_APOGEE', [ASTERS[1]], datetime(2020, 1, 2, 1, 32)), | |||
Event('CONJUNCTION', [ASTERS[2], ASTERS[5]], datetime(2020, 1, 2, 16, 41))]), | |||
(date(2020, 1, 2), [Event(EventType.MOON_APOGEE, [ASTERS[1]], datetime(2020, 1, 2, 1, 32)), | |||
Event(EventType.CONJUNCTION, [ASTERS[2], ASTERS[5]], datetime(2020, 1, 2, 16, 41))]), | |||
(date(2020, 1, 12), [Event('CONJUNCTION', [ASTERS[2], ASTERS[6]], datetime(2020, 1, 12, 9, 51)), | |||
Event('CONJUNCTION', [ASTERS[2], ASTERS[9]], datetime(2020, 1, 12, 10, 13)), | |||
Event('CONJUNCTION', [ASTERS[6], ASTERS[9]], datetime(2020, 1, 12, 16, 57))]), | |||
(date(2020, 1, 12), [Event(EventType.CONJUNCTION, [ASTERS[2], ASTERS[6]], datetime(2020, 1, 12, 9, 51)), | |||
Event(EventType.CONJUNCTION, [ASTERS[2], ASTERS[9]], datetime(2020, 1, 12, 10, 13)), | |||
Event(EventType.CONJUNCTION, [ASTERS[6], ASTERS[9]], datetime(2020, 1, 12, 16, 57))]), | |||
(date(2020, 2, 10), [Event('MAXIMAL_ELONGATION', [ASTERS[2]], datetime(2020, 2, 10, 13, 46), details='18.2°'), | |||
Event('MOON_PERIGEE', [ASTERS[1]], datetime(2020, 2, 10, 20, 34))]), | |||
(date(2020, 2, 10), [Event(EventType.MAXIMAL_ELONGATION, [ASTERS[2]], datetime(2020, 2, 10, 13, 46), details='18.2°'), | |||
Event(EventType.MOON_PERIGEE, [ASTERS[1]], datetime(2020, 2, 10, 20, 34))]), | |||
(date(2020, 3, 24), [Event('MAXIMAL_ELONGATION', [ASTERS[2]], datetime(2020, 3, 24, 1, 56), details='27.8°'), | |||
Event('MOON_APOGEE', [ASTERS[1]], datetime(2020, 3, 24, 15, 39)), | |||
Event('MAXIMAL_ELONGATION', [ASTERS[3]], datetime(2020, 3, 24, 21, 58), details='46.1°')]), | |||
(date(2020, 3, 24), [Event(EventType.MAXIMAL_ELONGATION, [ASTERS[2]], datetime(2020, 3, 24, 1, 56), details='27.8°'), | |||
Event(EventType.MOON_APOGEE, [ASTERS[1]], datetime(2020, 3, 24, 15, 39)), | |||
Event(EventType.MAXIMAL_ELONGATION, [ASTERS[3]], datetime(2020, 3, 24, 21, 58), details='46.1°')]), | |||
(date(2005, 6, 16), [Event('OCCULTATION', [ASTERS[1], ASTERS[5]], datetime(2005, 6, 16, 6, 31))]), | |||
(date(2005, 6, 16), [Event(EventType.OCCULTATION, [ASTERS[1], ASTERS[5]], datetime(2005, 6, 16, 6, 31))]), | |||
(date(2020, 4, 7), [Event('MOON_PERIGEE', [ASTERS[1]], datetime(2020, 4, 7, 18, 14))]), | |||
(date(2020, 4, 7), [Event(EventType.MOON_PERIGEE, [ASTERS[1]], datetime(2020, 4, 7, 18, 14))]), | |||
(date(2020, 1, 29), [Event('MOON_APOGEE', [ASTERS[1]], datetime(2020, 1, 29, 21, 32))]) | |||
(date(2020, 1, 29), [Event(EventType.MOON_APOGEE, [ASTERS[1]], datetime(2020, 1, 29, 21, 32))]) | |||
) | |||
@data_provider(expected_events_provider) | |||