diff --git a/periods/core.py b/periods/core.py index cd574a2..d260d80 100644 --- a/periods/core.py +++ b/periods/core.py @@ -14,7 +14,7 @@ def __init__(self, raise ValueError(f'The start of a period cannot be before its end') self.start = start self.end = end - self.duration = Duration(self) + self.duration = Duration(period=self) def __eq__(self, other): raise NotImplemented(f'Period class does not contain __eq__ method, inheriting classes must override it') @@ -23,7 +23,7 @@ def __contains__(self, item): raise NotImplemented(f'Period class does not contain __eq__ method, inheriting classes must override it') def __repr__(self): - return f'Period({self.start}, {self.end})' + return f"Period(start={self.start.__repr__()}, {self.end.__repr__()})" def __str__(self): return f'{self.start}/{self.end}' @@ -50,29 +50,47 @@ def __init__(self, start, end, **kwargs): class Duration: def __init__(self, - period: Period, - **kwargs): - if isinstance(period.start, time) and isinstance(period.end,time): + period: Period = None, + start: time | date | datetime = None, + end: time | date | datetime = None): + if period: + if isinstance(period, Period) or issubclass(type(period), Period): + self.period: Period = period + self.start = period.start + self.end = period.end + else: + raise ValueError(f"Provided object '{period}' is not an instance or child of {Period}") + if start and end: + self.period = None + if not isinstance(start, (time, date, datetime)): + raise ValueError(f"Provided value '{start}' for start is not an instance of datetime.time, " + f"datetime.date or datetime.datetime") + self.start = start + if not isinstance(end, (time, date, datetime)): + raise ValueError(f"Provided value '{end}' for end is not an instance of datetime.time, " + f"datetime.date or datetime.datetime") + self.end = end + if isinstance(self.start, time) and isinstance(self.end, time): # OOTB datetime.time does not support operations, so we'll turn it into a timedelta - _start = timedelta(hours=period.start.hour, - minutes=period.start.minute, - seconds=period.start.second) - _end = timedelta(hours=period.end.hour, - minutes=period.end.minute, - seconds=period.end.second) - td = _end - _start + _start = timedelta(hours=self.start.hour, + minutes=self.start.minute, + seconds=self.start.second) + _end = timedelta(hours=self.end.hour, + minutes=self.end.minute, + seconds=self.end.second) + self.timedelta = _end - _start else: - td = period.end - period.start + self.timedelta = self.end - self.start # TODO: Test this and account for leap years - self.seconds = 0 - self.hours = 0 - self.days = 0 - self.weeks = 0 - self.months = 0 - self.years = 0 - self.minutes = int(td.total_seconds() // 60) + self.seconds: int = 0 + self.hours: int = 0 + self.days: int = 0 + self.weeks: int = 0 + self.months: int = 0 + self.years: int = 0 + self.minutes: int = int(self.timedelta.total_seconds() // 60) if self.minutes >= 1: - self.seconds = int(td.total_seconds() - (self.minutes * 60)) + self.seconds = int(self.timedelta.total_seconds() - (self.minutes * 60)) if self.minutes // 60 >= 1: self.hours = self.minutes // 60 self.minutes = self.minutes - (self.hours * 60) @@ -88,7 +106,37 @@ def __init__(self, if self.months // 12 >= 1: self.years = self.months // 12 self.months = self.months - (self.years * 12) - """if self.days // 365 >= 1: - self.years = self.days // 365 - self.days = self.days - (self.years * 365)""" - print(f"P{self.years}Y{self.months}M{self.weeks}W{self.days}D{self.hours}H{self.minutes}M{self.seconds}S") + + def __str__(self): + return self.isoformat(fold=False) + + def __repr__(self): + if self.period: + return f'Duration(period={self.period.__repr__()})' + else: + return f'Duration(start={self.start.__repr__()}, end={self.end.__repr__()})' + + def isoformat(self, fold=True): + """ TODO: Docs; There must be a more intelligent way to do that """ + _rep = "P" + if self.years or not fold: + _rep = f"{_rep}{self.years}Y" + if self.months or not fold: + _rep = f"{_rep}{self.months}M" + if self.weeks or not fold: + _rep = f"{_rep}{self.weeks}W" + if self.days or not fold: + _rep = f"{_rep}{self.days}D" + # From now on, it's time elements, so we must append "T"; This is a bug if the duration has no time. + _rep = f"{_rep}T" + if self.hours or not fold: + _rep = f"{_rep}{self.hours}H" + if self.minutes or not fold: + _rep = f"{_rep}{self.minutes}M" + if self.seconds or not fold: + _rep = f"{_rep}{self.seconds}S" + return _rep + + def format(self, pattern, fold=False): + # TODO: Implement + pass