Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: v1.0.0: Dev to main Merge #206

Draft
wants to merge 76 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
0ee66ef
Fixed bugs and added half an hour relatives (WIP)
Ari24-cb24 May 1, 2022
2d5d2f3
Fixed typing issue
Ari24-cb24 May 1, 2022
f38f04c
Merge pull request #180 from aridevelopment-de/main
Jun 5, 2022
368f578
Added custom exception + fixed runtest.py
Jun 5, 2022
5f0c8b0
Fixed version
Jun 5, 2022
ad24a4a
Updated README.md
Jun 12, 2022
0a137f5
Updated file structure
Jun 12, 2022
299d712
Updated version number
Jun 12, 2022
83065c0
Updated version number
Jun 12, 2022
1c31c76
Merge pull request #181 from aridevelopment-de/improvement/file_struc…
Jun 16, 2022
dd3ee14
Improved exceptions for evaluator + removed useless function
Jun 16, 2022
a6c8617
Merge pull request #183 from aridevelopment-de/evaluator/improvements
Jun 16, 2022
d9f8a56
Fixed evaluator-bug returning wrong year
Jun 22, 2022
6882d88
Merge pull request #185 from aridevelopment-de/evaluator/bug_fix
Jun 22, 2022
4a3c5db
Fixed bug where 'next <WEEKDAY>' raises an error, if the wekday is th…
Jul 29, 2022
39f4673
Merge pull request #187 from aridevelopment-de/evaluator/bugfix
Jul 29, 2022
73fd635
Fixed bug in evaluator, using the wrong year for 'first of x' if date…
Aug 10, 2022
d6e52bc
Merge pull request #188 from aridevelopment-de/evaluator/bugfix
Aug 10, 2022
1dc20b2
Removed duplication in .gitignore
Aug 10, 2022
b9ba81e
Fixed arguments longer than 2 words in cutoff_words couldn't be recog…
Ari24-cb24 Aug 23, 2022
8fd611e
Added "after" cutoff word to ConstantsParser to fix a parsing issue w…
Ari24-cb24 Aug 23, 2022
411d9c7
Added testcases without validation for #45
Ari24-cb24 Aug 23, 2022
767e559
Fixed testrunner issue where exceptions and returned none where swapp…
Ari24-cb24 Aug 23, 2022
dbad71f
Bump of version number to 0.13.3
Ari24-cb24 Aug 23, 2022
f073eae
Fixed linting issues
Ari24-cb24 Aug 23, 2022
4942da3
Merge pull request #189 from aridevelopment-de/parser/bugfix
Ari24-cb24 Aug 23, 2022
ae073d6
Fixed bug with TimeConstants (f.e. 'in the morning')
Sep 11, 2022
dca96dc
Fixed bug with 'of' (f.e. 'x week of y')
Sep 11, 2022
41a131a
Fixed small mistake caused by wrong return value
Sep 11, 2022
6ade412
Changed version number
Sep 11, 2022
859f90f
Merge pull request #190 from aridevelopment-de/evaluator/bugfixes
Sep 11, 2022
5fb827c
Fixed ConstantRelativeExtensionParser not accepting arguments like "t…
Ari24-cb24 Sep 11, 2022
5b4a4ee
Merge pull request #191 from aridevelopment-de/parser/bugfix
Ari24-cb24 Sep 11, 2022
d3b136d
Fixed typingdown to 3.7
Sep 11, 2022
fabfb4d
Fixed version number
Sep 11, 2022
856a0c9
Merge pull request #192 from aridevelopment-de/improvement/typing
Sep 11, 2022
512d3fc
Fixed matrix strategy failing-fast
Ari24-cb24 Sep 11, 2022
0c067b5
Merge pull request #194 from aridevelopment-de/actions/workflow_runne…
Ari24-cb24 Sep 11, 2022
1cb9496
Added formula for calulationg sunrise/sunset
Oct 3, 2022
c6749f6
Started to implement coordinates for sunrise/sunset calculation (not …
Oct 27, 2022
100ff10
Fixed wrong suntimes, resolved bugs
Oct 27, 2022
7ff03cf
Added Result object as return value, containing the time, the timezon…
Oct 27, 2022
4cf17d3
Changed order in __all__ list
Oct 27, 2022
d4c5248
Changed version number
Oct 27, 2022
4d1c17a
Resolved conversation + fixed some bugs
Oct 28, 2022
a97ed72
Adjusted README.md + added more examples
Oct 28, 2022
c159fca
Fixed linting
Oct 28, 2022
4a2449e
Adjusted README.md + added docstring for Result class
Oct 29, 2022
7ee7c1c
Merge pull request #195 from aridevelopment-de/feature/suntime
Oct 29, 2022
9de1335
Fixed wrong testcases
Oct 29, 2022
e67e359
Merge pull request #196 from aridevelopment-de/testcases/adjustment
Oct 29, 2022
4464589
Added fix for summer/winter-time issue
Oct 29, 2022
1ee8bb8
Fixed typing
Oct 29, 2022
f011222
Merge pull request #197 from aridevelopment-de/feature/summer_winter_…
Oct 30, 2022
b38c340
fix requirements in setup.py
AlbertUnruh Nov 5, 2022
42fb173
Merge pull request #199 from AlbertUnruh/patch-1
Nov 5, 2022
d8798ce
retrieve `__version__` via regex
AlbertUnruh Nov 5, 2022
c86db5b
Merge pull request #201 from AlbertUnruh/patch-2
Nov 6, 2022
f2408f3
remove `packages=` (setup.py)
AlbertUnruh Nov 9, 2022
426d6c1
add `packages` (setup.cfg)
AlbertUnruh Nov 9, 2022
99c0884
Merge pull request #202 from AlbertUnruh/AlbertUnruh-patch-3
Ari24-cb24 Nov 10, 2022
5c01aa8
Removed pytz as package and replaced it with ZoneInfo from the zonein…
Dec 31, 2022
e828db7
Added tzdata package for windows
Dec 31, 2022
20f23be
fix version
AlbertUnruh Mar 28, 2023
16318da
Merge pull request #204 from AlbertUnruh/patch-1
Ari24-cb24 Mar 28, 2023
d95e85a
Fixed some testcases not taking current year into account
Ari24-cb24 Mar 28, 2023
e69c87b
Fixed #198
Ari24-cb24 Mar 28, 2023
bbb2ef4
Fixed one being prepended if first keyword was a NumberCountConstant …
Ari24-cb24 Mar 28, 2023
bd79820
Added testcases
Ari24-cb24 Mar 28, 2023
c829942
Fixed wrong testcase
Mar 30, 2023
9be7bfa
Adjusted evaluator
Mar 30, 2023
92786bf
Removed debug print-statements
Mar 30, 2023
a1ea008
Adjusted code
Mar 30, 2023
6e49149
Merge pull request #205 from aridevelopment-de/#198/dev
Ari24-cb24 Apr 3, 2023
213f9a0
Added 3.11 to GitHub workflow test matrix
Ari24-cb24 Apr 5, 2023
552e17c
Bumped version to 1.0.0
Ari24-cb24 Apr 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]

