From 5f78a99c5bee0953be95bb1d58694b487a966c8d Mon Sep 17 00:00:00 2001 From: Romain Bignon Date: Fri, 8 Feb 2013 13:29:46 +0100 Subject: [PATCH] create DateGuesser extracted from cragr it also moves utc2local and local2utc functions into date.py --- weboob/tools/date.py | 114 +++++++++++++++++++++++++++++++++++++++++++ weboob/tools/misc.py | 21 ++------ 2 files changed, 117 insertions(+), 18 deletions(-) create mode 100644 weboob/tools/date.py diff --git a/weboob/tools/date.py b/weboob/tools/date.py new file mode 100644 index 00000000..26178dcb --- /dev/null +++ b/weboob/tools/date.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2010-2013 Romain Bignon +# +# This file is part of weboob. +# +# weboob 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. +# +# weboob 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 weboob. If not, see . + + +from datetime import date, timedelta +try: + from dateutil import tz +except ImportError: + raise ImportError('Please install python-dateutil') + + +__all__ = ['local2utc', 'utc2local', 'LinearDateGuesser'] + + +def local2utc(date): + date = date.replace(tzinfo=tz.tzlocal()) + date = date.astimezone(tz.tzutc()) + return date + + +def utc2local(date): + date = date.replace(tzinfo=tz.tzutc()) + date = date.astimezone(tz.tzlocal()) + return date + +class LinearDateGuesser(object): + """ + The aim of this class is to guess the exact date object from + a day and a month, but not a year. + + It works with a start date (default is today), and all dates must be + sorted from recent to older. + """ + + def __init__(self, current_date=None, date_max_bump=timedelta(7)): + self.date_max_bump = date_max_bump + if current_date is None: + current_date = date.today() + self.current_date = current_date + + def try_assigning_year(self, day, month, start_year, max_year): + """ + Tries to create a date object with day, month and start_year and returns + it. + If it fails due to the year not matching the day+month combination + (i.e. due to a ValueError -- TypeError and OverflowError are not + handled), the previous or next years are tried until max_year is + reached. + In case initialization still fails with max_year, this function raises + a ValueError. + """ + while 1: + try: + return date(start_year, month, day) + except ValueError, e: + if start_year == max_year: + raise e + start_year += cmp(max_year, start_year) + + def set_current_date(self, current_date): + self.current_date = current_date + + def guess_date(self, day, month, change_current_date=True): + """ Returns a date object built from a given day/month pair. """ + + today = self.current_date + # The website only provides dates using the 'DD/MM' string, so we have to + # determine the most possible year by ourselves. This implies tracking + # the current date. + # However, we may also encounter "bumps" in the dates, e.g. "12/11, + # 10/11, 10/11, 12/11, 09/11", so we have to be, well, quite tolerant, + # by accepting dates in the near future (say, 7 days) of the current + # date. (Please, kill me...) + # We first try to keep the current year + naively_parsed_date = self.try_assigning_year(day, month, today.year, today.year - 5) + if (naively_parsed_date.year != today.year): + # we most likely hit a 29/02 leading to a change of year + if change_current_date: + self.set_current_date(naively_parsed_date) + return naively_parsed_date + + if (naively_parsed_date > today + self.date_max_bump): + # if the date ends up too far in the future, consider it actually + # belongs to the previous year + parsed_date = date(today.year - 1, month, day) + if change_current_date: + self.set_current_date(parsed_date) + elif (naively_parsed_date > today and naively_parsed_date <= today + self.date_max_bump): + # if the date is in the near future, consider it is a bump + parsed_date = naively_parsed_date + # do not keep it as current date though + else: + # if the date is in the past, as expected, simply keep it + parsed_date = naively_parsed_date + # and make it the new current date + if change_current_date: + self.set_current_date(parsed_date) + return parsed_date diff --git a/weboob/tools/misc.py b/weboob/tools/misc.py index f9b63b40..71b003cc 100644 --- a/weboob/tools/misc.py +++ b/weboob/tools/misc.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright(C) 2010-2011 Romain Bignon +# Copyright(C) 2010-2013 Romain Bignon # # This file is part of weboob. # @@ -20,11 +20,6 @@ from __future__ import with_statement -try: - from dateutil import tz -except ImportError: - raise ImportError('Please install python-dateutil') - from logging import warning from time import time, sleep from tempfile import gettempdir @@ -32,6 +27,8 @@ import os import sys import traceback import types +# keep compatibility +from .date import local2utc, utc2local __all__ = ['get_backtrace', 'get_bytes_size', 'html2text', 'iter_fields', @@ -88,12 +85,6 @@ def iter_fields(obj): yield attribute_name, attribute -def local2utc(date): - date = date.replace(tzinfo=tz.tzlocal()) - date = date.astimezone(tz.tzutc()) - return date - - def to_unicode(text): r""" >>> to_unicode('ascii') @@ -119,12 +110,6 @@ def to_unicode(text): return unicode(text, 'windows-1252', 'replace') -def utc2local(date): - date = date.replace(tzinfo=tz.tzutc()) - date = date.astimezone(tz.tzlocal()) - return date - - def limit(iterator, lim): count = 0 iterator = iter(iterator)