134 lines
5.8 KiB
Python
134 lines
5.8 KiB
Python
from datetime import date, timedelta
|
|
import random
|
|
|
|
def is_leap_year(year):
|
|
"""Determines if a year is a leap year."""
|
|
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
|
|
|
|
def days_in_year(year):
|
|
"""Returns the number of days in a given year."""
|
|
return 366 if is_leap_year(year) else 365
|
|
|
|
def days_in_month(year, month):
|
|
"""Returns the number of days in a specific month of a year."""
|
|
if month == 2:
|
|
return 29 if is_leap_year(year) else 28
|
|
elif month in [4, 6, 9, 11]:
|
|
return 30
|
|
else:
|
|
return 31
|
|
|
|
def get_random_date(start_year=2000, end_year=2030):
|
|
"""Generates a random date within a given year range."""
|
|
year = random.randint(start_year, end_year)
|
|
month = random.randint(1, 12)
|
|
day = random.randint(1, days_in_month(year, month))
|
|
return date(year, month, day)
|
|
|
|
def get_random_date_period(min_days=30, max_days=730, base_year_range=(1990, 2025)):
|
|
"""
|
|
Generates a random start date and an end date that is min_days to max_days after the start date.
|
|
Returns (start_date, end_date, number_of_days).
|
|
"""
|
|
start_date = get_random_date(base_year_range[0], base_year_range[1])
|
|
num_days_in_period = random.randint(min_days, max_days)
|
|
end_date = start_date + timedelta(days=num_days_in_period)
|
|
|
|
# The actual number of days between two dates, inclusive of start and exclusive of end,
|
|
# or inclusive of both if counting "duration". For interest calculations, it's often
|
|
# the difference as calculated by date objects.
|
|
# For exact simple interest, the problem usually implies counting the actual days in the period.
|
|
# timedelta already gives the difference. If the problem implies "from date X to date Y inclusive",
|
|
# then it might be (end_date - start_date).days + 1.
|
|
# However, the sample problems (e.g. Dec 27 to Mar 23) count days like:
|
|
# Dec: 31-27 = 4. Jan: 31. Feb: 28. Mar: 23. Total = 86.
|
|
# (date(2003,3,23) - date(2002,12,27)).days = 86. So timedelta is correct.
|
|
|
|
actual_days_difference = (end_date - start_date).days
|
|
|
|
return start_date, end_date, actual_days_difference
|
|
|
|
def calculate_exact_days(start_date_obj, end_date_obj):
|
|
"""
|
|
Calculates the exact number of days between two date objects.
|
|
This is consistent with how financial day counts are often done.
|
|
"""
|
|
if not isinstance(start_date_obj, date) or not isinstance(end_date_obj, date):
|
|
raise ValueError("Inputs must be datetime.date objects.")
|
|
if start_date_obj > end_date_obj:
|
|
raise ValueError("Start date cannot be after end date.")
|
|
|
|
return (end_date_obj - start_date_obj).days
|
|
|
|
def format_date_for_display(date_obj):
|
|
"""Formats a date object as 'Month Day, Year' (e.g., January 1, 2023)."""
|
|
if not isinstance(date_obj, date):
|
|
return str(date_obj)
|
|
return date_obj.strftime("%B %d, %Y")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
print("Testing Date Utilities:")
|
|
|
|
# Test is_leap_year
|
|
print(f"\n--- Testing is_leap_year ---")
|
|
print(f"Is 2000 a leap year? {is_leap_year(2000)} (Expected: True)")
|
|
assert is_leap_year(2000)
|
|
print(f"Is 1900 a leap year? {is_leap_year(1900)} (Expected: False)")
|
|
assert not is_leap_year(1900)
|
|
print(f"Is 2023 a leap year? {is_leap_year(2023)} (Expected: False)")
|
|
assert not is_leap_year(2023)
|
|
print(f"Is 2024 a leap year? {is_leap_year(2024)} (Expected: True)")
|
|
assert is_leap_year(2024)
|
|
|
|
# Test days_in_year
|
|
print(f"\n--- Testing days_in_year ---")
|
|
print(f"Days in 2023: {days_in_year(2023)} (Expected: 365)")
|
|
assert days_in_year(2023) == 365
|
|
print(f"Days in 2024: {days_in_year(2024)} (Expected: 366)")
|
|
assert days_in_year(2024) == 366
|
|
|
|
# Test days_in_month
|
|
print(f"\n--- Testing days_in_month ---")
|
|
print(f"Days in Feb 2023: {days_in_month(2023, 2)} (Expected: 28)")
|
|
assert days_in_month(2023, 2) == 28
|
|
print(f"Days in Feb 2024: {days_in_month(2024, 2)} (Expected: 29)")
|
|
assert days_in_month(2024, 2) == 29
|
|
print(f"Days in Apr 2023: {days_in_month(2023, 4)} (Expected: 30)")
|
|
assert days_in_month(2023, 4) == 30
|
|
print(f"Days in Jan 2023: {days_in_month(2023, 1)} (Expected: 31)")
|
|
assert days_in_month(2023, 1) == 31
|
|
|
|
# Test get_random_date
|
|
print(f"\n--- Testing get_random_date ---")
|
|
for _ in range(3):
|
|
rd = get_random_date(2020, 2022)
|
|
print(f"Random date: {rd} ({format_date_for_display(rd)})")
|
|
assert 2020 <= rd.year <= 2022
|
|
|
|
# Test get_random_date_period
|
|
print(f"\n--- Testing get_random_date_period ---")
|
|
for _ in range(3):
|
|
start, end, num_days = get_random_date_period(min_days=10, max_days=90)
|
|
print(f"Period: {format_date_for_display(start)} to {format_date_for_display(end)}, Days: {num_days}")
|
|
assert 10 <= num_days <= 90
|
|
assert (end - start).days == num_days
|
|
|
|
# Test calculate_exact_days (matches sample problem from notes)
|
|
# Problem 3: December 27, 2002 to March 23, 2003 -> 86 days
|
|
print(f"\n--- Testing calculate_exact_days (Sample Problem) ---")
|
|
d1 = date(2002, 12, 27)
|
|
d2 = date(2003, 3, 23)
|
|
exact_days_sample = calculate_exact_days(d1, d2)
|
|
print(f"Days from {format_date_for_display(d1)} to {format_date_for_display(d2)}: {exact_days_sample} (Expected: 86)")
|
|
assert exact_days_sample == 86
|
|
|
|
# Problem 5: February 14, 1984 to November 30, 1984 -> 290 days (1984 is leap)
|
|
d3 = date(1984, 2, 14)
|
|
d4 = date(1984, 11, 30)
|
|
exact_days_sample2 = calculate_exact_days(d3, d4)
|
|
print(f"Days from {format_date_for_display(d3)} to {format_date_for_display(d4)}: {exact_days_sample2} (Expected: 290)")
|
|
assert exact_days_sample2 == 290
|
|
|
|
print("\nAll date utility tests passed if no assertion errors.")
|