tools (common/date_utils): date_parse_ext + timezone handling:
- new date_parse_ext method parse a date where relative informations can be added. e.g.: "1 week ago", "now - 2 days", "01/01/01 01:01 + 3 hours". - new TZ_UTC and TZ_LOCAL constants can be used to use UTC time or local time zone. - date_parse* can now use a default_tz, which will be used if timezone if not specified in date format (default UTC). - tz_info can now be used in date_format to format the time in given timezone (default: UTC).
This commit is contained in:
parent
8e27547531
commit
4b81275fdf
1 changed files with 80 additions and 23 deletions
|
|
@ -20,34 +20,78 @@
|
|||
"""tools to help manipulating time and dates"""
|
||||
|
||||
from sat.core.constants import Const as C
|
||||
from sat.core.i18n import _
|
||||
import datetime
|
||||
from dateutil import parser as dateutil_parser
|
||||
import dateutil
|
||||
from dateutil import tz
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from dateutil.utils import default_tzinfo
|
||||
from babel import dates
|
||||
import calendar
|
||||
import time
|
||||
import re
|
||||
|
||||
RELATIVE_RE = re.compile(ur"(?P<date>.*?)(?P<direction>[-+]?) *(?P<quantity>\d+) *"
|
||||
ur"(?P<unit>(second|minute|hour|day|week|month|year))s?"
|
||||
ur"(?P<ago> +ago)?", re.I)
|
||||
|
||||
TZ_UTC = tz.tzutc()
|
||||
TZ_LOCAL = tz.gettz()
|
||||
|
||||
|
||||
def date_parse(value):
|
||||
def date_parse(value, default_tz=TZ_UTC):
|
||||
"""Parse a date and return corresponding unix timestamp
|
||||
|
||||
@param value(unicode): date to parse, in any format supported by dateutil.parser
|
||||
@param default_tz(datetime.tzinfo): default timezone
|
||||
@return (int): timestamp
|
||||
"""
|
||||
return calendar.timegm(dateutil_parser.parse(unicode(value)).utctimetuple())
|
||||
dt = default_tzinfo(dateutil.parser.parse(unicode(value), dayfirst=True), default_tz)
|
||||
return calendar.timegm(dt.utctimetuple())
|
||||
|
||||
def date_parse_ext(value, default_tz=TZ_UTC):
|
||||
"""Extended date parse which accept relative date
|
||||
|
||||
@param value(unicode): date to parse, in any format supported by dateutil.parser
|
||||
and with the hability to specify X days/weeks/months/years in the past or future.
|
||||
Relative date are specified either with something like `[main_date] +1 week`
|
||||
or with something like `3 days ago`, and it is case insensitive. [main_date] is
|
||||
a date parsable by dateutil.parser, or empty to specify current date/time.
|
||||
"now" can also be used to specify current date/time.
|
||||
@param default_tz(datetime.tzinfo): same as for date_parse
|
||||
@return (int): timestamp
|
||||
"""
|
||||
m = RELATIVE_RE.match(value)
|
||||
if m is None:
|
||||
return date_parse(value, default_tz=default_tz)
|
||||
|
||||
if m.group(u"direction") and m.group(u"ago"):
|
||||
raise ValueError(
|
||||
_(u"You can't use a direction (+ or -) and \"ago\" at the same time"))
|
||||
|
||||
if m.group(u"direction") == u'-' or m.group(u"ago"):
|
||||
direction = -1
|
||||
else:
|
||||
direction = 1
|
||||
|
||||
date = m.group(u"date").strip().lower()
|
||||
if not date or date == u"now":
|
||||
dt = datetime.datetime.now(tz.tzutc())
|
||||
else:
|
||||
dt = default_tzinfo(dateutil.parser.parse(date, dayfirst=True))
|
||||
|
||||
quantity = int(m.group(u"quantity"))
|
||||
key = m.group(u"unit").lower() + u"s"
|
||||
delta_kw = {key: direction * quantity}
|
||||
dt = dt + relativedelta(**delta_kw)
|
||||
return calendar.timegm(dt.utctimetuple())
|
||||
|
||||
|
||||
def date_fmt(
|
||||
timestamp,
|
||||
fmt="short",
|
||||
date_only=False,
|
||||
auto_limit=7,
|
||||
auto_old_fmt="short",
|
||||
auto_new_fmt="relative",
|
||||
locale_str=C.DEFAULT_LOCALE,
|
||||
):
|
||||
def date_fmt(timestamp, fmt="short", date_only=False, auto_limit=7, auto_old_fmt="short",
|
||||
auto_new_fmt="relative", locale_str=C.DEFAULT_LOCALE, tz_info=TZ_UTC):
|
||||
"""format date according to locale
|
||||
|
||||
@param timestamp(basestring, int): unix time
|
||||
@param timestamp(basestring, float): unix time
|
||||
@param fmt(str): one of:
|
||||
- short: e.g. u'31/12/17'
|
||||
- medium: e.g. u'Apr 1, 2007'
|
||||
|
|
@ -63,43 +107,56 @@ def date_fmt(
|
|||
- auto_day: shorcut to set auto format with change on day
|
||||
old format will be short, and new format will be time only
|
||||
or a free value which is passed to babel.dates.format_datetime
|
||||
(see http://babel.pocoo.org/en/latest/dates.html?highlight=pattern#pattern-syntax)
|
||||
@param date_only(bool): if True, only display date (not datetime)
|
||||
@param auto_limit (int): limit in days before using auto_old_fmt
|
||||
use 0 to have a limit at last midnight (day change)
|
||||
@param auto_old_fmt(unicode): format to use when date is older than limit
|
||||
@param auto_new_fmt(unicode): format to use when date is equal to or more recent
|
||||
than limit
|
||||
@param locale_str(unicode): locale to use (as understood by babel)
|
||||
@param tz_info(datetime.tzinfo): time zone to use
|
||||
|
||||
"""
|
||||
timestamp = float(timestamp)
|
||||
if fmt == "auto_day":
|
||||
fmt, auto_limit, auto_old_fmt, auto_new_fmt = "auto", 0, "short", "HH:mm"
|
||||
if fmt == "auto":
|
||||
if auto_limit == 0:
|
||||
today = time.mktime(datetime.date.today().timetuple())
|
||||
if int(timestamp) < today:
|
||||
now = datetime.datetime.now(tz_info)
|
||||
# we want to use given tz_info, so we don't use date() or today()
|
||||
today = datetime.datetime(year=now.year, month=now.month, day=now.day,
|
||||
tzinfo=now.tzinfo)
|
||||
today = calendar.timegm(today.utctimetuple())
|
||||
if timestamp < today:
|
||||
fmt = auto_old_fmt
|
||||
else:
|
||||
fmt = auto_new_fmt
|
||||
else:
|
||||
days_delta = (time.time() - int(timestamp)) / 3600
|
||||
days_delta = (time.time() - timestamp) / 3600
|
||||
if days_delta > (auto_limit or 7):
|
||||
fmt = auto_old_fmt
|
||||
else:
|
||||
fmt = auto_new_fmt
|
||||
|
||||
if fmt == "relative":
|
||||
delta = int(timestamp) - time.time()
|
||||
delta = timestamp - time.time()
|
||||
return dates.format_timedelta(
|
||||
delta, granularity="minute", add_direction=True, locale=locale_str
|
||||
)
|
||||
elif fmt in ("short", "long"):
|
||||
formatter = dates.format_date if date_only else dates.format_datetime
|
||||
return formatter(int(timestamp), format=fmt, locale=locale_str)
|
||||
if date_only:
|
||||
dt = datetime.fromtimestamp(timestamp, tz_info)
|
||||
return dates.format_date(dt, format=fmt, locale=locale_str)
|
||||
else:
|
||||
return dates.format_datetime(timestamp, format=fmt, locale=locale_str,
|
||||
tzinfo=tz_info)
|
||||
elif fmt == "iso":
|
||||
if date_only:
|
||||
fmt = "yyyy-MM-dd"
|
||||
else:
|
||||
fmt = "yyyy-MM-ddTHH:mm:ss'Z'"
|
||||
return dates.format_datetime(int(timestamp), format=fmt)
|
||||
return dates.format_datetime(timestamp, format=fmt)
|
||||
else:
|
||||
return dates.format_datetime(int(timestamp), format=fmt, locale=locale_str)
|
||||
return dates.format_datetime(timestamp, format=fmt, locale=locale_str,
|
||||
tzinfo=tz_info)
|
||||
|
|
|
|||
Loading…
Reference in a new issue