# Calculate implicit dates

I need help to simplify this code about implicit date calculation.

The context: I have sentences with implicit and explicit dates. For instance:

"From the 5th to the 7th of September 2017, the 12th, 13th and 15th of June, from the 25th to the 31th December, the 1st and the 2nd of January 2019."

A program (not shown here) can parse implicit and explicit dates, so I can get an ordered list (in chronological order) like that:

    history = [
[5],
[7, 9, 2017],
[12],
[13],
[15, 6],
[25],
[31, 12],
[1],
[2, 1, 2019],
]


The purpose of the program below is to calculate the dates.

import datetime

class ImplicitDate(object):
def __init__(self, day: int, month: int = None, year: int = None):
self.day = day
self.month = month
self.year = year

def __repr__(self):
cls = self.__class__.__name__
return f"<{cls}(day={self.day!r}, month={self.month!r}, year={self.year!r})>"

def __str__(self):
s = []
if self.year:
s.append(f"{self.year:04d}")
if self.month:
s.append(f"{self.month:02d}")
if self.day:
s.append(f"{self.day:02d}")
return "-".join(s)

@property
def date(self) -> datetime.date or None:
if self.year is None or self.month is None or self.day is None:
return None
return datetime.date(self.year, self.month, self.day)

def estimate_date(self, future_date: datetime.date) -> datetime.date:
if self.day is None:
# weird
return future_date
elif self.month is None:
day = self.day
month = future_date.month
year = future_date.year
date = datetime.date(year, month, day)
if date < future_date:
return date
else:
month = future_date.month - 1
if month == 0:
month = 12
year = future_date.year - 1
date = datetime.date(year, month, day)
return date
elif self.year is None:
day = self.day
month = self.month
year = future_date.year
date = datetime.date(year, month, day)
if date < future_date:
return date
else:
year = future_date.year - 1
date = datetime.date(year, month, day)
return date
else:
day = self.day
month = self.month
year = self.year
date = datetime.date(year, month, day)
return date

def demo_implicit_dates():
history = [
ImplicitDate(5),
ImplicitDate(7, 9, 2017),
ImplicitDate(12),
ImplicitDate(13),
ImplicitDate(15, 6),
ImplicitDate(25),
ImplicitDate(31, 12),
ImplicitDate(1),
ImplicitDate(2, 1, 2019),
]

curr = history.pop()
dates = [curr.date]
while history:
top = dates[-1]
curr = history.pop()
estimated = curr.estimate_date(top)
dates.append(estimated)
dates.reverse()

for date in dates:
print(date.isoformat())

# 2017-09-05
# 2017-09-07
# 2018-06-12
# 2018-06-13
# 2018-06-15
# 2018-12-25
# 2018-12-31
# 2019-01-01
# 2019-01-02


Do you think this program is complex or over-designed? How to simplify it?

Do you think this algorithm is well-implemented? Is there something I am missing?

        elif self.month is None:
day = self.day
month = future_date.month
year = future_date.year
date = datetime.date(year, month, day)
if date < future_date:
return date
else:
month = future_date.month - 1
if month == 0:
month = 12
year = future_date.year - 1
date = datetime.date(year, month, day)
return date 


This can be simplified to:

if len(a_date) > 1:
if (curr_month and a_date[1] > curr_month):
curr_year -= 1
curr_month = a_date[1]


Also,

        elif self.year is None:
day = self.day
month = self.month
year = future_date.year
date = datetime.date(year, month, day)
if date < future_date:
return date
else:
year = future_date.year - 1
date = datetime.date(year, month, day)
return date 


Can now use a shared statement such as:

if len(a_date) > 1:
if (curr_month and a_date[1] > curr_month):
curr_year -= 1
curr_month = a_date[1]
if len(a_date) > 2:
curr_year = a_date[2]


Then,

if self.year is None or self.month is None or self.day is None


Can be simplified using the any() operator which checks a list of items against a condition (in this case if they are not None) then returns true/false depending on if all items satisfied the condition.

day = self.day
month = future_date.month
year = future_date.year
date = datetime.date(year, month, day)


I would turn this into a function as it is repeated again.

The final code after condensing the logic is:

def implicit_dates(history):
computed_dates = []
curr_day = None
curr_month = None
curr_year = None
history = reversed(history)
for a_date in history:
curr_day = a_date[0]
if len(a_date) > 1:
if (curr_month and a_date[1] > curr_month):
curr_year -= 1
curr_month = a_date[1]
if len(a_date) > 2:
curr_year = a_date[2]
if (curr_day and curr_month and curr_year):
computed_dates.append(
datetime.datetime(curr_year, curr_month, curr_day))
return reversed(computed_dates)

for historical_date in implicit_dates(history):
print(historical_date.strftime("%Y-%m-%d"))


Interesting exercise! Some suggestions:

• It looks like a cleaner design might be to have a function (as opposed to a method) which takes a List[ImplicitDate] and returns List[datetime.datetime].
• datetime.datetime months are one-offset, so I'm puzzled at the month = future_date.month - 1 line.
• * Actually, using a function was my first idea. Since estimate_date uses self, I prefer using a method. * months are one-offest, so I use: if month == 0: month = 12; year = future_date.year - 1 to go one month backward in the past. – Laurent LAPORTE Jul 8 '19 at 8:44