bijoe/bijoe/relative_time.py

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