Skip to content

Commit f58c1b6

Browse files
committed
updates age-diff algo
1 parent f0c143a commit f58c1b6

File tree

1 file changed

+48
-27
lines changed

1 file changed

+48
-27
lines changed

maths/age_difference.py

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1+
#!/usr/bin/env python3
12
"""
2-
Algorithm to calculate the absolute age difference between two dates (YYYY-MM-DD).
3+
Age difference calculator.
34
4-
Wikipedia: https://en.wikipedia.org/wiki/Chronological_age
5+
This module provides functions to compute the absolute age difference between
6+
two birthdates in years, months, and days.
7+
8+
Doctest usage:
9+
python3 -m doctest -v maths/age_difference.py
510
"""
611

712
from __future__ import annotations
813

914
from datetime import date
15+
from typing import Tuple
1016

1117

1218
def parse_date(dob: str) -> date:
@@ -25,45 +31,60 @@ def parse_date(dob: str) -> date:
2531
try:
2632
year, month, day = map(int, dob.split("-"))
2733
return date(year, month, day)
28-
except Exception as e:
29-
raise ValueError(f"Invalid date format or value: {dob}") from e
34+
except Exception as e: # noqa: BLE001
35+
msg = f"Invalid date format or value: {dob}"
36+
raise ValueError(msg) from e
37+
3038

39+
def _days_in_previous_month(ref: date) -> int:
40+
"""
41+
Return the number of days in the month before the given date's month.
3142
32-
def absolute_age_difference(dob1: str, dob2: str) -> tuple[int, int, int]:
43+
>>> _days_in_previous_month(date(2020, 3, 5)) # Feb in leap year
44+
29
45+
>>> _days_in_previous_month(date(2021, 3, 5)) # Feb non-leap
46+
28
3347
"""
34-
Returns the absolute age difference as (years, months, days).
48+
first_of_month = ref.replace(day=1)
49+
previous_month_last_day = first_of_month - date.resolution
50+
return previous_month_last_day.day
51+
52+
53+
def absolute_age_difference(d1: str, d2: str) -> Tuple[int, int, int]:
54+
"""
55+
Return the absolute difference between two dates as (years, months, days).
56+
57+
Rules:
58+
- Order does not matter
59+
- Uses calendar-accurate borrowing of months and days
60+
- Handles leap years
3561
62+
>>> absolute_age_difference("2000-01-01", "2000-01-01")
63+
(0, 0, 0)
3664
>>> absolute_age_difference("1990-05-10", "1995-07-15")
3765
(5, 2, 5)
66+
>>> absolute_age_difference("1995-07-15", "1990-05-10")
67+
(5, 2, 5)
3868
>>> absolute_age_difference("2000-02-29", "2001-02-28")
3969
(0, 11, 30)
40-
>>> absolute_age_difference("2000-01-01", "2000-01-01")
41-
(0, 0, 0)
4270
"""
43-
d1, d2 = parse_date(dob1), parse_date(dob2)
44-
45-
if d1 == d2:
46-
return (0, 0, 0)
71+
a = parse_date(d1)
72+
b = parse_date(d2)
4773

48-
# Order dates so d1 is always the earlier date
49-
if d1 > d2:
50-
d1, d2 = d2, d1
74+
# Ensure a <= b
75+
if a > b:
76+
a, b = b, a
5177

52-
years = d2.year - d1.year
53-
months = d2.month - d1.month
54-
days = d2.day - d1.day
78+
years = b.year - a.year
79+
months = b.month - a.month
80+
days = b.day - a.day
5581

56-
# Adjust for negative days
82+
# Borrow days if needed
5783
if days < 0:
5884
months -= 1
59-
previous_month = d2.month - 1 or 12
60-
previous_year = d2.year if d2.month != 1 else d2.year - 1
61-
days += (
62-
date(previous_year, previous_month + 1, 1)
63-
- date(previous_year, previous_month, 1)
64-
).days
65-
66-
# Adjust for negative months
85+
days += _days_in_previous_month(b)
86+
87+
# Borrow months if needed
6788
if months < 0:
6889
years -= 1
6990
months += 12

0 commit comments

Comments
 (0)