From d7730bd2ad5e1bd0d28de759c674e0c799c0bb06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Deuchnord?= Date: Sun, 16 Feb 2020 13:47:16 +0100 Subject: [PATCH] feat: add support for timezones --- .pylintrc | 3 +- .scripts/tests-e2e.sh | 2 + kosmorrolib/core.py | 4 +- kosmorrolib/data.py | 13 +++-- kosmorrolib/dumper.py | 94 ++++++++++++++++++++++++++------ kosmorrolib/ephemerides.py | 5 ++ kosmorrolib/events.py | 6 +- kosmorrolib/locales/messages.pot | 83 ++++++++++++++++------------ kosmorrolib/main.py | 6 +- test/dumper.py | 69 +++++++++++++++-------- test/ephemerides.py | 30 +++++----- test/events.py | 4 +- 12 files changed, 213 insertions(+), 106 deletions(-) diff --git a/.pylintrc b/.pylintrc index ce646ba..3632123 100644 --- a/.pylintrc +++ b/.pylintrc @@ -146,7 +146,8 @@ disable=print-statement, too-many-branches, too-few-public-methods, protected-access, - unnecessary-comprehension + unnecessary-comprehension, + too-many-arguments # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/.scripts/tests-e2e.sh b/.scripts/tests-e2e.sh index cf438c8..2f7e93e 100644 --- a/.scripts/tests-e2e.sh +++ b/.scripts/tests-e2e.sh @@ -79,6 +79,8 @@ assertSuccess "$PIP_BIN install dist/kosmorro-$VERSION.tar.gz" "CI" assertSuccess kosmorro assertSuccess "kosmorro --latitude=50.5876 --longitude=3.0624" assertSuccess "kosmorro --latitude=50.5876 --longitude=3.0624 -d 27 -m 1 -y 2020" +assertSuccess "kosmorro --latitude=50.5876 --longitude=3.0624 -d 27 -m 1 -y 2020 --timezone=1" +assertSuccess "kosmorro --latitude=50.5876 --longitude=3.0624 -d 27 -m 1 -y 2020 --timezone=-1" assertSuccess "kosmorro --latitude=50.5876 --longitude=3.0624 -d 27 -m 1 -y 2020 --format=json" assertFailure "kosmorro --latitude=50.5876 --longitude=3.0624 -d 27 -m 1 -y 2020 --format=pdf" # Missing dependencies, should fail diff --git a/kosmorrolib/core.py b/kosmorrolib/core.py index 4e52c58..7768fb1 100644 --- a/kosmorrolib/core.py +++ b/kosmorrolib/core.py @@ -95,7 +95,9 @@ def skyfield_to_moon_phase(times: [Time], vals: [int], now: Time) -> Union[MoonP next_phase_time = times[j] break - return MoonPhase(current_phase, current_phase_time, next_phase_time) + 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): diff --git a/kosmorrolib/data.py b/kosmorrolib/data.py index 07b3a3f..89d9fa3 100644 --- a/kosmorrolib/data.py +++ b/kosmorrolib/data.py @@ -18,9 +18,9 @@ from abc import ABC, abstractmethod from typing import Union +from datetime import datetime from skyfield.api import Topos -from skyfield.timelib import Time from .i18n import _ @@ -42,7 +42,7 @@ EVENTS = { class MoonPhase: - def __init__(self, identifier: str, time: Union[Time, None], next_phase_date: Union[Time, None]): + def __init__(self, identifier: str, time: Union[datetime, None], next_phase_date: Union[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)) @@ -87,9 +87,9 @@ class Position: class AsterEphemerides: def __init__(self, - rise_time: Union[Time, None], - culmination_time: Union[Time, None], - set_time: Union[Time, None]): + 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 @@ -141,7 +141,8 @@ class Satellite(Object): class Event: - def __init__(self, event_type: str, objects: [Object], start_time: Time, end_time: Union[Time, None] = None): + def __init__(self, event_type: str, objects: [Object], start_time: datetime, + end_time: Union[datetime, None] = 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()), diff --git a/kosmorrolib/dumper.py b/kosmorrolib/dumper.py index b7fe4c2..7a584cd 100644 --- a/kosmorrolib/dumper.py +++ b/kosmorrolib/dumper.py @@ -21,7 +21,6 @@ import datetime import json import os from tabulate import tabulate -from skyfield.timelib import Time from numpy import int64 from termcolor import colored from .data import Object, AsterEphemerides, MoonPhase, Event @@ -35,17 +34,52 @@ except ImportError: FULL_DATE_FORMAT = _('{day_of_week} {month} {day_number}, {year}').format(day_of_week='%A', month='%B', day_number='%d', year='%Y') +SHORT_DATETIME_FORMAT = _('{month} {day_number}, {hours}:{minutes}').format(month='%b', day_number='%d', + hours='%H', minutes='%M') TIME_FORMAT = _('{hours}:{minutes}').format(hours='%H', minutes='%M') class Dumper(ABC): - def __init__(self, ephemeris: dict, events: [Event], date: datetime.date = datetime.date.today(), + def __init__(self, ephemeris: dict, events: [Event], date: datetime.date = datetime.date.today(), timezone: int = 0, with_colors: bool = True): self.ephemeris = ephemeris self.events = events self.date = date + self.timezone = timezone self.with_colors = with_colors + if self.timezone != 0: + self._convert_dates_to_timezones() + + 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) + + for event in self.events: + event.start_time = self._datetime_to_timezone(event.start_time) + if event.end_time is not None: + event.end_time = self._datetime_to_timezone(event.end_time) + + def _datetime_to_timezone(self, time: datetime.datetime): + return time.replace(tzinfo=datetime.timezone.utc).astimezone( + tz=datetime.timezone( + datetime.timedelta( + hours=self.timezone + ) + ) + ) + def get_date_as_string(self, capitalized: bool = False) -> str: date = self.date.strftime(FULL_DATE_FORMAT) @@ -77,8 +111,8 @@ class JsonDumper(Dumper): # See https://stackoverflow.com/a/50577730 if isinstance(obj, int64): return int(obj) - if isinstance(obj, Time): - return obj.utc_iso() + if isinstance(obj, datetime.datetime): + return obj.isoformat() if isinstance(obj, Object): obj = obj.__dict__ obj.pop('skyfield_name') @@ -113,7 +147,14 @@ class TextDumper(Dumper): text.append('\n'.join([self.style(_('Expected events:'), 'h2'), self.get_events(self.events)])) - text.append(self.style(_('Note: All the hours are given in UTC.'), 'em')) + if self.timezone == 0: + text.append(self.style(_('Note: All the hours are given in UTC.'), 'em')) + else: + tz_offset = str(self.timezone) + if self.timezone > 0: + tz_offset = ''.join(['+', tz_offset]) + text.append(self.style(_('Note: All the hours are given in the UTC{offset} timezone.').format( + offset=tz_offset), 'em')) return '\n\n'.join(text) @@ -138,17 +179,21 @@ class TextDumper(Dumper): name = self.style(aster.name, 'th') if aster.ephemerides.rise_time is not None: - planet_rise = aster.ephemerides.rise_time.utc_strftime(TIME_FORMAT) + 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) else: planet_rise = '-' if aster.ephemerides.culmination_time is not None: - planet_culmination = aster.ephemerides.culmination_time.utc_strftime(TIME_FORMAT) + time_fmt = TIME_FORMAT if aster.ephemerides.culmination_time.day == self.date.day \ + else SHORT_DATETIME_FORMAT + planet_culmination = aster.ephemerides.culmination_time.strftime(time_fmt) else: planet_culmination = '-' if aster.ephemerides.set_time is not None: - planet_set = aster.ephemerides.set_time.utc_strftime(TIME_FORMAT) + 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) else: planet_set = '-' @@ -164,7 +209,8 @@ class TextDumper(Dumper): data = [] for event in events: - data.append([self.style(event.start_time.utc_strftime(TIME_FORMAT), 'th'), + time_fmt = TIME_FORMAT if event.start_time.day == self.date.day else SHORT_DATETIME_FORMAT + data.append([self.style(event.start_time.strftime(time_fmt), 'th'), event.get_description()]) return tabulate(data, tablefmt='plain', stralign='left') @@ -173,8 +219,8 @@ class TextDumper(Dumper): 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( next_moon_phase=moon_phase.get_next_phase(), - next_moon_phase_date=moon_phase.next_phase_date.utc_strftime(FULL_DATE_FORMAT), - next_moon_phase_time=moon_phase.next_phase_date.utc_strftime(TIME_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) ) return '\n'.join([current_moon_phase, new_moon_phase]) @@ -214,8 +260,11 @@ class _LatexDumper(Dumper): .replace('+++INTRODUCTION+++', '\n\n'.join([ _("This document summarizes the ephemerides and the events of {date}. " - "It aims to help you to prepare your observation session.").format( - date=self.get_date_as_string()), + "It aims to help you to prepare your observation session. " + "All the hours are given in {timezone}.").format( + date=self.get_date_as_string(), + timezone='UTC+%d timezone' % self.timezone if self.timezone != 0 else 'UTC' + ), _("Don't forget to check the weather forecast before you go out with your material.") ])) \ .replace('+++SECTION-EPHEMERIDES+++', _('Ephemerides of the day')) \ @@ -237,17 +286,21 @@ class _LatexDumper(Dumper): for aster in self.ephemeris['details']: if aster.ephemerides.rise_time is not None: - aster_rise = aster.ephemerides.rise_time.utc_strftime(TIME_FORMAT) + 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: - aster_culmination = aster.ephemerides.culmination_time.utc_strftime(TIME_FORMAT) + 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: - aster_set = aster.ephemerides.set_time.utc_strftime(TIME_FORMAT) + 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 = '-' @@ -262,7 +315,7 @@ class _LatexDumper(Dumper): latex = [] for event in self.events: - latex.append(r'\event{%s}{%s}' % (event.start_time.utc_strftime(TIME_FORMAT), + latex.append(r'\event{%s}{%s}' % (event.start_time.strftime(TIME_FORMAT), event.get_description())) return ''.join(latex) @@ -288,9 +341,14 @@ class _LatexDumper(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): try: - latex_dumper = _LatexDumper(self.ephemeris, self.events, self.date, self.with_colors) + latex_dumper = _LatexDumper(self.ephemeris, self.events, + date=self.date, timezone=self.timezone, with_colors=self.with_colors) return self._compile(latex_dumper.to_string()) except RuntimeError: raise UnavailableFeatureError(_("Building PDFs was not possible, because some dependencies are not" diff --git a/kosmorrolib/ephemerides.py b/kosmorrolib/ephemerides.py index aad30a2..c1e76c4 100644 --- a/kosmorrolib/ephemerides.py +++ b/kosmorrolib/ephemerides.py @@ -100,6 +100,11 @@ class EphemeridesComputer: 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) + aster.ephemerides = AsterEphemerides(rise_time, culmination_time, set_time) return aster diff --git a/kosmorrolib/events.py b/kosmorrolib/events.py index 1eea49b..fc5e258 100644 --- a/kosmorrolib/events.py +++ b/kosmorrolib/events.py @@ -57,7 +57,7 @@ def _search_conjunction(start_time: Time, end_time: Time) -> [Event]: times, _ = find_discrete(start_time, end_time, is_in_conjunction) for time in times: - conjunctions.append(Event('CONJUNCTION', [aster1, aster2], time)) + conjunctions.append(Event('CONJUNCTION', [aster1, aster2], time.utc_datetime())) computed.append(aster1) @@ -86,7 +86,7 @@ def _search_oppositions(start_time: Time, end_time: Time) -> [Event]: times, _ = find_discrete(start_time, end_time, is_oppositing) for time in times: - events.append(Event('OPPOSITION', [aster], time)) + events.append(Event('OPPOSITION', [aster], time.utc_datetime())) return events @@ -98,4 +98,4 @@ def search_events(date: date_type) -> [Event]: return sorted(flatten_list([ _search_oppositions(start_time, end_time), _search_conjunction(start_time, end_time) - ]), key=lambda event: event.start_time.utc_datetime()) + ]), key=lambda event: event.start_time) diff --git a/kosmorrolib/locales/messages.pot b/kosmorrolib/locales/messages.pot index 55e8970..d5a1268 100644 --- a/kosmorrolib/locales/messages.pot +++ b/kosmorrolib/locales/messages.pot @@ -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-04 20:45+0100\n" +"POT-Creation-Date: 2020-02-17 20:58+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -99,71 +99,80 @@ msgstr "" msgid "%s and %s are in conjunction" msgstr "" -#: kosmorrolib/dumper.py:36 +#: kosmorrolib/dumper.py:35 msgid "{day_of_week} {month} {day_number}, {year}" msgstr "" -#: kosmorrolib/dumper.py:38 +#: kosmorrolib/dumper.py:37 +msgid "{month} {day_number}, {hours}:{minutes}" +msgstr "" + +#: kosmorrolib/dumper.py:39 msgid "{hours}:{minutes}" msgstr "" -#: kosmorrolib/dumper.py:113 +#: kosmorrolib/dumper.py:147 msgid "Expected events:" msgstr "" -#: kosmorrolib/dumper.py:116 +#: kosmorrolib/dumper.py:151 msgid "Note: All the hours are given in UTC." msgstr "" -#: kosmorrolib/dumper.py:157 kosmorrolib/dumper.py:222 +#: kosmorrolib/dumper.py:156 +msgid "Note: All the hours are given in the UTC{offset} timezone." +msgstr "" + +#: kosmorrolib/dumper.py:202 kosmorrolib/dumper.py:271 msgid "Object" msgstr "" -#: kosmorrolib/dumper.py:158 kosmorrolib/dumper.py:223 +#: kosmorrolib/dumper.py:203 kosmorrolib/dumper.py:272 msgid "Rise time" msgstr "" -#: kosmorrolib/dumper.py:159 kosmorrolib/dumper.py:224 +#: kosmorrolib/dumper.py:204 kosmorrolib/dumper.py:273 msgid "Culmination time" msgstr "" -#: kosmorrolib/dumper.py:160 kosmorrolib/dumper.py:225 +#: kosmorrolib/dumper.py:205 kosmorrolib/dumper.py:274 msgid "Set time" msgstr "" -#: kosmorrolib/dumper.py:173 kosmorrolib/dumper.py:228 +#: kosmorrolib/dumper.py:219 kosmorrolib/dumper.py:277 msgid "Moon phase:" msgstr "" -#: kosmorrolib/dumper.py:174 +#: kosmorrolib/dumper.py:220 msgid "{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}" msgstr "" -#: kosmorrolib/dumper.py:212 +#: kosmorrolib/dumper.py:258 msgid "A Summary of your Sky" msgstr "" -#: kosmorrolib/dumper.py:216 +#: kosmorrolib/dumper.py:262 msgid "" "This document summarizes the ephemerides and the events of {date}. It " -"aims to help you to prepare your observation session." +"aims to help you to prepare your observation session. All the hours are " +"given in {timezone}." msgstr "" -#: kosmorrolib/dumper.py:219 +#: kosmorrolib/dumper.py:268 msgid "" "Don't forget to check the weather forecast before you go out with your " "material." msgstr "" -#: kosmorrolib/dumper.py:221 +#: kosmorrolib/dumper.py:270 msgid "Ephemerides of the day" msgstr "" -#: kosmorrolib/dumper.py:230 +#: kosmorrolib/dumper.py:279 msgid "Expected events" msgstr "" -#: kosmorrolib/dumper.py:296 +#: kosmorrolib/dumper.py:354 msgid "" "Building PDFs was not possible, because some dependencies are not " "installed.\n" @@ -184,83 +193,87 @@ msgid "" "the observation coordinate." msgstr "" -#: kosmorrolib/main.py:82 +#: kosmorrolib/main.py:84 msgid "Could not save the output in \"{path}\": {error}" msgstr "" -#: kosmorrolib/main.py:87 +#: kosmorrolib/main.py:89 msgid "Selected output format needs an output file (--output)." msgstr "" -#: kosmorrolib/main.py:104 +#: kosmorrolib/main.py:106 msgid "Running on Python {python_version}" msgstr "" -#: kosmorrolib/main.py:110 +#: kosmorrolib/main.py:112 msgid "Do you really want to clear Kosmorro's cache? [yN] " msgstr "" -#: kosmorrolib/main.py:117 +#: kosmorrolib/main.py:119 msgid "Answer did not match expected options, cache not cleared." msgstr "" -#: kosmorrolib/main.py:126 +#: kosmorrolib/main.py:128 msgid "" "Compute the ephemerides and the events for a given date, at a given " "position on Earth." msgstr "" -#: kosmorrolib/main.py:128 +#: kosmorrolib/main.py:130 msgid "" "By default, only the events will be computed for today ({date}).\n" "To compute also the ephemerides, latitude and longitude arguments are " "needed." msgstr "" -#: kosmorrolib/main.py:133 +#: kosmorrolib/main.py:135 msgid "Show the program version" msgstr "" -#: kosmorrolib/main.py:135 +#: kosmorrolib/main.py:137 msgid "Delete all the files Kosmorro stored in the cache." msgstr "" -#: kosmorrolib/main.py:137 +#: kosmorrolib/main.py:139 msgid "The format under which the information have to be output" msgstr "" -#: kosmorrolib/main.py:139 +#: kosmorrolib/main.py:141 msgid "The observer's latitude on Earth" msgstr "" -#: kosmorrolib/main.py:141 +#: kosmorrolib/main.py:143 msgid "The observer's longitude on Earth" msgstr "" -#: kosmorrolib/main.py:143 +#: kosmorrolib/main.py:145 msgid "" "A number between 1 and 28, 29, 30 or 31 (depending on the month). The day" " you want to compute the ephemerides for. Defaults to {default_day} (the" " current day)." msgstr "" -#: kosmorrolib/main.py:147 +#: kosmorrolib/main.py:149 msgid "" "A number between 1 and 12. The month you want to compute the ephemerides " "for. Defaults to {default_month} (the current month)." msgstr "" -#: kosmorrolib/main.py:150 +#: kosmorrolib/main.py:152 msgid "" "The year you want to compute the ephemerides for. Defaults to " "{default_year} (the current year)." msgstr "" -#: kosmorrolib/main.py:153 +#: kosmorrolib/main.py:155 +msgid "The timezone to display the hours in (e.g. 2 for UTC+2 or -3 for UTC-3)." +msgstr "" + +#: kosmorrolib/main.py:157 msgid "Disable the colors in the console." msgstr "" -#: kosmorrolib/main.py:155 +#: kosmorrolib/main.py:159 msgid "" "A file to export the output to. If not given, the standard output is " "used. This argument is needed for PDF format." diff --git a/kosmorrolib/main.py b/kosmorrolib/main.py index 2d316ba..88b2b34 100644 --- a/kosmorrolib/main.py +++ b/kosmorrolib/main.py @@ -68,7 +68,9 @@ def main(): events_list = events.search_events(compute_date) - selected_dumper = output_formats[args.format](ephemerides, events_list, compute_date, args.colors) + selected_dumper = output_formats[args.format](ephemerides, events_list, + date=compute_date, timezone=args.timezone, + with_colors=args.colors) output = selected_dumper.to_string() except UnavailableFeatureError as error: print(colored(error.msg, 'red')) @@ -149,6 +151,8 @@ def get_args(output_formats: [str]): parser.add_argument('--year', '-y', type=int, default=today.year, help=_('The year you want to compute the ephemerides for.' ' Defaults to {default_year} (the current year).').format(default_year=today.year)) + parser.add_argument('--timezone', '-t', type=int, default=0, + help=_('The timezone to display the hours in (e.g. 2 for UTC+2 or -3 for UTC-3).')) parser.add_argument('--no-colors', dest='colors', action='store_false', help=_('Disable the colors in the console.')) parser.add_argument('--output', '-o', type=str, default=None, diff --git a/test/dumper.py b/test/dumper.py index 791368e..92ecb54 100644 --- a/test/dumper.py +++ b/test/dumper.py @@ -1,9 +1,8 @@ import unittest -from datetime import date +from datetime import date, datetime from kosmorrolib.data import AsterEphemerides, Planet, MoonPhase, Event from kosmorrolib.dumper import JsonDumper, TextDumper, _LatexDumper -from kosmorrolib.core import get_timescale class DumperTestCase(unittest.TestCase): @@ -11,12 +10,11 @@ class DumperTestCase(unittest.TestCase): self.maxDiff = None def test_json_dumper_returns_correct_json(self): - data = self._get_data() self.assertEqual('{\n' ' "moon_phase": {\n' - ' "next_phase_date": "2019-10-21T00:00:00Z",\n' + ' "next_phase_date": "2019-10-21T00:00:00",\n' ' "phase": "FULL_MOON",\n' - ' "date": "2019-10-14T00:00:00Z"\n' + ' "date": "2019-10-14T00:00:00"\n' ' },\n' ' "events": [\n' ' {\n' @@ -24,7 +22,7 @@ class DumperTestCase(unittest.TestCase): ' "objects": [\n' ' "Mars"\n' ' ],\n' - ' "start_time": "2018-07-27T05:12:00Z",\n' + ' "start_time": "2019-10-14T23:00:00",\n' ' "end_time": null\n' ' }\n' ' ],\n' @@ -38,16 +36,14 @@ class DumperTestCase(unittest.TestCase): ' }\n' ' }\n' ' ]\n' - '}', JsonDumper(data, - self._get_events() - ).to_string()) + '}', JsonDumper(self._get_data(), self._get_events()).to_string()) data = self._get_data(aster_rise_set=True) self.assertEqual('{\n' ' "moon_phase": {\n' - ' "next_phase_date": "2019-10-21T00:00:00Z",\n' + ' "next_phase_date": "2019-10-21T00:00:00",\n' ' "phase": "FULL_MOON",\n' - ' "date": "2019-10-14T00:00:00Z"\n' + ' "date": "2019-10-14T00:00:00"\n' ' },\n' ' "events": [\n' ' {\n' @@ -55,7 +51,7 @@ class DumperTestCase(unittest.TestCase): ' "objects": [\n' ' "Mars"\n' ' ],\n' - ' "start_time": "2018-07-27T05:12:00Z",\n' + ' "start_time": "2019-10-14T23:00:00",\n' ' "end_time": null\n' ' }\n' ' ],\n' @@ -63,9 +59,9 @@ class DumperTestCase(unittest.TestCase): ' {\n' ' "object": "Mars",\n' ' "details": {\n' - ' "rise_time": "2019-10-14T08:00:00Z",\n' - ' "culmination_time": "2019-10-14T13:00:00Z",\n' - ' "set_time": "2019-10-14T23:00:00Z"\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' @@ -103,7 +99,7 @@ class DumperTestCase(unittest.TestCase): 'Moon phase: Full Moon\n' 'Last Quarter on Monday October 21, 2019 at 00:00\n\n' 'Expected events:\n' - '05:12 Mars is in opposition\n\n' + '23:00 Mars is in opposition\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()) @@ -113,10 +109,35 @@ class DumperTestCase(unittest.TestCase): 'Moon phase: Full Moon\n' 'Last Quarter on Monday October 21, 2019 at 00:00\n\n' 'Expected events:\n' - '05:12 Mars is in opposition\n\n' + '23:00 Mars is in opposition\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_timezone_is_taken_in_account(self): + ephemerides = self._get_data(aster_rise_set=True) + self.assertEqual('Monday October 14, 2019\n\n' + 'Object Rise time Culmination time Set time\n' + '-------- ----------- ------------------ -------------\n' + 'Mars 09:00 14:00 Oct 15, 00:00\n\n' + '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' + '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()) + + ephemerides = self._get_data(aster_rise_set=True) + self.assertEqual('Monday October 14, 2019\n\n' + 'Object Rise time Culmination time Set time\n' + '-------- ----------- ------------------ ----------\n' + 'Mars 07:00 12:00 22:00\n\n' + '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' + '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()) + def test_latex_dumper(self): latex = _LatexDumper(self._get_data(), self._get_events(), date=date(2019, 10, 14)).to_string() self.assertRegex(latex, 'Monday October 14, 2019') @@ -124,7 +145,7 @@ class DumperTestCase(unittest.TestCase): self.assertRegex(latex, r'\\section{\\sffamily Expected events}') self.assertRegex(latex, r'\\section{\\sffamily Ephemerides of the day}') self.assertRegex(latex, r'\\object\{Mars\}\{-\}\{-\}\{-\}') - self.assertRegex(latex, r'\\event\{05:12\}\{Mars is in opposition\}') + self.assertRegex(latex, r'\\event\{23:00\}\{Mars is in opposition\}') latex = _LatexDumper(self._get_data(aster_rise_set=True), self._get_events(), date=date(2019, 10, 14)).to_string() @@ -135,7 +156,7 @@ class DumperTestCase(unittest.TestCase): self.assertRegex(latex, 'Monday October 14, 2019') self.assertRegex(latex, 'Full Moon') self.assertRegex(latex, r'\\section{\\sffamily Expected events}') - self.assertRegex(latex, r'\\event\{05:12\}\{Mars is in opposition\}') + self.assertRegex(latex, r'\\event\{23:00\}\{Mars is in opposition\}') self.assertNotRegex(latex, r'\\object\{Mars\}\{-\}\{-\}\{-\}') self.assertNotRegex(latex, r'\\section{\\sffamily Ephemerides of the day}') @@ -151,12 +172,12 @@ class DumperTestCase(unittest.TestCase): @staticmethod def _get_data(has_ephemerides: bool = True, aster_rise_set=False): - rise_time = get_timescale().utc(2019, 10, 14, 8) if aster_rise_set else None - culmination_time = get_timescale().utc(2019, 10, 14, 13) if aster_rise_set else None - set_time = get_timescale().utc(2019, 10, 14, 23) 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 + set_time = datetime(2019, 10, 14, 23) if aster_rise_set else None return { - 'moon_phase': MoonPhase('FULL_MOON', get_timescale().utc(2019, 10, 14), get_timescale().utc(2019, 10, 21)), + '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 [] } @@ -165,7 +186,7 @@ class DumperTestCase(unittest.TestCase): def _get_events(): return [Event('OPPOSITION', [Planet('Mars', 'MARS')], - get_timescale().utc(2018, 7, 27, 5, 12)) + datetime(2019, 10, 14, 23, 00)) ] diff --git a/test/ephemerides.py b/test/ephemerides.py index 63b4c89..06731c4 100644 --- a/test/ephemerides.py +++ b/test/ephemerides.py @@ -13,9 +13,9 @@ class EphemeridesComputerTestCase(unittest.TestCase): date=date(2019, 11, 18), position=position) - self.assertEqual('2019-11-18T05:41:31Z', star.ephemerides.rise_time.utc_iso()) - self.assertEqual('2019-11-18T11:45:02Z', star.ephemerides.culmination_time.utc_iso()) - self.assertEqual('2019-11-18T17:48:39Z', star.ephemerides.set_time.utc_iso()) + self.assertEqual('2019-11-18T05:41:30+00:00', star.ephemerides.rise_time.isoformat()) + self.assertEqual('2019-11-18T11:45:02+00:00', star.ephemerides.culmination_time.isoformat()) + self.assertEqual('2019-11-18T17:48:39+00:00', star.ephemerides.set_time.isoformat()) ################################################################################################################### ### MOON PHASE TESTS ### @@ -25,61 +25,61 @@ class EphemeridesComputerTestCase(unittest.TestCase): phase = EphemeridesComputer.get_moon_phase(2019, 11, 25) self.assertEqual('WANING_CRESCENT', phase.identifier) self.assertIsNone(phase.time) - self.assertRegexpMatches(phase.next_phase_date.utc_iso(), '^2019-11-26T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') phase = EphemeridesComputer.get_moon_phase(2019, 11, 26) self.assertEqual('NEW_MOON', phase.identifier) - self.assertRegexpMatches(phase.next_phase_date.utc_iso(), '^2019-12-04T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-12-04T') phase = EphemeridesComputer.get_moon_phase(2019, 11, 27) self.assertEqual('WAXING_CRESCENT', phase.identifier) self.assertIsNone(phase.time) - self.assertRegexpMatches(phase.next_phase_date.utc_iso(), '^2019-12-04T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-12-04T') def test_moon_phase_first_crescent(self): phase = EphemeridesComputer.get_moon_phase(2019, 11, 3) self.assertEqual('WAXING_CRESCENT', phase.identifier) self.assertIsNone(phase.time) - self.assertRegexpMatches(phase.next_phase_date.utc_iso(), '^2019-11-04T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-04T') phase = EphemeridesComputer.get_moon_phase(2019, 11, 4) self.assertEqual('FIRST_QUARTER', phase.identifier) - self.assertRegexpMatches(phase.next_phase_date.utc_iso(), '^2019-11-12T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') phase = EphemeridesComputer.get_moon_phase(2019, 11, 5) self.assertEqual('WAXING_GIBBOUS', phase.identifier) self.assertIsNone(phase.time) - self.assertRegexpMatches(phase.next_phase_date.utc_iso(), '^2019-11-12T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') def test_moon_phase_full_moon(self): phase = EphemeridesComputer.get_moon_phase(2019, 11, 11) self.assertEqual('WAXING_GIBBOUS', phase.identifier) self.assertIsNone(phase.time) - self.assertRegexpMatches(phase.next_phase_date.utc_iso(), '^2019-11-12T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') phase = EphemeridesComputer.get_moon_phase(2019, 11, 12) self.assertEqual('FULL_MOON', phase.identifier) - self.assertRegexpMatches(phase.next_phase_date.utc_iso(), '^2019-11-19T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') phase = EphemeridesComputer.get_moon_phase(2019, 11, 13) self.assertEqual('WANING_GIBBOUS', phase.identifier) self.assertIsNone(phase.time) - self.assertRegexpMatches(phase.next_phase_date.utc_iso(), '^2019-11-19T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') def test_moon_phase_last_quarter(self): phase = EphemeridesComputer.get_moon_phase(2019, 11, 18) self.assertEqual('WANING_GIBBOUS', phase.identifier) self.assertIsNone(phase.time) - self.assertRegexpMatches(phase.next_phase_date.utc_iso(), '^2019-11-19T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') phase = EphemeridesComputer.get_moon_phase(2019, 11, 19) self.assertEqual('LAST_QUARTER', phase.identifier) - self.assertRegexpMatches(phase.next_phase_date.utc_iso(), '^2019-11-26T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') phase = EphemeridesComputer.get_moon_phase(2019, 11, 20) self.assertEqual('WANING_CRESCENT', phase.identifier) self.assertIsNone(phase.time) - self.assertRegexpMatches(phase.next_phase_date.utc_iso(), '^2019-11-26T') + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') def test_moon_phase_prediction(self): phase = MoonPhase('NEW_MOON', None, None) diff --git a/test/events.py b/test/events.py index a56c8ec..60ff43b 100644 --- a/test/events.py +++ b/test/events.py @@ -24,7 +24,7 @@ class MyTestCase(unittest.TestCase): self.assertEqual(1, len(o), 'Expected 1 event for %s, got %d' % (expected_date, len(o))) self.assertEqual('OPPOSITION', o[0].event_type) self.assertEqual('MARS', o[0].objects[0].skyfield_name) - self.assertRegex(o[0].start_time.utc_iso(), expected_date) + self.assertRegex(o[0].start_time.isoformat(), expected_date) self.assertIsNone(o[0].end_time) self.assertEqual('Mars is in opposition', o[0].get_description()) @@ -45,7 +45,7 @@ class MyTestCase(unittest.TestCase): objects, expected_date = expected_dates[i] j = 0 - self.assertRegex(conjunction.start_time.utc_iso(), expected_date) + self.assertRegex(conjunction.start_time.isoformat(), expected_date) for object in objects: self.assertEqual(object, conjunction.objects[j].skyfield_name) j += 1