Bläddra i källkod

Merge pull request #100 from Deuchnord/fix-out-of-range-date

Handle out of range date error
tags/v0.8.1
Jérôme Deuchnord 4 år sedan
committed by GitHub
förälder
incheckning
e38fbcabed
Ingen känd nyckel hittad för denna signaturen i databasen GPG-nyckel ID: 4AEE18F83AFDEB23
20 ändrade filer med 282 tillägg och 163 borttagningar
  1. +1
    -1
      .github/workflows/unit-tests.yml
  2. +2
    -0
      .scripts/tests-e2e.sh
  3. +1
    -1
      CONTRIBUTING.md
  4. +8
    -0
      Makefile
  5. +1
    -1
      Pipfile
  6. +1
    -1
      Pipfile.lock
  7. Binär
      kosmorrolib/assets/moonphases/png/unknown.png
  8. +31
    -0
      kosmorrolib/assets/moonphases/svg/unknown.svg
  9. +3
    -2
      kosmorrolib/data.py
  10. +12
    -38
      kosmorrolib/dumper.py
  11. +51
    -31
      kosmorrolib/ephemerides.py
  12. +16
    -5
      kosmorrolib/events.py
  13. +13
    -0
      kosmorrolib/exceptions.py
  14. +7
    -0
      kosmorrolib/i18n.py
  15. +80
    -60
      kosmorrolib/locales/messages.pot
  16. +36
    -22
      kosmorrolib/main.py
  17. +1
    -1
      setup.py
  18. +4
    -0
      test/dumper.py
  19. +9
    -0
      test/ephemerides.py
  20. +5
    -0
      test/events.py

+ 1
- 1
.github/workflows/unit-tests.yml Visa fil

@@ -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

+ 2
- 0
.scripts/tests-e2e.sh Visa fil

@@ -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"


+ 1
- 1
CONTRIBUTING.md Visa fil

@@ -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.


+ 8
- 0
Makefile Visa fil

@@ -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


+ 1
- 1
Pipfile Visa fil

@@ -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 = "*"


+ 1
- 1
Pipfile.lock Visa fil

