Repository: scrapinghub/dateparser

Issue:
v1.2.0 returns wrong month with future dates
tested on 2023-11-27, 15:33 UTC:

v1.1.8:

```
>>> import dateparser
>>> dateparser.parse("Friday 15:53", settings={'PREFER_DATES_FROM': 'future'})
datetime.datetime(2023, 12, 1, 15, 53)
```

v1.2.0:

```
>>> import dateparser
>>> dateparser.parse("Friday 15:53", settings={'PREFER_DATES_FROM': 'future'})
datetime.datetime(2023, 11, 1, 15, 53)
```

"Friday" with preference set to future yields a Tuesday in the past.
```
Python 3.12.7 (main, Oct  1 2024, 02:05:46) [Clang 16.0.0 (clang-1600.0.26.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dateparser
>>> from datetime import datetime, timedelta
>>>
>>> def date(s):
...     parsed_date = dateparser.parse(s, settings={"PREFER_DATES_FROM": "future"})
...     return parsed_date.strftime("%a %b %d")
...
>>>
>>> date("Friday")
'Tue Oct 08'
>>> datetime.now()
datetime.datetime(2024, 10, 31, 21, 37, 14, 810455)
```

I called this method earlier today, before UTC time (I'm -4) was past midnight and I got Oct 1 instead, also a Tuesday. Checking the year reveals that it's indeed Oct 2024 which is in the past.

dateparser==1.2.0
Error parsing weekday which falls on previous year.
Today is Friday, January 03 2025.
This past Monday was December 30, 2024.
When parsing 'Monday' dateparser is returning January 30, 2024 (which was a Tuesday).
When parsing 'Monday 2025' it returns today's date.

parse returns wrong month when using RELATIVE_BASE
Thank you for creating such a useful package and for sharing it with all of us.

I am seeing the wrong month returned if I use a relative base of Wednesday, January 1st, 2025 and the string, "Sun 9am." I expect to see Sunday, December 29th, 2024.

# CALENDAR
For reference, see the calendar view surrounding **RELATIVE_BASE** day, **Wed Jan 1, 2025**:
```
    January 2025       # Jan 2025 is RELATIVE_BASE month
Su Mo Tu We Th Fr Sa   # EXPECTED for "Sun 9am": Sun Dec 29, 2024
29 30 31  1  2  3  4   # RELATIVE_BASE is Wed Jan 1, 2025
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31      

$ cal 1 1 2024        # Jan 2024 (ONE YEAR before RELATIVE_BASE, Wed Jan 1, 2025)
    January 2024
Su Mo Tu We Th Fr Sa
    1  2  3  4  5  6
 7  8  9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31            # ACTUAL for "Sun 9am": Mon??? _Jan_ 29, 2024
```


# EXPECTED BEHAVIOR
date = parse("Sun 9am", settings={'RELATIVE_BASE': datetime(2025, 1, 1)})
2025-01-01 00:00:00: RELATIVE_BASE
**2024-12-29** 09:00:00: "Sun 9am"

# ACTUAL BEHAVIOR
date = parse("Sun 9am", settings={'RELATIVE_BASE': datetime(2025, 1, 1)})
2025-01-01 00:00:00: RELATIVE_BASE
**2024-01-29** 09:00:00: "Sun 9am"

**1/29/2024 is not a Sunday, it is a Monday**    
**And the 1/29/2024 is a year too early** compared to the Jan 1, 2025 relative date


# CODE TO REPLICATE THE ERROR
```
from datetime import datetime
from dateparser import parse


def test_dateparser():
    """Test dateparser"""
    base = datetime(2025, 1, 1)
    timestr = 'Sun 9am'
    date = parse(timestr, settings={'RELATIVE_BASE': base})
    print(f'{base}: RELATIVE_BASE')
    print(f'{date}: "{timestr}"')
```




Discussion:
A workaround is to set the relative base with an explicit time zone (tested on Wednesday January 31st).

```
>>> import dateparser
>>> from datetime import datetime, timezone
>>> dateparser.parse("Thursday at 6pm")
datetime.datetime(2024, 1, 1, 18, 0)
>>> dateparser.parse("Thursday at 6pm", settings={"RELATIVE_BASE": datetime.now(timezone.utc)})
datetime.datetime(2024, 2, 1, 18, 0)
```

EDIT: This may have been because it's late enough in January, that changing the timezone pushed me over into February.
I have a hunch this change is what caused the issue: https://github.com/scrapinghub/dateparser/pull/1179

There is a change from `datetime.datetime.utcnow()` to `datetime.datetime.now(datetime.timezone.utc)`, which changes us from a naive timestamp to a timezone aware timestamp. Most notably [here](https://github.com/scrapinghub/dateparser/pull/1179/commits/b802834397728debf267799477f6b2ad54e399d6#diff-13d11598b0ad7964c1f2b3f88b72ad9aaa4b6d8fd07ec88213fb1496e8dd1dccR89).

However, this is purely a hunch. 
This is still broken in 1.2.1. The workaround above doesn't work with PREFER_DATES_FROM=future. Tested on 2025-05-27.

```
>>> import dateparser
>>> from datetime import datetime, timezone

>>> datetime.now(timezone.utc)
datetime.datetime(2025, 5, 27, 16, 59, 24, 757900, tzinfo=datetime.timezone.utc)

>>> dateparser.parse("Monday", settings={"PREFER_DATES_FROM": "future")})
datetime.datetime(2025, 5, 2, 0, 0)

>>> dateparser.parse("Monday", settings={"PREFER_DATES_FROM": "future", "RELATIVE_BASE": datetime.now(tz=timezone.utc)})
datetime.datetime(2025, 5, 2, 0, 0)
```

Note that the returned date 2025-05-02 is actually a Friday, not a Monday. Expected result is `datetime.datetime(2025, 6, 2, 0, 0)`.

I **think** what is happening is that in `parse()`, the date is correctly set by `_correct_for_time_frame()`, but then the month is changed by `_correct_for_month()` so that the RELATIVE_BASE setting is honoured. RELATIVE_BASE defaults to the current date and time, and can't be set to `None`.

https://github.com/scrapinghub/dateparser/blob/e9324ee9f3a123c51f129eed17d40570dcafd191/dateparser/parser.py#L611-L628


in other news, when I parse "Nov 1" I now get "Sat Nov 1" which is also incorrect since Nov 1 is a friday this year.
doesn't seem like the library is actively maintained, so for anyone running into this issue in the future, this worked for me:

```
def date(s):
    cal = parsedatetime.Calendar()
    time_struct, parse_status = cal.parse(s)
    date = datetime(*time_struct[:6])

    # Format in Eastern timezone if needed

    eastern_date = pytz.timezone("US/Eastern").localize(date)
    return eastern_date.strftime("%a %b %d")
```
This seems to be caused by a small bug in the "absolute-time" parser:
here: https://github.com/scrapinghub/dateparser/blob/47acb889f129fb8433659ccac9a32c3e4de0e082/dateparser/parser.py#L493

the delta is correctly applied. Applying the delta can change the month, but that change is reset using the relative base right afterwards:
https://github.com/scrapinghub/dateparser/blob/47acb889f129fb8433659ccac9a32c3e4de0e082/dateparser/parser.py#L621

see: https://github.com/scrapinghub/dateparser/blob/47acb889f129fb8433659ccac9a32c3e4de0e082/dateparser/utils/__init__.py#L193

I'm also seeing something that I think is related.

```
>>> dateparser.parse('now')
datetime.datetime(2025, 1, 1, 22, 13, 14, 778576)

>>> dateparser.parse('11:30pm friday')
datetime.datetime(2024, 1, 27, 23, 30)

^ actually a saturday

>>> dateparser.parse('11:30pm friday jan')
datetime.datetime(2025, 1, 1, 23, 30)

^ not a friday either
```








Patch:
diff --git a/dateparser/parser.py b/dateparser/parser.py
index 62b22a9ef..9d4ec893d 100644
--- a/dateparser/parser.py
+++ b/dateparser/parser.py
@@ -512,6 +512,10 @@ def _correct_for_time_frame(self, dateobj, tz):
 
             dateobj = dateobj + delta
 
+            # set the token_month here so that it is not subsequently
+            # altered by _correct_for_month
+            self._token_month = dateobj.month
+
         # NOTE: If this assert fires, self.now needs to be made offset-aware in a similar
         # way that dateobj is temporarily made offset-aware.
         assert not (self.now.tzinfo is None and dateobj.tzinfo is not None), (


Test patch:
diff --git a/tests/test_date_parser.py b/tests/test_date_parser.py
index 9be297847..bb920e8b9 100644
--- a/tests/test_date_parser.py
+++ b/tests/test_date_parser.py
@@ -628,6 +628,25 @@ def test_preferably_future_dates(self, date_string, expected):
         self.then_date_was_parsed_by_date_parser()
         self.then_date_obj_exactly_is(expected)
 
+    @parameterized.expand(
+        [
+            param("Monday", datetime(2015, 3, 2)),
+        ]
+    )
+    def test_preferably_future_dates_relative_last_week_of_month(
+        self, date_string, expected
+    ):
+        self.given_local_tz_offset(0)
+        self.given_parser(
+            settings={
+                "PREFER_DATES_FROM": "future",
+                "RELATIVE_BASE": datetime(2015, 2, 24, 15, 30),
+            }
+        )
+        self.when_date_is_parsed(date_string)
+        self.then_date_was_parsed_by_date_parser()
+        self.then_date_obj_exactly_is(expected)
+
     @parameterized.expand(
         [
             param("10 December", datetime(2015, 12, 10)),
diff --git a/tests/test_search.py b/tests/test_search.py
index c59d7d4de..bdc562411 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -410,7 +410,7 @@ def test_search_date_string(self, shortname, datetime_string):
                 "Die UdSSR blieb gemäß dem Neutralitätspakt "
                 "vom 13. April 1941 gegenüber Japan vorerst neutral.",
                 [
-                    ("Die", datetime.datetime(1999, 1, 28, 0, 0)),
+                    ("Die", datetime.datetime(1999, 12, 28, 0, 0)),
                     ("13. April 1941", datetime.datetime(1941, 4, 13, 0, 0)),
                 ],
                 settings={"RELATIVE_BASE": datetime.datetime(2000, 1, 1)},