@@ -109,7 +109,6 @@ def get_date(date_arg: str) -> date: | |||
months = get_offset(date_arg, 'm') | |||
years = get_offset(date_arg, 'y') | |||
if date_arg[0] == '+': | |||
return date.today() + relativedelta(days=days, months=months, years=years) | |||
return date.today() - relativedelta(days=days, months=months, years=years) | |||
@@ -0,0 +1,28 @@ | |||
#!/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 datetime import datetime, timezone, timedelta | |||
def translate_to_timezone(date: datetime, to_tz: int, from_tz: int = None): | |||
if from_tz is not None: | |||
source_tz = timezone(timedelta(hours=from_tz)) | |||
else: | |||
source_tz = timezone.utc | |||
return date.replace(tzinfo=source_tz).astimezone(tz=timezone(timedelta(hours=to_tz))) |
@@ -52,39 +52,6 @@ class Dumper(ABC): | |||
self.with_colors = with_colors | |||
self.show_graph = show_graph | |||
if self.timezone != 0: | |||
self._convert_dates_to_timezones() | |||
def _convert_dates_to_timezones(self): | |||
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: | |||
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) | |||
@@ -401,10 +368,6 @@ class _LatexDumper(Dumper): | |||
class PdfDumper(Dumper): | |||
def _convert_dates_to_timezones(self): | |||
"""This method is disabled in this dumper, because the timezone is already converted | |||
in :class:`_LatexDumper`.""" | |||
def to_string(self): | |||
try: | |||
latex_dumper = _LatexDumper(self.ephemerides, self.moon_phase, self.events, | |||
@@ -23,6 +23,7 @@ from skyfield.timelib import Time | |||
from skyfield.constants import tau | |||
from .data import Position, AsterEphemerides, MoonPhase, Object, ASTERS, skyfield_to_moon_phase | |||
from .dateutil import translate_to_timezone | |||
from .core import get_skf_objects, get_timescale, get_iau2000b | |||
RISEN_ANGLE = -0.8333 | |||
@@ -51,7 +52,7 @@ def get_moon_phase(compute_date: datetime.date) -> MoonPhase: | |||
return skyfield_to_moon_phase(times, phase, today) | |||
def get_ephemerides(date: datetime.date, position: Position) -> [AsterEphemerides]: | |||
def get_ephemerides(date: datetime.date, position: Position, timezone: int = 0) -> [AsterEphemerides]: | |||
ephemerides = [] | |||
def get_angle(for_aster: Object): | |||
@@ -67,8 +68,8 @@ def get_ephemerides(date: datetime.date, position: Position) -> [AsterEphemeride | |||
fun.rough_period = 0.5 | |||
return fun | |||
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) | |||
start_time = get_timescale().utc(date.year, date.month, date.day, -timezone) | |||
end_time = get_timescale().utc(date.year, date.month, date.day, 23 - timezone, 59, 59) | |||
for aster in ASTERS: | |||
rise_times, arr = find_discrete(start_time, end_time, is_risen(aster)) | |||
@@ -87,13 +88,16 @@ def get_ephemerides(date: datetime.date, position: Position) -> [AsterEphemeride | |||
# Convert the Time instances to Python datetime objects | |||
if rise_time is not None: | |||
rise_time = rise_time.utc_datetime().replace(microsecond=0) | |||
rise_time = translate_to_timezone(rise_time.utc_datetime().replace(microsecond=0), | |||
to_tz=timezone) | |||
if culmination_time is not None: | |||
culmination_time = culmination_time.utc_datetime().replace(microsecond=0) | |||
culmination_time = translate_to_timezone(culmination_time.utc_datetime().replace(microsecond=0), | |||
to_tz=timezone) | |||
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 = translate_to_timezone(set_time.utc_datetime().replace(microsecond=0), | |||
to_tz=timezone) | |||
ephemerides.append(AsterEphemerides(rise_time, culmination_time, set_time, aster=aster)) | |||
@@ -23,10 +23,11 @@ from skyfield.searchlib import find_discrete, find_maxima | |||
from numpy import pi | |||
from .data import Event, Star, Planet, ASTERS | |||
from .dateutil import translate_to_timezone | |||
from .core import get_timescale, get_skf_objects, flatten_list | |||
def _search_conjunction(start_time: Time, end_time: Time) -> [Event]: | |||
def _search_conjunction(start_time: Time, end_time: Time, timezone: int) -> [Event]: | |||
earth = get_skf_objects()['earth'] | |||
aster1 = None | |||
aster2 = None | |||
@@ -65,16 +66,18 @@ def _search_conjunction(start_time: Time, end_time: Time) -> [Event]: | |||
aster2] if aster1_pos.distance().km < aster2_pos.distance().km else [aster2, | |||
aster1] | |||
conjunctions.append(Event('OCCULTATION', occulting_aster, time.utc_datetime())) | |||
conjunctions.append(Event('OCCULTATION', occulting_aster, | |||
translate_to_timezone(time.utc_datetime(), timezone))) | |||
else: | |||
conjunctions.append(Event('CONJUNCTION', [aster1, aster2], time.utc_datetime())) | |||
conjunctions.append(Event('CONJUNCTION', [aster1, aster2], | |||
translate_to_timezone(time.utc_datetime(), timezone))) | |||
computed.append(aster1) | |||
return conjunctions | |||
def _search_oppositions(start_time: Time, end_time: Time) -> [Event]: | |||
def _search_oppositions(start_time: Time, end_time: Time, timezone: int) -> [Event]: | |||
earth = get_skf_objects()['earth'] | |||
sun = get_skf_objects()['sun'] | |||
aster = None | |||
@@ -96,12 +99,12 @@ 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.utc_datetime())) | |||
events.append(Event('OPPOSITION', [aster], translate_to_timezone(time.utc_datetime(), timezone))) | |||
return events | |||
def _search_maximal_elongations(start_time: Time, end_time: Time) -> [Event]: | |||
def _search_maximal_elongations(start_time: Time, end_time: Time, timezone: int) -> [Event]: | |||
earth = get_skf_objects()['earth'] | |||
sun = get_skf_objects()['sun'] | |||
aster = None | |||
@@ -124,18 +127,18 @@ def _search_maximal_elongations(start_time: Time, end_time: Time) -> [Event]: | |||
for i, time in enumerate(times): | |||
elongation = elongations[i] | |||
events.append(Event('MAXIMAL_ELONGATION', [aster], time.utc_datetime(), | |||
events.append(Event('MAXIMAL_ELONGATION', [aster], translate_to_timezone(time.utc_datetime(), timezone), | |||
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) | |||
def search_events(date: date_type, timezone: int = 0) -> [Event]: | |||
start_time = get_timescale().utc(date.year, date.month, date.day, -timezone) | |||
end_time = get_timescale().utc(date.year, date.month, date.day + 1, -timezone) | |||
return sorted(flatten_list([ | |||
_search_oppositions(start_time, end_time), | |||
_search_conjunction(start_time, end_time), | |||
_search_maximal_elongations(start_time, end_time) | |||
_search_oppositions(start_time, end_time, timezone), | |||
_search_conjunction(start_time, end_time, timezone), | |||
_search_maximal_elongations(start_time, end_time, timezone) | |||
]), key=lambda event: event.start_time) |
@@ -8,7 +8,7 @@ msgid "" | |||
msgstr "" | |||
"Project-Id-Version: kosmorro 0.8.0\n" | |||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | |||
"POT-Creation-Date: 2020-05-22 10:17+0200\n" | |||
"POT-Creation-Date: 2020-06-02 20:49+0200\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" | |||
@@ -21,7 +21,7 @@ msgstr "" | |||
msgid "The date {date} is not valid: {error}" | |||
msgstr "" | |||
#: kosmorrolib/core.py:118 | |||
#: kosmorrolib/core.py:117 | |||
msgid "" | |||
"The date {date} does not match the required YYYY-MM-DD format or the " | |||
"offset format." | |||
@@ -131,72 +131,72 @@ msgstr "" | |||
msgid "{hours}:{minutes}" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:153 | |||
#: kosmorrolib/dumper.py:120 | |||
msgid "Expected events:" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:157 | |||
#: kosmorrolib/dumper.py:124 | |||
msgid "Note: All the hours are given in UTC." | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:162 | |||
#: kosmorrolib/dumper.py:129 | |||
msgid "Note: All the hours are given in the UTC{offset} timezone." | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:208 kosmorrolib/dumper.py:288 | |||
#: kosmorrolib/dumper.py:175 kosmorrolib/dumper.py:255 | |||
msgid "Object" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:209 kosmorrolib/dumper.py:289 | |||
#: kosmorrolib/dumper.py:176 kosmorrolib/dumper.py:256 | |||
msgid "Rise time" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:210 kosmorrolib/dumper.py:290 | |||
#: kosmorrolib/dumper.py:177 kosmorrolib/dumper.py:257 | |||
msgid "Culmination time" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:211 kosmorrolib/dumper.py:291 | |||
#: kosmorrolib/dumper.py:178 kosmorrolib/dumper.py:258 | |||
msgid "Set time" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:225 kosmorrolib/dumper.py:295 | |||
#: kosmorrolib/dumper.py:192 kosmorrolib/dumper.py:262 | |||
msgid "Moon phase:" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:226 | |||
#: kosmorrolib/dumper.py:193 | |||
msgid "{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:275 | |||
#: kosmorrolib/dumper.py:242 | |||
msgid "A Summary of your Sky" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:279 | |||
#: kosmorrolib/dumper.py:246 | |||
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:285 | |||
#: kosmorrolib/dumper.py:252 | |||
msgid "" | |||
"Don't forget to check the weather forecast before you go out with your " | |||
"equipment." | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:287 | |||
#: kosmorrolib/dumper.py:254 | |||
msgid "Ephemerides of the day" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:293 | |||
#: kosmorrolib/dumper.py:260 | |||
msgid "hours" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:297 | |||
#: kosmorrolib/dumper.py:264 | |||
msgid "Expected events" | |||
msgstr "" | |||
#: kosmorrolib/dumper.py:415 | |||
#: kosmorrolib/dumper.py:378 | |||
msgid "" | |||
"Building PDFs was not possible, because some dependencies are not " | |||
"installed.\n" | |||
@@ -217,87 +217,87 @@ msgid "" | |||
"the observation coordinate." | |||
msgstr "" | |||
#: kosmorrolib/main.py:94 | |||
#: kosmorrolib/main.py:98 | |||
msgid "Could not save the output in \"{path}\": {error}" | |||
msgstr "" | |||
#: kosmorrolib/main.py:99 | |||
#: kosmorrolib/main.py:103 | |||
msgid "Selected output format needs an output file (--output)." | |||
msgstr "" | |||
#: kosmorrolib/main.py:116 | |||
#: kosmorrolib/main.py:120 | |||
msgid "Running on Python {python_version}" | |||
msgstr "" | |||
#: kosmorrolib/main.py:122 | |||
#: kosmorrolib/main.py:126 | |||
msgid "Do you really want to clear Kosmorro's cache? [yN] " | |||
msgstr "" | |||
#: kosmorrolib/main.py:129 | |||
#: kosmorrolib/main.py:133 | |||
msgid "Answer did not match expected options, cache not cleared." | |||
msgstr "" | |||
#: kosmorrolib/main.py:138 | |||
#: kosmorrolib/main.py:142 | |||
msgid "" | |||
"Compute the ephemerides and the events for a given date, at a given " | |||
"position on Earth." | |||
msgstr "" | |||
#: kosmorrolib/main.py:140 | |||
#: kosmorrolib/main.py:144 | |||
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:145 | |||
#: kosmorrolib/main.py:149 | |||
msgid "Show the program version" | |||
msgstr "" | |||
#: kosmorrolib/main.py:147 | |||
#: kosmorrolib/main.py:151 | |||
msgid "Delete all the files Kosmorro stored in the cache." | |||
msgstr "" | |||
#: kosmorrolib/main.py:149 | |||
#: kosmorrolib/main.py:153 | |||
msgid "The format under which the information have to be output" | |||
msgstr "" | |||
#: kosmorrolib/main.py:151 | |||
#: kosmorrolib/main.py:155 | |||
msgid "" | |||
"The observer's latitude on Earth. Can also be set in the " | |||
"KOSMORRO_LATITUDE environment variable." | |||
msgstr "" | |||
#: kosmorrolib/main.py:154 | |||
#: kosmorrolib/main.py:158 | |||
msgid "" | |||
"The observer's longitude on Earth. Can also be set in the " | |||
"KOSMORRO_LONGITUDE environment variable." | |||
msgstr "" | |||
#: kosmorrolib/main.py:157 | |||
#: kosmorrolib/main.py:161 | |||
msgid "" | |||
"The date for which the ephemerides must be computed (in the YYYY-MM-DD " | |||
"format), or as an interval in the \"[+-]YyMmDd\" format (with Y, M, and D" | |||
" numbers). Defaults to the current date ({default_date})" | |||
msgstr "" | |||
#: kosmorrolib/main.py:162 | |||
#: kosmorrolib/main.py:166 | |||
msgid "" | |||
"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." | |||
msgstr "" | |||
#: kosmorrolib/main.py:165 | |||
#: kosmorrolib/main.py:169 | |||
msgid "Disable the colors in the console." | |||
msgstr "" | |||
#: kosmorrolib/main.py:167 | |||
#: kosmorrolib/main.py:171 | |||
msgid "" | |||
"A file to export the output to. If not given, the standard output is " | |||
"used. This argument is needed for PDF format." | |||
msgstr "" | |||
#: kosmorrolib/main.py:170 | |||
#: kosmorrolib/main.py:174 | |||
msgid "" | |||
"Do not generate a graph to represent the rise and set times in the PDF " | |||
"format." | |||
@@ -66,11 +66,6 @@ def main(): | |||
"coordinate."), 'yellow')) | |||
try: | |||
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) | |||
timezone = args.timezone | |||
if timezone is None and environment.timezone is not None: | |||
@@ -78,6 +73,15 @@ def main(): | |||
elif timezone is None: | |||
timezone = 0 | |||
if position is not None: | |||
eph = ephemerides.get_ephemerides(date=compute_date, position=position, timezone=timezone) | |||
else: | |||
eph = None | |||
moon_phase = ephemerides.get_moon_phase(compute_date) | |||
events_list = events.search_events(compute_date, timezone) | |||
format_dumper = output_formats[output_format](ephemerides=eph, moon_phase=moon_phase, events=events_list, | |||
date=compute_date, timezone=timezone, with_colors=args.colors, | |||
show_graph=args.show_graph) | |||
@@ -4,3 +4,4 @@ from .dumper import * | |||
from .ephemerides import * | |||
from .events import * | |||
from .testutils import * | |||
from .dateutil import * |
@@ -0,0 +1,24 @@ | |||
import unittest | |||
from kosmorrolib import dateutil | |||
from datetime import datetime | |||
class DateUtilTestCase(unittest.TestCase): | |||
def test_translate_to_timezone(self): | |||
date = dateutil.translate_to_timezone(datetime(2020, 6, 9, 4), to_tz=-2) | |||
self.assertEqual(2, date.hour) | |||
date = dateutil.translate_to_timezone(datetime(2020, 6, 9, 0), to_tz=2) | |||
self.assertEqual(2, date.hour) | |||
date = dateutil.translate_to_timezone(datetime(2020, 6, 9, 8), to_tz=2, from_tz=6) | |||
self.assertEqual(4, date.hour) | |||
date = dateutil.translate_to_timezone(datetime(2020, 6, 9, 1), to_tz=0, from_tz=2) | |||
self.assertEqual(8, date.day) | |||
self.assertEqual(23, date.hour) | |||
if __name__ == '__main__': | |||
unittest.main() |
@@ -159,37 +159,6 @@ class DumperTestCase(unittest.TestCase): | |||
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): | |||
ephemerides = self._get_ephemerides(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' | |||
"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_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) | |||
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' | |||
"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_moon_phase(), self._get_events(), date=date(2019, 10, 14), | |||
with_colors=False, timezone=-1).to_string()) | |||
def test_latex_dumper(self): | |||
latex = _LatexDumper(self._get_ephemerides(), self._get_moon_phase(), self._get_events(), | |||
date=date(2019, 10, 14)).to_string() | |||