Spaces:
Paused
Paused
Download tests/unit_tests/utils/date_parser_tests.py from Leon4gr45/powerbi: direct link, hf CLI and curl.
- Browser
- Download file 33.4 kB
-
https://huggingface.co/spaces/Leon4gr45/powerbi/resolve/main/tests/unit_tests/utils/date_parser_tests.py
- Command line
-
hf download hf://spaces/Leon4gr45/powerbi/tests/unit_tests/utils/date_parser_tests.py
-
curl -L -o date_parser_tests.py https://huggingface.co/spaces/Leon4gr45/powerbi/resolve/main/tests/unit_tests/utils/date_parser_tests.py
33.4 kB
| # Licensed to the Apache Software Foundation (ASF) under one | |
| # or more contributor license agreements. See the NOTICE file | |
| # distributed with this work for additional information | |
| # regarding copyright ownership. The ASF licenses this file | |
| # to you under the Apache License, Version 2.0 (the | |
| # "License"); you may not use this file except in compliance | |
| # with the License. You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, | |
| # software distributed under the License is distributed on an | |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| # KIND, either express or implied. See the License for the | |
| # specific language governing permissions and limitations | |
| # under the License. | |
| import re | |
| from datetime import date, datetime, timedelta | |
| from typing import Optional | |
| from unittest.mock import Mock, patch | |
| import freezegun | |
| import pytest | |
| from dateutil.relativedelta import relativedelta | |
| from superset.commands.chart.exceptions import ( | |
| TimeRangeAmbiguousError, | |
| TimeRangeParseFailError, | |
| ) | |
| from superset.utils.date_parser import ( | |
| DateRangeMigration, | |
| datetime_eval, | |
| get_past_or_future, | |
| get_since_until, | |
| is_constant_human_timedelta, | |
| is_parseable_human_timedelta, | |
| normalize_time_delta, | |
| parse_human_datetime, | |
| parse_human_timedelta, | |
| parse_past_timedelta, | |
| TimeDeltaAmbiguousError, | |
| ) | |
| from tests.unit_tests.conftest import with_feature_flags | |
| def mock_parse_human_datetime(s: str) -> Optional[datetime]: # noqa: C901 | |
| if s == "now": | |
| return datetime(2016, 11, 7, 9, 30, 10) | |
| elif s == "2018": | |
| return datetime(2018, 1, 1) | |
| elif s == "2018-9": | |
| return datetime(2018, 9, 1) | |
| elif s == "today": | |
| return datetime(2016, 11, 7) | |
| elif s == "yesterday": | |
| return datetime(2016, 11, 6) | |
| elif s == "tomorrow": | |
| return datetime(2016, 11, 8) | |
| elif s == "Last year": | |
| return datetime(2015, 11, 7) | |
| elif s == "Last week": | |
| return datetime(2015, 10, 31) | |
| elif s == "Last 5 months": | |
| return datetime(2016, 6, 7) | |
| elif s == "Next 5 months": | |
| return datetime(2017, 4, 7) | |
| elif s in ["5 days", "5 days ago"]: | |
| return datetime(2016, 11, 2) | |
| elif s == "2000-01-01T00:00:00": | |
| return datetime(2000, 1, 1) | |
| elif s == "2018-01-01T00:00:00": | |
| return datetime(2018, 1, 1) | |
| elif s == "2018-01-10T00:00:00": | |
| return datetime(2018, 1, 10) | |
| elif s == "2018-12-31T23:59:59": | |
| return datetime(2018, 12, 31, 23, 59, 59) | |
| elif s == "2022-01-01T00:00:00": | |
| return datetime(2022, 1, 1) | |
| else: | |
| return None | |
| def test_get_since_until() -> None: | |
| result: tuple[Optional[datetime], Optional[datetime]] | |
| expected: tuple[Optional[datetime], Optional[datetime]] | |
| result = get_since_until() | |
| expected = None, datetime(2016, 11, 7) | |
| assert result == expected | |
| result = get_since_until(" : now") | |
| expected = None, datetime(2016, 11, 7, 9, 30, 10) | |
| assert result == expected | |
| result = get_since_until("yesterday : tomorrow") | |
| expected = datetime(2016, 11, 6), datetime(2016, 11, 8) | |
| assert result == expected | |
| result = get_since_until(" : now") | |
| expected = None, datetime(2016, 11, 7, 9, 30, 10) | |
| assert result == expected | |
| result = get_since_until(" : last 2 minutes") | |
| expected = None, datetime(2016, 11, 7, 9, 28, 10) | |
| assert result == expected | |
| result = get_since_until(" : prior 2 minutes") | |
| expected = None, datetime(2016, 11, 7, 9, 28, 10) | |
| assert result == expected | |
| result = get_since_until(" : next 2 minutes") | |
| expected = None, datetime(2016, 11, 7, 9, 32, 10) | |
| assert result == expected | |
| result = get_since_until("start of this month : ") | |
| expected = datetime(2016, 11, 1), None | |
| assert result == expected | |
| result = get_since_until("start of next month : ") | |
| expected = datetime(2016, 12, 1), None | |
| assert result == expected | |
| result = get_since_until("end of this month : ") | |
| expected = datetime(2016, 11, 30), None | |
| assert result == expected | |
| result = get_since_until("end of next month : ") | |
| expected = datetime(2016, 12, 31), None | |
| assert result == expected | |
| result = get_since_until("beginning of next year : ") | |
| expected = datetime(2017, 1, 1), None | |
| assert result == expected | |
| result = get_since_until("beginning of last year : ") | |
| expected = datetime(2015, 1, 1), None | |
| assert result == expected | |
| result = get_since_until("2018-01-01T00:00:00 : 2018-12-31T23:59:59") | |
| expected = datetime(2018, 1, 1), datetime(2018, 12, 31, 23, 59, 59) | |
| assert result == expected | |
| result = get_since_until("Last year") | |
| expected = datetime(2015, 11, 7), datetime(2016, 11, 7) | |
| assert result == expected | |
| result = get_since_until("Last quarter") | |
| expected = datetime(2016, 8, 7), datetime(2016, 11, 7) | |
| assert result == expected | |
| result = get_since_until("Last 5 months") | |
| expected = datetime(2016, 6, 7), datetime(2016, 11, 7) | |
| assert result == expected | |
| result = get_since_until("Last 1 month") | |
| expected = datetime(2016, 10, 7), datetime(2016, 11, 7) | |
| assert result == expected | |
| result = get_since_until("Next 5 months") | |
| expected = datetime(2016, 11, 7), datetime(2017, 4, 7) | |
| assert result == expected | |
| result = get_since_until("Next 1 month") | |
| expected = datetime(2016, 11, 7), datetime(2016, 12, 7) | |
| assert result == expected | |
| result = get_since_until(since="5 days") | |
| expected = datetime(2016, 11, 2), datetime(2016, 11, 7) | |
| assert result == expected | |
| result = get_since_until(since="5 days ago", until="tomorrow") | |
| expected = datetime(2016, 11, 2), datetime(2016, 11, 8) | |
| assert result == expected | |
| result = get_since_until(time_range="yesterday : tomorrow", time_shift="1 day") | |
| expected = datetime(2016, 11, 5), datetime(2016, 11, 7) | |
| assert result == expected | |
| result = get_since_until(time_range="5 days : now") | |
| expected = datetime(2016, 11, 2), datetime(2016, 11, 7, 9, 30, 10) | |
| assert result == expected | |
| result = get_since_until("Last week", relative_end="now") | |
| expected = datetime(2016, 10, 31), datetime(2016, 11, 7, 9, 30, 10) | |
| assert result == expected | |
| result = get_since_until("Last week", relative_start="now") | |
| expected = datetime(2016, 10, 31, 9, 30, 10), datetime(2016, 11, 7) | |
| assert result == expected | |
| result = get_since_until("Last week", relative_start="now", relative_end="now") | |
| expected = datetime(2016, 10, 31, 9, 30, 10), datetime(2016, 11, 7, 9, 30, 10) | |
| assert result == expected | |
| result = get_since_until("previous calendar week") | |
| expected = datetime(2016, 10, 31, 0, 0, 0), datetime(2016, 11, 7, 0, 0, 0) | |
| assert result == expected | |
| result = get_since_until("previous calendar month") | |
| expected = datetime(2016, 10, 1, 0, 0, 0), datetime(2016, 11, 1, 0, 0, 0) | |
| assert result == expected | |
| result = get_since_until("previous calendar year") | |
| expected = datetime(2015, 1, 1, 0, 0, 0), datetime(2016, 1, 1, 0, 0, 0) | |
| assert result == expected | |
| result = get_since_until("Current day") | |
| expected = datetime(2016, 11, 7, 0, 0, 0), datetime(2016, 11, 8, 0, 0, 0) | |
| assert result == expected | |
| result = get_since_until("Current week") | |
| expected = datetime(2016, 11, 7, 0, 0, 0), datetime(2016, 11, 14, 0, 0, 0) | |
| assert result == expected | |
| result = get_since_until("Current month") | |
| expected = datetime(2016, 11, 1, 0, 0, 0), datetime(2016, 12, 1, 0, 0, 0) | |
| assert result == expected | |
| result = get_since_until("Current quarter") | |
| expected = datetime(2016, 10, 1, 0, 0, 0), datetime(2017, 1, 1, 0, 0, 0) | |
| assert result == expected | |
| result = get_since_until("Current year") | |
| expected = expected = datetime(2016, 1, 1, 0, 0, 0), datetime(2017, 1, 1, 0, 0, 0) | |
| assert result == expected | |
| # Tests for our new instant_time_comparison logic and Feature Flag off | |
| result = get_since_until( | |
| time_range="2000-01-01T00:00:00 : 2018-01-01T00:00:00", | |
| instant_time_comparison_range="y", | |
| ) | |
| expected = datetime(2000, 1, 1), datetime(2018, 1, 1) | |
| assert result == expected | |
| result = get_since_until( | |
| time_range="2000-01-01T00:00:00 : 2018-01-01T00:00:00", | |
| instant_time_comparison_range="m", | |
| ) | |
| expected = datetime(2000, 1, 1), datetime(2018, 1, 1) | |
| assert result == expected | |
| result = get_since_until( | |
| time_range="2000-01-01T00:00:00 : 2018-01-01T00:00:00", | |
| instant_time_comparison_range="w", | |
| ) | |
| expected = datetime(2000, 1, 1), datetime(2018, 1, 1) | |
| assert result == expected | |
| result = get_since_until( | |
| time_range="2000-01-01T00:00:00 : 2018-01-01T00:00:00", | |
| instant_time_comparison_range="r", | |
| ) | |
| expected = datetime(2000, 1, 1), datetime(2018, 1, 1) | |
| assert result == expected | |
| result = get_since_until( | |
| time_range="2000-01-01T00:00:00 : 2018-01-01T00:00:00", | |
| time_shift="1 year ago", | |
| ) | |
| expected = datetime(1999, 1, 1), datetime(2017, 1, 1) | |
| assert result == expected | |
| result = get_since_until( | |
| time_range="2000-01-01T00:00:00 : 2018-01-01T00:00:00", | |
| time_shift="1 month ago", | |
| ) | |
| expected = datetime(1999, 12, 1), datetime(2017, 12, 1) | |
| assert result == expected | |
| result = get_since_until( | |
| time_range="2000-01-01T00:00:00 : 2018-01-01T00:00:00", | |
| time_shift="1 week ago", | |
| ) | |
| expected = datetime(1999, 12, 25), datetime(2017, 12, 25) | |
| assert result == expected | |
| # Test time_shift with date range format (contains ' : ') | |
| result = get_since_until( | |
| time_range="today : tomorrow", | |
| time_shift="yesterday : today", | |
| ) | |
| # When time_shift contains ' : ', it should be parsed as a new time range | |
| expected = datetime(2016, 11, 6), datetime(2016, 11, 7) | |
| assert result == expected | |
| with pytest.raises(ValueError): # noqa: PT011 | |
| get_since_until(time_range="tomorrow : yesterday") | |
| def test_get_since_until_instant_time_comparison_enabled() -> None: | |
| result: tuple[Optional[datetime], Optional[datetime]] | |
| expected: tuple[Optional[datetime], Optional[datetime]] | |
| result = get_since_until( | |
| time_range="2000-01-01T00:00:00 : 2018-01-01T00:00:00", | |
| instant_time_comparison_range="y", | |
| ) | |
| expected = datetime(1999, 1, 1), datetime(2017, 1, 1) | |
| assert result == expected | |
| result = get_since_until( | |
| time_range="2000-01-01T00:00:00 : 2018-01-01T00:00:00", | |
| instant_time_comparison_range="m", | |
| ) | |
| expected = datetime(1999, 12, 1), datetime(2017, 12, 1) | |
| assert result == expected | |
| result = get_since_until( | |
| time_range="2000-01-01T00:00:00 : 2018-01-01T00:00:00", | |
| instant_time_comparison_range="w", | |
| ) | |
| expected = datetime(1999, 12, 25), datetime(2017, 12, 25) | |
| assert result == expected | |
| result = get_since_until( | |
| time_range="2000-01-01T00:00:00 : 2018-01-01T00:00:00", | |
| instant_time_comparison_range="r", | |
| ) | |
| expected = datetime(1981, 12, 31), datetime(2000, 1, 1) | |
| assert result == expected | |
| result = get_since_until( | |
| time_range="2000-01-01T00:00:00 : 2018-01-01T00:00:00", | |
| instant_time_comparison_range="unknown", | |
| ) | |
| expected = datetime(2000, 1, 1), datetime(2018, 1, 1) | |
| assert result == expected | |
| def test_previous_calendar_quarter(): | |
| with freezegun.freeze_time("2023-01-15"): | |
| result = get_since_until("previous calendar quarter") | |
| expected = (datetime(2022, 10, 1), datetime(2023, 1, 1)) | |
| assert result == expected | |
| with freezegun.freeze_time("2023, 4, 15"): | |
| result = get_since_until("previous calendar quarter") | |
| expected = (datetime(2023, 1, 1), datetime(2023, 4, 1)) | |
| assert result == expected | |
| with freezegun.freeze_time("2023, 8, 15"): | |
| result = get_since_until("previous calendar quarter") | |
| expected = (datetime(2023, 4, 1), datetime(2023, 7, 1)) | |
| assert result == expected | |
| with freezegun.freeze_time("2023, 10, 15"): | |
| result = get_since_until("previous calendar quarter") | |
| expected = (datetime(2023, 7, 1), datetime(2023, 10, 1)) | |
| assert result == expected | |
| with freezegun.freeze_time("2024, 1, 1"): | |
| result = get_since_until("previous calendar quarter") | |
| expected = (datetime(2023, 10, 1), datetime(2024, 1, 1)) | |
| assert result == expected | |
| def test_datetime_eval() -> None: | |
| result = datetime_eval("datetime('now')") | |
| expected = datetime(2016, 11, 7, 9, 30, 10) | |
| assert result == expected | |
| result = datetime_eval("datetime('today')") | |
| expected = datetime(2016, 11, 7) | |
| assert result == expected | |
| result = datetime_eval("datetime('2018')") | |
| expected = datetime(2018, 1, 1) | |
| assert result == expected | |
| result = datetime_eval("datetime('2018-9')") | |
| expected = datetime(2018, 9, 1) | |
| assert result == expected | |
| # Parse compact arguments spelling | |
| result = datetime_eval("dateadd(datetime('today'),1,year,)") | |
| expected = datetime(2017, 11, 7) | |
| assert result == expected | |
| result = datetime_eval("dateadd(datetime('today'), -2, year)") | |
| expected = datetime(2014, 11, 7) | |
| assert result == expected | |
| result = datetime_eval("dateadd(datetime('today'), 2, quarter)") | |
| expected = datetime(2017, 5, 7) | |
| assert result == expected | |
| result = datetime_eval("dateadd(datetime('today'), 3, month)") | |
| expected = datetime(2017, 2, 7) | |
| assert result == expected | |
| result = datetime_eval("dateadd(datetime('today'), -3, week)") | |
| expected = datetime(2016, 10, 17) | |
| assert result == expected | |
| result = datetime_eval("dateadd(datetime('today'), 3, day)") | |
| expected = datetime(2016, 11, 10) | |
| assert result == expected | |
| result = datetime_eval("dateadd(datetime('now'), 3, hour)") | |
| expected = datetime(2016, 11, 7, 12, 30, 10) | |
| assert result == expected | |
| result = datetime_eval("dateadd(datetime('now'), 40, minute)") | |
| expected = datetime(2016, 11, 7, 10, 10, 10) | |
| assert result == expected | |
| result = datetime_eval("dateadd(datetime('now'), -11, second)") | |
| expected = datetime(2016, 11, 7, 9, 29, 59) | |
| assert result == expected | |
| result = datetime_eval("datetrunc(datetime('now'), year)") | |
| expected = datetime(2016, 1, 1, 0, 0, 0) | |
| assert result == expected | |
| result = datetime_eval("datetrunc(datetime('now'), quarter)") | |
| expected = datetime(2016, 10, 1, 0, 0, 0) | |
| assert result == expected | |
| result = datetime_eval("datetrunc(datetime('now'), month)") | |
| expected = datetime(2016, 11, 1, 0, 0, 0) | |
| assert result == expected | |
| result = datetime_eval("datetrunc(datetime('now'), day)") | |
| expected = datetime(2016, 11, 7, 0, 0, 0) | |
| assert result == expected | |
| result = datetime_eval("datetrunc(datetime('now'), week)") | |
| expected = datetime(2016, 11, 7, 0, 0, 0) | |
| assert result == expected | |
| result = datetime_eval("datetrunc(datetime('now'), hour)") | |
| expected = datetime(2016, 11, 7, 9, 0, 0) | |
| assert result == expected | |
| result = datetime_eval("datetrunc(datetime('now'), minute)") | |
| expected = datetime(2016, 11, 7, 9, 30, 0) | |
| assert result == expected | |
| result = datetime_eval("datetrunc(datetime('now'), second)") | |
| expected = datetime(2016, 11, 7, 9, 30, 10) | |
| assert result == expected | |
| result = datetime_eval("lastday(datetime('now'), year)") | |
| expected = datetime(2016, 12, 31, 0, 0, 0) | |
| assert result == expected | |
| result = datetime_eval("lastday(datetime('today'), month)") | |
| expected = datetime(2016, 11, 30, 0, 0, 0) | |
| assert result == expected | |
| result = datetime_eval("holiday('Christmas')") | |
| expected = datetime(2016, 12, 25, 0, 0, 0) | |
| assert result == expected | |
| result = datetime_eval("holiday('Labor day', datetime('2018-01-01T00:00:00'))") | |
| expected = datetime(2018, 9, 3, 0, 0, 0) | |
| assert result == expected | |
| # Saudi Arabia holidays - try both English and Arabic names as the default | |
| # language varies by environment (ar in some envs, en_US in CI) | |
| sa_holiday_test_passed = False | |
| for holiday_name in ["Eid al-Fitr", "عطلة عيد الفطر"]: | |
| try: | |
| result = datetime_eval( | |
| f"holiday('{holiday_name}', datetime('2000-01-01T00:00:00'), 'SA')" | |
| ) | |
| expected = datetime(2000, 1, 8, 0, 0, 0) | |
| assert result == expected | |
| sa_holiday_test_passed = True | |
| break | |
| except ValueError: | |
| continue | |
| assert sa_holiday_test_passed | |
| result = datetime_eval( | |
| "holiday('Boxing day', datetime('2018-01-01T00:00:00'), 'UK')" | |
| ) | |
| expected = datetime(2018, 12, 26, 0, 0, 0) | |
| assert result == expected | |
| result = datetime_eval( | |
| "holiday('Juneteenth', datetime('2022-01-01T00:00:00'), 'US')" | |
| ) | |
| expected = datetime(2022, 6, 19, 0, 0, 0) | |
| assert result == expected | |
| result = datetime_eval( | |
| "holiday('Independence Day', datetime('2022-01-01T00:00:00'), 'US')" | |
| ) | |
| expected = datetime(2022, 7, 4, 0, 0, 0) | |
| assert result == expected | |
| result = datetime_eval( | |
| "lastday(dateadd(datetime('2018-01-01T00:00:00'), 1, month), month)" | |
| ) | |
| expected = datetime(2018, 2, 28, 0, 0, 0) | |
| assert result == expected | |
| result = datetime_eval( | |
| "datediff(datetime('2018-01-01T00:00:00'), datetime('2018-01-10T00:00:00'))" # pylint: disable=line-too-long,useless-suppression | |
| ) | |
| assert result == 9 | |
| result = datetime_eval( | |
| "datediff(datetime('2018-01-10T00:00:00'), datetime('2018-01-01T00:00:00'))" # pylint: disable=line-too-long,useless-suppression | |
| ) | |
| assert result == -9 | |
| result = datetime_eval( | |
| "datediff(datetime('2018-01-01T00:00:00'), datetime('2018-01-10T00:00:00'), day)" # pylint: disable=line-too-long,useless-suppression # noqa: E501 | |
| ) | |
| assert result == 9 | |
| result = datetime_eval( | |
| "datediff(datetime('2018-01-01T00:00:00'), datetime('2018-01-10T00:00:00'), year)" # pylint: disable=line-too-long,useless-suppression # noqa: E501 | |
| ) | |
| assert result == 0 | |
| result = datetime_eval( | |
| "dateadd(" | |
| "datetime('2018-01-01T00:00:00'), " | |
| "datediff(datetime('2018-01-10T00:00:00'), datetime('2018-01-01T00:00:00')), " # pylint: disable=line-too-long,useless-suppression | |
| "day," | |
| ")," | |
| ) | |
| expected = datetime(2017, 12, 23, 0, 0, 0) | |
| assert result == expected | |
| def test_parse_human_timedelta(mock_datetime: Mock) -> None: | |
| mock_datetime.now.return_value = datetime(2019, 4, 1) | |
| mock_datetime.side_effect = lambda *args, **kw: datetime(*args, **kw) | |
| assert parse_human_timedelta("now") == timedelta(0) | |
| assert parse_human_timedelta("1 year") == timedelta(366) | |
| assert parse_human_timedelta("-1 year") == timedelta(-365) | |
| assert parse_human_timedelta(None) == timedelta(0) | |
| assert parse_human_timedelta("1 month", datetime(2019, 4, 1)) == timedelta(30) | |
| assert parse_human_timedelta("1 month", datetime(2019, 5, 1)) == timedelta(31) | |
| assert parse_human_timedelta("1 month", datetime(2019, 2, 1)) == timedelta(28) | |
| assert parse_human_timedelta("-1 month", datetime(2019, 2, 1)) == timedelta(-31) | |
| def test_parse_past_timedelta(mock_datetime: Mock) -> None: | |
| mock_datetime.now.return_value = datetime(2019, 4, 1) | |
| mock_datetime.side_effect = lambda *args, **kw: datetime(*args, **kw) | |
| assert parse_past_timedelta("1 year") == timedelta(365) | |
| assert parse_past_timedelta("-1 year") == timedelta(365) | |
| assert parse_past_timedelta("52 weeks") == timedelta(364) | |
| assert parse_past_timedelta("1 month") == timedelta(31) | |
| def test_get_past_or_future() -> None: | |
| # 2020 is a leap year | |
| dttm = datetime(2020, 2, 29) | |
| assert get_past_or_future("1 year", dttm) == datetime(2021, 2, 28) | |
| assert get_past_or_future("-1 year", dttm) == datetime(2019, 2, 28) | |
| assert get_past_or_future("1 month", dttm) == datetime(2020, 3, 29) | |
| assert get_past_or_future("3 month", dttm) == datetime(2020, 5, 29) | |
| def test_get_past_or_future_quarters() -> None: | |
| # parsedatetime has no notion of quarters (it would leave the source time | |
| # unchanged); quarter phrases are rewritten to months before parsing | |
| dttm = datetime(2024, 5, 15, 10, 30, 45) | |
| assert get_past_or_future("1 quarter ago", dttm) == datetime( | |
| 2024, 2, 15, 10, 30, 45 | |
| ) | |
| assert get_past_or_future("2 quarters ago", dttm) == datetime( | |
| 2023, 11, 15, 10, 30, 45 | |
| ) | |
| assert get_past_or_future("1 quarter later", dttm) == datetime( | |
| 2024, 8, 15, 10, 30, 45 | |
| ) | |
| assert get_past_or_future("1 QUARTER ago", dttm) == datetime( | |
| 2024, 2, 15, 10, 30, 45 | |
| ) | |
| # absurdly long digit runs are not rewritten (bounded to keep matching | |
| # linear and the month count small); they parse like any other | |
| # unintelligible phrase, i.e. the source time comes back unchanged | |
| assert get_past_or_future("0" * 100_000 + " quarters ago", dttm) == dttm | |
| def test_parse_human_timedelta_unparseable_is_zero() -> None: | |
| # the parser leaves unparseable phrases as the source time, so their | |
| # delta is zero; a zero delta alone cannot distinguish them from | |
| # legitimate zero-shift phrases (see is_parseable_human_timedelta) | |
| dttm = datetime(2024, 5, 15) | |
| assert parse_human_timedelta("not a real offset", dttm) == timedelta(0) | |
| def test_is_parseable_human_timedelta() -> None: | |
| assert is_parseable_human_timedelta("1 week ago") | |
| assert is_parseable_human_timedelta("one year ago") | |
| assert is_parseable_human_timedelta("1 quarter ago") | |
| # zero-shift phrases parse successfully even though their delta is zero | |
| assert is_parseable_human_timedelta("0 days ago") | |
| assert not is_parseable_human_timedelta("not a real offset") | |
| assert not is_parseable_human_timedelta("invalid-date-range") | |
| assert not is_parseable_human_timedelta("") | |
| assert not is_parseable_human_timedelta(None) | |
| # absurdly long digit runs are not rewritten to months; they are | |
| # unparseable like any other unintelligible phrase | |
| assert not is_parseable_human_timedelta("9" * 100_000 + " quarters ago") | |
| def test_is_constant_human_timedelta() -> None: | |
| # phrases that shift every source time by the same amount | |
| assert is_constant_human_timedelta("1 week ago") | |
| assert is_constant_human_timedelta("1 month ago") | |
| assert is_constant_human_timedelta("52 weeks ago") | |
| assert is_constant_human_timedelta("1 year ago") | |
| assert is_constant_human_timedelta("one year ago") | |
| assert is_constant_human_timedelta("1 quarter ago") | |
| assert is_constant_human_timedelta("2 days later") | |
| # a zero shift is still a constant one | |
| assert is_constant_human_timedelta("0 days ago") | |
| # anchors resolve to a fixed point instead: how far they move a source | |
| # time depends on where in the day that source time falls | |
| assert not is_constant_human_timedelta("yesterday") | |
| assert not is_constant_human_timedelta("last month") | |
| assert not is_constant_human_timedelta("noon") | |
| assert not is_constant_human_timedelta("friday") | |
| assert not is_constant_human_timedelta("june") | |
| # a phrase nothing can parse is not a delta either | |
| assert not is_constant_human_timedelta("not a real offset") | |
| assert not is_constant_human_timedelta("") | |
| assert not is_constant_human_timedelta(None) | |
| def test_is_constant_human_timedelta_matches_applied_shift() -> None: | |
| """ | |
| The probes stand in for real source timestamps, so the verdict has to hold | |
| for source times they never looked at: an accepted phrase shifts arbitrary | |
| timestamps equally, a rejected one does not. | |
| """ | |
| sources = [ | |
| datetime(2021, 3, 14, 2, 30), | |
| datetime(2021, 11, 7, 1, 30), | |
| datetime(2024, 2, 29, 23, 59, 59), | |
| datetime(2020, 1, 1, 0, 0), | |
| ] | |
| deltas = {get_past_or_future("1 week ago", src) - src for src in sources} | |
| assert deltas == {timedelta(days=-7)} | |
| anchored = {get_past_or_future("yesterday", src) - src for src in sources} | |
| assert len(anchored) > 1 | |
| def test_normalize_time_delta() -> None: | |
| assert normalize_time_delta("30 seconds ago") == {"seconds": -30} | |
| assert normalize_time_delta("5 minutes later") == {"minutes": 5} | |
| assert normalize_time_delta("12 hours ago") == {"hours": -12} | |
| assert normalize_time_delta("2 weeks ago") == {"weeks": -2} | |
| assert normalize_time_delta("28 days ago") == {"days": -28} | |
| assert normalize_time_delta("2 months later") == {"months": 2} | |
| assert normalize_time_delta("1 year ago") == {"years": -1} | |
| # quarters are converted to months (pd.DateOffset has no quarters argument) | |
| assert normalize_time_delta("1 quarter ago") == {"months": -3} | |
| assert normalize_time_delta("2 quarters later") == {"months": 6} | |
| # matching is case-insensitive and the result uses lowercase keys | |
| assert normalize_time_delta("1 QUARTER ago") == {"months": -3} | |
| assert normalize_time_delta("1 Year AGO") == {"years": -1} | |
| with pytest.raises(TimeDeltaAmbiguousError): | |
| normalize_time_delta("one year ago") | |
| def test_parse_human_datetime() -> None: | |
| with pytest.raises(TimeRangeAmbiguousError): | |
| parse_human_datetime("2 days") | |
| with pytest.raises(TimeRangeAmbiguousError): | |
| parse_human_datetime("2 day") | |
| with pytest.raises(TimeRangeParseFailError): | |
| parse_human_datetime("xxxxxxx") | |
| assert parse_human_datetime("2015-04-03") == datetime(2015, 4, 3, 0, 0) | |
| assert parse_human_datetime("2/3/1969") == datetime(1969, 2, 3, 0, 0) | |
| assert parse_human_datetime("now") <= datetime.now() | |
| assert parse_human_datetime("yesterday") < datetime.now() | |
| assert date.today() - timedelta(1) == parse_human_datetime("yesterday").date() | |
| assert ( | |
| parse_human_datetime("one year ago").date() | |
| == (datetime.now() - relativedelta(years=1)).date() | |
| ) | |
| assert ( | |
| parse_human_datetime("2 years after").date() | |
| == (datetime.now() + relativedelta(years=2)).date() | |
| ) | |
| def test_date_range_migration() -> None: | |
| params = '{"time_range": " 8 days : 2020-03-10T00:00:00"}' | |
| assert re.search(DateRangeMigration.x_dateunit_in_since, params) | |
| params = '{"time_range": "2020-03-10T00:00:00 : 8 days "}' | |
| assert re.search(DateRangeMigration.x_dateunit_in_until, params) | |
| params = '{"time_range": " 2 weeks : 8 days "}' | |
| assert re.search(DateRangeMigration.x_dateunit_in_since, params) | |
| assert re.search(DateRangeMigration.x_dateunit_in_until, params) | |
| params = '{"time_range": "2 weeks ago : 8 days later"}' | |
| assert not re.search(DateRangeMigration.x_dateunit_in_since, params) | |
| assert not re.search(DateRangeMigration.x_dateunit_in_until, params) | |
| field = " 8 days " | |
| assert re.search(DateRangeMigration.x_dateunit, field) | |
| field = "last week" | |
| assert not re.search(DateRangeMigration.x_dateunit, field) | |
| field = "10 years ago" | |
| assert not re.search(DateRangeMigration.x_dateunit, field) | |
| def test_first_of_with_explicit_scope() -> None: | |
| """Test 'first of [scope] [unit]' expressions that return a single date.""" | |
| result = get_since_until("first of this month : ") | |
| assert result == (datetime(2016, 11, 1), None) | |
| result = get_since_until("first of last month : ") | |
| assert result == (datetime(2016, 10, 1), None) | |
| result = get_since_until("first of next month : ") | |
| assert result == (datetime(2016, 12, 1), None) | |
| result = get_since_until("first of prior month : ") | |
| assert result == (datetime(2016, 10, 1), None) | |
| result = get_since_until("first day of this year : ") | |
| assert result == (datetime(2016, 1, 1), None) | |
| result = get_since_until("first day of last year : ") | |
| assert result == (datetime(2015, 1, 1), None) | |
| result = get_since_until("first day of this week : ") | |
| assert result == (datetime(2016, 11, 7), None) | |
| def test_first_of_with_default_scope() -> None: | |
| """Test 'first of the [unit]' expressions that default to 'this'.""" | |
| result = get_since_until("first of the month : ") | |
| assert result == (datetime(2016, 11, 1), None) | |
| result = get_since_until("first of the year : ") | |
| assert result == (datetime(2016, 1, 1), None) | |
| result = get_since_until("first day of the month : ") | |
| assert result == (datetime(2016, 11, 1), None) | |
| result = get_since_until("first day of the week : ") | |
| assert result == (datetime(2016, 11, 7), None) | |
| def test_first_subunit_of_with_explicit_scope() -> None: | |
| """Test 'first [subunit] of [scope] [unit]' expressions that return a range.""" | |
| result = get_since_until("first week of this year") | |
| assert result == (datetime(2016, 1, 1), datetime(2016, 1, 8)) | |
| result = get_since_until("first month of this quarter") | |
| assert result == (datetime(2016, 10, 1), datetime(2016, 11, 1)) | |
| result = get_since_until("first week of last month") | |
| assert result == (datetime(2016, 10, 1), datetime(2016, 10, 8)) | |
| def test_first_subunit_of_with_default_scope() -> None: | |
| """Test 'first [subunit] of the [unit]' expressions that default to 'this'.""" | |
| result = get_since_until("first week of the year") | |
| assert result == (datetime(2016, 1, 1), datetime(2016, 1, 8)) | |
| result = get_since_until("first month of the quarter") | |
| assert result == (datetime(2016, 10, 1), datetime(2016, 11, 1)) | |
| # Tests for bounded whitespace regex patterns in time_range_lookup | |
| def test_time_range_bounded_whitespace_regex_valid(time_range: str) -> None: | |
| """Match expressions with 1-5 spaces between tokens.""" | |
| result = get_since_until(time_range) | |
| assert result[0] is not None, f"Expected '{time_range}' to parse successfully" | |
| def test_time_range_bounded_whitespace_regex_invalid(time_range: str) -> None: | |
| """Reject expressions with 0 or 6+ spaces (fall back to DATETIME wrapping).""" | |
| result = get_since_until(time_range) | |
| assert result[0] is None, f"Expected '{time_range}' to NOT match bounded regex" | |
| def test_datetime_eval_does_not_emit_parsedatetime_debug_logs( | |
| caplog: pytest.LogCaptureFixture, | |
| ) -> None: | |
| """ | |
| Regression for #33365: ``parsedatetime`` emits a noisy DEBUG record | |
| (``parsedatetime:eval now with context - False, False``) every time | |
| ``datetime_eval`` runs against a relative expression. In production | |
| deployments running at DEBUG level this floods the logs — the user | |
| report notes "this appears frequently" and a search of the issue | |
| tracker turns up "dozens of places where people have posted a log | |
| with that in it." | |
| The fix is to silence the ``parsedatetime`` logger at module load | |
| in ``superset/utils/date_parser.py`` (e.g. | |
| ``logging.getLogger("parsedatetime").setLevel(logging.WARNING)``). | |
| Their own DEBUG output is internal library chatter that Superset | |
| does not surface to operators in any actionable way. | |
| This test captures all log records at DEBUG level during a single | |
| ``datetime_eval`` call and asserts that none of them come from the | |
| ``parsedatetime`` logger. If the suppression is removed or bypassed, | |
| the test fails immediately. | |
| """ | |
| import logging | |
| with caplog.at_level(logging.DEBUG): | |
| datetime_eval("datetime('now')") | |
| parsedatetime_records = [ | |
| r for r in caplog.records if r.name.startswith("parsedatetime") | |
| ] | |
| assert not parsedatetime_records, ( | |
| "parsedatetime emitted DEBUG records during datetime_eval — these " | |
| "flood production logs. Records: " | |
| + repr([(r.levelname, r.getMessage()) for r in parsedatetime_records]) | |
| ) | |