From d1dd9085f09fe8db5d62787cccfb73512a37d270 Mon Sep 17 00:00:00 2001 From: mic1on Date: Sat, 14 Mar 2026 13:34:32 +0800 Subject: [PATCH] feat(date): add 6 new date utility functions - is_today/is_yesterday/is_tomorrow: date checking - add_days: add or subtract days from date - add_months: add or subtract months from date - diff_days: calculate difference in days between dates With comprehensive unit tests (26 test cases) --- src/usepy/date/__init__.py | 11 ++- src/usepy/date/date_math.py | 100 ++++++++++++++++++++++++ src/usepy/date/is_day.py | 78 ++++++++++++++++++ tests/test_date_new.py | 152 ++++++++++++++++++++++++++++++++++++ 4 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 src/usepy/date/date_math.py create mode 100644 src/usepy/date/is_day.py create mode 100644 tests/test_date_new.py diff --git a/src/usepy/date/__init__.py b/src/usepy/date/__init__.py index 11f3b3d..36f0150 100644 --- a/src/usepy/date/__init__.py +++ b/src/usepy/date/__init__.py @@ -2,6 +2,8 @@ from .format import format from .now import now from .timestamp import timestamp, to_datetime +from .is_day import is_today, is_yesterday, is_tomorrow +from .date_math import add_days, add_months, diff_days __all__ = [ "parse", @@ -9,4 +11,11 @@ "now", "timestamp", "to_datetime", -] + # New functions + "is_today", + "is_yesterday", + "is_tomorrow", + "add_days", + "add_months", + "diff_days", +] \ No newline at end of file diff --git a/src/usepy/date/date_math.py b/src/usepy/date/date_math.py new file mode 100644 index 0000000..04e09af --- /dev/null +++ b/src/usepy/date/date_math.py @@ -0,0 +1,100 @@ +from datetime import datetime, date, timedelta +from typing import Union + + +def add_days(dt: Union[datetime, date, str], days: int) -> Union[datetime, date]: + """ + Adds (or subtracts) days from a datetime/date. + + Args: + dt (Union[datetime, date, str]): The datetime/date to modify. + days (int): The number of days to add (negative to subtract). + + Returns: + Union[datetime, date]: The new datetime/date with days added. + + Examples: + >>> add_days(date(2023, 1, 1), 5) + datetime.date(2023, 1, 6) + >>> add_days(datetime(2023, 1, 1, 12, 0), -3) + datetime.datetime(2022, 12, 29, 12, 0) + """ + if isinstance(dt, str): + from usepy.date.parse import parse + dt = parse(dt) + + return dt + timedelta(days=days) + + +def add_months(dt: Union[datetime, date, str], months: int) -> Union[datetime, date]: + """ + Adds (or subtracts) months from a datetime/date. + + Args: + dt (Union[datetime, date, str]): The datetime/date to modify. + months (int): The number of months to add (negative to subtract). + + Returns: + Union[datetime, date]: The new datetime/date with months added. + + Examples: + >>> add_months(date(2023, 1, 15), 2) + datetime.date(2023, 3, 15) + >>> add_months(date(2023, 3, 31), -1) + datetime.date(2023, 2, 28) + """ + if isinstance(dt, str): + from usepy.date.parse import parse + dt = parse(dt) + + # Calculate new month and year + year = dt.year + month = dt.month + months + + # Handle month overflow/underflow + while month > 12: + month -= 12 + year += 1 + while month < 1: + month += 12 + year -= 1 + + # Handle day overflow (e.g., Jan 31 -> Feb 28/29) + day = min(dt.day, [31, 29 if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1]) + + if isinstance(dt, datetime): + return datetime(year, month, day, dt.hour, dt.minute, dt.second, dt.microsecond) + else: + return date(year, month, day) + + +def diff_days(dt1: Union[datetime, date, str], dt2: Union[datetime, date, str]) -> int: + """ + Calculates the difference in days between two dates. + + Args: + dt1 (Union[datetime, date, str]): The first datetime/date. + dt2 (Union[datetime, date, str]): The second datetime/date. + + Returns: + int: The difference in days (dt1 - dt2). + + Examples: + >>> diff_days('2023-01-10', '2023-01-05') + 5 + >>> diff_days(date(2023, 1, 1), date(2023, 1, 5)) + -4 + """ + if isinstance(dt1, str): + from usepy.date.parse import parse + dt1 = parse(dt1) + if isinstance(dt2, str): + from usepy.date.parse import parse + dt2 = parse(dt2) + + if isinstance(dt1, datetime): + dt1 = dt1.date() + if isinstance(dt2, datetime): + dt2 = dt2.date() + + return (dt1 - dt2).days \ No newline at end of file diff --git a/src/usepy/date/is_day.py b/src/usepy/date/is_day.py new file mode 100644 index 0000000..876526e --- /dev/null +++ b/src/usepy/date/is_day.py @@ -0,0 +1,78 @@ +from datetime import datetime, date, timedelta +from typing import Union + + +def is_today(dt: Union[datetime, date, str]) -> bool: + """ + Checks if a datetime/date is today. + + Args: + dt (Union[datetime, date, str]): The datetime/date to check. + + Returns: + bool: True if the date is today, False otherwise. + + Examples: + >>> is_today(datetime.now()) + True + >>> is_today(date.today()) + True + >>> is_today('2023-01-01') + False + """ + if isinstance(dt, str): + from usepy.date.parse import parse + dt = parse(dt) + + if isinstance(dt, datetime): + dt = dt.date() + + return dt == date.today() + + +def is_yesterday(dt: Union[datetime, date, str]) -> bool: + """ + Checks if a datetime/date is yesterday. + + Args: + dt (Union[datetime, date, str]): The datetime/date to check. + + Returns: + bool: True if the date is yesterday, False otherwise. + + Examples: + >>> is_yesterday(date.today() - timedelta(days=1)) + True + """ + if isinstance(dt, str): + from usepy.date.parse import parse + dt = parse(dt) + + if isinstance(dt, datetime): + dt = dt.date() + + return dt == date.today() - timedelta(days=1) + + +def is_tomorrow(dt: Union[datetime, date, str]) -> bool: + """ + Checks if a datetime/date is tomorrow. + + Args: + dt (Union[datetime, date, str]): The datetime/date to check. + + Returns: + bool: True if the date is tomorrow, False otherwise. + + Examples: + >>> is_tomorrow(date.today() + timedelta(days=1)) + True + """ + if isinstance(dt, str): + from usepy.date.parse import parse + dt = parse(dt) + + if isinstance(dt, datetime): + dt = dt.date() + + return dt == date.today() + timedelta(days=1) \ No newline at end of file diff --git a/tests/test_date_new.py b/tests/test_date_new.py new file mode 100644 index 0000000..1aa4bf0 --- /dev/null +++ b/tests/test_date_new.py @@ -0,0 +1,152 @@ +import pytest +from datetime import datetime, date, timedelta +from usepy.date import is_today, is_yesterday, is_tomorrow, add_days, add_months, diff_days + + +class TestIsToday: + """Tests for is_today() function""" + + def test_is_today_datetime(self): + """Test is_today with datetime""" + assert is_today(datetime.now()) is True + + def test_is_today_date(self): + """Test is_today with date""" + assert is_today(date.today()) is True + + def test_is_today_yesterday(self): + """Test is_today with yesterday""" + assert is_today(date.today() - timedelta(days=1)) is False + + def test_is_today_tomorrow(self): + """Test is_today with tomorrow""" + assert is_today(date.today() + timedelta(days=1)) is False + + def test_is_today_string(self): + """Test is_today with string""" + today_str = date.today().strftime('%Y-%m-%d') + assert is_today(today_str) is True + assert is_today('2023-01-01') is False + + +class TestIsYesterday: + """Tests for is_yesterday() function""" + + def test_is_yesterday_true(self): + """Test is_yesterday with yesterday""" + assert is_yesterday(date.today() - timedelta(days=1)) is True + + def test_is_yesterday_today(self): + """Test is_yesterday with today""" + assert is_yesterday(date.today()) is False + + def test_is_yesterday_datetime(self): + """Test is_yesterday with datetime""" + yesterday = datetime.now() - timedelta(days=1) + assert is_yesterday(yesterday) is True + + +class TestIsTomorrow: + """Tests for is_tomorrow() function""" + + def test_is_tomorrow_true(self): + """Test is_tomorrow with tomorrow""" + assert is_tomorrow(date.today() + timedelta(days=1)) is True + + def test_is_tomorrow_today(self): + """Test is_tomorrow with today""" + assert is_tomorrow(date.today()) is False + + def test_is_tomorrow_datetime(self): + """Test is_tomorrow with datetime""" + tomorrow = datetime.now() + timedelta(days=1) + assert is_tomorrow(tomorrow) is True + + +class TestAddDays: + """Tests for add_days() function""" + + def test_add_days_positive(self): + """Test adding positive days""" + result = add_days(date(2023, 1, 1), 5) + assert result == date(2023, 1, 6) + + def test_add_days_negative(self): + """Test subtracting days""" + result = add_days(date(2023, 1, 10), -5) + assert result == date(2023, 1, 5) + + def test_add_days_zero(self): + """Test adding zero days""" + result = add_days(date(2023, 1, 1), 0) + assert result == date(2023, 1, 1) + + def test_add_days_datetime(self): + """Test adding days to datetime""" + result = add_days(datetime(2023, 1, 1, 12, 30), 3) + assert result == datetime(2023, 1, 4, 12, 30) + + def test_add_days_string(self): + """Test adding days to string date""" + result = add_days('2023-01-01', 5) + assert result == datetime(2023, 1, 6) + + +class TestAddMonths: + """Tests for add_months() function""" + + def test_add_months_positive(self): + """Test adding positive months""" + result = add_months(date(2023, 1, 15), 2) + assert result == date(2023, 3, 15) + + def test_add_months_negative(self): + """Test subtracting months""" + result = add_months(date(2023, 3, 15), -2) + assert result == date(2023, 1, 15) + + def test_add_months_year_rollover(self): + """Test month addition with year rollover""" + result = add_months(date(2023, 11, 15), 3) + assert result == date(2024, 2, 15) + + def test_add_months_day_overflow(self): + """Test month addition with day overflow""" + # Jan 31 -> Feb 28 (non-leap year) + result = add_months(date(2023, 1, 31), 1) + assert result == date(2023, 2, 28) + + def test_add_months_leap_year(self): + """Test month addition in leap year""" + # Jan 31 -> Feb 29 (leap year) + result = add_months(date(2024, 1, 31), 1) + assert result == date(2024, 2, 29) + + +class TestDiffDays: + """Tests for diff_days() function""" + + def test_diff_days_positive(self): + """Test positive difference""" + result = diff_days(date(2023, 1, 10), date(2023, 1, 5)) + assert result == 5 + + def test_diff_days_negative(self): + """Test negative difference""" + result = diff_days(date(2023, 1, 5), date(2023, 1, 10)) + assert result == -5 + + def test_diff_days_zero(self): + """Test zero difference""" + result = diff_days(date(2023, 1, 1), date(2023, 1, 1)) + assert result == 0 + + def test_diff_days_datetime(self): + """Test difference with datetime""" + result = diff_days(datetime(2023, 1, 10, 12, 0), datetime(2023, 1, 5, 8, 0)) + assert result == 5 + + def test_diff_days_string(self): + """Test difference with string dates""" + result = diff_days('2023-01-10', '2023-01-05') + assert result == 5 \ No newline at end of file