@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "afd17771258a86b04cab79d21d2afbfb1faf44bbe6e760da9140e72b0999a5b1"
"sha256": "9ed5ee6bbfde75ee77c89fdc09a793f5c00f9782968dc310e1eb8d3386378d9e"
},
"pipfile-spec": 6,
"requires": {


Binär
kosmorrolib/assets/moonphases/png/unknown.png Visa fil

Före Efter
Bredd: 312  |  Höjd: 312  |  Storlek: 26 KiB

+ 31
- 0
kosmorrolib/assets/moonphases/svg/unknown.svg Visa fil

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="92.682mm" height="92.684mm" version="1.1" viewBox="0 0 92.682 92.684" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<radialGradient id="radialGradient4622" cx="245.39" cy="354.57" r="234.94" fx="248.94" gradientTransform="translate(-41.85,-50.912)" gradientUnits="userSpaceOnUse">
<stop stop-color="#b7c3cc" offset="0"/>
<stop stop-color="#001e29" offset="1"/>
</radialGradient>
</defs>
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g transform="translate(-15.647 -114.59)">
<g transform="matrix(.26458 0 0 .26458 7.4191 107.94)">
<g fill-rule="evenodd">
<path d="m362.49 200.25a156.25 156.25 0 1 1-312.5 0 156.25 156.25 0 1 1 312.5 0z" fill="#000024" stroke-width="1.0102"/>
<path d="m164.32 56.438c-25.06 6.447-47.957 20.982-65.936 39.456-33.589 34.689-49.415 85.84-39.274 133.3 7.7684 38.332 30.319 73.818 63.261 95.34 39.082 26.542 90.824 33.344 135.13 16.523 44.589-15.935 80.583-53.959 92.968-99.846 14.03-47.365 2.42-101-30.04-138.25-27.522-32.74-69.296-52.414-112.07-52.8-2.4338-0.40982-30.229 1.5565-44.039 6.2796z" fill="url(#radialGradient4622)"/>
<path d="m297.02 188.91c-10.37 17.497 15.281 19.939 24.511 25.141-3.4576-31.524 10.512-20.807 13.141-30.822-2.4603-15.476-20.24-18.289-30.195-28.185-0.36892-19.454-33.444-17.876-30.691-31.892-5.3458-59.729-15.582-5.0632-17.324-14.89-11.086 12.896-14.512-1.6954-11.166-10.995 0.26364-11.365-18.639-11.91-25.607-6.5757-5.8842 3.1388-14.403 3.9638-17.373 11.046-13.66 2.7421-12.28-17.783-26.048-16.916-15.276-6.4266-21.571-23.77-33.99-13.45-8.9365 2.8834-27.358 38.752-33.694 23.765-13.5-9.5163-20.451 15.279-20.026 25.654-6.3485 13.802-17.899 25.142-24.397 39.356-8.0969 13.6-8.0574 31.852-2.7715 46.369 16.911-8.3049 18.513 27.307 24.313 42.266 8.439 10.014 21.898-12.821 25.417-6.9099 22.751 14.162-5.2534 18.337-1.2166 29.083 4.539 15.527 24.377 5.6814 35.735 9.2032 4.7599 1.8134 10.807 12.467 13.777 3.1751-1.815-6.8086-5.6283-13.445 4.7747-13.371 7.3152-6.3929 17.349-5.3491 24.821-7.2415 9.0393-11.572-10.092-19.333-4.8746-30.443 2.8779-11.759-12.73-8.4941-10.462-18.846-7.0124-5.8828-11.048-16.424 0.22233-20.807 7.9487-5.15 29.707 5.1288 24.31-11.199-6.8963-0.46855-16.33-6.6026-8.8168-14.327 10.756-7.5046 26.836 6.3362 36.887-4.9453 1.0925-5.4659 4.6002-20.74 11.751-10.347-2.0036 12.706 4.4139 23.496 16.934 28.605 5.6256 3.7827 11.442 24.659 18.963 12.144-10.579-15.807-1.3253-36.935 21.493-9.7167-0.27777 2.0581-0.63554 4.9085 1.6013 6.0725zm-112.57-59.732c-3.3727-9.642-15.855 0.88715-7.0124 5.9448-1.4949 18.168-23.383 19.29-37.295 19.685-8.7173 4.2923-20.669 6.5967-27.583 10.788-1.5637 11.397 17.747 10.622 15.712 22.827 8.4646 11.957 22.01 19.653 28.373 33.172 12.227 10.86 6.5682-16.046-1.1103-17.921-11.846-4.4912-26.133-24.017-7.6284-30.074 12.233-3.4412 6.304-25.137 22.916-20.183 15.144 3.9218 14.776-14.074 24.42-20.796 2.2128-9.6425-5.7023-6.6942-10.791-3.4427z" fill-opacity=".25126"/>
</g>
<path d="m358.45 201.09c0 83.168-68.058 150.67-151.91 150.67s-151.91-67.499-151.91-150.67 64.682-150.67 151.91-150.67c89.933 0 151.91 67.499 151.91 150.67z" fill="#000024" fill-opacity=".86432"/>
</g>
</g>
<g fill="#fff" fill-opacity=".33333" aria-label="?">
<path d="m37.427 57.422q0-4.3215 1.4111-7.7611 1.4111-3.4396 5.8208-5.9972 1.3229-0.79375 3.2632-1.7639 1.9403-1.0583 3.7924-2.4694 1.8521-1.4111 3.0868-3.2632 1.3229-1.9403 1.3229-4.4097 0-1.8521-0.70556-3.2632-0.70556-1.4111-1.8521-2.3812t-2.734-1.4111q-1.4993-0.52917-3.0868-0.52917-1.9403 0-3.5278 0.70556-1.5875 0.61736-2.9104 1.6757-1.2347 0.97014-2.2931 2.2931-0.97014 1.2347-1.6757 2.3812l-7.6729-5.2917q1.1465-2.734 2.9986-4.8507 1.9403-2.1167 4.3215-3.5278 2.4694-1.4993 5.2917-2.2049 2.8222-0.79375 5.8208-0.79375 3.3514 0 6.6146 0.97014 3.2632 0.97014 5.8208 3.0868 2.6458 2.1167 4.2333 5.4681 1.6757 3.2632 1.6757 7.8493 0 2.8222-0.70556 5.0271-0.61736 2.1167-1.8521 3.8806-1.2347 1.7639-2.9104 3.175-1.6757 1.4111-3.7924 2.734-1.7639 1.0583-3.5278 2.0285-1.7639 0.97014-3.2632 2.2049-1.4111 1.1465-2.3812 2.734-0.88194 1.4993-0.88194 3.7042zm0.17639 20.549v-12.171h9.7896v12.171z" fill="#fff" fill-opacity=".33333" stroke-width=".26458"/>
</g>
</svg>

+ 3
- 2
kosmorrolib/data.py Visa fil

@@ -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))


+ 12
- 38
kosmorrolib/dumper.py Visa fil

@@ -22,10 +22,9 @@ import json
import os
from pathlib import Path
from tabulate import tabulate
from numpy import int64
from termcolor import colored
from .data import ASTERS, Object, AsterEphemerides, MoonPhase, Event
from .i18n import _
from .data import ASTERS, AsterEphemerides, MoonPhase, Event
from .i18n import _, FULL_DATE_FORMAT, SHORT_DATETIME_FORMAT, TIME_FORMAT
from .version import VERSION
from .exceptions import UnavailableFeatureError
try:
@@ -33,12 +32,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 +53,9 @@ class Dumper(ABC):

