Commit | Line | Data |
---|---|---|
f1c3807d J |
1 | # Copyright (c) Django Software Foundation and individual contributors. |
2 | # All rights reserved. | |
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without modification, | |
5 | # are permitted provided that the following conditions are met: | |
6 | # | |
7 | # 1. Redistributions of source code must retain the above copyright notice, | |
8 | # this list of conditions and the following disclaimer. | |
9 | # | |
10 | # 2. Redistributions in binary form must reproduce the above copyright | |
11 | # notice, this list of conditions and the following disclaimer in the | |
12 | # documentation and/or other materials provided with the distribution. | |
13 | # | |
14 | # 3. Neither the name of Django nor the names of its contributors may be used | |
15 | # to endorse or promote products derived from this software without | |
16 | # specific prior written permission. | |
17 | # | |
18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |
19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | |
22 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |
25 | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
28 | ||
29 | from __future__ import unicode_literals | |
30 | ||
31 | import datetime | |
32 | import pytz | |
33 | ||
34 | from mediagoblin.tools.translate import pass_to_ugettext, lazy_pass_to_ungettext as _ | |
35 | ||
36 | """UTC time zone as a tzinfo instance.""" | |
37 | utc = pytz.utc if pytz else UTC() | |
38 | ||
39 | def is_aware(value): | |
40 | """ | |
41 | Determines if a given datetime.datetime is aware. | |
42 | ||
43 | The logic is described in Python's docs: | |
44 | http://docs.python.org/library/datetime.html#datetime.tzinfo | |
45 | """ | |
46 | return value.tzinfo is not None and value.tzinfo.utcoffset(value) is not None | |
47 | ||
48 | def timesince(d, now=None, reversed=False): | |
49 | """ | |
50 | Takes two datetime objects and returns the time between d and now | |
51 | as a nicely formatted string, e.g. "10 minutes". If d occurs after now, | |
52 | then "0 minutes" is returned. | |
53 | ||
54 | Units used are years, months, weeks, days, hours, and minutes. | |
55 | Seconds and microseconds are ignored. Up to two adjacent units will be | |
56 | displayed. For example, "2 weeks, 3 days" and "1 year, 3 months" are | |
57 | possible outputs, but "2 weeks, 3 hours" and "1 year, 5 days" are not. | |
58 | ||
59 | Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since | |
60 | """ | |
61 | chunks = ( | |
62 | (60 * 60 * 24 * 365, lambda n: _('year', 'years', n)), | |
63 | (60 * 60 * 24 * 30, lambda n: _('month', 'months', n)), | |
64 | (60 * 60 * 24 * 7, lambda n : _('week', 'weeks', n)), | |
65 | (60 * 60 * 24, lambda n : _('day', 'days', n)), | |
66 | (60 * 60, lambda n: _('hour', 'hours', n)), | |
67 | (60, lambda n: _('minute', 'minutes', n)) | |
68 | ) | |
69 | # Convert datetime.date to datetime.datetime for comparison. | |
70 | if not isinstance(d, datetime.datetime): | |
71 | d = datetime.datetime(d.year, d.month, d.day) | |
72 | if now and not isinstance(now, datetime.datetime): | |
73 | now = datetime.datetime(now.year, now.month, now.day) | |
74 | ||
75 | if not now: | |
76 | now = datetime.datetime.now(utc if is_aware(d) else None) | |
77 | ||
78 | delta = (d - now) if reversed else (now - d) | |
79 | # ignore microseconds | |
80 | since = delta.days * 24 * 60 * 60 + delta.seconds | |
81 | if since <= 0: | |
82 | # d is in the future compared to now, stop processing. | |
83 | return '0 ' + pass_to_ugettext('minutes') | |
84 | for i, (seconds, name) in enumerate(chunks): | |
85 | count = since // seconds | |
86 | if count != 0: | |
87 | break | |
88 | s = pass_to_ugettext('%(number)d %(type)s') % {'number': count, 'type': name(count)} | |
89 | if i + 1 < len(chunks): | |
90 | # Now get the second item | |
91 | seconds2, name2 = chunks[i + 1] | |
92 | count2 = (since - (seconds * count)) // seconds2 | |
93 | if count2 != 0: | |
94 | s += pass_to_ugettext(', %(number)d %(type)s') % {'number': count2, 'type': name2(count2)} | |
95 | return s |