2019-02-19 20:37:13 +03:00
|
|
|
import datetime
|
|
|
|
from calendar import monthrange
|
2019-03-03 00:55:22 +03:00
|
|
|
from os.path import commonprefix
|
2019-04-11 11:49:56 +03:00
|
|
|
import math
|
2019-02-19 20:37:13 +03:00
|
|
|
|
|
|
|
|
2019-03-03 00:55:22 +03:00
|
|
|
FIRST_SCROBBLE = int(datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).timestamp())
|
|
|
|
|
|
|
|
def register_scrobbletime(timestamp):
|
|
|
|
global FIRST_SCROBBLE
|
|
|
|
if timestamp < FIRST_SCROBBLE:
|
|
|
|
FIRST_SCROBBLE = int(timestamp)
|
|
|
|
|
2019-04-02 12:40:02 +03:00
|
|
|
def start_of_scrobbling():
|
|
|
|
global FIRST_SCROBBLE
|
|
|
|
f = datetime.datetime.utcfromtimestamp(FIRST_SCROBBLE)
|
|
|
|
return [f.year]
|
|
|
|
|
|
|
|
def end_of_scrobbling():
|
|
|
|
global FIRST_SCROBBLE
|
|
|
|
f = datetime.datetime.now()
|
|
|
|
return [f.year]
|
2019-03-03 15:38:53 +03:00
|
|
|
|
2019-02-19 20:37:13 +03:00
|
|
|
|
|
|
|
|
2019-04-10 12:52:34 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### EVERYTHING NEW AGAIN
|
|
|
|
|
|
|
|
# only for ranges, timestamps are separate
|
|
|
|
|
2019-04-10 16:45:50 +03:00
|
|
|
class MRangeDescriptor:
|
|
|
|
|
|
|
|
def __eq__(self,other):
|
2019-04-10 18:56:40 +03:00
|
|
|
if not isinstance(other,MRangeDescriptor): return False
|
|
|
|
return (self.first_stamp() == other.first_stamp() and self.last_stamp() == other.last_stamp())
|
2019-04-10 16:45:50 +03:00
|
|
|
|
2019-04-12 18:57:28 +03:00
|
|
|
# gives a hashable object that uniquely identifies this time range
|
|
|
|
def hashable(self):
|
|
|
|
return self.first_stamp(),self.last_stamp()
|
|
|
|
|
2019-04-10 16:45:50 +03:00
|
|
|
|
|
|
|
def info(self):
|
|
|
|
return {
|
|
|
|
"fromstring":self.fromstr(),
|
|
|
|
"tostr":self.tostr(),
|
|
|
|
"uri":self.uri(),
|
|
|
|
"fromstamp":self.first_stamp(),
|
|
|
|
"tostamp":self.last_stamp(),
|
|
|
|
"description":self.desc()
|
|
|
|
}
|
|
|
|
|
2019-05-08 18:42:56 +03:00
|
|
|
def __json__(self):
|
|
|
|
return {
|
|
|
|
"fromstring":self.fromstr(),
|
|
|
|
"tostr":self.tostr(),
|
|
|
|
"fromstamp":self.first_stamp(),
|
|
|
|
"tostamp":self.last_stamp(),
|
|
|
|
"description":self.desc()
|
|
|
|
}
|
|
|
|
|
2019-04-10 16:45:50 +03:00
|
|
|
def uri(self):
|
|
|
|
return "&".join(k + "=" + self.urikeys[k] for k in self.urikeys)
|
|
|
|
|
2019-04-10 19:50:56 +03:00
|
|
|
def unlimited(self):
|
|
|
|
return False
|
|
|
|
|
2019-04-26 18:05:29 +03:00
|
|
|
# returns the description of the range including buttons to go back and forth
|
|
|
|
#def desc_interactive(self,**kwargs):
|
|
|
|
# if self.next(1) is None:
|
|
|
|
# return self.desc(**kwargs)
|
|
|
|
# else:
|
|
|
|
# prevrange = self.next(-1)
|
|
|
|
# nextrange = self.next(1)
|
2019-04-10 16:45:50 +03:00
|
|
|
|
|
|
|
# a range that is exactly a gregorian calendar unit (year, month or day)
|
|
|
|
class MTime(MRangeDescriptor):
|
2019-04-10 12:52:34 +03:00
|
|
|
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
|
2019-05-08 19:09:03 +03:00
|
|
|
self.dateobject = datetime.date(dt[0],dt[1],dt[2])
|
2019-04-10 12:52:34 +03:00
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return "/".join(str(part) for part in self.tup)
|
2019-04-10 16:45:50 +03:00
|
|
|
def fromstr(self):
|
|
|
|
return str(self)
|
|
|
|
def tostr(self):
|
|
|
|
return str(self)
|
2019-04-10 12:52:34 +03:00
|
|
|
|
2019-04-12 18:57:28 +03:00
|
|
|
# whether we currently live or will ever again live in this range
|
|
|
|
def active(self):
|
2019-05-08 19:09:03 +03:00
|
|
|
tod = datetime.date.today()
|
2019-04-12 18:57:28 +03:00
|
|
|
if tod.year > self.year: return False
|
|
|
|
if self.precision == 1: return True
|
|
|
|
if tod.year == self.year:
|
|
|
|
if tod.month > self.month: return False
|
|
|
|
if self.precision == 2: return True
|
|
|
|
if tod.month == self.month:
|
|
|
|
if tod.day > self.day: return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-04-10 16:45:50 +03:00
|
|
|
def urikeys(self):
|
|
|
|
return {"in":str(self)}
|
2019-04-10 12:52:34 +03:00
|
|
|
|
|
|
|
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)
|
2019-05-08 19:09:03 +03:00
|
|
|
today = datetime.date(now.year,now.month,now.day)
|
2019-04-10 12:52:34 +03:00
|
|
|
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)
|
|
|
|
|
2019-04-10 16:45:50 +03:00
|
|
|
# next range of equal length (not exactly same amount of days, but same precision level)
|
|
|
|
def next(self,step=1):
|
2019-04-11 11:49:56 +03:00
|
|
|
if abs(step) == math.inf: return None
|
2019-04-10 16:45:50 +03:00
|
|
|
if self.precision == 1:
|
|
|
|
return MTime(self.year + step)
|
|
|
|
elif self.precision == 2:
|
|
|
|
dt = [self.year,self.month]
|
|
|
|
dt[1] += step
|
|
|
|
while dt[1] > 12:
|
|
|
|
dt[1] -= 12
|
|
|
|
dt[0] += 1
|
|
|
|
while dt[1] < 1:
|
|
|
|
dt[1] += 12
|
|
|
|
dt[0] -= 1
|
|
|
|
return MTime(*dt)
|
|
|
|
elif self.precision == 3:
|
|
|
|
dt = self.dateobject
|
|
|
|
d = datetime.timedelta(days=step)
|
|
|
|
newdate = dt + d
|
|
|
|
return MTime(newdate.year,newdate.month,newdate.day)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# a range that is exactly one christian week (starting on sunday)
|
|
|
|
class MTimeWeek(MRangeDescriptor):
|
2019-04-10 12:52:34 +03:00
|
|
|
def __init__(self,year=None,week=None):
|
|
|
|
self.year = year
|
|
|
|
self.week = week
|
|
|
|
|
2019-04-11 13:07:57 +03:00
|
|
|
# assume the first day of the first week of this year is 1/1
|
2019-05-08 19:09:03 +03:00
|
|
|
firstday = datetime.date(year,1,1)
|
2019-04-10 12:52:34 +03:00
|
|
|
y,w,d = firstday.chrcalendar()
|
|
|
|
if y == self.year:
|
|
|
|
firstday -= datetime.timedelta(days=(d-1))
|
|
|
|
else:
|
|
|
|
firstday += datetime.timedelta(days=8-d)
|
2019-04-11 13:07:57 +03:00
|
|
|
# now we know the real first day, add the weeks we need
|
|
|
|
firstday = firstday + datetime.timedelta(days=7*(week-1))
|
|
|
|
lastday = firstday + datetime.timedelta(days=6)
|
|
|
|
# turn them into local overwritten date objects
|
2019-05-08 19:09:03 +03:00
|
|
|
self.firstday = datetime.date(firstday.year,firstday.month,firstday.day)
|
|
|
|
self.lastday = datetime.date(lastday.year,lastday.month,lastday.day)
|
2019-04-11 13:07:57 +03:00
|
|
|
# now check if we're still in the same year
|
|
|
|
y,w,_ = self.firstday.chrcalendar()
|
|
|
|
self.year,self.week = y,w
|
|
|
|
# firstday and lastday are already correct
|
|
|
|
|
2019-04-10 12:52:34 +03:00
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return str(self.year) + "/W" + str(self.week)
|
2019-04-10 16:45:50 +03:00
|
|
|
def fromstr(self):
|
|
|
|
return str(self)
|
|
|
|
def tostr(self):
|
|
|
|
return str(self)
|
2019-04-10 12:52:34 +03:00
|
|
|
|
2019-04-12 18:57:28 +03:00
|
|
|
# whether we currently live or will ever again live in this range
|
|
|
|
def active(self):
|
2019-05-08 19:09:03 +03:00
|
|
|
tod = datetime.date.today()
|
2019-04-12 18:57:28 +03:00
|
|
|
if tod.year > self.year: return False
|
|
|
|
if tod.year == self.year:
|
|
|
|
if tod.chrcalendar()[1] > self.week: return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
2019-04-10 16:45:50 +03:00
|
|
|
def urikeys(self):
|
|
|
|
return {"in":str(self)}
|
2019-04-10 12:52:34 +03:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2019-04-10 16:45:50 +03:00
|
|
|
def next(self,step=1):
|
2019-04-11 11:49:56 +03:00
|
|
|
if abs(step) == math.inf: return None
|
2019-04-11 13:07:57 +03:00
|
|
|
return MTimeWeek(self.year,self.week + step)
|
2019-04-10 12:52:34 +03:00
|
|
|
|
2019-04-10 16:45:50 +03:00
|
|
|
# a range that is defined by separate start and end
|
|
|
|
class MRange(MRangeDescriptor):
|
2019-04-10 12:52:34 +03:00
|
|
|
|
|
|
|
def __init__(self,since=None,to=None):
|
|
|
|
since,to = time_pad(since,to)
|
|
|
|
self.since = since
|
|
|
|
self.to = to
|
2019-04-11 11:49:56 +03:00
|
|
|
if isinstance(self.since,MRange): self.since = self.since.start()
|
|
|
|
if isinstance(self.to,MRange): self.to = self.to.end()
|
2019-04-10 19:50:56 +03:00
|
|
|
|
2019-04-10 12:52:34 +03:00
|
|
|
def __str__(self):
|
|
|
|
return str(self.since) + " - " + str(self.to)
|
2019-04-10 16:45:50 +03:00
|
|
|
def fromstr(self):
|
|
|
|
return str(self.since)
|
|
|
|
def tostr(self):
|
|
|
|
return str(self.to)
|
2019-04-10 12:52:34 +03:00
|
|
|
|
2019-04-12 18:57:28 +03:00
|
|
|
# whether we currently live or will ever again live in this range
|
|
|
|
def active(self):
|
|
|
|
if self.to is None: return True
|
|
|
|
return self.to.active()
|
|
|
|
|
2019-04-10 19:50:56 +03:00
|
|
|
def unlimited(self):
|
|
|
|
return (self.since is None and self.to is None)
|
2019-04-10 16:45:50 +03:00
|
|
|
|
|
|
|
def urikeys(self):
|
|
|
|
keys = {}
|
|
|
|
if self.since is not None: keys["since"] = str(self.since)
|
|
|
|
if self.to is not None: keys["to"] = str(self.to)
|
|
|
|
return keys
|
2019-04-10 12:52:34 +03:00
|
|
|
|
|
|
|
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()
|
2019-04-10 18:56:40 +03:00
|
|
|
if self.since is None and self.to is None:
|
|
|
|
return ""
|
2019-04-10 12:52:34 +03:00
|
|
|
|
|
|
|
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):
|
2019-04-10 16:45:50 +03:00
|
|
|
if self.since is None: return FIRST_SCROBBLE
|
|
|
|
else: return self.since.first_stamp()
|
2019-04-10 12:52:34 +03:00
|
|
|
def last_stamp(self):
|
2019-04-10 16:45:50 +03:00
|
|
|
if self.to is None: return int(datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).timestamp())
|
|
|
|
else: return self.to.last_stamp()
|
|
|
|
|
|
|
|
def next(self,step=1):
|
2019-04-11 11:49:56 +03:00
|
|
|
if abs(step) == math.inf: return None
|
2019-04-26 18:05:29 +03:00
|
|
|
if self.since is None or self.to is None: return None
|
2019-04-10 16:45:50 +03:00
|
|
|
# hop from the start element by one until we reach the end element
|
|
|
|
diff = 1
|
|
|
|
nxt = self.since
|
|
|
|
while (nxt != self.to):
|
|
|
|
diff += 1
|
|
|
|
nxt = nxt.next(step=1)
|
|
|
|
|
|
|
|
newstart = self.since.next(step=diff*step)
|
|
|
|
newend = self.to.next(step=diff*step)
|
|
|
|
|
|
|
|
return MRange(newstart,newend)
|
|
|
|
|
2019-04-10 12:52:34 +03:00
|
|
|
|
|
|
|
## test
|
|
|
|
|
|
|
|
w = MTimeWeek(2018,40)
|
|
|
|
d = MTime(2019,4,9)
|
|
|
|
m = MTime(2019,7)
|
|
|
|
y = MTime(2020)
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-04-10 18:56:40 +03:00
|
|
|
def range_desc(r,**kwargs):
|
|
|
|
if r is None: return ""
|
|
|
|
return r.desc(**kwargs)
|
2019-04-10 12:52:34 +03:00
|
|
|
|
2019-04-08 14:04:31 +03:00
|
|
|
def time_str(t):
|
2019-04-10 18:56:40 +03:00
|
|
|
obj = time_fix(t)
|
|
|
|
return obj.desc()
|
2019-04-04 21:01:06 +03:00
|
|
|
|
2019-04-10 12:52:34 +03:00
|
|
|
# converts strings and stuff to objects
|
2019-03-03 00:55:22 +03:00
|
|
|
def time_fix(t):
|
2019-04-10 16:45:50 +03:00
|
|
|
if t is None: return None
|
|
|
|
if isinstance(t,MRangeDescriptor): return t
|
2019-03-03 00:55:22 +03:00
|
|
|
|
2019-03-04 15:43:19 +03:00
|
|
|
if isinstance(t, str):
|
2019-03-03 00:55:22 +03:00
|
|
|
tod = datetime.datetime.utcnow()
|
2019-03-04 15:43:19 +03:00
|
|
|
months = ["january","february","march","april","may","june","july","august","september","october","november","december"]
|
|
|
|
weekdays = ["sunday","monday","tuesday","wednesday","thursday","friday","saturday"]
|
2019-04-02 12:40:02 +03:00
|
|
|
|
2019-03-04 15:43:19 +03:00
|
|
|
if t.lower() in ["today","day"]:
|
2019-04-10 18:56:40 +03:00
|
|
|
return today()
|
|
|
|
elif t.lower() in ["month","thismonth"]:
|
|
|
|
return thismonth()
|
|
|
|
elif t.lower() in ["year","thisyear"]:
|
|
|
|
return thisyear()
|
|
|
|
elif t.lower() in ["week","thisweek"]:
|
|
|
|
return thisweek()
|
2019-04-02 12:40:02 +03:00
|
|
|
|
|
|
|
|
2019-03-04 15:43:19 +03:00
|
|
|
elif t.lower() in months:
|
|
|
|
#diff = (tod.month - months.index(t.lower()) - 1)
|
|
|
|
month = months.index(t.lower()) + 1
|
|
|
|
t = [tod.year,month]
|
|
|
|
if month > tod.month: t[0] -= 1
|
|
|
|
elif t.lower() in weekdays:
|
|
|
|
weekday = weekdays.index(t.lower())
|
|
|
|
diff = (tod.isoweekday() - weekday) % 7
|
|
|
|
dt = tod - datetime.timedelta(diff)
|
|
|
|
t = [dt.year,dt.month,dt.day]
|
2019-02-19 20:37:13 +03:00
|
|
|
|
2019-03-03 00:55:22 +03:00
|
|
|
if isinstance(t,str): t = t.split("/")
|
|
|
|
#if isinstance(t,tuple): t = list(t)
|
2019-04-08 16:39:10 +03:00
|
|
|
try:
|
|
|
|
t = [int(p) for p in t]
|
2019-04-10 12:52:34 +03:00
|
|
|
return MTime(t[:3])
|
2019-04-08 16:39:10 +03:00
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
2019-04-09 14:49:28 +03:00
|
|
|
if isinstance(t[1],str) and t[1].startswith("W"):
|
2019-04-08 16:39:10 +03:00
|
|
|
try:
|
2019-04-10 16:45:50 +03:00
|
|
|
year = int(t[0])
|
2019-04-08 16:39:10 +03:00
|
|
|
weeknum = int(t[1][1:])
|
2019-04-10 16:45:50 +03:00
|
|
|
return MTimeWeek(year=year,week=weeknum)
|
2019-04-08 16:39:10 +03:00
|
|
|
except:
|
2019-04-10 16:45:50 +03:00
|
|
|
raise
|
|
|
|
|
2019-04-08 16:39:10 +03:00
|
|
|
|
2019-04-10 12:52:34 +03:00
|
|
|
|
2019-04-10 16:45:50 +03:00
|
|
|
def get_range_object(since=None,to=None,within=None):
|
|
|
|
|
|
|
|
since,to,within = time_fix(since),time_fix(to),time_fix(within)
|
|
|
|
|
|
|
|
# check if we can simplify
|
|
|
|
if since is not None and to is not None and since == to: within = since
|
|
|
|
# TODO
|
|
|
|
|
|
|
|
if within is not None:
|
|
|
|
return within
|
|
|
|
else:
|
|
|
|
return MRange(since,to)
|
|
|
|
|
2019-04-10 12:52:34 +03:00
|
|
|
|
|
|
|
|
2019-03-03 00:55:22 +03:00
|
|
|
|
2019-04-02 12:40:02 +03:00
|
|
|
# makes times the same precision level
|
|
|
|
def time_pad(f,t,full=False):
|
2019-04-10 12:52:34 +03:00
|
|
|
|
|
|
|
if f is None or t is None: return f,t
|
2019-04-08 16:39:10 +03:00
|
|
|
|
|
|
|
# week handling
|
2019-04-10 12:52:34 +03:00
|
|
|
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()
|
2019-04-08 16:39:10 +03:00
|
|
|
|
2019-04-10 12:52:34 +03:00
|
|
|
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()
|
2019-04-08 16:39:10 +03:00
|
|
|
|
2019-04-10 12:52:34 +03:00
|
|
|
return f,t
|
2019-04-02 12:40:02 +03:00
|
|
|
|
|
|
|
|
2019-04-10 18:56:40 +03:00
|
|
|
|
2019-04-02 12:40:02 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
2019-03-03 00:55:22 +03:00
|
|
|
|
2019-03-03 15:38:53 +03:00
|
|
|
|
2019-04-10 12:52:34 +03:00
|
|
|
### TIMESTAMPS
|
2019-03-03 15:38:53 +03:00
|
|
|
|
2019-04-10 12:52:34 +03:00
|
|
|
def timestamp_desc(t,short=False):
|
2019-04-02 12:40:02 +03:00
|
|
|
|
2019-04-10 12:52:34 +03:00
|
|
|
if short:
|
|
|
|
now = datetime.datetime.now(tz=datetime.timezone.utc)
|
|
|
|
difference = int(now.timestamp() - t)
|
2019-04-02 12:40:02 +03:00
|
|
|
|
2019-04-10 12:52:34 +03:00
|
|
|
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"
|
2019-04-11 18:44:33 +03:00
|
|
|
if difference < 300 or timeobject.year == now.year: return timeobject.strftime("%B")
|
2019-04-10 12:52:34 +03:00
|
|
|
#if difference < 300: return tim.strftime("%B %Y")
|
2019-04-02 12:40:02 +03:00
|
|
|
|
2019-04-11 18:44:33 +03:00
|
|
|
return timeobject.strftime("%Y")
|
2019-04-10 12:52:34 +03:00
|
|
|
else:
|
|
|
|
timeobject = datetime.datetime.utcfromtimestamp(t)
|
|
|
|
return timeobject.strftime("%d. %b %Y %I:%M %p")
|
2019-04-02 12:40:02 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-04-10 16:45:50 +03:00
|
|
|
def time_stamps(since=None,to=None,within=None,range=None):
|
2019-04-02 12:40:02 +03:00
|
|
|
|
2019-04-10 16:45:50 +03:00
|
|
|
if range is None: range = get_range_object(since=since,to=to,within=within)
|
|
|
|
return range.first_stamp(),range.last_stamp()
|
|
|
|
#print(range.desc())
|
|
|
|
# if (since==None): stamp1 = FIRST_SCROBBLE
|
|
|
|
# else: stamp1 = range.first_stamp()
|
|
|
|
# if (to==None): stamp2 = int(datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).timestamp())
|
|
|
|
# else: stamp2 = range.last_stamp()
|
|
|
|
# return stamp1,stamp2
|
|
|
|
# if (since==None): stamp1 = FIRST_SCROBBLE
|
|
|
|
# else:
|
|
|
|
# stamp1 = since1
|
|
|
|
# since = time_fix(since)
|
|
|
|
# date = [1970,1,1]
|
|
|
|
# date[:len(since)] = since
|
|
|
|
# stamp1 = int(datetime.datetime(date[0],date[1],date[2],tzinfo=datetime.timezone.utc).timestamp())
|
|
|
|
#
|
|
|
|
# if (to==None): stamp2 = int(datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).timestamp())
|
|
|
|
# else:
|
|
|
|
# to = time_fix(to)
|
|
|
|
# to = _get_next(to)
|
|
|
|
# date = [1970,1,1]
|
|
|
|
# date[:len(to)] = to
|
|
|
|
# stamp2 = int(datetime.datetime(date[0],date[1],date[2],tzinfo=datetime.timezone.utc).timestamp())
|
|
|
|
#
|
|
|
|
#
|
|
|
|
# return (stamp1,stamp2-1)
|
2019-04-02 12:40:02 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-03-03 03:29:55 +03:00
|
|
|
def delimit_desc(step="month",stepn=1,trail=1):
|
2019-03-03 00:55:22 +03:00
|
|
|
txt = ""
|
|
|
|
if stepn is not 1: txt += _num(stepn) + "-"
|
2019-04-10 18:56:40 +03:00
|
|
|
txt += {"year":"Yearly","month":"Monthly","week":"Weekly","day":"Daily"}[step.lower()]
|
2019-03-03 00:55:22 +03:00
|
|
|
#if trail is not 1: txt += " " + _num(trail) + "-Trailing"
|
2019-04-11 11:49:56 +03:00
|
|
|
if trail is math.inf: txt += " Cumulative"
|
|
|
|
elif trail is not 1: txt += " Trailing" #we don't need all the info in the title
|
2019-04-02 12:40:02 +03:00
|
|
|
|
2019-03-03 00:55:22 +03:00
|
|
|
return txt
|
2019-04-02 12:40:02 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-04-10 16:45:50 +03:00
|
|
|
def day_from_timestamp(stamp):
|
|
|
|
dt = datetime.datetime.utcfromtimestamp(stamp)
|
|
|
|
return MTime(dt.year,dt.month,dt.day)
|
|
|
|
def month_from_timestamp(stamp):
|
|
|
|
dt = datetime.datetime.utcfromtimestamp(stamp)
|
|
|
|
return MTime(dt.year,dt.month)
|
|
|
|
def year_from_timestamp(stamp):
|
|
|
|
dt = datetime.datetime.utcfromtimestamp(stamp)
|
|
|
|
return MTime(dt.year)
|
|
|
|
def week_from_timestamp(stamp):
|
|
|
|
dt = datetime.datetime.utcfromtimestamp(stamp)
|
2019-05-08 19:09:03 +03:00
|
|
|
d = datetime.date(dt.year,dt.month,dt.day)
|
2019-04-10 16:45:50 +03:00
|
|
|
y,w,_ = d.chrcalendar()
|
|
|
|
return MTimeWeek(y,w)
|
2019-03-03 03:29:55 +03:00
|
|
|
|
2019-04-10 16:45:50 +03:00
|
|
|
def from_timestamp(stamp,unit):
|
|
|
|
if unit == "day": return day_from_timestamp(stamp)
|
|
|
|
if unit == "week": return week_from_timestamp(stamp)
|
|
|
|
if unit == "month": return month_from_timestamp(stamp)
|
|
|
|
if unit == "year": return year_from_timestamp(stamp)
|
2019-03-03 03:29:55 +03:00
|
|
|
|
|
|
|
|
2019-04-10 19:50:56 +03:00
|
|
|
# since, to and within can accept old notations or objects. timerange can only be a new object.
|
2019-04-10 18:56:40 +03:00
|
|
|
def ranges(since=None,to=None,within=None,timerange=None,step="month",stepn=1,trail=1,max_=None):
|
2019-04-10 16:45:50 +03:00
|
|
|
|
2019-04-10 18:56:40 +03:00
|
|
|
(firstincluded,lastincluded) = time_stamps(since=since,to=to,within=within,range=timerange)
|
2019-04-10 16:45:50 +03:00
|
|
|
|
|
|
|
d_start = from_timestamp(firstincluded,step)
|
2019-04-11 11:49:56 +03:00
|
|
|
d_start = d_start.next(stepn-1) #last part of first included range
|
2019-03-03 03:29:55 +03:00
|
|
|
i = 0
|
2019-04-11 11:49:56 +03:00
|
|
|
current_end = d_start
|
2019-04-11 18:44:33 +03:00
|
|
|
#ranges = []
|
2019-04-11 11:49:56 +03:00
|
|
|
while current_end.first_stamp() <= lastincluded and (max_ is None or i < max_):
|
2019-04-11 18:44:33 +03:00
|
|
|
|
2019-04-11 11:49:56 +03:00
|
|
|
current_start = current_end.next((stepn*trail-1)*-1)
|
|
|
|
if current_start == current_end:
|
|
|
|
yield current_start
|
2019-04-11 18:44:33 +03:00
|
|
|
#ranges.append(current_start)
|
2019-04-10 18:56:40 +03:00
|
|
|
else:
|
2019-04-11 11:49:56 +03:00
|
|
|
yield MRange(current_start,current_end)
|
2019-04-11 18:44:33 +03:00
|
|
|
#ranges.append(MRange(current_start,current_end))
|
2019-04-11 11:49:56 +03:00
|
|
|
current_end = current_end.next(stepn)
|
2019-04-11 18:44:33 +03:00
|
|
|
|
2019-03-03 03:29:55 +03:00
|
|
|
i += 1
|
|
|
|
|
2019-04-11 18:44:33 +03:00
|
|
|
#return ranges
|
2019-03-03 03:29:55 +03:00
|
|
|
|
|
|
|
|
2019-04-10 18:56:40 +03:00
|
|
|
|
|
|
|
def today():
|
2019-04-14 16:08:56 +03:00
|
|
|
tod = datetime.datetime.utcnow()
|
2019-04-10 18:56:40 +03:00
|
|
|
return MTime(tod.year,tod.month,tod.day)
|
|
|
|
def thisweek():
|
2019-04-14 16:08:56 +03:00
|
|
|
tod = datetime.datetime.utcnow()
|
2019-05-08 19:09:03 +03:00
|
|
|
tod = datetime.date(tod.year,tod.month,tod.day)
|
2019-04-10 18:56:40 +03:00
|
|
|
y,w,_ = tod.chrcalendar()
|
|
|
|
return MTimeWeek(y,w)
|
|
|
|
def thismonth():
|
2019-04-14 16:08:56 +03:00
|
|
|
tod = datetime.datetime.utcnow()
|
2019-04-10 18:56:40 +03:00
|
|
|
return MTime(tod.year,tod.month)
|
|
|
|
def thisyear():
|
2019-04-14 16:08:56 +03:00
|
|
|
tod = datetime.datetime.utcnow()
|
2019-04-10 18:56:40 +03:00
|
|
|
return MTime(tod.year)
|
|
|
|
|
2019-04-10 16:45:50 +03:00
|
|
|
#def _get_start_of(timestamp,unit):
|
|
|
|
# date = datetime.datetime.utcfromtimestamp(timestamp)
|
|
|
|
# if unit == "year":
|
|
|
|
# #return [date.year,1,1]
|
|
|
|
# return [date.year]
|
|
|
|
# elif unit == "month":
|
|
|
|
# #return [date.year,date.month,1]
|
|
|
|
# return [date.year,date.month]
|
|
|
|
# elif unit == "day":
|
|
|
|
# return [date.year,date.month,date.day]
|
|
|
|
# elif unit == "week":
|
|
|
|
# change = (date.weekday() + 1) % 7
|
|
|
|
# d = datetime.timedelta(days=change)
|
|
|
|
# newdate = date - d
|
|
|
|
# return [newdate.year,newdate.month,newdate.day]
|
|
|
|
#
|
|
|
|
#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
|
|
|
|
# else: unit = [None,"year","month","day"][len(time)]
|
|
|
|
# #while len(time) < 3:
|
|
|
|
# # time.append(1)
|
|
|
|
#
|
|
|
|
# if unit == "year":
|
|
|
|
# #return [time[0] + step,time[1],time[2]]
|
|
|
|
# result[0] += step
|
|
|
|
# return result
|
|
|
|
# elif unit == "month":
|
|
|
|
# #result = [time[0],time[1] + step,time[2]]
|
|
|
|
# result[1] += step
|
|
|
|
# while result[1] > 12:
|
|
|
|
# result[1] -= 12
|
|
|
|
# result[0] += 1
|
|
|
|
# while result[1] < 1:
|
|
|
|
# result[1] += 12
|
|
|
|
# result[0] -= 1
|
|
|
|
# return result
|
|
|
|
# elif unit == "day":
|
|
|
|
# dt = datetime.datetime(time[0],time[1],time[2])
|
|
|
|
# d = datetime.timedelta(days=step)
|
|
|
|
# newdate = dt + d
|
|
|
|
# return [newdate.year,newdate.month,newdate.day]
|
|
|
|
# #eugh
|
|
|
|
# elif unit == "week":
|
|
|
|
# return _get_next(time,"day",step * 7)
|
|
|
|
#
|
2019-03-03 03:29:55 +03:00
|
|
|
# like _get_next(), but gets the last INCLUDED day / month whatever
|
2019-04-10 16:45:50 +03:00
|
|
|
#def _get_end(time,unit="auto",step=1):
|
|
|
|
# if step == 1:
|
|
|
|
# if unit == "auto": return time[:]
|
|
|
|
# if unit == "year" and len(time) == 1: return time[:]
|
|
|
|
# if unit == "month" and len(time) == 2: return time[:]
|
|
|
|
# if unit == "day" and len(time) == 3: return time[:]
|
|
|
|
# exc = _get_next(time,unit,step)
|
|
|
|
# inc = _get_next(exc,"auto",-1)
|
|
|
|
# return inc
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
|
|
#def _is_past(date,limit):
|
|
|
|
# date_, limit_ = date[:], limit[:]
|
|
|
|
# while len(date_) != 3: date_.append(1)
|
|
|
|
# while len(limit_) != 3: limit_.append(1)
|
|
|
|
# if not date_[0] == limit_[0]:
|
|
|
|
# return date_[0] > limit_[0]
|
|
|
|
# if not date_[1] == limit_[1]:
|
|
|
|
# return date_[1] > limit_[1]
|
|
|
|
# return (date_[2] > limit_[2])
|
|
|
|
##
|