1
0
mirror of https://github.com/mail-in-a-box/mailinabox.git synced 2025-04-03 00:07:05 +00:00
mailinabox/management/reporting/uidata/Timeseries.py
2022-09-19 14:45:11 -04:00

137 lines
4.2 KiB
Python

#####
##### This file is part of Mail-in-a-Box-LDAP which is released under the
##### terms of the GNU Affero General Public License as published by the
##### Free Software Foundation, either version 3 of the License, or (at
##### your option) any later version. See file LICENSE or go to
##### https://github.com/downtownallday/mailinabox-ldap for full license
##### details.
#####
import datetime
import bisect
class Timeseries(object):
def __init__(self, desc, start_date, end_date, binsize):
# start_date: 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
# start: 'YYYY-MM-DD HH:MM:SS'
self.start = self.full_datetime_str(start_date, False)
self.start_unixepoch = self.unix_time(self.start)
# end_date: 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
# end: 'YYYY-MM-DD HH:MM:SS'
self.end = self.full_datetime_str(end_date, True)
# binsize: integer in minutes
self.binsize = binsize
# timefmt is a format string for sqlite strftime() that puts a
# sqlite datetime into a "bin" date
self.timefmt = '%Y-%m-%d %H:%M:%S'
# parsefmt is a date parser string to be used to re-interpret
# "bin" grouping dates (data.dates) to native dates. server
# always returns utc dates
self.parsefmt = '%Y-%m-%d %H:%M:%S'
self.dates = [] # dates must be "bin" date strings
self.series = []
self.data = {
'range': [ self.start, self.end ],
'range_parse_format': '%Y-%m-%d %H:%M:%S',
'binsize': self.binsize,
'date_parse_format': self.parsefmt,
'y': desc,
'dates': self.dates,
'series': self.series
}
def full_datetime_str(self, date_str, next_day):
if ':' in date_str:
return date_str
elif not next_day:
return date_str + " 00:00:00"
else:
d = datetime.datetime.strptime(date_str, '%Y-%m-%d')
d = d + datetime.timedelta(days=1)
return d.strftime('%Y-%m-%d 00:00:00')
def unix_time(self, full_datetime_str):
d = datetime.datetime.strptime(
full_datetime_str + ' UTC',
'%Y-%m-%d %H:%M:%S %Z'
)
return int(d.timestamp())
def binsizeWithUnit(self):
# normalize binsize (which is a time span in minutes)
days = int(self.binsize / (24 * 60))
hours = int((self.binsize - days*24*60) / 60 )
mins = self.binsize - days*24*60 - hours*60
if days == 0 and hours == 0:
return {
'unit': 'minute',
'value': mins
}
if days == 0:
return {
'unit': 'hour',
'value': hours
}
return {
'unit': 'day',
'value': days
}
def append_date(self, date_str):
'''date_str should be a "bin" date - that is a date formatted with
self.timefmt.
1. it should be greater than the previous bin so that the date
list remains sorted
2. d3js does not require that all dates be added for a
timespan if there is no data for the bin
'''
self.dates.append(date_str)
def insert_date(self, date_str):
'''adds bin date if it does not exist and returns the new index. if
the date already exists, returns the existing index.
'''
i = bisect.bisect_right(self.dates, date_str)
if len(self.dates)>0 and self.dates[i-1] == date_str:
return i-1
elif i == len(self.dates):
self.dates.append(date_str)
else:
self.dates.insert(i, date_str)
''' add zero values to all series for the new date '''
for series in self.series:
series['values'].insert(i, 0)
return i
def add_series(self, id, name):
s = {
'id': id,
'name': name,
'values': []
}
self.series.append(s)
for date in self.dates:
s['values'].append(0)
return s
def asDict(self):
return self.data