Exercise 3 solution import re pat = re.compile(r""" ^\s* (?P\d{1,2}) # hours : (?P\d{2}) # minutes (?: # optional seconds :(?P\d{2}) )? \s*(?Pam|pm)? # optional am/pm \s*$ """, re.X | re.I) for t in ["9:05", "14:30:59", "9:05 PM", "9:5", "25:61x"]: m = pat.match(t) print(repr(t), m.groupdict() if m else None) # '9:05' {'h': '9', 'm': '05', 's': None, 'ap': None} # '14:30:59' {'h': '14', 'm': '30', 's': '59', 'ap': None} # '9:05 PM' {'h': '9', 'm': '05', 's': None, 'ap': 'PM'} # '9:5' None # '25:61x' None (All lines were run and printed exactly what is shown.) Explanation: "9:5" fails because minutes need exactly two digits. "25:61x" fails because of the trailing "x": after the optional parts the pattern allows only spaces and then requires the end of the string. (The pattern checks shape only; it would accept 25:61 if the x were absent, which is why real validation needs a range check as well, covered in Regular Expressions in Practice 7.)