steps:
- uses: actions/checkout@v2
Expand Down
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,3 @@ dmypy.json

# Pyre type checker
.pyre/

/.idea
.idea/
34 changes: 28 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,27 @@ 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"))
# 2025-04-06 11:43:28
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"))
# 2023-05-01 17:59:52
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"))
# <Result: time='2022-10-28 08:15:19', timezone='Europe/Berlin', coordinates=[longitude='7.188402', latitude='50.652927999999996]'>

print(parse("sunrise", timezone="Asia/Dubai"))
# <Result: time='2022-10-28 06:23:17', timezone='Asia/Dubai', coordinates=[longitude='55.455098', latitude='25.269651999999997]'>

# 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)
# <Result: time='2022-10-28 16:50:04', timezone='Asia/Tokyo', coordinates=[longitude='139.839478', latitude='35.652832]'>
```

## Installation
Expand Down Expand Up @@ -115,6 +125,18 @@ We highly appreciate everyone who wants to help our project!
<li>valentine day</li>
</ul>
</details><details>
<summary><code>pi day</code></summary>
<ul>
<li>piday</li>
<li>pi-day</li>
</ul>
</details><details>
<summary><code>tau day</code></summary>
<ul>
<li>tauday</li>
<li>tau-day</li>
</ul>
</details><details>
<summary><code>summer end</code></summary>
<ul>
<li>end of summer</li>
Expand Down
5 changes: 2 additions & 3 deletions datetimeparser/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from datetimeparser import parser
from datetimeparser import evaluator
from datetimeparser import enums
from datetimeparser import baseclasses
from datetimeparser import parsermethods
from datetimeparser.utils import baseclasses, enums
from datetimeparser.parser import parsermethods

from datetimeparser.datetimeparser import parse
24 changes: 16 additions & 8 deletions datetimeparser/datetimeparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,42 @@
Main module which provides the parse function.
"""

__all__ = ['parse', '__version__', '__author__']
__version__ = "0.12.2"
__all__ = ['parse', 'Result', '__version__', '__author__']
__version__ = "1.0.0"
__author__ = "aridevelopment"

import datetime
from typing import Union
from typing import Optional, Tuple

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") -> Union[datetime.datetime, None]:
def parse(
datetime_string: str,
timezone: str = "Europe/Berlin",
coordinates: Optional[Tuple[float, float]] = None
) -> Optional[Result]:
"""
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
:return: A datetime object or None
: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 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
"""
parser_result = Parser(datetime_string).parse()

if parser_result is None:
return None

evaluator_result = Evaluator(parser_result, tz=timezone).evaluate()
evaluator_result, tz, coordinates = Evaluator(parser_result, tz=timezone, coordinates=coordinates).evaluate()

if evaluator_result is None:
return None

return evaluator_result
return Result(evaluator_result, tz, coordinates)
52 changes: 0 additions & 52 deletions datetimeparser/evaluator.py

This file was deleted.

1 change: 1 addition & 0 deletions datetimeparser/evaluator/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .evaluator import Evaluator
60 changes: 60 additions & 0 deletions datetimeparser/evaluator/evaluator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from datetime import datetime
from typing import Optional, Tuple, Union
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError

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", 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 = ZoneInfo(tz)
except ZoneInfoNotFoundError:
raise InvalidValue(f"Unknown timezone: '{tz}'")

self.parsed_object_type = parsed_object[0]
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: ZoneInfo = tiz
self.coordinates = coordinates

def evaluate(self) -> Union[Tuple[datetime, str, Tuple[float, float]], None]:
ev_out: Optional[datetime] = None
coordinates: Optional[Tuple[float, float]] = None
ev = EvaluatorMethods(self.parsed_object_content, self.current_datetime, self.timezone.key, self.coordinates, self.offset)

if self.parsed_object_type == Method.ABSOLUTE_DATE_FORMATS:
ev_out = ev.evaluate_absolute_date_formats()

if self.parsed_object_type == Method.ABSOLUTE_PREPOSITIONS:
ev_out = ev.evaluate_absolute_prepositions()

if self.parsed_object_type == Method.CONSTANTS:
ev_out, coordinates = ev.evaluate_constants()

if self.parsed_object_type == Method.RELATIVE_DATETIMES:
ev_out = ev.evaluate_relative_datetime()

if self.parsed_object_type == Method.CONSTANTS_RELATIVE_EXTENSIONS:
ev_out = ev.evaluate_constant_relatives()

if self.parsed_object_type == Method.DATETIME_DELTA_CONSTANTS:
ev_out = ev.evaluate_datetime_delta_constants()

if ev_out:
return ev_out, self.timezone.key, self.coordinates or coordinates
else:
raise FailedEvaluation(self.parsed_object_content)
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
from .baseclasses import *
from .enums import *
from .evaluatorutils import EvaluatorUtils
from typing import Any, Optional, Tuple

from datetimeparser.evaluator.evaluatorutils import EvaluatorUtils
from datetimeparser.utils.baseclasses import *
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):
"""
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: 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
"""

self.parsed = parsed
self.current_time = current_time
self.offset = offset
self.coordinates = coordinates
self.timezone = timezone

def evaluate_absolute_date_formats(self) -> datetime:
ev_out = datetime(
Expand All @@ -32,7 +43,7 @@ def evaluate_absolute_date_formats(self) -> datetime:
return ev_out

def evaluate_constant_relatives(self) -> datetime:
sanitized = self.sanitize_input(self.current_time, self.parsed)
sanitized, _ = self.sanitize_input(self.current_time, self.parsed)
base: datetime = self.current_time
ev_out = None

Expand All @@ -45,7 +56,7 @@ def evaluate_constant_relatives(self) -> datetime:
ev_out = datetime(base.year, base.month, base.day, hour, minute, sec)

elif isinstance(sanitized[-1], RelativeDateTime):
base += self.prepare_relative_delta(sanitized[-1])
base = self.add_relative_delta(base, sanitized[-1], self.current_time)

if sanitized[-2] in WeekdayConstants.ALL:
base = self.cut_time(base)
Expand Down Expand Up @@ -89,14 +100,17 @@ def evaluate_constant_relatives(self) -> datetime:

def evaluate_absolute_prepositions(self) -> datetime:
base_year = self.current_time.year
sanitized = self.sanitize_input(self.current_time, self.parsed)
base = self.get_base(sanitized, base_year, self.current_time)
sanitized, given_year = self.sanitize_input(self.current_time, self.parsed)
if not given_year:
base = self.get_base(sanitized, base_year, self.current_time)
else:
base = self.get_base(sanitized, given_year, self.current_time, forced=True)
rel_out = self.calc_relative_time(sanitized)
base += self.prepare_relative_delta(rel_out)
base = self.add_relative_delta(base, rel_out, self.current_time)

return self.remove_milli_seconds(base)
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]

Expand All @@ -109,18 +123,29 @@ def evaluate_constants(self) -> datetime:
dt = object_type.time_value(object_year + 1)

else:
if object_type.name == "infinity":
raise ValueError("'infinity' isn't a valid time")
if object_type.name == "infinity": # TODO: has to be improved for more invalid constants if needed
raise InvalidValue(object_type.name)

elif object_type in WeekdayConstants.ALL:
dt: datetime = datetime.strptime(
object_type.time_value(self.cut_time(self.current_time)),
"%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
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),
object_type.name == "sunrise"
)

else:
dt = object_type.time_value(self.current_time.year)

if isinstance(dt, tuple):
dt = datetime(
year=self.current_time.year,
Expand All @@ -130,23 +155,31 @@ def evaluate_constants(self) -> datetime:
minute=dt[1],
second=dt[2]
)
return dt, self.coordinates

if self.current_time > dt and self.parsed[0] not in Constants.ALL_RELATIVE_CONSTANTS:
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:
dt += relativedelta(days=7)

ev_out = datetime(
dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second
)

if object_type.offset:
ev_out += self.prepare_relative_delta(self.get_offset(object_type, self.offset))
ev_out = self.add_relative_delta(ev_out, self.get_offset(object_type, self.offset), self.current_time)
if self.daylight_saving(self.timezone):
ev_out -= timedelta(hours=1)

return ev_out
return ev_out, self.coordinates

def evaluate_relative_datetime(self) -> datetime:
out: datetime = self.current_time

out += self.prepare_relative_delta(self.parsed)
out = self.add_relative_delta(out, self.parsed, self.current_time)
ev_out = datetime(
out.year, out.month, out.day, out.hour, out.minute, out.second
)
Expand Down
Loading