From 1cb9496c9139030a07eec67da77e546daef90a6b Mon Sep 17 00:00:00 2001 From: Inf-inity <83883849+Inf-inity@users.noreply.github.com> Date: Mon, 3 Oct 2022 12:15:17 +0200 Subject: [PATCH 01/10] Added formula for calulationg sunrise/sunset --- datetimeparser/utils/formulars.py | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/datetimeparser/utils/formulars.py b/datetimeparser/utils/formulars.py index 3547977..e04314e 100644 --- a/datetimeparser/utils/formulars.py +++ b/datetimeparser/utils/formulars.py @@ -1,4 +1,13 @@ from datetime import datetime, timedelta +import math + + +def day_of_year(dt: datetime) -> int: + n1 = math.floor(275 * dt.month / 9) + n2 = math.floor((dt.month + 9) / 12) + n3 = (1 + math.floor((dt.year - 4 * math.floor(dt.year / 4) + 2) / 3)) + + return n1 - (n2 * n3) + dt.day - 30 def eastern_calc(year_time: int) -> datetime: @@ -34,3 +43,42 @@ def days_feb(year_time: int) -> int: def year_start(year_time: int) -> datetime: return datetime(year=year_time, month=1, day=1) + + +def calc_sun_time(dt: datetime, timezone: tuple[float, float, float], sunrise: bool = True) -> datetime: + """ + Calculates the time for sunrise and sunset based on coordinates and a date + :param dt: The date for calculating the sunset + :param timezone: A tuple with longitude and magnitude and timezone offset + :param sunrise: If True the sunrise will be calculated if False the sunset + :returns: The time for the sunrise/sunset + """ + + to_rad: float = math.pi / 180 + day: int = day_of_year(dt) + longitude_to_hour = timezone[0] / 15 + + b = timezone[1] * to_rad + h = -50 * to_rad / 60 + + time_equation = -0.171 * math.sin(0.0337 * day + 0.465) - 0.1299 * math.sin(0.01787 * day - 0.168) + declination = 0.4095 * math.sin(0.016906 * (day - 80.086)) + + time_difference = 12 * math.acos((math.sin(h) - math.sin(b) * math. sin(declination)) / (math.cos(b) * math.cos(declination))) / math.pi + + if sunrise: + time = 12 - time_difference + else: + time = 12 + time_difference + + time: float = (time - time_equation) + longitude_to_hour + timezone[2] + + hour: int = int(time) + minutes_left: float = time - int(time) + minutes_with_seconds = minutes_left * 60 + minute: int = int(minutes_with_seconds) + second: int = int((minutes_with_seconds - minute) * 60) + + out: datetime = datetime(year=dt.year, month=dt.month, day=dt.day, hour=hour, minute=minute, second=second) + + return out From c6749f6748c24854b7054609e8aa023aed6108db Mon Sep 17 00:00:00 2001 From: Inf-inity <83883849+Inf-inity@users.noreply.github.com> Date: Thu, 27 Oct 2022 14:13:16 +0200 Subject: [PATCH 02/10] Started to implement coordinates for sunrise/sunset calculation (not functional atm) --- datetimeparser/datetimeparser.py | 13 +++++++++--- datetimeparser/evaluator/evaluator.py | 16 +++++++++----- datetimeparser/evaluator/evaluatormethods.py | 18 ++++++++++++++-- datetimeparser/utils/enums.py | 7 +++++-- datetimeparser/utils/formulars.py | 2 +- datetimeparser/utils/geometry.py | 22 ++++++++++++++++++++ requirements.txt | 3 ++- tests/runtests.py | 5 +++-- 8 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 datetimeparser/utils/geometry.py diff --git a/datetimeparser/datetimeparser.py b/datetimeparser/datetimeparser.py index 810b2d5..540e83b 100644 --- a/datetimeparser/datetimeparser.py +++ b/datetimeparser/datetimeparser.py @@ -13,13 +13,20 @@ from datetimeparser.parser import Parser -def parse(datetime_string: str, timezone: str = "Europe/Berlin") -> Optional[datetime.datetime]: +def parse( + datetime_string: str, + timezone: str = "Europe/Berlin", + coordinates: Optional[tuple[float, float]] = None +) -> Optional[tuple[datetime.datetime, str, tuple[float, float]]]: """ Parses a datetime string and returns a datetime object. If the datetime string cannot be parsed, None is returned. :param datetime_string: The datetime string to parse. :param timezone: The timezone to use. Should be a valid timezone for pytz.timezone(). Default: Europe/Berlin + :param coordinates: A tuple containing longitude and latitude. If coordinates are given, the timezone will be calculated, + independently of the given timezone param + NOTE: It takes a longer time to calculate the timezone, it can happen that it takes up to 30 seconds for a result :return: A datetime object or None """ parser_result = Parser(datetime_string).parse() @@ -27,9 +34,9 @@ def parse(datetime_string: str, timezone: str = "Europe/Berlin") -> Optional[dat if parser_result is None: return None - evaluator_result = Evaluator(parser_result, tz=timezone).evaluate() + evaluator_result, tz, coords = Evaluator(parser_result, tz=timezone, coordinates=coordinates).evaluate() if evaluator_result is None: return None - return evaluator_result + return evaluator_result, tz, coords diff --git a/datetimeparser/evaluator/evaluator.py b/datetimeparser/evaluator/evaluator.py index 50980d9..3f7bc4e 100644 --- a/datetimeparser/evaluator/evaluator.py +++ b/datetimeparser/evaluator/evaluator.py @@ -1,20 +1,24 @@ from datetime import datetime from pytz import timezone, UnknownTimeZoneError -from typing import Union +from typing import Optional, Union from datetimeparser.utils.baseclasses import AbsoluteDateTime, RelativeDateTime from datetimeparser.utils.enums import Method from datetimeparser.evaluator.evaluatormethods import EvaluatorMethods from datetimeparser.utils.exceptions import FailedEvaluation, InvalidValue +from datetimeparser.utils.geometry import TimeZoneManager class Evaluator: - def __init__(self, parsed_object, tz="Europe/Berlin"): + def __init__(self, parsed_object, tz="Europe/Berlin", coordinates: Optional[tuple[float, float]] = None): """ :param parsed_object: the parsed object from parser :param tz: the timezone for the datetime + :param coordinates: longitude and latitude for timezone calculation and for sunrise and sunset """ + if coordinates: + tz = TimeZoneManager().timezone_at(lng=coordinates[0], lat=coordinates[1]) try: tiz = timezone(tz) except UnknownTimeZoneError: @@ -24,10 +28,12 @@ def __init__(self, parsed_object, tz="Europe/Berlin"): self.parsed_object_content: Union[list, AbsoluteDateTime, RelativeDateTime] = parsed_object[1] self.current_datetime: datetime = datetime.strptime(datetime.strftime(datetime.now(tz=tiz), "%Y-%m-%d %H:%M:%S"), "%Y-%m-%d %H:%M:%S") self.offset = tiz.utcoffset(self.current_datetime) + self.timezone = tiz + self.coordinates = coordinates or TimeZoneManager().get_coordinates(tiz.zone) - def evaluate(self) -> Union[datetime, None]: + def evaluate(self) -> Union[tuple[datetime, str, tuple[float, float]], None]: ev_out = None - ev = EvaluatorMethods(self.parsed_object_content, self.current_datetime, self.offset) + ev = EvaluatorMethods(self.parsed_object_content, self.current_datetime, self.coordinates, self.offset) if self.parsed_object_type == Method.ABSOLUTE_DATE_FORMATS: ev_out = ev.evaluate_absolute_date_formats() @@ -48,6 +54,6 @@ def evaluate(self) -> Union[datetime, None]: ev_out = ev.evaluate_datetime_delta_constants() if ev_out: - return ev_out + return ev_out, self.timezone.zone, self.coordinates else: raise FailedEvaluation(self.parsed_object_content) diff --git a/datetimeparser/evaluator/evaluatormethods.py b/datetimeparser/evaluator/evaluatormethods.py index dc71491..1130edf 100644 --- a/datetimeparser/evaluator/evaluatormethods.py +++ b/datetimeparser/evaluator/evaluatormethods.py @@ -2,6 +2,7 @@ from datetimeparser.utils.baseclasses import * from datetimeparser.utils.enums import * from datetimeparser.utils.exceptions import InvalidValue +from datetimeparser.utils.formulars import calc_sun_time class EvaluatorMethods(EvaluatorUtils): @@ -9,16 +10,18 @@ class EvaluatorMethods(EvaluatorUtils): Evaluates a datetime-object from a given list returned from the parser """ - def __init__(self, parsed, current_time: datetime, offset: timedelta = None): + def __init__(self, parsed, current_time: datetime, coordinates: tuple[float, float], offset: timedelta = None): """ :param parsed: object returned from the parser :param current_time: the current datetime + :param coordinates: coordinates from the timezone :param offset: the UTC-offset from the current timezone. Default: None """ self.parsed = parsed self.current_time = current_time self.offset = offset + self.coordinates = coordinates def evaluate_absolute_date_formats(self) -> datetime: ev_out = datetime( @@ -122,6 +125,15 @@ def evaluate_constants(self) -> datetime: "%Y-%m-%d %H:%M:%S" ) + elif object_type.name == "sunset" or object_type.name == "sunrise": + ofs = self.offset.total_seconds()/60/60 # -> to hours + # TODO: at the moment summer and winter time change the result for the offset around 1 hour + dt = calc_sun_time( + self.current_time, + (self.coordinates[0], self.coordinates[1], ofs), # something does not work properly, Berlin works with offset 0, Dubai not, idk why + object_type.name == "sunrise" + ) + else: dt = object_type.time_value(self.current_time.year) if isinstance(dt, tuple): @@ -135,7 +147,9 @@ def evaluate_constants(self) -> datetime: ) return dt - if self.current_time >= dt and self.parsed[0] not in (Constants.ALL_RELATIVE_CONSTANTS and WeekdayConstants.ALL): + if self.current_time >= dt and self.parsed[0] not in ( + Constants.ALL_RELATIVE_CONSTANTS and WeekdayConstants.ALL and DatetimeDeltaConstants.CHANGING + ): dt = object_type.time_value(self.current_time.year + 1) if self.current_time >= dt and self.parsed[0] in WeekdayConstants.ALL: diff --git a/datetimeparser/utils/enums.py b/datetimeparser/utils/enums.py index 5404418..721f54f 100644 --- a/datetimeparser/utils/enums.py +++ b/datetimeparser/utils/enums.py @@ -119,7 +119,7 @@ class DatetimeDeltaConstants: DAYLIGHT_CHANGE = Constant('daylight change', ['daylight saving', 'daylight saving time'], value=0, options=[ConstantOption.YEAR_VARIABLE, ConstantOption.DATE_VARIABLE], time_value=lambda _: (6, 0, 0)) - SUNRISE = Constant('sunrise', value=0, options=[ConstantOption.DATE_VARIABLE], time_value=lambda _: (7, 0, 0)) + SUNRISE = Constant('sunrise', value=0, options=[ConstantOption.DATE_VARIABLE], time_value=lambda _: None) MORNING = Constant('morning', value=0, options=[ConstantOption.DATE_VARIABLE], time_value=lambda _: (6, 0, 0)) BREAKFAST = Constant('breakfast', value=0, options=[ConstantOption.DATE_VARIABLE], time_value=lambda _: (8, 0, 0)) @@ -132,12 +132,15 @@ class DatetimeDeltaConstants: time_value=lambda _: (19, 0, 0)) DAWN = Constant('dawn', value=12, options=[ConstantOption.DATE_VARIABLE], time_value=lambda _: (6, 0, 0)) DUSK = Constant('dusk', value=12, options=[ConstantOption.DATE_VARIABLE], time_value=lambda _: (20, 0, 0)) - SUNSET = Constant('sunset', value=12, options=[ConstantOption.DATE_VARIABLE], time_value=lambda _: (18, 30, 0)) + SUNSET = Constant('sunset', value=12, options=[ConstantOption.DATE_VARIABLE], time_value=lambda _: None) ALL = [ MORNING, AFTERNOON, EVENING, NIGHT, MORNING_NIGHT, DAYLIGHT_CHANGE, MIDNIGHT, MIDDAY, DAWN, DUSK, SUNRISE, SUNSET, LUNCH, DINNER, BREAKFAST ] + CHANGING = [ + SUNRISE, SUNSET + ] class NumberConstants: diff --git a/datetimeparser/utils/formulars.py b/datetimeparser/utils/formulars.py index e04314e..740c1b9 100644 --- a/datetimeparser/utils/formulars.py +++ b/datetimeparser/utils/formulars.py @@ -49,7 +49,7 @@ def calc_sun_time(dt: datetime, timezone: tuple[float, float, float], sunrise: b """ Calculates the time for sunrise and sunset based on coordinates and a date :param dt: The date for calculating the sunset - :param timezone: A tuple with longitude and magnitude and timezone offset + :param timezone: A tuple with longitude and latitude and timezone offset :param sunrise: If True the sunrise will be calculated if False the sunset :returns: The time for the sunrise/sunset """ diff --git a/datetimeparser/utils/geometry.py b/datetimeparser/utils/geometry.py new file mode 100644 index 0000000..c70e284 --- /dev/null +++ b/datetimeparser/utils/geometry.py @@ -0,0 +1,22 @@ +from timezonefinder import TimezoneFinder + + +class TimeZoneManager(TimezoneFinder): + + def __init__(self): + super(TimeZoneManager, self).__init__(in_memory=True) + + def get_coordinates(self, timezone: str) -> tuple[float, float]: + coords = self.get_geometry(tz_name=timezone, coords_as_pairs=True) + + while not isinstance(coords[0], tuple): + coords = coords[len(coords)//2] + + coords: tuple[float, float] = coords[len(coords)//2] + + # timezone = self.timezone_at(lng=coords[0] + 1, lat=coords[1]) + # TODO: needs to be improved, at the moment it's just a small fix, not tested if it works with all timezones + # TODO: add testcases for ALL timezones if possible to check if the "+1" fix is working + # at the moment it returns "Europe/Belgium" if the timezone "Europe/Berlin" is used -> the "+1" on longitude fixes that + + return coords[0] + 1, coords[1] diff --git a/requirements.txt b/requirements.txt index 0b65956..a1e6ab3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ python-dateutil pytz -typing \ No newline at end of file +typing +timezonefinder diff --git a/tests/runtests.py b/tests/runtests.py index bfc8600..02e9ee3 100644 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -45,10 +45,11 @@ def get_testcase_results(testcase: str, expected_value: datetime.datetime = None if parser_result is None: return StatusType.PARSER_RETURNS_NONE, None - evaluator = Evaluator(parser_result, tz="Europe/Berlin") + evaluator = Evaluator(parser_result, tz="Europe/Berlin", coordinates=(13.41053, 52.52437)) + # Berlin (13.41053, 52.52437), Dubai (55.2962, 25.2684) try: - evaluator_result = evaluator.evaluate() + evaluator_result, _, _ = evaluator.evaluate() except BaseException as error: if expected_value == ThrowException: return StatusType.SUCCESS, "Evaluator threw exception but it was expected" From 100ff1067589d58a20db5b8637e625a75e7a3a9b Mon Sep 17 00:00:00 2001 From: Inf-inity <83883849+Inf-inity@users.noreply.github.com> Date: Thu, 27 Oct 2022 18:09:14 +0200 Subject: [PATCH 03/10] Fixed wrong suntimes, resolved bugs --- datetimeparser/evaluator/evaluator.py | 4 ++-- datetimeparser/evaluator/evaluatormethods.py | 8 ++++++-- datetimeparser/utils/formulars.py | 10 +++++----- tests/runtests.py | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/datetimeparser/evaluator/evaluator.py b/datetimeparser/evaluator/evaluator.py index 3f7bc4e..47c771f 100644 --- a/datetimeparser/evaluator/evaluator.py +++ b/datetimeparser/evaluator/evaluator.py @@ -29,11 +29,11 @@ def __init__(self, parsed_object, tz="Europe/Berlin", coordinates: Optional[tupl self.current_datetime: datetime = datetime.strptime(datetime.strftime(datetime.now(tz=tiz), "%Y-%m-%d %H:%M:%S"), "%Y-%m-%d %H:%M:%S") self.offset = tiz.utcoffset(self.current_datetime) self.timezone = tiz - self.coordinates = coordinates or TimeZoneManager().get_coordinates(tiz.zone) + self.coordinates = coordinates def evaluate(self) -> Union[tuple[datetime, str, tuple[float, float]], None]: ev_out = None - ev = EvaluatorMethods(self.parsed_object_content, self.current_datetime, self.coordinates, self.offset) + ev = EvaluatorMethods(self.parsed_object_content, self.current_datetime, self.coordinates, self.timezone.zone, self.offset) if self.parsed_object_type == Method.ABSOLUTE_DATE_FORMATS: ev_out = ev.evaluate_absolute_date_formats() diff --git a/datetimeparser/evaluator/evaluatormethods.py b/datetimeparser/evaluator/evaluatormethods.py index 1130edf..e3e629d 100644 --- a/datetimeparser/evaluator/evaluatormethods.py +++ b/datetimeparser/evaluator/evaluatormethods.py @@ -3,6 +3,7 @@ from datetimeparser.utils.enums import * from datetimeparser.utils.exceptions import InvalidValue from datetimeparser.utils.formulars import calc_sun_time +from datetimeparser.utils.geometry import TimeZoneManager class EvaluatorMethods(EvaluatorUtils): @@ -10,7 +11,7 @@ class EvaluatorMethods(EvaluatorUtils): Evaluates a datetime-object from a given list returned from the parser """ - def __init__(self, parsed, current_time: datetime, coordinates: tuple[float, float], offset: timedelta = None): + def __init__(self, parsed, current_time: datetime, coordinates: tuple[float, float], timezone: str, offset: timedelta = None): """ :param parsed: object returned from the parser :param current_time: the current datetime @@ -22,6 +23,7 @@ def __init__(self, parsed, current_time: datetime, coordinates: tuple[float, flo self.current_time = current_time self.offset = offset self.coordinates = coordinates + self.timezone = timezone def evaluate_absolute_date_formats(self) -> datetime: ev_out = datetime( @@ -128,9 +130,11 @@ def evaluate_constants(self) -> datetime: elif object_type.name == "sunset" or object_type.name == "sunrise": ofs = self.offset.total_seconds()/60/60 # -> to hours # TODO: at the moment summer and winter time change the result for the offset around 1 hour + if not self.coordinates: + self.coordinates = TimeZoneManager().get_coordinates(self.timezone) dt = calc_sun_time( self.current_time, - (self.coordinates[0], self.coordinates[1], ofs), # something does not work properly, Berlin works with offset 0, Dubai not, idk why + (self.coordinates[0], self.coordinates[1], ofs), object_type.name == "sunrise" ) diff --git a/datetimeparser/utils/formulars.py b/datetimeparser/utils/formulars.py index 740c1b9..cea869d 100644 --- a/datetimeparser/utils/formulars.py +++ b/datetimeparser/utils/formulars.py @@ -57,7 +57,6 @@ def calc_sun_time(dt: datetime, timezone: tuple[float, float, float], sunrise: b to_rad: float = math.pi / 180 day: int = day_of_year(dt) longitude_to_hour = timezone[0] / 15 - b = timezone[1] * to_rad h = -50 * to_rad / 60 @@ -66,12 +65,12 @@ def calc_sun_time(dt: datetime, timezone: tuple[float, float, float], sunrise: b time_difference = 12 * math.acos((math.sin(h) - math.sin(b) * math. sin(declination)) / (math.cos(b) * math.cos(declination))) / math.pi - if sunrise: - time = 12 - time_difference + if sunrise: # woz -> True time at location + woz = 12 - time_difference else: - time = 12 + time_difference + woz = 12 + time_difference - time: float = (time - time_equation) + longitude_to_hour + timezone[2] + time: float = (woz - time_equation) - longitude_to_hour + timezone[2] hour: int = int(time) minutes_left: float = time - int(time) @@ -82,3 +81,4 @@ def calc_sun_time(dt: datetime, timezone: tuple[float, float, float], sunrise: b out: datetime = datetime(year=dt.year, month=dt.month, day=dt.day, hour=hour, minute=minute, second=second) return out + diff --git a/tests/runtests.py b/tests/runtests.py index 02e9ee3..5e478e4 100644 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -45,7 +45,7 @@ def get_testcase_results(testcase: str, expected_value: datetime.datetime = None if parser_result is None: return StatusType.PARSER_RETURNS_NONE, None - evaluator = Evaluator(parser_result, tz="Europe/Berlin", coordinates=(13.41053, 52.52437)) + evaluator = Evaluator(parser_result, tz="Europe/Berlin", coordinates=None) # Berlin (13.41053, 52.52437), Dubai (55.2962, 25.2684) try: From 7ff03cf0d6e23a89c624a390952cfc09b3190a5c Mon Sep 17 00:00:00 2001 From: Inf-inity <83883849+Inf-inity@users.noreply.github.com> Date: Thu, 27 Oct 2022 18:42:05 +0200 Subject: [PATCH 04/10] Added Result object as return value, containing the time, the timezone and if used, coordinates --- datetimeparser/datetimeparser.py | 13 +++++++------ datetimeparser/utils/models.py | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 datetimeparser/utils/models.py diff --git a/datetimeparser/datetimeparser.py b/datetimeparser/datetimeparser.py index 540e83b..7b6e36b 100644 --- a/datetimeparser/datetimeparser.py +++ b/datetimeparser/datetimeparser.py @@ -2,22 +2,22 @@ Main module which provides the parse function. """ -__all__ = ['parse', '__version__', '__author__'] +__all__ = ['parse', '__version__', '__author__', 'Result'] __version__ = "0.13.5" __author__ = "aridevelopment" -import datetime from typing import Optional from datetimeparser.evaluator import Evaluator from datetimeparser.parser import Parser +from datetimeparser.utils.models import Result def parse( datetime_string: str, timezone: str = "Europe/Berlin", coordinates: Optional[tuple[float, float]] = None -) -> Optional[tuple[datetime.datetime, str, tuple[float, float]]]: +) -> Optional[Result]: """ Parses a datetime string and returns a datetime object. If the datetime string cannot be parsed, None is returned. @@ -27,16 +27,17 @@ def parse( :param coordinates: A tuple containing longitude and latitude. If coordinates are given, the timezone will be calculated, independently of the given timezone param NOTE: It takes a longer time to calculate the timezone, it can happen that it takes up to 30 seconds for a result - :return: A datetime object or None + :return: A result object containing the returned time, the timezone and optional coordinates. + If the process fails, None will be returned """ parser_result = Parser(datetime_string).parse() if parser_result is None: return None - evaluator_result, tz, coords = Evaluator(parser_result, tz=timezone, coordinates=coordinates).evaluate() + evaluator_result, tz, coordinates = Evaluator(parser_result, tz=timezone, coordinates=coordinates).evaluate() if evaluator_result is None: return None - return evaluator_result, tz, coords + return Result(evaluator_result, tz, coordinates) diff --git a/datetimeparser/utils/models.py b/datetimeparser/utils/models.py new file mode 100644 index 0000000..a666e54 --- /dev/null +++ b/datetimeparser/utils/models.py @@ -0,0 +1,18 @@ +from datetime import datetime + + +class Result: + time: datetime + timezone: str + coordinates: tuple[float, float] + + def __init__(self, time, timezone: str, coordinates: tuple[float, float] = None): + self.time = time + self.timezone = timezone + self.coordinates = coordinates + + def __repr__(self): + out: str = "'None'" + if self.coordinates: + out: str = f"[longitude='{self.coordinates[0]}', latitude='{self.coordinates[1]}]'" + return f"" From 4cf17d3b83a9f54ed95399a25dec257388b971b4 Mon Sep 17 00:00:00 2001 From: Inf-inity <83883849+Inf-inity@users.noreply.github.com> Date: Thu, 27 Oct 2022 18:55:11 +0200 Subject: [PATCH 05/10] Changed order in __all__ list --- datetimeparser/datetimeparser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datetimeparser/datetimeparser.py b/datetimeparser/datetimeparser.py index 7b6e36b..21b147f 100644 --- a/datetimeparser/datetimeparser.py +++ b/datetimeparser/datetimeparser.py @@ -2,7 +2,7 @@ Main module which provides the parse function. """ -__all__ = ['parse', '__version__', '__author__', 'Result'] +__all__ = ['parse', 'Result', '__version__', '__author__'] __version__ = "0.13.5" __author__ = "aridevelopment" From d4c5248b1f7df360ca019743efdcf8c252ad140a Mon Sep 17 00:00:00 2001 From: Inf-inity <83883849+Inf-inity@users.noreply.github.com> Date: Thu, 27 Oct 2022 18:55:56 +0200 Subject: [PATCH 06/10] Changed version number --- datetimeparser/datetimeparser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datetimeparser/datetimeparser.py b/datetimeparser/datetimeparser.py index 21b147f..b139c57 100644 --- a/datetimeparser/datetimeparser.py +++ b/datetimeparser/datetimeparser.py @@ -3,7 +3,7 @@ """ __all__ = ['parse', 'Result', '__version__', '__author__'] -__version__ = "0.13.5" +__version__ = "0.14.0" __author__ = "aridevelopment" from typing import Optional From 4d1c17a94ce9b1e37a2533d09a85822bf6c77779 Mon Sep 17 00:00:00 2001 From: Inf-inity <83883849+Inf-inity@users.noreply.github.com> Date: Fri, 28 Oct 2022 11:56:54 +0200 Subject: [PATCH 07/10] Resolved conversation + fixed some bugs --- datetimeparser/datetimeparser.py | 4 ++-- datetimeparser/evaluator/evaluator.py | 9 +++++---- datetimeparser/evaluator/evaluatormethods.py | 14 ++++++++++---- datetimeparser/utils/models.py | 5 ++++- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/datetimeparser/datetimeparser.py b/datetimeparser/datetimeparser.py index b139c57..0fb05f1 100644 --- a/datetimeparser/datetimeparser.py +++ b/datetimeparser/datetimeparser.py @@ -25,8 +25,8 @@ def parse( :param datetime_string: The datetime string to parse. :param timezone: The timezone to use. Should be a valid timezone for pytz.timezone(). Default: Europe/Berlin :param coordinates: A tuple containing longitude and latitude. If coordinates are given, the timezone will be calculated, - independently of the given timezone param - NOTE: It takes a longer time to calculate the timezone, it can happen that it takes up to 30 seconds for a result + independently of the given timezone param. + NOTE: It can take some seconds until a result is returned :return: A result object containing the returned time, the timezone and optional coordinates. If the process fails, None will be returned """ diff --git a/datetimeparser/evaluator/evaluator.py b/datetimeparser/evaluator/evaluator.py index 47c771f..1503b8b 100644 --- a/datetimeparser/evaluator/evaluator.py +++ b/datetimeparser/evaluator/evaluator.py @@ -32,8 +32,9 @@ def __init__(self, parsed_object, tz="Europe/Berlin", coordinates: Optional[tupl self.coordinates = coordinates def evaluate(self) -> Union[tuple[datetime, str, tuple[float, float]], None]: - ev_out = None - ev = EvaluatorMethods(self.parsed_object_content, self.current_datetime, self.coordinates, self.timezone.zone, self.offset) + ev_out: Optional[datetime] = None + coordinates: Optional[tuple[float, float]] = None + ev = EvaluatorMethods(self.parsed_object_content, self.current_datetime, self.timezone.zone, self.coordinates, self.offset) if self.parsed_object_type == Method.ABSOLUTE_DATE_FORMATS: ev_out = ev.evaluate_absolute_date_formats() @@ -42,7 +43,7 @@ def evaluate(self) -> Union[tuple[datetime, str, tuple[float, float]], None]: ev_out = ev.evaluate_absolute_prepositions() if self.parsed_object_type == Method.CONSTANTS: - ev_out = ev.evaluate_constants() + ev_out, coordinates = ev.evaluate_constants() if self.parsed_object_type == Method.RELATIVE_DATETIMES: ev_out = ev.evaluate_relative_datetime() @@ -54,6 +55,6 @@ def evaluate(self) -> Union[tuple[datetime, str, tuple[float, float]], None]: ev_out = ev.evaluate_datetime_delta_constants() if ev_out: - return ev_out, self.timezone.zone, self.coordinates + return ev_out, self.timezone.zone, self.coordinates or coordinates else: raise FailedEvaluation(self.parsed_object_content) diff --git a/datetimeparser/evaluator/evaluatormethods.py b/datetimeparser/evaluator/evaluatormethods.py index e3e629d..2aab74e 100644 --- a/datetimeparser/evaluator/evaluatormethods.py +++ b/datetimeparser/evaluator/evaluatormethods.py @@ -1,3 +1,5 @@ +from typing import Any, Optional + from datetimeparser.evaluator.evaluatorutils import EvaluatorUtils from datetimeparser.utils.baseclasses import * from datetimeparser.utils.enums import * @@ -11,10 +13,13 @@ class EvaluatorMethods(EvaluatorUtils): Evaluates a datetime-object from a given list returned from the parser """ - def __init__(self, parsed, current_time: datetime, coordinates: tuple[float, float], timezone: str, offset: timedelta = None): + def __init__( + self, parsed: Any, current_time: datetime, timezone: str, coordinates: Optional[tuple[float, float]], offset: timedelta = None + ): """ :param parsed: object returned from the parser :param current_time: the current datetime + :param timezone: the given timezone :param coordinates: coordinates from the timezone :param offset: the UTC-offset from the current timezone. Default: None """ @@ -105,7 +110,7 @@ def evaluate_absolute_prepositions(self) -> datetime: return base - def evaluate_constants(self) -> datetime: + def evaluate_constants(self) -> tuple[datetime, Optional[tuple[float, float]]]: dt: datetime = self.current_time object_type: Constant = self.parsed[0] @@ -132,6 +137,7 @@ def evaluate_constants(self) -> datetime: # TODO: at the moment summer and winter time change the result for the offset around 1 hour if not self.coordinates: self.coordinates = TimeZoneManager().get_coordinates(self.timezone) + dt = calc_sun_time( self.current_time, (self.coordinates[0], self.coordinates[1], ofs), @@ -149,7 +155,7 @@ def evaluate_constants(self) -> datetime: minute=dt[1], second=dt[2] ) - return dt + return dt, self.coordinates if self.current_time >= dt and self.parsed[0] not in ( Constants.ALL_RELATIVE_CONSTANTS and WeekdayConstants.ALL and DatetimeDeltaConstants.CHANGING @@ -166,7 +172,7 @@ def evaluate_constants(self) -> datetime: if object_type.offset: ev_out = self.add_relative_delta(ev_out, self.get_offset(object_type, self.offset), self.current_time) - return ev_out + return ev_out, self.coordinates def evaluate_relative_datetime(self) -> datetime: out: datetime = self.current_time diff --git a/datetimeparser/utils/models.py b/datetimeparser/utils/models.py index a666e54..e32fc24 100644 --- a/datetimeparser/utils/models.py +++ b/datetimeparser/utils/models.py @@ -6,7 +6,7 @@ class Result: timezone: str coordinates: tuple[float, float] - def __init__(self, time, timezone: str, coordinates: tuple[float, float] = None): + def __init__(self, time: datetime, timezone: str, coordinates: tuple[float, float] = None): self.time = time self.timezone = timezone self.coordinates = coordinates @@ -16,3 +16,6 @@ def __repr__(self): if self.coordinates: out: str = f"[longitude='{self.coordinates[0]}', latitude='{self.coordinates[1]}]'" return f"" + + def __str__(self): + return self.__repr__() From a97ed72213dfc63003c5a303c13f1267830b063f Mon Sep 17 00:00:00 2001 From: Inf-inity <83883849+Inf-inity@users.noreply.github.com> Date: Fri, 28 Oct 2022 12:09:39 +0200 Subject: [PATCH 08/10] Adjusted README.md + added more examples --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5ffed22..7a18d62 100644 --- a/README.md +++ b/README.md @@ -30,16 +30,26 @@ Below you can find some examples of how datetimeparser can be used. from datetimeparser import parse print(parse("next 3 years and 2 months")) -# 2025-04-06 11:43:28 +# print(parse("begin of advent of code 2022")) -# 2022-12-01 06:00:00 +# print(parse("in 1 Year 2 months 3 weeks 4 days 5 hours 6 minutes 7 seconds")) -# 2023-05-01 17:59:52 +# print(parse("10 days and 2 hours after 3 months before christmas 2020")) -# 2020-10-05 02:00:00 +# + +print(parse("sunrise")) +# + +print(parse("sunrise", timezone="Asia/Dubai")) +# + +# https://www.timeanddate.com/sun/japan/tokyo states that the sunset today (2022-10-28) is at '16:50' in Tokyo +print(parse("sunset", coordinates=(139.839478, 35.652832))) # (Tokyo in Japan) +# ``` ## Installation From c159fcab0b1ec3efc985dbd3d0443afd24630305 Mon Sep 17 00:00:00 2001 From: Inf-inity <83883849+Inf-inity@users.noreply.github.com> Date: Fri, 28 Oct 2022 12:15:24 +0200 Subject: [PATCH 09/10] Fixed linting --- datetimeparser/evaluator/evaluatormethods.py | 2 +- datetimeparser/evaluator/evaluatorutils.py | 2 +- datetimeparser/utils/formulars.py | 1 - datetimeparser/utils/geometry.py | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/datetimeparser/evaluator/evaluatormethods.py b/datetimeparser/evaluator/evaluatormethods.py index 2aab74e..7ae69b4 100644 --- a/datetimeparser/evaluator/evaluatormethods.py +++ b/datetimeparser/evaluator/evaluatormethods.py @@ -133,7 +133,7 @@ def evaluate_constants(self) -> tuple[datetime, Optional[tuple[float, float]]]: ) elif object_type.name == "sunset" or object_type.name == "sunrise": - ofs = self.offset.total_seconds()/60/60 # -> to hours + ofs = self.offset.total_seconds() / 60 / 60 # -> to hours # TODO: at the moment summer and winter time change the result for the offset around 1 hour if not self.coordinates: self.coordinates = TimeZoneManager().get_coordinates(self.timezone) diff --git a/datetimeparser/evaluator/evaluatorutils.py b/datetimeparser/evaluator/evaluatorutils.py index f769fca..5a32d9d 100644 --- a/datetimeparser/evaluator/evaluatorutils.py +++ b/datetimeparser/evaluator/evaluatorutils.py @@ -109,7 +109,7 @@ def sanitize_input( current_time ) if current_time > test_out and not given_year: - parsed_list = EvaluatorUtils.x_week_of_month(relative_dt, idx, pars2, year+1) + parsed_list = EvaluatorUtils.x_week_of_month(relative_dt, idx, pars2, year + 1) return list(filter(lambda e: e not in Keywords.ALL and not isinstance(e, str), parsed_list)), given_year diff --git a/datetimeparser/utils/formulars.py b/datetimeparser/utils/formulars.py index cea869d..62dfc4a 100644 --- a/datetimeparser/utils/formulars.py +++ b/datetimeparser/utils/formulars.py @@ -81,4 +81,3 @@ def calc_sun_time(dt: datetime, timezone: tuple[float, float, float], sunrise: b out: datetime = datetime(year=dt.year, month=dt.month, day=dt.day, hour=hour, minute=minute, second=second) return out - diff --git a/datetimeparser/utils/geometry.py b/datetimeparser/utils/geometry.py index c70e284..2949e72 100644 --- a/datetimeparser/utils/geometry.py +++ b/datetimeparser/utils/geometry.py @@ -10,9 +10,9 @@ def get_coordinates(self, timezone: str) -> tuple[float, float]: coords = self.get_geometry(tz_name=timezone, coords_as_pairs=True) while not isinstance(coords[0], tuple): - coords = coords[len(coords)//2] + coords = coords[len(coords) // 2] - coords: tuple[float, float] = coords[len(coords)//2] + coords: tuple[float, float] = coords[len(coords) // 2] # timezone = self.timezone_at(lng=coords[0] + 1, lat=coords[1]) # TODO: needs to be improved, at the moment it's just a small fix, not tested if it works with all timezones From 4a2449e9173caa317b35cb8d16b213f3d19d7cea Mon Sep 17 00:00:00 2001 From: Inf-inity <83883849+Inf-inity@users.noreply.github.com> Date: Sat, 29 Oct 2022 12:22:04 +0200 Subject: [PATCH 10/10] Adjusted README.md + added docstring for Result class --- README.md | 16 ++++++++-------- datetimeparser/utils/models.py | 9 +++++++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7a18d62..064bec4 100644 --- a/README.md +++ b/README.md @@ -29,17 +29,17 @@ Below you can find some examples of how datetimeparser can be used. ```python from datetimeparser import parse -print(parse("next 3 years and 2 months")) -# +print(parse("next 3 years and 2 months").time) +# 2025-12-28 11:57:25 -print(parse("begin of advent of code 2022")) -# +print(parse("begin of advent of code 2022").time) +# 2022-12-01 06:00:00 -print(parse("in 1 Year 2 months 3 weeks 4 days 5 hours 6 minutes 7 seconds")) -# +print(parse("in 1 Year 2 months 3 weeks 4 days 5 hours 6 minutes 7 seconds").time) +# 2024-01-22 17:04:26 -print(parse("10 days and 2 hours after 3 months before christmas 2020")) -# +print(parse("10 days and 2 hours after 3 months before christmas 2020").time) +# 2020-10-05 02:00:00 print(parse("sunrise")) # diff --git a/datetimeparser/utils/models.py b/datetimeparser/utils/models.py index e32fc24..a0705e0 100644 --- a/datetimeparser/utils/models.py +++ b/datetimeparser/utils/models.py @@ -2,6 +2,15 @@ class Result: + """ + The returned Result by the parse function, containing the output information + + - Attributes: + - time (datetime): The parsed time + - timezone (str): The used timezone + - coordinates (Optional[tuple[float, float]]): Coordinates used for parsing + + """ time: datetime timezone: str coordinates: tuple[float, float]