diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index 48872fd..24bc50b 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -20,5 +20,5 @@ jobs:
env:
COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
run: |
- pipenv run python -m coverage run -m unittest test
+ make test
COVERALLS_REPO_TOKEN=$COVERALLS_TOKEN pipenv run coveralls
diff --git a/.scripts/tests-e2e.sh b/.scripts/tests-e2e.sh
index b64e84a..3c35f9f 100755
--- a/.scripts/tests-e2e.sh
+++ b/.scripts/tests-e2e.sh
@@ -81,6 +81,8 @@ assertSuccess "kosmorro -h"
assertSuccess "kosmorro -d 2020-01-27"
assertFailure "kosmorro -d yolo-yo-lo"
assertFailure "kosmorro -d 2020-13-32"
+assertFailure "kosmorro --date=1789-05-05"
+assertFailure "kosmorro --date=3000-01-01"
assertSuccess "kosmorro --date='+3y 5m3d'"
assertSuccess "kosmorro --date='-1y3d'"
assertFailure "kosmorro --date='+3d4m"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 76a790e..4806fe9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -80,7 +80,7 @@ Kosmorro's unit tests use Python's official `unittest` module. They live in the
You can also run them by invoking the following command:
```shell
-pipenv run python -m unittest test
+make test
```
Note: there are currently some memory leaks in the unit tests, making the result quite difficult to read. I am working to fix this.
diff --git a/Makefile b/Makefile
index 00398ba..d6ed8b7 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,11 @@
+.PHONY: test
+
+test:
+ unset KOSMORRO_LATITUDE; \
+ unset KOSMORRO_LONGITUDE; \
+ unset KOSMORRO_TIMEZONE; \
+ LANG=C pipenv run python3 -m coverage run -m unittest test
+
build:
ronn --roff manpage/kosmorro.1.md
ronn --roff manpage/kosmorro.7.md
diff --git a/Pipfile b/Pipfile
index 0074f6c..b0a4c55 100644
--- a/Pipfile
+++ b/Pipfile
@@ -11,7 +11,7 @@ unittest-data-provider = "*"
coveralls = "*"
[packages]
-skyfield = ">=1.13.0,<2.0.0"
+skyfield = ">=1.21.0,<2.0.0"
tabulate = "*"
numpy = ">=1.17.0,<2.0.0"
termcolor = "*"
diff --git a/Pipfile.lock b/Pipfile.lock
index 84b2b70..7371fe8 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "afd17771258a86b04cab79d21d2afbfb1faf44bbe6e760da9140e72b0999a5b1"
+ "sha256": "9ed5ee6bbfde75ee77c89fdc09a793f5c00f9782968dc310e1eb8d3386378d9e"
},
"pipfile-spec": 6,
"requires": {
diff --git a/kosmorrolib/assets/moonphases/png/unknown.png b/kosmorrolib/assets/moonphases/png/unknown.png
new file mode 100644
index 0000000..deff19a
Binary files /dev/null and b/kosmorrolib/assets/moonphases/png/unknown.png differ
diff --git a/kosmorrolib/assets/moonphases/svg/unknown.svg b/kosmorrolib/assets/moonphases/svg/unknown.svg
new file mode 100644
index 0000000..3adc8c4
--- /dev/null
+++ b/kosmorrolib/assets/moonphases/svg/unknown.svg
@@ -0,0 +1,31 @@
+
+
diff --git a/kosmorrolib/data.py b/kosmorrolib/data.py
index 6a66157..3d3de80 100644
--- a/kosmorrolib/data.py
+++ b/kosmorrolib/data.py
@@ -36,7 +36,8 @@ MOON_PHASES = {
'FULL_MOON': _('Full Moon'),
'WANING_GIBBOUS': _('Waning gibbous'),
'LAST_QUARTER': _('Last Quarter'),
- 'WANING_CRESCENT': _('Waning crescent')
+ 'WANING_CRESCENT': _('Waning crescent'),
+ 'UNKNOWN': _('Unavailable')
}
EVENTS = {
@@ -54,7 +55,7 @@ class Serializable(ABC):
class MoonPhase(Serializable):
- def __init__(self, identifier: str, time: Union[datetime, None], next_phase_date: Union[datetime, None]):
+ 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))
diff --git a/kosmorrolib/dumper.py b/kosmorrolib/dumper.py
index a21e9b1..53d8701 100644
--- a/kosmorrolib/dumper.py
+++ b/kosmorrolib/dumper.py
@@ -25,7 +25,7 @@ from tabulate import tabulate
from numpy import int64
from termcolor import colored
from .data import ASTERS, Object, AsterEphemerides, MoonPhase, Event
-from .i18n import _
+from .i18n import _, FULL_DATE_FORMAT, SHORT_DATETIME_FORMAT, TIME_FORMAT
from .version import VERSION
from .exceptions import UnavailableFeatureError
try:
@@ -33,12 +33,6 @@ try:
except ImportError:
build_pdf = None
-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, ephemerides: [AsterEphemerides] = None, moon_phase: MoonPhase = None, events: [Event] = None,
@@ -60,6 +54,9 @@ class Dumper(ABC):
return date
+ def __str__(self):
+ return self.to_string()
+
@abstractmethod
def to_string(self):
pass
@@ -189,6 +186,9 @@ class TextDumper(Dumper):
return tabulate(data, tablefmt='plain', stralign='left')
def get_moon(self, moon_phase: MoonPhase) -> str:
+ 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()])
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(),
@@ -212,6 +212,10 @@ class _LatexDumper(Dumper):
def _make_document(self, template: str) -> str:
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('_', '-'),
diff --git a/kosmorrolib/ephemerides.py b/kosmorrolib/ephemerides.py
index ec50b88..33642a7 100644
--- a/kosmorrolib/ephemerides.py
+++ b/kosmorrolib/ephemerides.py
@@ -21,15 +21,17 @@ import datetime
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 .dateutil import translate_to_timezone
from .core import get_skf_objects, get_timescale, get_iau2000b
+from .exceptions import OutOfRangeDateError
RISEN_ANGLE = -0.8333
-def get_moon_phase(compute_date: datetime.date) -> MoonPhase:
+def get_moon_phase(compute_date: datetime.date, timezone: int = 0) -> MoonPhase:
earth = get_skf_objects()['earth']
moon = get_skf_objects()['moon']
sun = get_skf_objects()['sun']
@@ -47,7 +49,16 @@ def get_moon_phase(compute_date: datetime.date) -> MoonPhase:
time1 = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day - 10)
time2 = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day + 10)
- times, phase = find_discrete(time1, time2, moon_phase_at)
+ try:
+ times, phase = find_discrete(time1, time2, moon_phase_at)
+ except EphemerisRangeError as error:
+ start = translate_to_timezone(error.start_time.utc_datetime(), timezone)
+ end = translate_to_timezone(error.end_time.utc_datetime(), timezone)
+
+ start = datetime.date(start.year, start.month, start.day) + datetime.timedelta(days=12)
+ end = datetime.date(end.year, end.month, end.day) - datetime.timedelta(days=12)
+
+ raise OutOfRangeDateError(start, end)
return skyfield_to_moon_phase(times, phase, today)
@@ -71,34 +82,43 @@ def get_ephemerides(date: datetime.date, position: Position, timezone: int = 0)
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))
- try:
- culmination_time, _ = find_maxima(start_time, end_time, f=get_angle(aster), epsilon=1./3600/24, num=12)
- culmination_time = culmination_time[0] if len(culmination_time) > 0 else None
- except ValueError:
- culmination_time = None
-
- if len(rise_times) == 2:
- rise_time = rise_times[0 if arr[0] else 1]
- set_time = rise_times[1 if not arr[1] else 0]
- else:
- rise_time = rise_times[0] if arr[0] else None
- set_time = rise_times[0] if not arr[0] else None
-
- # Convert the Time instances to Python datetime objects
- if rise_time is not None:
- rise_time = translate_to_timezone(rise_time.utc_datetime().replace(microsecond=0),
- to_tz=timezone)
-
- if culmination_time is not None:
- culmination_time = translate_to_timezone(culmination_time.utc_datetime().replace(microsecond=0),
- to_tz=timezone)
-
- if set_time is not 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))
+ try:
+ for aster in ASTERS:
+ rise_times, arr = find_discrete(start_time, end_time, is_risen(aster))
+ try:
+ culmination_time, _ = find_maxima(start_time, end_time, f=get_angle(aster), epsilon=1./3600/24, num=12)
+ culmination_time = culmination_time[0] if len(culmination_time) > 0 else None
+ except ValueError:
+ culmination_time = None
+
+ if len(rise_times) == 2:
+ rise_time = rise_times[0 if arr[0] else 1]
+ set_time = rise_times[1 if not arr[1] else 0]
+ else:
+ rise_time = rise_times[0] if arr[0] else None
+ set_time = rise_times[0] if not arr[0] else None
+
+ # Convert the Time instances to Python datetime objects
+ if rise_time is not None:
+ rise_time = translate_to_timezone(rise_time.utc_datetime().replace(microsecond=0),
+ to_tz=timezone)
+
+ if culmination_time is not None:
+ culmination_time = translate_to_timezone(culmination_time.utc_datetime().replace(microsecond=0),
+ to_tz=timezone)
+
+ if set_time is not 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))
+ except EphemerisRangeError as error:
+ start = translate_to_timezone(error.start_time.utc_datetime(), timezone)
+ end = translate_to_timezone(error.end_time.utc_datetime(), timezone)
+
+ start = datetime.date(start.year, start.month, start.day + 1)
+ end = datetime.date(end.year, end.month, end.day - 1)
+
+ raise OutOfRangeDateError(start, end)
return ephemerides
diff --git a/kosmorrolib/events.py b/kosmorrolib/events.py
index d7f49a2..cfbf87a 100644
--- a/kosmorrolib/events.py
+++ b/kosmorrolib/events.py
@@ -18,12 +18,14 @@
from datetime import date as date_type
+from skyfield.errors import EphemerisRangeError
from skyfield.timelib import Time
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 .exceptions import OutOfRangeDateError
from .core import get_timescale, get_skf_objects, flatten_list
@@ -137,8 +139,17 @@ 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, timezone),
- _search_conjunction(start_time, end_time, timezone),
- _search_maximal_elongations(start_time, end_time, timezone)
- ]), key=lambda event: event.start_time)
+ try:
+ return sorted(flatten_list([
+ _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)
+ except EphemerisRangeError as error:
+ start_date = translate_to_timezone(error.start_time.utc_datetime(), timezone)
+ end_date = translate_to_timezone(error.end_time.utc_datetime(), timezone)
+
+ start_date = date_type(start_date.year, start_date.month, start_date.day)
+ end_date = date_type(end_date.year, end_date.month, end_date.day)
+
+ raise OutOfRangeDateError(start_date, end_date)
diff --git a/kosmorrolib/exceptions.py b/kosmorrolib/exceptions.py
index d6a87d8..9467d38 100644
--- a/kosmorrolib/exceptions.py
+++ b/kosmorrolib/exceptions.py
@@ -16,8 +16,21 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+from datetime import date
+from .i18n import _, SHORT_DATE_FORMAT
+
class UnavailableFeatureError(RuntimeError):
def __init__(self, msg: str):
super(UnavailableFeatureError, self).__init__()
self.msg = msg
+
+
+class OutOfRangeDateError(RuntimeError):
+ def __init__(self, min_date: date, max_date: date):
+ super(OutOfRangeDateError, self).__init__()
+ self.min_date = min_date
+ self.max_date = max_date
+ self.msg = _('The date must be between {minimum_date}'
+ ' and {maximum_date}').format(minimum_date=min_date.strftime(SHORT_DATE_FORMAT),
+ maximum_date=max_date.strftime(SHORT_DATE_FORMAT))
diff --git a/kosmorrolib/i18n.py b/kosmorrolib/i18n.py
index b5d09b4..d0257b1 100644
--- a/kosmorrolib/i18n.py
+++ b/kosmorrolib/i18n.py
@@ -24,6 +24,13 @@ _TRANSLATION = gettext.translation('messages', localedir=_LOCALE_DIR, fallback=T
_ = _TRANSLATION.gettext
+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')
+SHORT_DATE_FORMAT = _('{month} {day_number}, {year}').format(month='%b', day_number='%d', year='%Y')
+TIME_FORMAT = _('{hours}:{minutes}').format(hours='%H', minutes='%M')
+
def ngettext(msgid1, msgid2, number):
# Not using ngettext = _TRANSLATION.ngettext because the linter will give an invalid-name error otherwise
diff --git a/kosmorrolib/locales/messages.pot b/kosmorrolib/locales/messages.pot
index 71f240c..6d609db 100644
--- a/kosmorrolib/locales/messages.pot
+++ b/kosmorrolib/locales/messages.pot
@@ -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-06-02 20:49+0200\n"
+"POT-Creation-Date: 2020-06-06 16:43+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -59,107 +59,103 @@ msgstr ""
msgid "Waning crescent"
msgstr ""
-#: kosmorrolib/data.py:43
+#: kosmorrolib/data.py:40
+msgid "Unavailable"
+msgstr ""
+
+#: kosmorrolib/data.py:44
#, python-format
msgid "%s is in opposition"
msgstr ""
-#: kosmorrolib/data.py:44
+#: kosmorrolib/data.py:45
#, python-format
msgid "%s and %s are in conjunction"
msgstr ""
-#: kosmorrolib/data.py:45
+#: kosmorrolib/data.py:46
#, python-format
msgid "%s occults %s"
msgstr ""
-#: kosmorrolib/data.py:46
+#: kosmorrolib/data.py:47
#, python-format
msgid "%s's largest elongation"
msgstr ""
-#: kosmorrolib/data.py:261
+#: kosmorrolib/data.py:262
msgid "Sun"
msgstr ""
-#: kosmorrolib/data.py:262
+#: kosmorrolib/data.py:263
msgid "Moon"
msgstr ""
-#: kosmorrolib/data.py:263
+#: kosmorrolib/data.py:264
msgid "Mercury"
msgstr ""
-#: kosmorrolib/data.py:264
+#: kosmorrolib/data.py:265
msgid "Venus"
msgstr ""
-#: kosmorrolib/data.py:265
+#: kosmorrolib/data.py:266
msgid "Mars"
msgstr ""
-#: kosmorrolib/data.py:266
+#: kosmorrolib/data.py:267
msgid "Jupiter"
msgstr ""
-#: kosmorrolib/data.py:267
+#: kosmorrolib/data.py:268
msgid "Saturn"
msgstr ""
-#: kosmorrolib/data.py:268
+#: kosmorrolib/data.py:269
msgid "Uranus"
msgstr ""
-#: kosmorrolib/data.py:269
+#: kosmorrolib/data.py:270
msgid "Neptune"
msgstr ""
-#: kosmorrolib/data.py:270
+#: kosmorrolib/data.py:271
msgid "Pluto"
msgstr ""
-#: kosmorrolib/dumper.py:36
-msgid "{day_of_week} {month} {day_number}, {year}"
-msgstr ""
-
-#: kosmorrolib/dumper.py:38
-msgid "{month} {day_number}, {hours}:{minutes}"
-msgstr ""
-
-#: kosmorrolib/dumper.py:40
-msgid "{hours}:{minutes}"
-msgstr ""
-
-#: kosmorrolib/dumper.py:120
+#: kosmorrolib/dumper.py:117
msgid "Expected events:"
msgstr ""
-#: kosmorrolib/dumper.py:124
+#: kosmorrolib/dumper.py:121
msgid "Note: All the hours are given in UTC."
msgstr ""
-#: kosmorrolib/dumper.py:129
+#: kosmorrolib/dumper.py:126
msgid "Note: All the hours are given in the UTC{offset} timezone."
msgstr ""
-#: kosmorrolib/dumper.py:175 kosmorrolib/dumper.py:255
+#: kosmorrolib/dumper.py:172 kosmorrolib/dumper.py:259
msgid "Object"
msgstr ""
-#: kosmorrolib/dumper.py:176 kosmorrolib/dumper.py:256
+#: kosmorrolib/dumper.py:173 kosmorrolib/dumper.py:260
msgid "Rise time"
msgstr ""
-#: kosmorrolib/dumper.py:177 kosmorrolib/dumper.py:257
+#: kosmorrolib/dumper.py:174 kosmorrolib/dumper.py:261
msgid "Culmination time"
msgstr ""
-#: kosmorrolib/dumper.py:178 kosmorrolib/dumper.py:258
+#: kosmorrolib/dumper.py:175 kosmorrolib/dumper.py:262
msgid "Set time"
msgstr ""
-#: kosmorrolib/dumper.py:192 kosmorrolib/dumper.py:262
+#: kosmorrolib/dumper.py:190
+msgid "Moon phase is unavailable for this date."
+msgstr ""
+
+#: kosmorrolib/dumper.py:192 kosmorrolib/dumper.py:266
msgid "Moon phase:"
msgstr ""
@@ -167,36 +163,36 @@ msgstr ""
msgid "{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}"
msgstr ""
-#: kosmorrolib/dumper.py:242
+#: kosmorrolib/dumper.py:246
msgid "A Summary of your Sky"
msgstr ""
-#: kosmorrolib/dumper.py:246
+#: kosmorrolib/dumper.py:250
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:252
+#: kosmorrolib/dumper.py:256
msgid ""
"Don't forget to check the weather forecast before you go out with your "
"equipment."
msgstr ""
-#: kosmorrolib/dumper.py:254
+#: kosmorrolib/dumper.py:258
msgid "Ephemerides of the day"
msgstr ""
-#: kosmorrolib/dumper.py:260
+#: kosmorrolib/dumper.py:264
msgid "hours"
msgstr ""
-#: kosmorrolib/dumper.py:264
+#: kosmorrolib/dumper.py:268
msgid "Expected events"
msgstr ""
-#: kosmorrolib/dumper.py:378
+#: kosmorrolib/dumper.py:382
msgid ""
"Building PDFs was not possible, because some dependencies are not "
"installed.\n"
@@ -204,6 +200,26 @@ msgid ""
"information."
msgstr ""
+#: kosmorrolib/exceptions.py:34
+msgid "The date must be between {minimum_date} and {maximum_date}"
+msgstr ""
+
+#: kosmorrolib/i18n.py:27
+msgid "{day_of_week} {month} {day_number}, {year}"
+msgstr ""
+
+#: kosmorrolib/i18n.py:29
+msgid "{month} {day_number}, {hours}:{minutes}"
+msgstr ""
+
+#: kosmorrolib/i18n.py:31
+msgid "{month} {day_number}, {year}"
+msgstr ""
+
+#: kosmorrolib/i18n.py:32
+msgid "{hours}:{minutes}"
+msgstr ""
+
#: kosmorrolib/main.py:61
msgid ""
"Save the planet and paper!\n"
@@ -217,87 +233,91 @@ msgid ""
"the observation coordinate."
msgstr ""
-#: kosmorrolib/main.py:98
+#: kosmorrolib/main.py:90
msgid "Could not save the output in \"{path}\": {error}"
msgstr ""
-#: kosmorrolib/main.py:103
+#: kosmorrolib/main.py:95
msgid "Selected output format needs an output file (--output)."
msgstr ""
-#: kosmorrolib/main.py:120
+#: kosmorrolib/main.py:112
+msgid "Moon phase can only be displayed between {min_date} and {max_date}"
+msgstr ""
+
+#: kosmorrolib/main.py:134
msgid "Running on Python {python_version}"
msgstr ""
-#: kosmorrolib/main.py:126
+#: kosmorrolib/main.py:140
msgid "Do you really want to clear Kosmorro's cache? [yN] "
msgstr ""
-#: kosmorrolib/main.py:133
+#: kosmorrolib/main.py:147
msgid "Answer did not match expected options, cache not cleared."
msgstr ""
-#: kosmorrolib/main.py:142
+#: kosmorrolib/main.py:156
msgid ""
"Compute the ephemerides and the events for a given date, at a given "
"position on Earth."
msgstr ""
-#: kosmorrolib/main.py:144
+#: kosmorrolib/main.py:158
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:149
+#: kosmorrolib/main.py:163
msgid "Show the program version"
msgstr ""
-#: kosmorrolib/main.py:151
+#: kosmorrolib/main.py:165
msgid "Delete all the files Kosmorro stored in the cache."
msgstr ""
-#: kosmorrolib/main.py:153
+#: kosmorrolib/main.py:167
msgid "The format under which the information have to be output"
msgstr ""
-#: kosmorrolib/main.py:155
+#: kosmorrolib/main.py:169
msgid ""
"The observer's latitude on Earth. Can also be set in the "
"KOSMORRO_LATITUDE environment variable."
msgstr ""
-#: kosmorrolib/main.py:158
+#: kosmorrolib/main.py:172
msgid ""
"The observer's longitude on Earth. Can also be set in the "
"KOSMORRO_LONGITUDE environment variable."
msgstr ""
-#: kosmorrolib/main.py:161
+#: kosmorrolib/main.py:175
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:166
+#: kosmorrolib/main.py:180
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:169
+#: kosmorrolib/main.py:183
msgid "Disable the colors in the console."
msgstr ""
-#: kosmorrolib/main.py:171
+#: kosmorrolib/main.py:185
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:174
+#: kosmorrolib/main.py:188
msgid ""
"Do not generate a graph to represent the rise and set times in the PDF "
"format."
diff --git a/kosmorrolib/main.py b/kosmorrolib/main.py
index d649bcb..5cfa960 100644
--- a/kosmorrolib/main.py
+++ b/kosmorrolib/main.py
@@ -29,7 +29,7 @@ from . import core
from . import events
from .data import Position, EARTH
-from .exceptions import UnavailableFeatureError
+from .exceptions import UnavailableFeatureError, OutOfRangeDateError
from .i18n import _
from . import ephemerides
from .version import VERSION
@@ -65,39 +65,31 @@ def main():
print(colored(_("PDF output will not contain the ephemerides, because you didn't provide the observation "
"coordinate."), 'yellow'))
- try:
- timezone = args.timezone
-
- if timezone is None and environment.timezone is not None:
- timezone = int(environment.timezone)
- 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)
+ timezone = args.timezone
- events_list = events.search_events(compute_date, timezone)
+ if timezone is None and environment.timezone is not None:
+ timezone = int(environment.timezone)
+ elif timezone is None:
+ timezone = 0
- 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)
- output = format_dumper.to_string()
+ try:
+ output = get_information(compute_date, position, timezone, output_format,
+ args.colors, args.show_graph)
except UnavailableFeatureError as error:
print(colored(error.msg, 'red'))
return 2
+ except OutOfRangeDateError as error:
+ print(colored(error.msg, 'red'))
+ return 1
if args.output is not None:
try:
with open(args.output, 'wb') as output_file:
- output_file.write(output)
+ output_file.write(output.to_string())
except OSError as error:
print(_('Could not save the output in "{path}": {error}').format(path=args.output,
error=error.strerror))
- elif not format_dumper.is_file_output_needed():
+ elif not output.is_file_output_needed():
print(output)
else:
print(colored(_('Selected output format needs an output file (--output).'), color='red'))
@@ -106,6 +98,28 @@ def main():
return 0
+def get_information(compute_date: date, position: Position, timezone: int,
+ output_format: str, colors: bool, show_graph: bool) -> dumper.Dumper:
+ if position is not None:
+ eph = ephemerides.get_ephemerides(date=compute_date, position=position, timezone=timezone)
+ else:
+ eph = None
+
+ try:
+ moon_phase = ephemerides.get_moon_phase(compute_date)
+ except OutOfRangeDateError as error:
+ moon_phase = None
+ print(colored(_('Moon phase can only be displayed'
+ ' between {min_date} and {max_date}').format(min_date=error.min_date,
+ max_date=error.max_date), 'yellow'))
+
+ events_list = events.search_events(compute_date, timezone)
+
+ return get_dumpers()[output_format](ephemerides=eph, moon_phase=moon_phase, events=events_list,
+ date=compute_date, timezone=timezone, with_colors=colors,
+ show_graph=show_graph)
+
+
def get_dumpers() -> {str: dumper.Dumper}:
return {
'text': dumper.TextDumper,
diff --git a/setup.py b/setup.py
index 232dd8f..badbe2f 100644
--- a/setup.py
+++ b/setup.py
@@ -42,7 +42,7 @@ setup(
('man/man1', ['manpage/kosmorro.1']),
('man/man7', ['manpage/kosmorro.7'])
],
- install_requires=['skyfield>=1.17.0,<2.0.0', 'tabulate', 'numpy>=1.17.0,<2.0.0', 'termcolor', 'python-dateutil'],
+ install_requires=['skyfield>=1.21.0,<2.0.0', 'tabulate', 'numpy>=1.17.0,<2.0.0', 'termcolor', 'python-dateutil'],
classifiers=[
'Development Status :: 3 - Alpha',
'Operating System :: POSIX :: Linux',
diff --git a/test/dumper.py b/test/dumper.py
index 857380a..a6d553e 100644
--- a/test/dumper.py
+++ b/test/dumper.py
@@ -269,6 +269,10 @@ class DumperTestCase(unittest.TestCase):
self._get_events(), date=date(2019, 10, 14)).to_string()
self.assertRegex(latex, r'\\object\{Mars\}\{08:00\}\{13:00\}\{23:00\}')
+ def test_get_moon_with_moon_phase_none(self):
+ dumper = TextDumper()
+ self.assertEqual('Moon phase is unavailable for this date.', dumper.get_moon(None))
+
@staticmethod
def _get_ephemerides(aster_rise_set=False) -> [AsterEphemerides]:
rise_time = datetime(2019, 10, 14, 8) if aster_rise_set else None
diff --git a/test/ephemerides.py b/test/ephemerides.py
index 8eb81ad..278ef57 100644
--- a/test/ephemerides.py
+++ b/test/ephemerides.py
@@ -3,6 +3,7 @@ from .testutils import expect_assertions
from kosmorrolib import ephemerides
from kosmorrolib.data import EARTH, Position, MoonPhase
from datetime import date
+from kosmorrolib.exceptions import OutOfRangeDateError
class EphemeridesTestCase(unittest.TestCase):
@@ -107,6 +108,14 @@ class EphemeridesTestCase(unittest.TestCase):
phase = MoonPhase('WANING_CRESCENT', None, None)
self.assertEqual('New Moon', phase.get_next_phase_name())
+ def test_get_ephemerides_raises_exception_on_out_of_date_range(self):
+ with self.assertRaises(OutOfRangeDateError):
+ ephemerides.get_ephemerides(date(1789, 5, 5), Position(0, 0, EARTH))
+
+ def test_get_moon_phase_raises_exception_on_out_of_date_range(self):
+ with self.assertRaises(OutOfRangeDateError):
+ ephemerides.get_moon_phase(date(1789, 5, 5))
+
if __name__ == '__main__':
unittest.main()
diff --git a/test/events.py b/test/events.py
index 37db791..fc78e88 100644
--- a/test/events.py
+++ b/test/events.py
@@ -6,6 +6,7 @@ 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.exceptions import OutOfRangeDateError
class EventTestCase(unittest.TestCase):
@@ -61,6 +62,10 @@ class EventTestCase(unittest.TestCase):
self.assertEqual(expected_event.__dict__, actual_event.__dict__)
+ def test_get_events_raises_exception_on_out_of_date_range(self):
+ with self.assertRaises(OutOfRangeDateError):
+ events.search_events(date(1789, 5, 5))
+
if __name__ == '__main__':
unittest.main()