Mercurial > libervia-backend
comparison sat/tools/common/date_utils.py @ 2703:3ba53b1cd1e6
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).
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 01 Dec 2018 10:40:19 +0100 |
parents | 56f94936df1e |
children | 003b8b4b56a7 |
comparison
equal
deleted
inserted
replaced
2702:6555e9835ff8 | 2703:3ba53b1cd1e6 |
---|---|
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | 19 |
20 """tools to help manipulating time and dates""" | 20 """tools to help manipulating time and dates""" |
21 | 21 |
22 from sat.core.constants import Const as C | 22 from sat.core.constants import Const as C |
23 from sat.core.i18n import _ | |
23 import datetime | 24 import datetime |
24 from dateutil import parser as dateutil_parser | 25 import dateutil |
26 from dateutil import tz | |
27 from dateutil.relativedelta import relativedelta | |
28 from dateutil.utils import default_tzinfo | |
25 from babel import dates | 29 from babel import dates |
26 import calendar | 30 import calendar |
27 import time | 31 import time |
32 import re | |
33 | |
34 RELATIVE_RE = re.compile(ur"(?P<date>.*?)(?P<direction>[-+]?) *(?P<quantity>\d+) *" | |
35 ur"(?P<unit>(second|minute|hour|day|week|month|year))s?" | |
36 ur"(?P<ago> +ago)?", re.I) | |
37 | |
38 TZ_UTC = tz.tzutc() | |
39 TZ_LOCAL = tz.gettz() | |
28 | 40 |
29 | 41 |
30 def date_parse(value): | 42 def date_parse(value, default_tz=TZ_UTC): |
31 """Parse a date and return corresponding unix timestamp | 43 """Parse a date and return corresponding unix timestamp |
32 | 44 |
33 @param value(unicode): date to parse, in any format supported by dateutil.parser | 45 @param value(unicode): date to parse, in any format supported by dateutil.parser |
46 @param default_tz(datetime.tzinfo): default timezone | |
34 @return (int): timestamp | 47 @return (int): timestamp |
35 """ | 48 """ |
36 return calendar.timegm(dateutil_parser.parse(unicode(value)).utctimetuple()) | 49 dt = default_tzinfo(dateutil.parser.parse(unicode(value), dayfirst=True), default_tz) |
50 return calendar.timegm(dt.utctimetuple()) | |
51 | |
52 def date_parse_ext(value, default_tz=TZ_UTC): | |
53 """Extended date parse which accept relative date | |
54 | |
55 @param value(unicode): date to parse, in any format supported by dateutil.parser | |
56 and with the hability to specify X days/weeks/months/years in the past or future. | |
57 Relative date are specified either with something like `[main_date] +1 week` | |
58 or with something like `3 days ago`, and it is case insensitive. [main_date] is | |
59 a date parsable by dateutil.parser, or empty to specify current date/time. | |
60 "now" can also be used to specify current date/time. | |
61 @param default_tz(datetime.tzinfo): same as for date_parse | |
62 @return (int): timestamp | |
63 """ | |
64 m = RELATIVE_RE.match(value) | |
65 if m is None: | |
66 return date_parse(value, default_tz=default_tz) | |
67 | |
68 if m.group(u"direction") and m.group(u"ago"): | |
69 raise ValueError( | |
70 _(u"You can't use a direction (+ or -) and \"ago\" at the same time")) | |
71 | |
72 if m.group(u"direction") == u'-' or m.group(u"ago"): | |
73 direction = -1 | |
74 else: | |
75 direction = 1 | |
76 | |
77 date = m.group(u"date").strip().lower() | |
78 if not date or date == u"now": | |
79 dt = datetime.datetime.now(tz.tzutc()) | |
80 else: | |
81 dt = default_tzinfo(dateutil.parser.parse(date, dayfirst=True)) | |
82 | |
83 quantity = int(m.group(u"quantity")) | |
84 key = m.group(u"unit").lower() + u"s" | |
85 delta_kw = {key: direction * quantity} | |
86 dt = dt + relativedelta(**delta_kw) | |
87 return calendar.timegm(dt.utctimetuple()) | |
37 | 88 |
38 | 89 |
39 def date_fmt( | 90 def date_fmt(timestamp, fmt="short", date_only=False, auto_limit=7, auto_old_fmt="short", |
40 timestamp, | 91 auto_new_fmt="relative", locale_str=C.DEFAULT_LOCALE, tz_info=TZ_UTC): |
41 fmt="short", | |
42 date_only=False, | |
43 auto_limit=7, | |
44 auto_old_fmt="short", | |
45 auto_new_fmt="relative", | |
46 locale_str=C.DEFAULT_LOCALE, | |
47 ): | |
48 """format date according to locale | 92 """format date according to locale |
49 | 93 |
50 @param timestamp(basestring, int): unix time | 94 @param timestamp(basestring, float): unix time |
51 @param fmt(str): one of: | 95 @param fmt(str): one of: |
52 - short: e.g. u'31/12/17' | 96 - short: e.g. u'31/12/17' |
53 - medium: e.g. u'Apr 1, 2007' | 97 - medium: e.g. u'Apr 1, 2007' |
54 - long: e.g. u'April 1, 2007' | 98 - long: e.g. u'April 1, 2007' |
55 - full: e.g. u'Sunday, April 1, 2007' | 99 - full: e.g. u'Sunday, April 1, 2007' |
61 - auto: use auto_old_fmt if date is older than auto_limit | 105 - auto: use auto_old_fmt if date is older than auto_limit |
62 else use auto_new_fmt | 106 else use auto_new_fmt |
63 - auto_day: shorcut to set auto format with change on day | 107 - auto_day: shorcut to set auto format with change on day |
64 old format will be short, and new format will be time only | 108 old format will be short, and new format will be time only |
65 or a free value which is passed to babel.dates.format_datetime | 109 or a free value which is passed to babel.dates.format_datetime |
110 (see http://babel.pocoo.org/en/latest/dates.html?highlight=pattern#pattern-syntax) | |
66 @param date_only(bool): if True, only display date (not datetime) | 111 @param date_only(bool): if True, only display date (not datetime) |
67 @param auto_limit (int): limit in days before using auto_old_fmt | 112 @param auto_limit (int): limit in days before using auto_old_fmt |
68 use 0 to have a limit at last midnight (day change) | 113 use 0 to have a limit at last midnight (day change) |
69 @param auto_old_fmt(unicode): format to use when date is older than limit | 114 @param auto_old_fmt(unicode): format to use when date is older than limit |
70 @param auto_new_fmt(unicode): format to use when date is equal to or more recent | 115 @param auto_new_fmt(unicode): format to use when date is equal to or more recent |
71 than limit | 116 than limit |
117 @param locale_str(unicode): locale to use (as understood by babel) | |
118 @param tz_info(datetime.tzinfo): time zone to use | |
72 | 119 |
73 """ | 120 """ |
121 timestamp = float(timestamp) | |
74 if fmt == "auto_day": | 122 if fmt == "auto_day": |
75 fmt, auto_limit, auto_old_fmt, auto_new_fmt = "auto", 0, "short", "HH:mm" | 123 fmt, auto_limit, auto_old_fmt, auto_new_fmt = "auto", 0, "short", "HH:mm" |
76 if fmt == "auto": | 124 if fmt == "auto": |
77 if auto_limit == 0: | 125 if auto_limit == 0: |
78 today = time.mktime(datetime.date.today().timetuple()) | 126 now = datetime.datetime.now(tz_info) |
79 if int(timestamp) < today: | 127 # we want to use given tz_info, so we don't use date() or today() |
128 today = datetime.datetime(year=now.year, month=now.month, day=now.day, | |
129 tzinfo=now.tzinfo) | |
130 today = calendar.timegm(today.utctimetuple()) | |
131 if timestamp < today: | |
80 fmt = auto_old_fmt | 132 fmt = auto_old_fmt |
81 else: | 133 else: |
82 fmt = auto_new_fmt | 134 fmt = auto_new_fmt |
83 else: | 135 else: |
84 days_delta = (time.time() - int(timestamp)) / 3600 | 136 days_delta = (time.time() - timestamp) / 3600 |
85 if days_delta > (auto_limit or 7): | 137 if days_delta > (auto_limit or 7): |
86 fmt = auto_old_fmt | 138 fmt = auto_old_fmt |
87 else: | 139 else: |
88 fmt = auto_new_fmt | 140 fmt = auto_new_fmt |
89 | 141 |
90 if fmt == "relative": | 142 if fmt == "relative": |
91 delta = int(timestamp) - time.time() | 143 delta = timestamp - time.time() |
92 return dates.format_timedelta( | 144 return dates.format_timedelta( |
93 delta, granularity="minute", add_direction=True, locale=locale_str | 145 delta, granularity="minute", add_direction=True, locale=locale_str |
94 ) | 146 ) |
95 elif fmt in ("short", "long"): | 147 elif fmt in ("short", "long"): |
96 formatter = dates.format_date if date_only else dates.format_datetime | 148 if date_only: |
97 return formatter(int(timestamp), format=fmt, locale=locale_str) | 149 dt = datetime.fromtimestamp(timestamp, tz_info) |
150 return dates.format_date(dt, format=fmt, locale=locale_str) | |
151 else: | |
152 return dates.format_datetime(timestamp, format=fmt, locale=locale_str, | |
153 tzinfo=tz_info) | |
98 elif fmt == "iso": | 154 elif fmt == "iso": |
99 if date_only: | 155 if date_only: |
100 fmt = "yyyy-MM-dd" | 156 fmt = "yyyy-MM-dd" |
101 else: | 157 else: |
102 fmt = "yyyy-MM-ddTHH:mm:ss'Z'" | 158 fmt = "yyyy-MM-ddTHH:mm:ss'Z'" |
103 return dates.format_datetime(int(timestamp), format=fmt) | 159 return dates.format_datetime(timestamp, format=fmt) |
104 else: | 160 else: |
105 return dates.format_datetime(int(timestamp), format=fmt, locale=locale_str) | 161 return dates.format_datetime(timestamp, format=fmt, locale=locale_str, |
162 tzinfo=tz_info) |