return date

def __str__(self):
return self.to_string()

@abstractmethod
def to_string(self):
pass
@@ -77,35 +73,6 @@ class JsonDumper(Dumper):
'events': [event.serialize() for event in self.events]
}, indent=4)

@staticmethod
def _json_default(obj):
# Fixes the "TypeError: Object of type int64 is not JSON serializable"
# See https://stackoverflow.com/a/50577730
if isinstance(obj, int64):
return int(obj)
if isinstance(obj, datetime.datetime):
return obj.isoformat()
if isinstance(obj, Object):
obj = obj.__dict__
obj.pop('skyfield_name')
obj.pop('radius')
obj['object'] = obj.pop('name')
obj['details'] = obj.pop('ephemerides')
return obj
if isinstance(obj, AsterEphemerides):
return obj.__dict__
if isinstance(obj, MoonPhase):
moon_phase = obj.__dict__
moon_phase['phase'] = moon_phase.pop('identifier')
moon_phase['date'] = moon_phase.pop('time')
return moon_phase
if isinstance(obj, Event):
event = obj.__dict__
event['objects'] = [object.name for object in event['objects']]
return event

raise TypeError('Object of type "%s" could not be integrated in the JSON' % str(type(obj)))


class TextDumper(Dumper):
def to_string(self):
@@ -189,6 +156,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 +182,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('_', '-'),


+ 51
- 31
kosmorrolib/ephemerides.py Visa fil

@@ -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

+ 16
- 5
kosmorrolib/events.py Visa fil

@@ -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)

+ 13
- 0
kosmorrolib/exceptions.py Visa fil

@@ -16,8 +16,21 @@
# 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 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))

+ 7
- 0
kosmorrolib/i18n.py Visa fil

@@ -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


+ 80
- 60
kosmorrolib/locales/messages.pot Visa fil

@@ -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:51+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"
@@ -59,144 +59,140 @@ 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:87
msgid "Expected events:"
msgstr ""

#: kosmorrolib/dumper.py:124
#: kosmorrolib/dumper.py:91
msgid "Note: All the hours are given in UTC."
msgstr ""

#: kosmorrolib/dumper.py:129
#: kosmorrolib/dumper.py:96
msgid "Note: All the hours are given in the UTC{offset} timezone."
msgstr ""

#: kosmorrolib/dumper.py:175 kosmorrolib/dumper.py:255
#: kosmorrolib/dumper.py:142 kosmorrolib/dumper.py:229
msgid "Object"
msgstr ""

#: kosmorrolib/dumper.py:176 kosmorrolib/dumper.py:256
#: kosmorrolib/dumper.py:143 kosmorrolib/dumper.py:230
msgid "Rise time"
msgstr ""

#: kosmorrolib/dumper.py:177 kosmorrolib/dumper.py:257
#: kosmorrolib/dumper.py:144 kosmorrolib/dumper.py:231
msgid "Culmination time"
msgstr ""

#: kosmorrolib/dumper.py:178 kosmorrolib/dumper.py:258
#: kosmorrolib/dumper.py:145 kosmorrolib/dumper.py:232
msgid "Set time"
msgstr ""

#: kosmorrolib/dumper.py:192 kosmorrolib/dumper.py:262
#: kosmorrolib/dumper.py:160
msgid "Moon phase is unavailable for this date."
msgstr ""

#: kosmorrolib/dumper.py:162 kosmorrolib/dumper.py:236
msgid "Moon phase:"
msgstr ""

#: kosmorrolib/dumper.py:193
#: kosmorrolib/dumper.py:163
msgid "{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}"
msgstr ""

#: kosmorrolib/dumper.py:242
#: kosmorrolib/dumper.py:216
msgid "A Summary of your Sky"
msgstr ""

#: kosmorrolib/dumper.py:246
#: kosmorrolib/dumper.py:220
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:226
msgid ""
"Don't forget to check the weather forecast before you go out with your "
"equipment."
msgstr ""

#: kosmorrolib/dumper.py:254
#: kosmorrolib/dumper.py:228
msgid "Ephemerides of the day"
msgstr ""

#: kosmorrolib/dumper.py:260
#: kosmorrolib/dumper.py:234
msgid "hours"
msgstr ""

#: kosmorrolib/dumper.py:264
#: kosmorrolib/dumper.py:238
msgid "Expected events"
msgstr ""

#: kosmorrolib/dumper.py:378
#: kosmorrolib/dumper.py:352
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."


+ 36
- 22
kosmorrolib/main.py Visa fil

@@ -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,


+ 1
- 1
setup.py Visa fil

@@ -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',


+ 4
- 0
test/dumper.py Visa fil

@@ -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


+ 9
- 0
test/ephemerides.py Visa fil

@@ -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()

+ 5
- 0
test/events.py Visa fil

@@ -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()

Laddar…
Avbryt
Spara