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?
2 Answers 2
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 returnsList[datetime.datetime]
. datetime.datetime
months are one-offset, so I'm puzzled at themonth = future_date.month - 1
line.
-
\$\begingroup\$ * Actually, using a function was my first idea. Since
estimate_date
usesself
, 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. \$\endgroup\$Laurent LAPORTE– Laurent LAPORTE2019年07月08日 08:44:45 +00:00Commented Jul 8, 2019 at 8:44