Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 10 additions & 1 deletion src/usepy/date/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@
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",
"format",
"now",
"timestamp",
"to_datetime",
]
# New functions
"is_today",
"is_yesterday",
"is_tomorrow",
"add_days",
"add_months",
"diff_days",
]
100 changes: 100 additions & 0 deletions src/usepy/date/date_math.py
Original file line number Diff line number Diff line change
@@ -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
78 changes: 78 additions & 0 deletions src/usepy/date/is_day.py
Original file line number Diff line number Diff line change
@@ -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)
152 changes: 152 additions & 0 deletions tests/test_date_new.py
Original file line number Diff line number Diff line change
@@ -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
Loading