from __future__ import annotations
__all__ = ["TimeValue", "TimeInterval"]
from dataclasses import dataclass
from datetime import datetime
from dateutil.relativedelta import relativedelta
from hijri_converter import Gregorian, Hijri
from . import constants
[docs]class TimeValue(relativedelta):
def __init__(
self,
dt1=None,
dt2=None,
years=None,
months=None,
days=None,
leapdays=None,
weeks=None,
hours=None,
minutes=None,
seconds=None,
microseconds=None,
year=None,
month=None,
day=None,
weekday=None,
yearday=None,
nlyearday=None,
hour=None,
minute=None,
second=None,
microsecond=None,
am_pm=None,
next_month=None,
prev_month=None,
hijri=None,
):
super().__init__(
dt1=dt1,
dt2=dt2,
years=years if years is not None else 0,
months=months if months is not None else 0,
days=days if days is not None else 0,
leapdays=leapdays if leapdays is not None else 0,
hours=hours if hours is not None else 0,
minutes=minutes if minutes is not None else 0,
seconds=seconds if seconds is not None else 0,
microseconds=microseconds if microseconds is not None else 0,
year=year,
month=month,
day=day,
weekday=weekday,
yearday=yearday,
nlyearday=nlyearday,
hour=hour,
minute=minute,
second=second,
microsecond=microsecond,
)
self._years = years
self._months = months
self._days = days
self._leapdays = leapdays
self._weeks = weeks
self._hours = hours
self._minutes = minutes
self._seconds = seconds
self._microseconds = microseconds
self.next_month = next_month
self.prev_month = prev_month
self.am_pm = am_pm
self.hijri = hijri
@property
[docs] def am_pm(self):
return self._am_pm
@am_pm.setter
def am_pm(self, am_pm):
self._am_pm = am_pm
# handle hour am pm
if am_pm == "PM" and self.hour is not None and self.hour < 12:
self.hour += 12
@property
[docs] def weeks(self):
return self._weeks
@weeks.setter
def weeks(self, value):
self._weeks = value
[docs] def is_years_set(self):
return self._years is not None or self.year is not None
[docs] def is_months_set(self):
return self._months is not None or self.month is not None
[docs] def is_days_set(self):
return self._days is not None or self.day is not None
[docs] def is_leapdays_set(self):
return self._leapdays is not None
[docs] def is_weeks_set(self):
return self.weeks is not None or self.weekday is not None
[docs] def is_hours_set(self):
return self._hours is not None or self.hour is not None
[docs] def is_minutes_set(self):
return self._minutes is not None or self.minute is not None
[docs] def is_seconds_set(self):
return self._seconds is not None or self.second is not None
[docs] def is_microseconds_set(self):
return self._microseconds is not None or self.microsecond is not None
[docs] def is_am_pm_set(self):
return self.am_pm is not None
[docs] def is_hijri_set(self):
return self.hijri is not None
def _add(self, value1, value2):
if value1 is not None and value2 is not None:
return value1 + value2
if value1 is None:
return value2
if value2 is None:
return value1
def __add__(self, other):
if isinstance(other, TimeValue):
return self.__class__(
years=self._add(other._years, self._years),
months=self._add(other._months, self._months),
days=self._add(other._days, self._days),
leapdays=self._add(other._leapdays, self._leapdays),
weeks=self._add(other._weeks, self.weeks),
hours=self._add(other._hours, self._hours),
minutes=self._add(other._minutes, self._minutes),
seconds=self._add(other._seconds, self._seconds),
microseconds=self._add(other._microseconds, self._microseconds),
year=(other.year if other.year is not None else self.year),
month=(other.month if other.month is not None else self.month),
day=(other.day if other.day is not None else self.day),
weekday=(other.weekday if other.weekday is not None else self.weekday),
hour=(other.hour if other.hour is not None else self.hour),
minute=(other.minute if other.minute is not None else self.minute),
second=(other.second if other.second is not None else self.second),
microsecond=(
other.microsecond
if other.microsecond is not None
else self.microsecond
),
am_pm=other.am_pm or self.am_pm,
next_month=(
other.next_month
if other.next_month is not None
else self.next_month
),
prev_month=(
other.prev_month
if other.prev_month is not None
else self.prev_month
),
hijri=other.hijri or self.hijri,
)
old_values = self.__dict__.copy()
# Handle next/prev week
if isinstance(other, datetime) and self.weeks:
self.days = self._days or 0
current_day = other.weekday()
if self._days is not None:
self.days += self.weeks * 7
else:
start_of_week = (current_day + 7 - constants.START_OF_WEEK) % 7
# next week(s)
if self.weeks > 0:
self.days += 7 - start_of_week + (self.weeks - 1) * 7
# prev week(s)
elif self.weeks < 0:
self.days -= start_of_week - 7 * self.weeks
# Handle hijri date
if isinstance(other, datetime) and self.hijri:
current_hijri = Gregorian.fromdate(other.date()).to_hijri()
hijri_year = self.year or current_hijri.year
hijri_month = self.month or current_hijri.month
hijri_day = self.day or current_hijri.day
month_lengths = [0] + [
Hijri(hijri_year, i, 1).month_length() for i in range(1, 13)
]
hijri_day = min(hijri_day, month_lengths[hijri_month])
hijri_year += self.years
hijri_month += self.months
hijri_day += self.days
while hijri_day > month_lengths[hijri_month]:
if hijri_month > 12:
hijri_year += self.months // 12
hijri_month = self.months % 12
hijri_day -= month_lengths[hijri_month]
hijri_month += 1
if self.next_month:
hijri_year += 1 if self.next_month <= current_hijri.month else 0
hijri_month = self.next_month
elif self.prev_month:
hijri_year += 0 if self.prev_month <= current_hijri.month else -1
hijri_month = self.prev_month
new_date = Hijri(hijri_year, hijri_month, hijri_day).to_gregorian()
self.year = new_date.year
self.month = new_date.month
self.day = new_date.day
self.years = 0
self.months = 0
self.days = 0
elif isinstance(other, datetime):
current_month = other.month
if self.next_month:
self.years += 1 if self.next_month <= current_month else 0
self.month = self.next_month
elif self.prev_month:
self.years += 0 if self.prev_month <= current_month else -1
self.month = self.prev_month
output = super().__add__(other)
self.__dict__ = old_values
return output
def __repr__(self):
l = []
for attr in [
"_years",
"_months",
"_weeks",
"_days",
"_leapdays",
"_hours",
"_minutes",
"_seconds",
"_microseconds",
"_am_pm",
"next_month",
"prev_month",
"year",
"month",
"day",
"weekday",
"hour",
"minute",
"second",
"microsecond",
"hijri",
]:
value = getattr(self, attr)
if value is not None:
l.append(
"{attr}={value}".format(attr=attr.strip("_"), value=repr(value))
)
return "{classname}({attrs})".format(
classname=self.__class__.__name__, attrs=", ".join(l)
)
def __eq__(self, other):
if not isinstance(other, TimeValue):
return NotImplemented
if self.weekday or other.weekday:
if not self.weekday or not other.weekday:
return False
if self.weekday.weekday != other.weekday.weekday:
return False
n1, n2 = self.weekday.n, other.weekday.n
if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)):
return False
return (
self._years == other._years
and self._months == other._months
and self._days == other._days
and self._hours == other._hours
and self._minutes == other._minutes
and self._seconds == other._seconds
and self._microseconds == other._microseconds
and self._leapdays == other._leapdays
and self.year == other.year
and self.month == other.month
and self.day == other.day
and self.hour == other.hour
and self.minute == other.minute
and self.second == other.second
and self.microsecond == other.microsecond
and self.weekday == other.weekday
and self.am_pm == other.am_pm
and self.weeks == other.weeks
and self.hijri == other.hijri
)
@dataclass
[docs]class TimeInterval:
[docs] start: TimeValue | None = None
[docs] end: TimeValue | None = None