mirror of
https://github.com/krateng/maloja.git
synced 2023-08-10 21:12:55 +03:00
Complete rework of time descriptors
This commit is contained in:
parent
3f4011f7c4
commit
652bc7eb94
421
malojatime.py
421
malojatime.py
@ -32,10 +32,231 @@ def end_of_scrobbling():
|
|||||||
# return str(t)
|
# return str(t)
|
||||||
|
|
||||||
|
|
||||||
def time_str(t):
|
### helpers
|
||||||
return "/".join(str(tp) for tp in t)
|
|
||||||
|
|
||||||
# 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):
|
def time_fix(t):
|
||||||
|
|
||||||
|
|
||||||
@ -67,160 +288,76 @@ def time_fix(t):
|
|||||||
#if isinstance(t,tuple): t = list(t)
|
#if isinstance(t,tuple): t = list(t)
|
||||||
try:
|
try:
|
||||||
t = [int(p) for p in t]
|
t = [int(p) for p in t]
|
||||||
return t[:3]
|
return MTime(t[:3])
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if isinstance(t[1],str) and t[1].startswith("W"):
|
if isinstance(t[1],str) and t[1].startswith("W"):
|
||||||
try:
|
try:
|
||||||
weeknum = int(t[1][1:])
|
weeknum = int(t[1][1:])
|
||||||
return [t[0],"W",t[1]]
|
return MTimeWeek(year=t[0],week=t[1])
|
||||||
except:
|
except:
|
||||||
pass
|
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
|
# makes times the same precision level
|
||||||
def time_pad(f,t,full=False):
|
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
|
# 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:
|
else:
|
||||||
t = time_fix(t)
|
timeobject = datetime.datetime.utcfromtimestamp(t)
|
||||||
date = [1970,1,1]
|
return timeobject.strftime("%d. %b %Y %I:%M %p")
|
||||||
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:
|
|
||||||
|
|
||||||
|
|
||||||
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
|
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):
|
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):
|
def _get_next(time,unit="auto",step=1):
|
||||||
result = time[:]
|
result = time[:]
|
||||||
if unit == "auto":
|
if unit == "auto":
|
||||||
|
if is_week(time): unit = "week"
|
||||||
# see how long the list is, increment by the last specified unit
|
# 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:
|
#while len(time) < 3:
|
||||||
# time.append(1)
|
# time.append(1)
|
||||||
|
|
||||||
|
@ -69,6 +69,7 @@ countas LE EXID
|
|||||||
replacetitle HOT PINK Hot Pink
|
replacetitle HOT PINK Hot Pink
|
||||||
replacetitle AH YEAH Ah Yeah
|
replacetitle AH YEAH Ah Yeah
|
||||||
replacetitle WHOZ THAT GIRL Whoz that girl
|
replacetitle WHOZ THAT GIRL Whoz that girl
|
||||||
|
replaceartist Jeonghwa Junghwa
|
||||||
|
|
||||||
# TWICE
|
# TWICE
|
||||||
replacetitle CHEER UP Cheer Up
|
replacetitle CHEER UP Cheer Up
|
||||||
|
Can't render this file because it has a wrong number of fields in line 5.
|
Loading…
Reference in New Issue
Block a user