1 # This file is part of Indico.
2 # Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
4 # Indico is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3 of the
7 # License, or (at your option) any later version.
9 # Indico is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Indico; if not, see <http://www.gnu.org/licenses/>.
19 from collections
import OrderedDict
20 from datetime
import timedelta
, datetime
21 from datetime
import time
as dt_time
24 from flask
import request
25 from babel
.dates
import format_datetime
as _format_datetime
26 from babel
.dates
import format_time
as _format_time
27 from babel
.dates
import format_date
as _format_date
28 from babel
.dates
import format_timedelta
as _format_timedelta
29 from babel
.dates
import get_timezone
30 from babel
.numbers
import format_number
as _format_number
31 from dateutil
.rrule
import rrule
, DAILY
, MO
, TU
, WE
, TH
, FR
, SA
, SU
32 from dateutil
.relativedelta
import relativedelta
34 from MaKaC
.common
.timezoneUtils
import nowutc
, DisplayTZ
35 from indico
.core
.config
import Config
36 from indico
.util
.i18n
import get_current_locale
, _
, ngettext
42 def utc_timestamp(datetimeVal
):
43 return int(calendar
.timegm(datetimeVal
.utctimetuple()))
47 """Returns the given datetime with tzinfo=UTC.
49 The given datetime object **MUST** be naive but already contain UTC!
51 return pytz
.utc
.localize(dt
)
54 def server_to_utc(dt
):
55 """Converts the given datetime in the server's TZ to UTC.
57 The given datetime **MUST** be naive but already contain the correct time in the server's TZ.
59 server_tz
= get_timezone(Config
.getInstance().getDefaultTimezone())
60 return server_tz
.localize(dt
).astimezone(pytz
.utc
)
63 def utc_to_server(dt
):
64 """Converts the given UTC datetime to the server's TZ."""
65 server_tz
= get_timezone(Config
.getInstance().getDefaultTimezone())
66 return dt
.astimezone(server_tz
)
69 def format_datetime(dt
, format
='medium', locale
=None, timezone
=None, server_tz
=False, keep_tz
=False):
71 Basically a wrapper around Babel's own format_datetime
74 locale
= get_current_locale()
76 assert timezone
is None
78 elif not timezone
and dt
.tzinfo
:
79 timezone
= DisplayTZ().getDisplayTZ()
81 timezone
= Config
.getInstance().getDefaultTimezone()
83 return _format_datetime(dt
, format
=format
, locale
=locale
, tzinfo
=timezone
).encode('utf-8')
86 def format_date(d
, format
='medium', locale
=None):
88 Basically a wrapper around Babel's own format_date
91 locale
= get_current_locale()
93 return _format_date(d
, format
=format
, locale
=locale
).encode('utf-8')
96 def format_time(t
, format
='short', locale
=None, timezone
=None, server_tz
=False):
98 Basically a wrapper around Babel's own format_time
101 locale
= get_current_locale()
102 if not timezone
and t
.tzinfo
:
103 timezone
= DisplayTZ().getDisplayTZ()
105 timezone
= Config
.getInstance().getDefaultTimezone()
107 timezone
= get_timezone(timezone
)
109 return _format_time(t
, format
=format
, locale
=locale
, tzinfo
=timezone
).encode('utf-8')
112 def format_timedelta(td
, format
='short', locale
=None):
114 Basically a wrapper around Babel's own format_timedelta
117 locale
= get_current_locale()
119 return _format_timedelta(td
, format
=format
, locale
=locale
).encode('utf-8')
122 def format_human_timedelta(delta
, granularity
='seconds'):
123 """Formats a timedelta in a human-readable way
125 :param delta: the timedelta to format
126 :param granularity: the granularity, i.e. the lowest unit that is
127 still displayed. when set e.g. to 'minutes',
128 the output will never contain seconds unless
129 the whole timedelta spans less than a minute.
130 Accepted values are 'seconds', 'minutes',
133 field_order
= ('days', 'hours', 'minutes', 'seconds')
135 'seconds': lambda n
: ngettext(u
'{0} second', u
'{0} seconds', n
).format(n
),
136 'minutes': lambda n
: ngettext(u
'{0} minute', u
'{0} minutes', n
).format(n
),
137 'hours': lambda n
: ngettext(u
'{0} hour', u
'{0} hours', n
).format(n
),
138 'days': lambda n
: ngettext(u
'{0} day', u
'{0} days', n
).format(n
),
141 'seconds': lambda n
: ngettext(u
'{0}s', u
'{0}s', n
).format(n
),
142 'minutes': lambda n
: ngettext(u
'{0}m', u
'{0}m', n
).format(n
),
143 'hours': lambda n
: ngettext(u
'{0}h', u
'{0}h', n
).format(n
),
144 'days': lambda n
: ngettext(u
'{0}d', u
'{0}d', n
).format(n
),
147 values
= OrderedDict((key
, 0) for key
in field_order
)
148 values
['seconds'] = delta
.total_seconds()
149 values
['days'], values
['seconds'] = divmod(values
['seconds'], 86400)
150 values
['hours'], values
['seconds'] = divmod(values
['seconds'], 3600)
151 values
['minutes'], values
['seconds'] = divmod(values
['seconds'], 60)
152 for key
, value
in values
.iteritems():
153 values
[key
] = int(value
)
154 # keep all fields covered by the granularity, and if that results in
155 # no non-zero fields, include otherwise excluded ones
156 used_fields
= set(field_order
[:field_order
.index(granularity
) + 1])
157 available_fields
= [x
for x
in field_order
if x
not in used_fields
]
158 used_fields
-= {k
for k
, v
in values
.iteritems() if not v
}
159 while not sum(values
[x
] for x
in used_fields
) and available_fields
:
160 used_fields
.add(available_fields
.pop(0))
161 for key
in available_fields
:
163 nonzero
= OrderedDict((k
, v
) for k
, v
in values
.iteritems() if v
)
165 return long_names
[granularity
](0)
166 elif len(nonzero
) == 1:
167 key
, value
= nonzero
.items()[0]
168 return long_names
[key
](value
)
170 parts
= [short_names
[key
](value
) for key
, value
in nonzero
.iteritems()]
171 return u
' '.join(parts
)
174 def format_human_date(dt
, format
='medium', locale
=None):
176 Return the date in a human-like format for yesterday, today and tomorrow.
177 Format the date otherwise.
179 today
= nowutc().date()
180 oneday
= timedelta(days
=1)
183 locale
= get_current_locale()
185 if dt
== today
- oneday
:
186 return _("yesterday")
189 elif dt
== today
+ oneday
:
192 return format_date(dt
, format
, locale
=locale
)
195 def format_number(number
, locale
=None):
197 locale
= get_current_locale()
198 return _format_number(number
, locale
=locale
).encode('utf-8')
201 def is_same_month(date_1
, date_2
):
203 This method ensures that is the same month of the same year
205 return date_1
.month
== date_2
.month
and date_1
.year
== date_2
.year
208 def timedelta_split(delta
):
210 Decomposes a timedelta into hours, minutes and seconds
211 (timedelta only stores days and seconds)n
213 sec
= delta
.seconds
+ delta
.days
* 24 * 3600
214 hours
, remainder
= divmod(sec
, 3600)
215 minutes
, seconds
= divmod(remainder
, 60)
216 return hours
, minutes
, seconds
219 ## ATTENTION: Do not use this one for new developments ##
220 # It is flawed, as even though the returned value is DST-safe,
221 # it is in the _local timezone_, meaning that the number of seconds
222 # returned is the one for the hour with the same "value" for the
224 def int_timestamp(datetimeVal
, tz
=pytz
.timezone('UTC')):
226 Returns the number of seconds from the local epoch to the UTC time
228 return int(time
.mktime(datetimeVal
.astimezone(tz
).timetuple()))
231 def overlaps(range1
, range2
, inclusive
=False):
232 start1
, end1
= range1
233 start2
, end2
= range2
236 return start1
<= end2
and start2
<= end1
238 return start1
< end2
and start2
< end1
241 def get_overlap(range1
, range2
):
242 if not overlaps(range1
, range2
):
245 start1
, end1
= range1
246 start2
, end2
= range2
248 latest_start
= max(start1
, start2
)
249 earliest_end
= min(end1
, end2
)
251 return latest_start
, earliest_end
254 def iterdays(start
, end
, skip_weekends
=False):
255 weekdays
= (MO
, TU
, WE
, TH
, FR
) if skip_weekends
else None
256 start
= get_day_start(start
) if isinstance(start
, datetime
) else start
257 end
= get_day_end(end
) if isinstance(end
, datetime
) else end
258 return rrule(DAILY
, dtstart
=start
, until
=end
, byweekday
=weekdays
)
262 return d
.weekday() in [e
.weekday
for e
in (SA
, SU
)]
265 def get_datetime_from_request(prefix
='', default
=None, source
=None):
266 """Retrieves date and time from request data."""
268 source
= request
.values
271 default
= datetime
.now()
273 date_str
= source
.get('{}date'.format(prefix
), '')
274 time_str
= source
.get('{}time'.format(prefix
), '')
277 parsed_date
= datetime
.strptime(date_str
, '%Y-%m-%d').date()
279 parsed_date
= default
.date()
282 parsed_time
= datetime
.strptime(time_str
, '%H:%M').time()
284 parsed_time
= default
.time()
286 return datetime
.combine(parsed_date
, parsed_time
)
289 def get_day_start(day
):
291 if isinstance(day
, datetime
):
294 return datetime
.combine(day
, dt_time(0, tzinfo
=tzinfo
))
297 def get_day_end(day
):
299 if isinstance(day
, datetime
):
302 return datetime
.combine(day
, dt_time(23, 59, tzinfo
=tzinfo
))
305 def round_up_to_minutes(dt
, precision
=15):
307 Rounds up a date time object to the given precision in minutes.
309 :param dt: datetime -- the time to round up
310 :param precision: int -- the precision to round up by in minutes. Negative
311 values for the precision are allowed but will round down instead of up.
312 :returns: datetime -- the time rounded up by the given precision in minutes.
314 increment
= precision
* 60
315 secs_in_current_hour
= (dt
.minute
* 60) + dt
.second
+ (dt
.microsecond
* 1e-6)
316 delta
= (secs_in_current_hour
// increment
) * increment
+ increment
- secs_in_current_hour
317 return dt
+ timedelta(seconds
=delta
)
320 def get_month_start(date
):
321 return date
+ relativedelta(day
=1)
324 def get_month_end(date
):
325 return date
+ relativedelta(day
=1, months
=+1, days
=-1)
328 def round_up_month(date
, from_day
=1):
329 """Rounds up a date to the next month unless its day is before *from_day*.
331 :param date: date object
332 :param from_day: day from which one to round *date* up
334 if date
.day
>= from_day
:
335 return date
+ relativedelta(day
=1, months
=+1)