155 lines
5.4 KiB
Python
155 lines
5.4 KiB
Python
# bijoe - BI dashboard
|
|
# Copyright (C) 2015 Entr'ouvert
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
# 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.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import re
|
|
from datetime import date
|
|
|
|
import isodate
|
|
from dateutil.relativedelta import MO, relativedelta
|
|
|
|
|
|
class RelativeDate(date):
|
|
__TEMPLATES = [
|
|
{
|
|
'pattern': ' *cette +année *',
|
|
'truncate': 'year',
|
|
},
|
|
{
|
|
'pattern': ' *l\'année +dernière *',
|
|
'truncate': 'year',
|
|
'timedelta': {'years': -1},
|
|
},
|
|
{
|
|
'pattern': ' *l\'année +prochaine *',
|
|
'truncate': 'year',
|
|
'timedelta': {'years': 1},
|
|
},
|
|
{
|
|
'pattern': ' *les +(?P<years>[1-9][0-9]*)+ +dernières +années*',
|
|
'truncate': 'year',
|
|
'timedelta': {'years': '-years'},
|
|
},
|
|
{
|
|
'pattern': ' *ce +mois *',
|
|
'truncate': 'month',
|
|
},
|
|
{
|
|
'pattern': ' *le +mois +dernier *',
|
|
'truncate': 'month',
|
|
'timedelta': {'months': -1},
|
|
},
|
|
{
|
|
'pattern': ' *les +(?P<months>[1-9][0-9]*)+ +derniers +mois *',
|
|
'truncate': 'month',
|
|
'timedelta': {'months': '-months'},
|
|
},
|
|
{
|
|
'pattern': ' *le +mois +prochain *',
|
|
'truncate': 'month',
|
|
'timedelta': {'months': 1},
|
|
},
|
|
{
|
|
'pattern': ' *les +(?P<months>[1-9][0-9]*) +prochains +mois *',
|
|
'truncate': 'month',
|
|
'timedelta': {'months': '+months'},
|
|
},
|
|
{
|
|
'pattern': ' *cette +semaine *',
|
|
'truncate': 'week',
|
|
},
|
|
{
|
|
'pattern': ' *ce +trimestre *',
|
|
'truncate': 'quarter',
|
|
},
|
|
{
|
|
'pattern': ' *le +dernier +trimestre *',
|
|
'truncate': 'quarter',
|
|
'timedelta': {'months': -3},
|
|
},
|
|
{
|
|
'pattern': ' *les +(?P<quarters>[1-9][0-9]*) +derniers +trimestres *',
|
|
'truncate': 'quarter',
|
|
'timedelta': {'months': '-3*quarters'},
|
|
},
|
|
{
|
|
'pattern': ' *le +prochain +trimestre *',
|
|
'truncate': 'quarter',
|
|
'timedelta': {'months': 3},
|
|
},
|
|
{
|
|
'pattern': ' *les +(?P<quarters>[1-9][0-9]*) +prochains +trimestres *',
|
|
'truncate': 'quarter',
|
|
'timedelta': {'months': '3*quarters'},
|
|
},
|
|
{
|
|
'pattern': ' *maintenant *',
|
|
},
|
|
]
|
|
|
|
def __new__(cls, s, today=None):
|
|
s = s.strip()
|
|
try:
|
|
d = isodate.parse_date(s)
|
|
except isodate.ISO8601Error:
|
|
for template in cls.__TEMPLATES:
|
|
pattern = template['pattern']
|
|
m = re.match(pattern, s)
|
|
if not m:
|
|
continue
|
|
d = today or date.today()
|
|
if 'truncate' in template:
|
|
if template['truncate'] == 'year':
|
|
d = d.replace(month=1, day=1)
|
|
if template['truncate'] == 'month':
|
|
d = d.replace(day=1)
|
|
if template['truncate'] == 'week':
|
|
d = d + relativedelta(weekday=MO(-1))
|
|
if template['truncate'] == 'quarter':
|
|
d = d.replace(day=1)
|
|
d = d + relativedelta(months=3 - ((d.month - 1) % 3))
|
|
if 'timedelta' in template:
|
|
timedelta = template['timedelta']
|
|
kwargs = {}
|
|
for key, group in timedelta.items():
|
|
if hasattr(group, 'encode'): # string like
|
|
sign = 1
|
|
if group.startswith('+'):
|
|
group = group[1:]
|
|
elif group.startswith('-'):
|
|
sign = -1
|
|
group = group[1:]
|
|
n = re.match(r'(\d+)\*(\w+)', group)
|
|
try:
|
|
if n:
|
|
value = int(n.group(1) * m.group(n.group(2)))
|
|
else:
|
|
value = sign * int(m.group(group))
|
|
kwargs[key] = value
|
|
except IndexError:
|
|
raise RuntimeError('invalid template %r' % template)
|
|
else:
|
|
kwargs[key] = group
|
|
d += relativedelta(**kwargs)
|
|
break
|
|
else:
|
|
raise ValueError('invalid string %r' % s)
|
|
self = date.__new__(cls, d.year, d.month, d.day)
|
|
self.__string_repr = s
|
|
return self
|
|
|
|
def __repr__(self):
|
|
return self.__string_repr
|