From a2208c03e62c71c8356c90517fbe6912fdd609d6 Mon Sep 17 00:00:00 2001 From: Deuchnord Date: Mon, 22 Sep 2025 17:14:02 +0200 Subject: [PATCH] feat(timezone): add support for the tz database --- kosmorro/__main__.py | 41 ++++++++++--- kosmorro/environment.py | 2 + kosmorro/locales/messages.pot | 51 ++++++++++------ kosmorro/utils.py | 11 ++++ manpage/kosmorro.1.md | 2 +- poetry.lock | 14 ++++- pyproject.toml | 1 + tests/general.py | 16 +++-- tests/timezone.py | 106 ++++++++++++++++++++-------------- 9 files changed, 166 insertions(+), 78 deletions(-) diff --git a/kosmorro/__main__.py b/kosmorro/__main__.py index 177fdbd..ccc24b8 100644 --- a/kosmorro/__main__.py +++ b/kosmorro/__main__.py @@ -17,14 +17,18 @@ # along with this program. If not, see . import argparse +import datetime import sys import os.path +import pytz from babel.dates import format_date from kosmorrolib import Position, get_ephemerides, get_events, get_moon_phase from kosmorrolib.exceptions import OutOfRangeDateError from datetime import date +from pytz import timezone + from . import dumper, environment, debug from .date import parse_date from .geolocation import get_position @@ -34,6 +38,7 @@ from .utils import ( colored, set_colors_activated, print_stderr, + get_timezone, ) from .exceptions import ( InvalidOutputFormatError, @@ -88,12 +93,31 @@ def run(): ) ) - timezone = args.timezone + timezone = 0 - if timezone is None and env_vars.timezone is not None: - timezone = int(env_vars.timezone) - elif timezone is None: - timezone = 0 + try: + if args.timezone is not None: + timezone = get_timezone(args.timezone) + elif env_vars.tz is not None: + timezone = get_timezone(env_vars.tz) + elif env_vars.timezone is not None: + print_stderr( + colored( + _( + "Environment variable KOSMORRO_TIMEZONE is deprecated. Use TZ instead, which is more standard." + ), + "yellow", + ) + ) + timezone = get_timezone(env_vars.timezone) + except pytz.UnknownTimeZoneError as error: + print_stderr( + colored( + _("Unknown timezone: {timezone}").format(timezone=error.args[0]), + color="red", + ) + ) + return -1 try: use_colors = not environment.NO_COLOR and args.colors @@ -290,11 +314,12 @@ def get_args(output_formats: [str]): parser.add_argument( "--timezone", "-t", - type=int, + type=str, default=None, help=_( - "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." + "The timezone to use to display the hours. It can be either a number (e.g. 1 for UTC+1) or a timezone name (e.g. Europe/Paris). " + "See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones to find your timezone. " + "Can also be set in the TZ environment variable." ), ) parser.add_argument( diff --git a/kosmorro/environment.py b/kosmorro/environment.py index be3dac2..309213f 100644 --- a/kosmorro/environment.py +++ b/kosmorro/environment.py @@ -44,6 +44,8 @@ class Environment: def get_env_vars() -> Environment: environment = Environment() + environment.tz = os.getenv("TZ") + for var in os.environ: if not re.search("^KOSMORRO_", var): continue diff --git a/kosmorro/locales/messages.pot b/kosmorro/locales/messages.pot index c211d64..22df864 100644 --- a/kosmorro/locales/messages.pot +++ b/kosmorro/locales/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2025-04-21 15:35+0200\n" +"POT-Creation-Date: 2025-09-22 17:24+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,89 +17,102 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.17.0\n" -#: kosmorro/__main__.py:84 +#: kosmorro/__main__.py:89 msgid "" "Output file will not contain the ephemerides, because you didn't provide " "the observation coordinates." msgstr "" -#: kosmorro/__main__.py:135 +#: kosmorro/__main__.py:107 +msgid "" +"Environment variable KOSMORRO_TIMEZONE is deprecated. Use TZ instead, " +"which is more standard." +msgstr "" + +#: kosmorro/__main__.py:116 +#, python-brace-format +msgid "Unknown timezone: {timezone}" +msgstr "" + +#: kosmorro/__main__.py:159 #, python-brace-format msgid "The file could not be saved in \"{path}\": {error}" msgstr "" -#: kosmorro/__main__.py:149 +#: kosmorro/__main__.py:173 msgid "Please provide a file path to export in this format (--output)." msgstr "" -#: kosmorro/__main__.py:181 +#: kosmorro/__main__.py:205 #, python-brace-format msgid "Moon phase can only be computed between {min_date} and {max_date}" msgstr "" -#: kosmorro/__main__.py:228 +#: kosmorro/__main__.py:252 #, python-brace-format msgid "Running on Python {python_version} with Kosmorrolib v{kosmorrolib_version}" msgstr "" -#: kosmorro/__main__.py:241 +#: kosmorro/__main__.py:265 msgid "" "Compute the ephemerides and the events for a given date and a given " "position on Earth." msgstr "" -#: kosmorro/__main__.py:244 +#: kosmorro/__main__.py:268 msgid "" "By default, only the events will be computed for today.\n" "To compute also the ephemerides, latitude and longitude arguments are " "needed." msgstr "" -#: kosmorro/__main__.py:256 +#: kosmorro/__main__.py:280 msgid "Show the program version" msgstr "" -#: kosmorro/__main__.py:265 +#: kosmorro/__main__.py:289 msgid "" "The format to output the information to. If not provided, the output " "format will be inferred from the file extension of the output file." msgstr "" -#: kosmorro/__main__.py:275 +#: kosmorro/__main__.py:299 #, python-brace-format msgid "" "The observer's position on Earth, in the \"{latitude},{longitude}\" " "format. Can also be set in the KOSMORRO_POSITION environment variable." msgstr "" -#: kosmorro/__main__.py:285 +#: kosmorro/__main__.py:309 msgid "" "The date for which the ephemerides must be calculated. Can be in the " "YYYY-MM-DD format or an interval in the \"[+-]YyMmDd\" format (with Y, M," " and D numbers). Defaults to current date." msgstr "" -#: kosmorro/__main__.py:296 +#: kosmorro/__main__.py:320 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." +"The timezone to use to display the hours. It can be either a number (e.g." +" 1 for UTC+1) or a timezone name (e.g. Europe/Paris). See " +"https://en.wikipedia.org/wiki/List_of_tz_database_time_zones to find your" +" timezone. Can also be set in the TZ environment variable." msgstr "" -#: kosmorro/__main__.py:304 +#: kosmorro/__main__.py:329 msgid "Disable the colors in the console." msgstr "" -#: kosmorro/__main__.py:312 +#: kosmorro/__main__.py:337 msgid "A file to export the output to. If not given, the standard output is used." msgstr "" -#: kosmorro/__main__.py:320 +#: kosmorro/__main__.py:345 msgid "" "Do not generate a graph to represent the rise and set times in the LaTeX " "file." msgstr "" -#: kosmorro/__main__.py:327 +#: kosmorro/__main__.py:352 msgid "Show debugging messages" msgstr "" diff --git a/kosmorro/utils.py b/kosmorro/utils.py index bd68ae9..892d9c4 100644 --- a/kosmorro/utils.py +++ b/kosmorro/utils.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 +from datetime import datetime +import pytz from termcolor import colored as do_color from sys import stderr @@ -27,3 +29,12 @@ def colored(text, color=None, on_color=None, attrs=None): def print_stderr(*values: object): print(*values, file=stderr) + + +def get_timezone(value: int | str) -> float: + try: + timezone = float(value) + except ValueError: + timezone = pytz.timezone(value).utcoffset(datetime.now()).total_seconds() / 3600 + + return timezone diff --git a/manpage/kosmorro.1.md b/manpage/kosmorro.1.md index f44bb4b..3f25024 100644 --- a/manpage/kosmorro.1.md +++ b/manpage/kosmorro.1.md @@ -23,7 +23,7 @@ The date for which the ephemerides must be computed, either 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 `--timezone=`_TIMEZONE_, `-t` _TIMEZONE_ - the timezone to display the hours in; e.g. 2 for UTC+2 or -3 for UTC-3 + the timezone to use to display the hours; it can be either a number (e.g. 1 for UTC+1) or a timezone name (e.g. Europe/Paris) `--no-colors` disable the colors in the console diff --git a/poetry.lock b/poetry.lock index 6903d0c..6ceb6e5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -391,6 +391,18 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "pytz" +version = "2025.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, + {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, +] + [[package]] name = "sgp4" version = "2.25" @@ -529,4 +541,4 @@ tests = ["pytest", "pytest-cov"] [metadata] lock-version = "2.1" python-versions = "^3.12" -content-hash = "edfb347df82f9895ce51f3af9005b1d924129c24aa7e38b5e2f7bd858e33ec25" +content-hash = "12bf9cdda9cfaf9616bf3d899450e105991f788267601a41a79fed1b5cbd73a9" diff --git a/pyproject.toml b/pyproject.toml index 07c9b58..9ccdc97 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ kosmorrolib = "^1.0" python-dateutil = "^2.8" Babel = "^2.9" openlocationcode = "^1.0" +pytz = "^2025.2" [tool.poetry.group.dev.dependencies] black = "^24.4" diff --git a/tests/general.py b/tests/general.py index 290699d..d12ace7 100644 --- a/tests/general.py +++ b/tests/general.py @@ -62,9 +62,11 @@ options: "[+-]YyMmDd" format (with Y, M, and D numbers). Defaults to current date. --timezone TIMEZONE, -t TIMEZONE - 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. + The timezone to use to display the hours. It can be + either a number (e.g. 1 for UTC+1) or a timezone name + (e.g. Europe/Paris). See https://en.wikipedia.org/wiki + /List_of_tz_database_time_zones to find your timezone. + Can also be set in the TZ environment variable. --no-colors Disable the colors in the console. --output OUTPUT, -o OUTPUT A file to export the output to. If not given, the @@ -103,9 +105,11 @@ options: "[+-]YyMmDd" format (with Y, M, and D numbers). Defaults to current date. --timezone, -t TIMEZONE - 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. + The timezone to use to display the hours. It can be + either a number (e.g. 1 for UTC+1) or a timezone name + (e.g. Europe/Paris). See https://en.wikipedia.org/wiki + /List_of_tz_database_time_zones to find your timezone. + Can also be set in the TZ environment variable. --no-colors Disable the colors in the console. --output, -o OUTPUT A file to export the output to. If not given, the standard output is used. diff --git a/tests/timezone.py b/tests/timezone.py index a1f6802..6226f48 100644 --- a/tests/timezone.py +++ b/tests/timezone.py @@ -6,68 +6,88 @@ from .utils import ( ) -def check_command_return_t_plus_one(result): +def test_timezone_with_command_line_arg(): + result = execute(KOSMORRO + ["--timezone=1", "-d2020-01-27"]) assert result.successful - assert ( - result.stdout - == """Monday, January 27, 2020 + assert "Note: All the hours are given in the UTC+1.0 timezone." in result.stdout -New Moon -First Quarter on Sunday, February 2, 2020 at 2:41 AM + result = execute(KOSMORRO + ["--timezone=Europe/Paris", "-d2020-01-27"]) + assert result.successful + assert "Note: All the hours are given in the UTC+1.0 timezone." not in result.stdout -Expected events: -9:00 PM Venus and Neptune are in conjunction + result = execute(KOSMORRO + ["--timezone=-5", "-d2020-01-27"]) + assert result.successful + assert "Note: All the hours are given in the UTC-5.0 timezone." in result.stdout -Note: All the hours are given in the UTC+1 timezone. -""" - ) + result = execute(KOSMORRO + ["--timezone=America/Chicago", "-d2020-01-27"]) + assert result.successful + assert "Note: All the hours are given in the UTC-5.0 timezone." in result.stdout -def check_command_return_t_minus_one(result): +def test_timezone_with_env_var(): + result = execute(KOSMORRO + ["-d2020-01-27"], environment={"TZ": "1"}) assert result.successful - assert ( - result.stdout - == """Monday, January 27, 2020 + assert "Note: All the hours are given in the UTC+1.0 timezone." in result.stdout -New Moon -First Quarter on Sunday, February 2, 2020 at 12:41 AM + result = execute(KOSMORRO + ["-d2020-01-27"], environment={"TZ": "Europe/Paris"}) + assert result.successful + assert "Note: All the hours are given in the UTC+1.0 timezone." not in result.stdout + + result = execute(KOSMORRO + ["-d2020-01-27"], environment={"TZ": "-5"}) + assert result.successful + assert "Note: All the hours are given in the UTC-5.0 timezone." in result.stdout + + result = execute(KOSMORRO + ["-d2020-01-27"], environment={"TZ": "America/Chicago"}) + assert result.successful + assert "Note: All the hours are given in the UTC-5.0 timezone." in result.stdout -Expected events: -7:00 PM Venus and Neptune are in conjunction -Note: All the hours are given in the UTC-1 timezone. -""" +def test_timezone_with_env_var_and_command_line_arg(): + result = execute( + KOSMORRO + ["--timezone=3", "-d2020-01-27"], environment={"TZ": "Europe/Paris"} ) + assert result.successful + assert "Note: All the hours are given in the UTC+3.0 timezone." in result.stdout -def test_timezone(): - check_command_return_t_plus_one( - execute(KOSMORRO + ["--timezone=1", "-d2020-01-27"]) +def test_timezone_with_deprecated_env_var(): + result = execute( + KOSMORRO + ["-d2020-01-27"], environment={"KOSMORRO_TIMEZONE": "1"} ) - check_command_return_t_minus_one( - execute(KOSMORRO + ["--timezone=-1", "-d2020-01-27"]) + assert result.successful + assert ( + "Environment variable KOSMORRO_TIMEZONE is deprecated. Use TZ instead, which is more standard." + in result.stderr ) + assert "Note: All the hours are given in the UTC+1.0 timezone." in result.stdout - -def test_timezone_with_env_var(): - check_command_return_t_plus_one( - execute(KOSMORRO + ["-d2020-01-27"], environment={"KOSMORRO_TIMEZONE": "1"}) + result = execute( + KOSMORRO + ["-d2020-01-27"], environment={"KOSMORRO_TIMEZONE": "Europe/Paris"} ) - check_command_return_t_minus_one( - execute(KOSMORRO + ["-d2020-01-27"], environment={"KOSMORRO_TIMEZONE": "-1"}) + assert result.successful + assert ( + "Environment variable KOSMORRO_TIMEZONE is deprecated. Use TZ instead, which is more standard." + in result.stderr ) + assert "Note: All the hours are given in the UTC+1.0 timezone." not in result.stdout - # If both environment variable and argument are set, use argument: + result = execute( + KOSMORRO + ["-d2020-01-27"], environment={"KOSMORRO_TIMEZONE": "-5"} + ) + assert result.successful + assert ( + "Environment variable KOSMORRO_TIMEZONE is deprecated. Use TZ instead, which is more standard." + in result.stderr + ) + assert "Note: All the hours are given in the UTC-5.0 timezone." in result.stdout - check_command_return_t_plus_one( - execute( - KOSMORRO + ["--timezone=1", "-d2020-01-27"], - environment={"KOSMORRO_TIMEZONE": "-1"}, - ) + result = execute( + KOSMORRO + ["-d2020-01-27"], + environment={"KOSMORRO_TIMEZONE": "America/Chicago"}, ) - check_command_return_t_minus_one( - execute( - KOSMORRO + ["--timezone=-1", "-d2020-01-27"], - environment={"KOSMORRO_TIMEZONE": "1"}, - ) + assert result.successful + assert ( + "Environment variable KOSMORRO_TIMEZONE is deprecated. Use TZ instead, which is more standard." + in result.stderr ) + assert "Note: All the hours are given in the UTC-5.0 timezone." in result.stdout