@@ -6,8 +6,9 @@ | |||
\usepackage{graphicx} | |||
\usepackage{hyperref} | |||
% Fix non-break spaces issues | |||
% Fix Unicode issues | |||
\DeclareUnicodeCharacter{202F}{~} | |||
\DeclareUnicodeCharacter{00B0}{$^\circ$} | |||
\hypersetup{pdfinfo={ | |||
Title={+++DOCUMENT-TITLE+++}, | |||
@@ -18,30 +18,13 @@ | |||
from shutil import rmtree | |||
from pathlib import Path | |||
from typing import Union | |||
from skyfield.api import Loader | |||
from skyfield.timelib import Time | |||
from skyfield.nutationlib import iau2000b | |||
from .data import Star, Planet, Satellite, MOON_PHASES, MoonPhase | |||
from .i18n import _ | |||
CACHE_FOLDER = str(Path.home()) + '/.kosmorro-cache' | |||
MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] | |||
ASTERS = [Star(_('Sun'), 'SUN'), | |||
Satellite(_('Moon'), 'MOON'), | |||
Planet(_('Mercury'), 'MERCURY'), | |||
Planet(_('Venus'), 'VENUS'), | |||
Planet(_('Mars'), 'MARS'), | |||
Planet(_('Jupiter'), 'JUPITER BARYCENTER'), | |||
Planet(_('Saturn'), 'SATURN BARYCENTER'), | |||
Planet(_('Uranus'), 'URANUS BARYCENTER'), | |||
Planet(_('Neptune'), 'NEPTUNE BARYCENTER'), | |||
Planet(_('Pluto'), 'PLUTO BARYCENTER')] | |||
def get_loader(): | |||
return Loader(CACHE_FOLDER) | |||
@@ -63,43 +46,6 @@ def clear_cache(): | |||
rmtree(CACHE_FOLDER) | |||
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) | |||
def flatten_list(the_list: list): | |||
new_list = [] | |||
for item in the_list: | |||
@@ -20,8 +20,10 @@ from abc import ABC, abstractmethod | |||
from typing import Union | |||
from datetime import datetime | |||
from skyfield.api import Topos | |||
from skyfield.api import Topos, Time | |||
from skyfield.vectorlib import VectorSum as SkfPlanet | |||
from .core import get_skf_objects, get_timescale | |||
from .i18n import _ | |||
MOON_PHASES = { | |||
@@ -37,7 +39,8 @@ MOON_PHASES = { | |||
EVENTS = { | |||
'OPPOSITION': {'message': _('%s is in opposition')}, | |||
'CONJUNCTION': {'message': _('%s and %s are in conjunction')} | |||
'CONJUNCTION': {'message': _('%s and %s are in conjunction')}, | |||
'MAXIMAL_ELONGATION': {'message': _("%s's largest elongation")} | |||
} | |||
@@ -115,6 +118,9 @@ class Object(ABC): | |||
self.skyfield_name = skyfield_name | |||
self.ephemerides = ephemerides | |||
def get_skyfield_object(self) -> SkfPlanet: | |||
return get_skf_objects()[self.skyfield_name] | |||
@abstractmethod | |||
def get_type(self) -> str: | |||
pass | |||
@@ -142,23 +148,77 @@ class Satellite(Object): | |||
class Event: | |||
def __init__(self, event_type: str, objects: [Object], start_time: datetime, | |||
end_time: Union[datetime, None] = None): | |||
end_time: Union[datetime, None] = None, details: str = None): | |||
if event_type not in EVENTS.keys(): | |||
raise ValueError('event_type parameter must be one of the following: %s (got %s)' % ( | |||
', '.join(EVENTS.keys()), | |||
event_type) | |||
) | |||
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 | |||
self.end_time = end_time | |||
self.details = details | |||
def get_description(self) -> str: | |||
return EVENTS[self.event_type]['message'] % self._get_objects_name() | |||
def get_description(self, show_details: bool = True) -> str: | |||
description = EVENTS[self.event_type]['message'] % self._get_objects_name() | |||
if show_details and self.details is not None: | |||
description += ' ({:s})'.format(self.details) | |||
return description | |||
def _get_objects_name(self): | |||
if len(self.objects) == 1: | |||
return self.objects[0].name | |||
return tuple(object.name for object in self.objects) | |||
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) | |||
MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] | |||
ASTERS = [Star(_('Sun'), 'SUN'), | |||
Satellite(_('Moon'), 'MOON'), | |||
Planet(_('Mercury'), 'MERCURY'), | |||
Planet(_('Venus'), 'VENUS'), | |||
Planet(_('Mars'), 'MARS'), | |||
Planet(_('Jupiter'), 'JUPITER BARYCENTER'), | |||
Planet(_('Saturn'), 'SATURN BARYCENTER'), | |||
Planet(_('Uranus'), 'URANUS BARYCENTER'), | |||
Planet(_('Neptune'), 'NEPTUNE BARYCENTER'), | |||
Planet(_('Pluto'), 'PLUTO BARYCENTER')] |
@@ -24,8 +24,8 @@ from skyfield.searchlib import find_discrete, find_maxima | |||
from skyfield.timelib import Time | |||
from skyfield.constants import tau | |||
from .data import Object, Position, AsterEphemerides, MoonPhase | |||
from .core import get_skf_objects, get_timescale, get_iau2000b, ASTERS, MONTHS, skyfield_to_moon_phase | |||
from .data import Object, Position, AsterEphemerides, MoonPhase, ASTERS, MONTHS, skyfield_to_moon_phase | |||
from .core import get_skf_objects, get_timescale, get_iau2000b | |||
RISEN_ANGLE = -0.8333 | |||
@@ -88,6 +88,7 @@ class EphemeridesComputer: | |||
rise_times, arr = find_discrete(start_time, end_time, is_risen) | |||
try: | |||
culmination_time, _ = find_maxima(start_time, end_time, f=get_angle, epsilon=1./3600/24, num=12) | |||
culmination_time = culmination_time[0] if len(culmination_time) > 0 else None | |||
except ValueError: | |||
culmination_time = None | |||
@@ -98,12 +99,15 @@ class EphemeridesComputer: | |||
rise_time = rise_times[0] if arr[0] else None | |||
set_time = rise_times[0] if not arr[0] else None | |||
culmination_time = culmination_time[0] if culmination_time is not None else None | |||
# Convert the Time instances to Python datetime objects | |||
rise_time = rise_time.utc_datetime().replace(microsecond=0) | |||
culmination_time = culmination_time.utc_datetime().replace(microsecond=0) | |||
set_time = set_time.utc_datetime().replace(microsecond=0) | |||
if rise_time is not None: | |||
rise_time = rise_time.utc_datetime().replace(microsecond=0) | |||
if culmination_time is not None: | |||
culmination_time = culmination_time.utc_datetime().replace(microsecond=0) | |||
if set_time is not 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 | |||
@@ -19,10 +19,10 @@ | |||
from datetime import date as date_type | |||
from skyfield.timelib import Time | |||
from skyfield.almanac import find_discrete | |||
from skyfield.searchlib import find_discrete, find_maxima | |||
from .data import Event, Planet | |||
from .core import get_timescale, get_skf_objects, ASTERS, flatten_list | |||
from .data import Event, Planet, ASTERS | |||
from .core import get_timescale, get_skf_objects, flatten_list | |||
def _search_conjunction(start_time: Time, end_time: Time) -> [Event]: | |||
@@ -91,11 +91,41 @@ def _search_oppositions(start_time: Time, end_time: Time) -> [Event]: | |||
return events | |||
def _search_maximal_elongations(start_time: Time, end_time: Time) -> [Event]: | |||
earth = get_skf_objects()['earth'] | |||
sun = get_skf_objects()['sun'] | |||
aster = None | |||
def get_elongation(time: Time): | |||
sun_pos = (sun - earth).at(time) | |||
aster_pos = (aster.get_skyfield_object() - earth).at(time) | |||
separation = sun_pos.separation_from(aster_pos) | |||
return separation.degrees | |||
get_elongation.rough_period = 1.0 | |||
events = [] | |||
for aster in ASTERS: | |||
if aster.skyfield_name not in ['MERCURY', 'VENUS']: | |||
continue | |||
times, elongations = find_maxima(start_time, end_time, f=get_elongation, epsilon=1./24/3600, num=12) | |||
for i, time in enumerate(times): | |||
elongation = elongations[i] | |||
events.append(Event('MAXIMAL_ELONGATION', [aster], time.utc_datetime(), | |||
details='{:.3n}°'.format(elongation))) | |||
return events | |||
def search_events(date: date_type) -> [Event]: | |||
start_time = get_timescale().utc(date.year, date.month, date.day) | |||
end_time = get_timescale().utc(date.year, date.month, date.day + 1) | |||
return sorted(flatten_list([ | |||
_search_oppositions(start_time, end_time), | |||
_search_conjunction(start_time, end_time) | |||
_search_conjunction(start_time, end_time), | |||
_search_maximal_elongations(start_time, end_time) | |||
]), key=lambda event: event.start_time) |
@@ -8,7 +8,7 @@ msgid "" | |||
msgstr "" | |||
"Project-Id-Version: kosmorro 0.5.2\n" | |||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | |||
"POT-Creation-Date: 2020-02-17 20:58+0100\n" | |||
"POT-Creation-Date: 2020-02-21 20:14+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" | |||
@@ -17,86 +17,91 @@ msgstr "" | |||
"Content-Transfer-Encoding: 8bit\n" | |||
"Generated-By: Babel 2.8.0\n" | |||
#: kosmorrolib/core.py:34 | |||
msgid "Sun" | |||
#: kosmorrolib/data.py:30 | |||
msgid "New Moon" | |||
msgstr "" | |||
#: kosmorrolib/core.py:35 | |||
msgid "Moon" | |||
#: kosmorrolib/data.py:31 | |||
msgid "Waxing crescent" | |||
msgstr "" | |||
#: kosmorrolib/core.py:36 | |||
msgid "Mercury" | |||
#: kosmorrolib/data.py:32 | |||
msgid "First Quarter" | |||
msgstr "" | |||
#: kosmorrolib/core.py:37 | |||
msgid "Venus" | |||
#: kosmorrolib/data.py:33 | |||
msgid "Waxing gibbous" | |||
msgstr "" | |||
#: kosmorrolib/core.py:38 | |||
msgid "Mars" | |||
#: kosmorrolib/data.py:34 | |||
msgid "Full Moon" | |||
msgstr "" | |||
#: kosmorrolib/core.py:39 | |||
msgid "Jupiter" | |||
#: kosmorrolib/data.py:35 | |||
msgid "Waning gibbous" | |||
msgstr "" | |||
#: kosmorrolib/core.py:40 | |||
msgid "Saturn" | |||
#: kosmorrolib/data.py:36 | |||
msgid "Last Quarter" | |||
msgstr "" | |||
#: kosmorrolib/core.py:41 | |||
msgid "Uranus" | |||
#: kosmorrolib/data.py:37 | |||
msgid "Waning crescent" | |||
msgstr "" | |||
#: kosmorrolib/core.py:42 | |||
msgid "Neptune" | |||
#: kosmorrolib/data.py:41 | |||
#, python-format | |||
msgid "%s is in opposition" | |||
msgstr "" | |||
#: kosmorrolib/core.py:43 | |||
msgid "Pluto" | |||
#: kosmorrolib/data.py:42 | |||
#, python-format | |||
msgid "%s and %s are in conjunction" | |||
msgstr "" | |||
#: kosmorrolib/data.py:28 | |||
msgid "New Moon" | |||
#: kosmorrolib/data.py:43 | |||
#, python-format | |||
msgid "%s's largest elongation" | |||
msgstr "" | |||
#: kosmorrolib/data.py:29 | |||
msgid "Waxing crescent" | |||
#: kosmorrolib/data.py:215 | |||
msgid "Sun" | |||
msgstr "" | |||
#: kosmorrolib/data.py:30 | |||
msgid "First Quarter" | |||
#: kosmorrolib/data.py:216 | |||
msgid "Moon" | |||
msgstr "" | |||
#: kosmorrolib/data.py:31 | |||
msgid "Waxing gibbous" | |||
#: kosmorrolib/data.py:217 | |||
msgid "Mercury" | |||
msgstr "" | |||
#: kosmorrolib/data.py:32 | |||
msgid "Full Moon" | |||
#: kosmorrolib/data.py:218 | |||
msgid "Venus" | |||
msgstr "" | |||
#: kosmorrolib/data.py:33 | |||
msgid "Waning gibbous" | |||
#: kosmorrolib/data.py:219 | |||
msgid "Mars" | |||
msgstr "" | |||
#: kosmorrolib/data.py:34 | |||
msgid "Last Quarter" | |||
#: kosmorrolib/data.py:220 | |||
msgid "Jupiter" | |||
msgstr "" | |||
#: kosmorrolib/data.py:35 | |||
msgid "Waning crescent" | |||
#: kosmorrolib/data.py:221 | |||
msgid "Saturn" | |||
msgstr "" | |||
#: kosmorrolib/data.py:39 | |||
#, python-format | |||
msgid "%s is in opposition" | |||
#: kosmorrolib/data.py:222 | |||
msgid "Uranus" | |||
msgstr "" | |||
#: kosmorrolib/data.py:40 | |||
#, python-format | |||
msgid "%s and %s are in conjunction" | |||
#: kosmorrolib/data.py:223 | |||
msgid "Neptune" | |||
msgstr "" | |||
#: kosmorrolib/data.py:224 | |||
msgid "Pluto" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:35 | |||
@@ -23,7 +23,17 @@ class DumperTestCase(unittest.TestCase): | |||
' "Mars"\n' | |||
' ],\n' | |||
' "start_time": "2019-10-14T23:00:00",\n' | |||
' "end_time": null\n' | |||
' "end_time": null,\n' | |||
' "details": null\n' | |||
' },\n' | |||
' {\n' | |||
' "event_type": "MAXIMAL_ELONGATION",\n' | |||
' "objects": [\n' | |||
' "Venus"\n' | |||
' ],\n' | |||
' "start_time": "2019-10-14T12:00:00",\n' | |||
' "end_time": null,\n' | |||
' "details": "42.0\\u00b0"\n' | |||
' }\n' | |||
' ],\n' | |||
' "ephemerides": [\n' | |||
@@ -52,7 +62,17 @@ class DumperTestCase(unittest.TestCase): | |||
' "Mars"\n' | |||
' ],\n' | |||
' "start_time": "2019-10-14T23:00:00",\n' | |||
' "end_time": null\n' | |||
' "end_time": null,\n' | |||
' "details": null\n' | |||
' },\n' | |||
' {\n' | |||
' "event_type": "MAXIMAL_ELONGATION",\n' | |||
' "objects": [\n' | |||
' "Venus"\n' | |||
' ],\n' | |||
' "start_time": "2019-10-14T12:00:00",\n' | |||
' "end_time": null,\n' | |||
' "details": "42.0\\u00b0"\n' | |||
' }\n' | |||
' ],\n' | |||
' "ephemerides": [\n' | |||
@@ -92,15 +112,16 @@ class DumperTestCase(unittest.TestCase): | |||
def test_text_dumper_with_events(self): | |||
ephemerides = self._get_data() | |||
self.assertEqual('Monday October 14, 2019\n\n' | |||
'Object Rise time Culmination time Set time\n' | |||
'-------- ----------- ------------------ ----------\n' | |||
'Mars - - -\n\n' | |||
'Moon phase: Full Moon\n' | |||
'Last Quarter on Monday October 21, 2019 at 00:00\n\n' | |||
'Expected events:\n' | |||
'23:00 Mars is in opposition\n\n' | |||
'Note: All the hours are given in UTC.', | |||
self.assertEqual("Monday October 14, 2019\n\n" | |||
"Object Rise time Culmination time Set time\n" | |||
"-------- ----------- ------------------ ----------\n" | |||
"Mars - - -\n\n" | |||
"Moon phase: Full Moon\n" | |||
"Last Quarter on Monday October 21, 2019 at 00:00\n\n" | |||
"Expected events:\n" | |||
"23:00 Mars is in opposition\n" | |||
"12:00 Venus's largest elongation (42.0°)\n\n" | |||
"Note: All the hours are given in UTC.", | |||
TextDumper(ephemerides, self._get_events(), date=date(2019, 10, 14), with_colors=False).to_string()) | |||
def test_text_dumper_without_ephemerides_and_with_events(self): | |||
@@ -109,7 +130,8 @@ class DumperTestCase(unittest.TestCase): | |||
'Moon phase: Full Moon\n' | |||
'Last Quarter on Monday October 21, 2019 at 00:00\n\n' | |||
'Expected events:\n' | |||
'23:00 Mars is in opposition\n\n' | |||
'23:00 Mars is in opposition\n' | |||
"12:00 Venus's largest elongation (42.0°)\n\n" | |||
'Note: All the hours are given in UTC.', | |||
TextDumper(ephemerides, self._get_events(), date=date(2019, 10, 14), with_colors=False).to_string()) | |||
@@ -122,7 +144,8 @@ class DumperTestCase(unittest.TestCase): | |||
'Moon phase: Full Moon\n' | |||
'Last Quarter on Monday October 21, 2019 at 01:00\n\n' | |||
'Expected events:\n' | |||
'Oct 15, 00:00 Mars is in opposition\n\n' | |||
'Oct 15, 00:00 Mars is in opposition\n' | |||
"13:00 Venus's largest elongation (42.0°)\n\n" | |||
'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()) | |||
@@ -134,7 +157,8 @@ class DumperTestCase(unittest.TestCase): | |||
'Moon phase: Full Moon\n' | |||
'Last Quarter on Sunday October 20, 2019 at 23:00\n\n' | |||
'Expected events:\n' | |||
'22:00 Mars is in opposition\n\n' | |||
'22:00 Mars is in opposition\n' | |||
"11:00 Venus's largest elongation (42.0°)\n\n" | |||
'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()) | |||
@@ -146,6 +170,7 @@ class DumperTestCase(unittest.TestCase): | |||
self.assertRegex(latex, r'\\section{\\sffamily Ephemerides of the day}') | |||
self.assertRegex(latex, r'\\object\{Mars\}\{-\}\{-\}\{-\}') | |||
self.assertRegex(latex, r'\\event\{23:00\}\{Mars is in opposition\}') | |||
self.assertRegex(latex, r"\\event\{12:00\}\{Venus's largest elongation \(42.0°\)\}") | |||
latex = _LatexDumper(self._get_data(aster_rise_set=True), | |||
self._get_events(), date=date(2019, 10, 14)).to_string() | |||
@@ -157,6 +182,7 @@ class DumperTestCase(unittest.TestCase): | |||
self.assertRegex(latex, 'Full Moon') | |||
self.assertRegex(latex, r'\\section{\\sffamily Expected events}') | |||
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.assertNotRegex(latex, r'\\object\{Mars\}\{-\}\{-\}\{-\}') | |||
self.assertNotRegex(latex, r'\\section{\\sffamily Ephemerides of the day}') | |||
@@ -186,7 +212,10 @@ class DumperTestCase(unittest.TestCase): | |||
def _get_events(): | |||
return [Event('OPPOSITION', | |||
[Planet('Mars', 'MARS')], | |||
datetime(2019, 10, 14, 23, 00)) | |||
datetime(2019, 10, 14, 23, 00)), | |||
Event('MAXIMAL_ELONGATION', | |||
[Planet('Venus', 'VENUS')], | |||
datetime(2019, 10, 14, 12, 00), details='42.0°'), | |||
] | |||
@@ -55,6 +55,33 @@ class MyTestCase(unittest.TestCase): | |||
i += 1 | |||
def test_find_maximal_elongation(self): | |||
e = events.search_events(date(2020, 2, 10)) | |||
self.assertEquals(1, len(e), 'Expected 1 events, got %d.' % len(e)) | |||
e = e[0] | |||
self.assertEquals('MAXIMAL_ELONGATION', e.event_type) | |||
self.assertEquals(1, len(e.objects)) | |||
self.assertEquals('MERCURY', e.objects[0].skyfield_name) | |||
self.assertEqual('18.2°', e.details) | |||
self.assertEquals((2020, 2, 10, 13, 46), (e.start_time.year, e.start_time.month, e.start_time.day, | |||
e.start_time.hour, e.start_time.minute)) | |||
e = events.search_events(date(2020, 3, 24)) | |||
self.assertEquals(2, len(e), 'Expected 2 events, got %d.' % len(e)) | |||
self.assertEquals('MAXIMAL_ELONGATION', e[0].event_type) | |||
self.assertEquals(1, len(e[0].objects)) | |||
self.assertEquals('MERCURY', e[0].objects[0].skyfield_name) | |||
self.assertEqual('27.8°', e[0].details) | |||
self.assertEquals((2020, 3, 24, 1, 56), (e[0].start_time.year, e[0].start_time.month, e[0].start_time.day, | |||
e[0].start_time.hour, e[0].start_time.minute)) | |||
self.assertEquals('MAXIMAL_ELONGATION', e[1].event_type) | |||
self.assertEquals(1, len(e[1].objects)) | |||
self.assertEquals('VENUS', e[1].objects[0].skyfield_name) | |||
self.assertEqual('46.1°', e[1].details) | |||
self.assertEquals((2020, 3, 24, 21, 58), (e[1].start_time.year, e[1].start_time.month, e[1].start_time.day, | |||
e[1].start_time.hour, e[1].start_time.minute)) | |||
if __name__ == '__main__': | |||
unittest.main() |