diff --git a/malojatime.py b/malojatime.py index c8ea5f3..2382d22 100644 --- a/malojatime.py +++ b/malojatime.py @@ -32,10 +32,231 @@ def end_of_scrobbling(): # return str(t) -def time_str(t): - return "/".join(str(tp) for tp in t) +### helpers -# converts strings and stuff to lists +# adjusting to sunday-first calendar +# damn iso heathens +class expandeddate(datetime.date): + + def chrweekday(self): + return self.isoweekday() + 1 % 7 + + def chrcalendar(self): + tomorrow = self + datetime.timedelta(days=1) + cal = tomorrow.isocalendar() + return (cal[0],cal[1],cal[2] % 7) + +date = expandeddate + + + + +### EVERYTHING NEW AGAIN + +# only for ranges, timestamps are separate + +class MTime: + def __init__(self,*ls): + # in case we want to call with non-unpacked arguments + if isinstance(ls[0],tuple) or isinstance(ls[0],list): + ls = ls[0] + + self.tup = tuple(ls) + self.precision = len(ls) + self.year = ls[0] + if len(ls)>1: self.month = ls[1] + if len(ls)>2: self.day = ls[2] + dt = [1970,1,1] + dt[:len(ls)] = ls + self.dateobject = date(dt[0],dt[1],dt[2]) + + def __str__(self): + return "/".join(str(part) for part in self.tup) + + def uri(self): + return "in=" + str(self) + + def desc(self,prefix=False): + if self.precision == 3: + if prefix: + return "on " + self.dateobject.strftime("%d. %B %Y") + else: + return self.dateobject.strftime("%d. %B %Y") + if self.precision == 2: + if prefix: + return "in " + self.dateobject.strftime("%B %Y") + else: + return self.dateobject.strftime("%B %Y") + if self.precision == 1: + if prefix: + return "in " + self.dateobject.strftime("%Y") + else: + return self.dateobject.strftime("%Y") + + def informal_desc(self): + now = datetime.datetime.now(tz=datetime.timezone.utc) + today = date(now.year,now.month,now.day) + if self.precision == 3: + diff = (today - dateobject).days + if diff == 0: return "Today" + if diff == 1: return "Yesterday" + if diff < 7 and diff > 1: return timeobject.strftime("%A") + #elif len(t) == 2: + return self.desc() + + # describes only the parts that are different than another range object + def contextual_desc(self,other): + if isinstance(other,MTime): + relevant = self.desc().split(" ") + if self.year == other.year: + relevant.pop() + if self.precision > 1 and other.precision > 1 and self.month == other.month: + relevant.pop() + if self.precision > 2 and other.precision > 2 and self.day == other.day: + relevant.pop() + return " ".join(relevant) + return self.desc() + + # gets object with one higher precision that starts this one + def start(self): + if self.precision == 1: return MTime(self.tup + (1,)) + elif self.precision == 2: return MTime(self.tup + (1,)) + # gets object with one higher precision that ends this one + def end(self): + if self.precision == 1: return MTime(self.tup + (12,)) + elif self.precision == 2: return MTime(self.tup + (monthrange(self.year,self.month)[1],)) + + def first_day(self): + if self.precision == 3: return self + else: return self.start().first_day() + def last_day(self): + if self.precision == 3: return self + else: return self.end().last_day() + + def first_stamp(self): + day = self.first_day().dateobject + return int(datetime.datetime.combine(day,datetime.time(tzinfo=datetime.timezone.utc)).timestamp()) + def last_stamp(self): + day = self.last_day().dateobject + datetime.timedelta(days=1) + return int(datetime.datetime.combine(day,datetime.time(tzinfo=datetime.timezone.utc)).timestamp() - 1) + + + +class MTimeWeek: + def __init__(self,year=None,week=None): + self.year = year + self.week = week + + firstday = date(year,1,1) + y,w,d = firstday.chrcalendar() + if y == self.year: + firstday -= datetime.timedelta(days=(d-1)) + else: + firstday += datetime.timedelta(days=8-d) + self.firstday = firstday + datetime.timedelta(days=7*(week-1)) + self.lastday = self.firstday + datetime.timedelta(days=6) + + def __str__(self): + return str(self.year) + "/W" + str(self.week) + + def uri(self): + return "in=" + str(self) + + def desc(self,prefix=False): + if prefix: + return "in " + "Week " + str(self.week) + " " + str(self.year) + else: + return "Week " + str(self.week) + " " + str(self.year) + + def informal_desc(self): + now = datetime.datetime.now(tz=datetime.timezone.utc) + if now.year == self.year: return "Week " + str(self.week) + return self.desc() + + def contextual_desc(self,other): + if isinstance(other,MTimeWeek): + if other.year == self.year: return "Week " + str(self.week) + return self.desc() + + def start(self): + return self.first_day() + def end(self): + return self.last_day() + + def first_day(self): + return MTime(self.firstday.year,self.firstday.month,self.firstday.day) + def last_day(self): + return MTime(self.lastday.year,self.lastday.month,self.lastday.day) + + def first_stamp(self): + day = self.firstday + return int(datetime.datetime.combine(day,datetime.time(tzinfo=datetime.timezone.utc)).timestamp()) + def last_stamp(self): + day = self.lastday + datetime.timedelta(days=1) + return int(datetime.datetime.combine(day,datetime.time(tzinfo=datetime.timezone.utc)).timestamp() - 1) + + +class MRange: + + def __init__(self,since=None,to=None): + since,to = time_pad(since,to) + self.since = since + self.to = to + + def __str__(self): + return str(self.since) + " - " + str(self.to) + + def uri(self): + keys = [] + if self.since is not None: keys.append("since=" + uri(self.since)) + if self.to is not None: keys.append("&to=" + uri(self.to)) + return "&".join(keys) + + def desc(self,prefix=False): + if self.since is not None and self.to is not None: + if prefix: + return "from " + self.since.contextual_desc(self.to) + " to " + self.to.desc() + else: + return self.since.contextual_desc(self.to) + " to " + self.to.desc() + if self.since is not None and self.to is None: + return "since " + self.since.desc() + if self.since is None and self.to is not None: + return "until " + self.to.desc() + + def informal_desc(self): + # dis gonna be hard + return "Not implemented" + + def start(self): + return self.since + + def end(self): + return self.to + + def first_day(self): + return self.since.first_day() + def last_day(self): + return self.to.last_day() + + def first_stamp(self): + return self.since.first_stamp() + def last_stamp(self): + return self.to.last_stamp() + +## test + +w = MTimeWeek(2018,40) +d = MTime(2019,4,9) +m = MTime(2019,7) +y = MTime(2020) + + + + +def time_str(t): + return str(t) + +# converts strings and stuff to objects def time_fix(t): @@ -67,160 +288,76 @@ def time_fix(t): #if isinstance(t,tuple): t = list(t) try: t = [int(p) for p in t] - return t[:3] + return MTime(t[:3]) except: pass if isinstance(t[1],str) and t[1].startswith("W"): try: weeknum = int(t[1][1:]) - return [t[0],"W",t[1]] + return MTimeWeek(year=t[0],week=t[1]) except: pass -# checks if time is a week -def is_week(t): - return ((len(t) == 3) and (t[1] == "W")) -def week_to_days(t): - pass + + + # makes times the same precision level def time_pad(f,t,full=False): - f,t = time_fix(f), time_fix(t) + + if f is None or t is None: return f,t # week handling + if isinstance(f,MTimeWeek) and isinstance(t,MTimeWeek): + if full: return f.start(),t.end() + else: return f,t + if not isinstance(f,MTimeWeek) and isinstance(t,MTimeWeek): + t = t.end() + if isinstance(f,MTimeWeek) and not isinstance(t,MTimeWeek): + f = f.start() + + while (f.precision < t.precision) or (full and f.precision < 3): + f = f.start() + while (f.precision > t.precision) or (full and t.precision < 3): + t = t.end() + + return f,t - while (len(f) < len(t)) or (full and len(f) < 3): - if len(f) == 1: f.append(1) - elif len(f) == 2: f.append(1) - while (len(f) > len(t)) or (full and len(t) < 3): - if len(t) == 1: t.append(12) - elif len(t) == 2: t.append(monthrange(*t)[1]) - - return (f,t) -def time_desc(t,short=False): - if isinstance(t,int): - if short: - now = datetime.datetime.now(tz=datetime.timezone.utc) - difference = int(now.timestamp() - t) - if difference < 10: return "just now" - if difference < 60: return str(difference) + " seconds ago" - difference = int(difference/60) - if difference < 60: return str(difference) + " minutes ago" if difference>1 else str(difference) + " minute ago" - difference = int(difference/60) - if difference < 24: return str(difference) + " hours ago" if difference>1 else str(difference) + " hour ago" - difference = int(difference/24) - timeobject = datetime.datetime.utcfromtimestamp(t) - if difference < 5: return timeobject.strftime("%A") - if difference < 31: return str(difference) + " days ago" if difference>1 else str(difference) + " day ago" - #if difference < 300 and tim.year == now.year: return tim.strftime("%B") - #if difference < 300: return tim.strftime("%B %Y") - return timeobject.strftime("%d. %B %Y") - else: - timeobject = datetime.datetime.utcfromtimestamp(t) - return timeobject.strftime("%d. %b %Y %I:%M %p") +### TIMESTAMPS + +def timestamp_desc(t,short=False): + + if short: + now = datetime.datetime.now(tz=datetime.timezone.utc) + difference = int(now.timestamp() - t) + + if difference < 10: return "just now" + if difference < 60: return str(difference) + " seconds ago" + difference = int(difference/60) + if difference < 60: return str(difference) + " minutes ago" if difference>1 else str(difference) + " minute ago" + difference = int(difference/60) + if difference < 24: return str(difference) + " hours ago" if difference>1 else str(difference) + " hour ago" + difference = int(difference/24) + timeobject = datetime.datetime.utcfromtimestamp(t) + if difference < 5: return timeobject.strftime("%A") + if difference < 31: return str(difference) + " days ago" if difference>1 else str(difference) + " day ago" + #if difference < 300 and tim.year == now.year: return tim.strftime("%B") + #if difference < 300: return tim.strftime("%B %Y") + + return timeobject.strftime("%d. %B %Y") else: - t = time_fix(t) - date = [1970,1,1] - date[:len(t)] = t - timeobject = datetime.datetime(date[0],date[1],date[2],tzinfo=datetime.timezone.utc) - - nowdate = [1970,1,1] - nowobject = datetime.datetime.now(tz=datetime.timezone.utc) - nowdate[:len(t)] = [nowobject.year, nowobject.month, nowobject.day][:len(t)] - nowobject = datetime.datetime(nowdate[0],nowdate[1],nowdate[2],tzinfo=datetime.timezone.utc) - if short: - if len(t) == 3: - diff = (nowobject - timeobject).days - if diff == 0: return "Today" - if diff == 1: return "Yesterday" - if diff < 7 and diff > 1: return timeobject.strftime("%A") - #elif len(t) == 2: + timeobject = datetime.datetime.utcfromtimestamp(t) + return timeobject.strftime("%d. %b %Y %I:%M %p") - if len(t) == 3: return timeobject.strftime("%d. %B %Y") - if len(t) == 2: return timeobject.strftime("%B %Y") - if len(t) == 1: return timeobject.strftime("%Y") -def range_desc(since=None,to=None,within=None,short=False): - - # the 'short' var we pass down to some of the time_desc calls is a different one than the one here - # the function-wide one indicates whether we want the 'in' 'from' etc at the start - # the other one is if we want exact dates or weekdays etc - # but we still hand it down because it makes sense - - - if within is not None: - since = within - to = within - if since is None: - sincestr = "" - if to is None: - tostr = "" - - if isinstance(since,int) and to is None: - sincestr = "since " + time_desc(since) - shortsincestr = sincestr - elif isinstance(to,int) and since is None: - tostr = "up until " + time_desc(to) - elif isinstance(since,int) and not isinstance(to,int): - sincestr = "from " + time_desc(since) - shortsincestr = time_desc(since) - tostr = "to the end of " + time_desc(to) - elif isinstance(to,int) and not isinstance(since,int): - sincestr = "from the start of " + time_desc(since) - shortsincestr = time_desc(since) - tostr = "to " + time_desc(to) - -# if isinstance(since,int) and isinstance(to,int): result = "from " + time_desc(since) + " to " + time_desc(to) -# elif isinstance(since,int): result = "from " + time_desc(since) + " to the end of " + time_desc(to) -# elif isinstance(to,int): result = "from the start of " + time_desc(since) + " to " + time_desc(to) - else: - if since is not None and to is not None: - since,to = time_pad(since,to) - if since == to: - if len(since) == 3: - sincestr = "on " + time_desc(since) - else: - sincestr = "in " + time_desc(since) - shortsincestr = time_desc(since,short=True) - tostr = "" - elif _week(since,to): - - sincestr = "in " + _week(since,to) - shortsincestr = _week(since,to) - tostr = "" - else: - fparts = time_desc(since).split(" ") - tparts = time_desc(to).split(" ") - - fparts.reverse() - tparts.reverse() - - fparts = fparts[len(commonprefix([fparts,tparts])):] - - fparts.reverse() - tparts.reverse() - - sincestr = "from " + " ".join(fparts) - shortsincestr = " ".join(fparts) - tostr = "to " + " ".join(tparts) - - else: - if since is not None: - sincestr = "since " + time_desc(since) - shortsincestr = sincestr - if to is not None: - tostr = "up until " + time_desc(to) - - if short: return shortsincestr + " " + tostr - else: return sincestr + " " + tostr @@ -267,23 +404,6 @@ def delimit_desc(step="month",stepn=1,trail=1): return txt -def _week(since,to): - if len(since) != 3 or len(to) != 3: return False - dt_since, dt_to = datetime.datetime(*since,tzinfo=datetime.timezone.utc), datetime.datetime(*to,tzinfo=datetime.timezone.utc) - if (dt_to - dt_since).days != 6: return False - if dt_since.weekday() != 6: return False - - c = dt_to.isocalendar()[:2] - return str("Week " + str(c[1]) + " " + str(c[0])) - -def _num(i): - names = ["Zero","One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten","Eleven","Twelve"] - if i < len(names): return names[i] - else: return str(i) - - - - def ranges(since=None,to=None,within=None,step="month",stepn=1,trail=1,max_=None): @@ -325,8 +445,9 @@ def _get_start_of(timestamp,unit): def _get_next(time,unit="auto",step=1): result = time[:] if unit == "auto": + if is_week(time): unit = "week" # see how long the list is, increment by the last specified unit - unit = [None,"year","month","day"][len(time)] + else: unit = [None,"year","month","day"][len(time)] #while len(time) < 3: # time.append(1) diff --git a/rules/predefined/krateng_kpopgirlgroups.tsv b/rules/predefined/krateng_kpopgirlgroups.tsv index ec074cb..6333e38 100644 --- a/rules/predefined/krateng_kpopgirlgroups.tsv +++ b/rules/predefined/krateng_kpopgirlgroups.tsv @@ -69,6 +69,7 @@ countas LE EXID replacetitle HOT PINK Hot Pink replacetitle AH YEAH Ah Yeah replacetitle WHOZ THAT GIRL Whoz that girl +replaceartist Jeonghwa Junghwa # TWICE replacetitle CHEER UP Cheer Up