Source: Python Morsels

Description

Evaluate date strings in the US-specific MM/DD/YYYY format, doing no date validation and accepting invalid date combinations.

Bonus: accept any number of arguments.

Notes

min() takes an optional key argument. As with sorting functions, this argument is a function that determines how items are compared:

1
2
3
4
5
>>> s = 'abba','babba','boo'
>>>  min(s)
'abba'
>>> min(s, key=lambda x: x[1])  # compare using second character of string
'babba'

My solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
"""
Manipulate and evaluate dates in MM/DD/YYYY format.

>>> ymd('01/01/1970')
'19700101'

>>> get_earliest('01/01/1970', '02/02/1980', '03/03/1990')
'01/01/1970'
"""

from cmd import Cmd


def ymd(d: str) -> str:
    """Return `str` reformatted as YYYYMMDD.

    Expects string in MM/DD/YYYY format. Performs no validation of the
    date itself.

    >>> ymd('01/01/1970')
    '19700101'
    """
    mm, dd, yyyy = d.split("/")
    return f"{yyyy}{mm}{dd}"


def get_earliest(*ds: str) -> str:
    """Return the earliest of the dates passed as arguments.

    Expects arguments in MM/DD/YYYY format, and performs no
    validation of the dates they represent.

    >>> get_earliest('01/01/1970', '02/02/1980', '03/03/1990')
    '01/01/1970'
    """
    ymds = [ymd(x) for x in ds]
    earliest_idx = ymds.index(min(ymds))
    return ds[earliest_idx]


class MyPrompt(Cmd):
    """Line-oriented command interpreter."""

    def do_ymd(self, arg):
        """Given a date in MM/DD/YYYY format, return it as YYYYMMDD."""
        print(ymd(arg))

    def do_get_earliest(self, arg):
        """Given dates in MM/DD/YYYY format, return the earliest date."""
        print(get_earliest(*tuple(arg.split())))


if __name__ == "__main__":
    MyPrompt().cmdloop()

Provided solution

Passes a key argument to min(). This is a function that determines how items are compared. Its return value is the tuple (y, m, d), ensuring that min() does its comparison in YYYYMMDD order.

1
2
3
4
5
6
def get_earliest(*dates):
    """Return earliest of given MM/DD/YYYY-formatted date strings."""
    def date_key(date):
        (m, d, y) = date.split('/')
        return (y, m, d)
    return min(dates, key=date_key)

Tests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
"""Tests for solution.py."""

from datetime import date
from hypothesis import given
from random import randint

from solution import get_earliest
from solution import ymd

import hypothesis.strategies as st


def test_ymd():
    """Test specified date."""
    assert ymd("01/01/1970") == "19700101", "failed on Unix epoch"


def test_get_earliest_mytest_spec():
    """Test specified dates."""
    assert (
        get_earliest("01/01/1970", "01/01/1970") == "01/01/1970"
    ), "failed on identical dates"
    assert (
        get_earliest("01/01/1970", "01/31/1970") == "01/01/1970"
    ), "failed on a < b"
    assert (
        get_earliest("01/31/1970", "01/01/1970") == "01/01/1970"
    ), "failed on a > b"


def d():
    """Generate a date string in format MM/DD/YYYY."""
    return f"{r2(1, 13)}/{r2(1, 31)}/{str(randint(1500, 2020)).zfill(4)}"


def fmtd(d):
    """Return mm/dd/YYYY representation of a date object."""
    return d.strftime("%m/%d/%Y")


def r2(a, b):
    """Return a two-digit string between `a` and `b`."""
    return str(randint(a, b)).zfill(2)


def test_get_earliest_mytest_rand():
    """Test randomly generated dates."""
    test_cases = 25

    for _ in range(test_cases):
        d1 = d()
        # Add one to `d1`, unless that would mean carrying.
        if int(d1[-1]) < 9:
            d2 = d1[:-1] + str(int(d1[-1]) + 1)
        else:
            d2 = d1
        assert d1 == get_earliest(d1, d2), "failed on d1 < d2"

    for _ in range(test_cases):
        d1 = d()
        # Subtract one from `d1`, unless that would mean carrying.
        if int(d1[-1]) > 1:
            d2 = d1[:-1] + str(int(d1[-1]) - 1)
        else:
            d2 = d1
        assert d2 == get_earliest(d1, d2), "failed on d1 > d2"


def test_get_earliest_mytest_faker():
    """Tests generated using package `faker`."""
    from faker import Faker

    fake = Faker()
    test_cases = 25

    for _ in range(test_cases):
        d1 = fmtd(fake.date_this_century(before_today=True, after_today=False))
        d2 = fmtd(fake.date_this_century(before_today=False, after_today=True))
        assert d1 == get_earliest(d1, d2), "failed on d1 < d2"


@given(
    d1=st.dates(date(1, 1, 1), date(5000, 12, 31)),
    d2=st.dates(date(5001, 1, 1), date(9999, 12, 31)),
)
def test_get_earliest_mytest_prop(d1, d2):
    """Property tests generated using package `hypothesis`."""
    assert fmtd(d1) == get_earliest(fmtd(d1), fmtd(d2)), "failed on d1 < d2"


def test_provided_1():
    """Tests provided in body of exercise."""
    assert get_earliest("01/27/1832", "01/27/1756") == "01/27/1756"
    assert get_earliest("02/29/1972", "12/21/1946") == "12/21/1946"
    assert get_earliest("02/24/1946", "03/21/1946") == "02/24/1946"
    assert get_earliest("06/21/1958", "06/24/1958") == "06/21/1958"


def test_provided_1_bonus():
    """Tests for bonus feature provided in body of exercise."""
    assert (
        get_earliest("02/24/1946", "01/29/1946", "03/29/1945") == "03/29/1945"
    )


def test_provided_same_month_and_day():
    """Test provided in test file."""
    newer = "01/27/1832"
    older = "01/27/1756"
    assert get_earliest(newer, older) == older


def test_provided_february_29th():
    """Test provided in test file."""
    newer = "02/29/1972"
    older = "12/21/1946"
    assert get_earliest(newer, older) == older


def test_provided_smaller_month_bigger_day():
    """Test provided in test file."""
    newer = "03/21/1946"
    older = "02/24/1946"
    assert get_earliest(newer, older) == older


def test_provided_same_month_and_year():
    """Test provided in test file."""
    newer = "06/24/1958"
    older = "06/21/1958"
    assert get_earliest(newer, older) == older


def test_provided_invalid_date_allowed():
    """Test provided in test file."""
    newer = "02/29/2006"
    older = "02/28/2006"
    assert get_earliest(newer, older) == older


def test_provided_two_invalid_dates():
    """Test provided in test file."""
    newer = "02/30/2006"
    older = "02/29/2006"
    assert get_earliest(newer, older) == older


def test_provided_many_dates():
    """Test for bonus feature provided in test file."""
    d1 = "01/24/2007"
    d2 = "01/21/2008"
    d3 = "02/29/2009"
    d4 = "02/30/2006"
    d5 = "02/28/2006"
    d6 = "02/29/2006"
    assert get_earliest(d1, d2, d3) == d1
    assert get_earliest(d1, d2, d3, d4) == d4
    assert get_earliest(d1, d2, d3, d4, d5, d6) == d5