1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
1 |
# IVLE
|
2 |
# Copyright (C) 2008 The University of Melbourne
|
|
3 |
#
|
|
4 |
# This program is free software; you can redistribute it and/or modify
|
|
5 |
# it under the terms of the GNU General Public License as published by
|
|
6 |
# the Free Software Foundation; either version 2 of the License, or
|
|
7 |
# (at your option) any later version.
|
|
8 |
#
|
|
9 |
# This program is distributed in the hope that it will be useful,
|
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12 |
# GNU General Public License for more details.
|
|
13 |
#
|
|
14 |
# You should have received a copy of the GNU General Public License
|
|
15 |
# along with this program; if not, write to the Free Software
|
|
16 |
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17 |
||
1172
by William Grant
Let functions in ivle.date take datetimes, and deal in datetimes internally. |
18 |
'''Utilities for making nice, human readable dates.'''
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
19 |
|
20 |
import time |
|
1172
by William Grant
Let functions in ivle.date take datetimes, and deal in datetimes internally. |
21 |
import datetime |
22 |
||
23 |
def get_datetime(datetime_or_seconds): |
|
1265
by William Grant
Add some doctests to ivle.date. |
24 |
'''Return the given datetime, or convert the given seconds since epoch.
|
25 |
||
26 |
>>> get_datetime(1000000000)
|
|
27 |
datetime.datetime(2001, 9, 9, 11, 46, 40)
|
|
28 |
>>> get_datetime(2000000000)
|
|
29 |
datetime.datetime(2033, 5, 18, 13, 33, 20)
|
|
30 |
||
31 |
>>> get_datetime(datetime.datetime(2009, 5, 26, 11, 38, 53))
|
|
32 |
datetime.datetime(2009, 5, 26, 11, 38, 53)
|
|
33 |
>>> get_datetime(datetime.datetime(2001, 9, 9, 11, 46, 40))
|
|
34 |
datetime.datetime(2001, 9, 9, 11, 46, 40)
|
|
35 |
'''
|
|
1172
by William Grant
Let functions in ivle.date take datetimes, and deal in datetimes internally. |
36 |
if type(datetime_or_seconds) is datetime.datetime: |
37 |
return datetime_or_seconds |
|
38 |
return datetime.datetime.fromtimestamp(datetime_or_seconds) |
|
39 |
||
40 |
def make_date_nice(datetime_or_seconds): |
|
41 |
"""Generate a full human-readable representation of a date and time.
|
|
42 |
||
43 |
Given a datetime or number of seconds elapsed since the epoch,
|
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
44 |
generates a string representing the date/time in human-readable form.
|
45 |
"ddd mmm dd, yyyy h:m a"
|
|
1265
by William Grant
Add some doctests to ivle.date. |
46 |
|
47 |
>>> make_date_nice(datetime.datetime(2009, 5, 26, 11, 38, 53))
|
|
48 |
'Tue May 26 2009, 11:38am'
|
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
49 |
"""
|
1172
by William Grant
Let functions in ivle.date take datetimes, and deal in datetimes internally. |
50 |
dt = get_datetime(datetime_or_seconds) |
1263
by William Grant
Drop leading 0s from pretty hours, and lowercase AM/PM. |
51 |
return dt.strftime("%a %b %d %Y, %l:%M%P") |
1172
by William Grant
Let functions in ivle.date take datetimes, and deal in datetimes internally. |
52 |
|
53 |
def make_date_nice_short(datetime_or_seconds): |
|
54 |
"""Generate a very compact human-readable representation of a date.
|
|
55 |
||
56 |
Given a datetime or number of seconds elapsed since the epoch,
|
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
57 |
generates a string representing the date in human-readable form.
|
58 |
Does not include the time.
|
|
1265
by William Grant
Add some doctests to ivle.date. |
59 |
|
60 |
>>> now = datetime.datetime.now()
|
|
61 |
>>> make_date_nice_short(now)
|
|
62 |
'Today'
|
|
63 |
>>> make_date_nice_short(now - datetime.timedelta(1))
|
|
64 |
'Yesterday'
|
|
65 |
>>> make_date_nice_short(now - datetime.timedelta(2))
|
|
66 |
'2 days ago'
|
|
67 |
>>> make_date_nice_short(now - datetime.timedelta(5))
|
|
68 |
'5 days ago'
|
|
69 |
>>> make_date_nice_short(1242783748)
|
|
1500
by William Grant
Unbreak existing tests. |
70 |
'May 20, 2009'
|
1172
by William Grant
Let functions in ivle.date take datetimes, and deal in datetimes internally. |
71 |
"""
|
72 |
||
73 |
dt = get_datetime(datetime_or_seconds) |
|
74 |
now = datetime.datetime.now() |
|
75 |
||
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
76 |
# Use a "naturalisation" algorithm.
|
1172
by William Grant
Let functions in ivle.date take datetimes, and deal in datetimes internally. |
77 |
delta = now - dt |
78 |
||
79 |
if delta.days <= 5: |
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
80 |
# Dates today or yesterday, return "today" or "yesterday".
|
1172
by William Grant
Let functions in ivle.date take datetimes, and deal in datetimes internally. |
81 |
if delta.days == 0: |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
82 |
return "Today" |
1172
by William Grant
Let functions in ivle.date take datetimes, and deal in datetimes internally. |
83 |
elif delta.days == 1: |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
84 |
return "Yesterday" |
85 |
else: |
|
1172
by William Grant
Let functions in ivle.date take datetimes, and deal in datetimes internally. |
86 |
# Dates in the last 5 days, return "n days ago".
|
87 |
return str(delta.days) + " days ago" |
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
88 |
# Other dates, return a short date format.
|
89 |
# If within the same year, omit the year (mmm dd)
|
|
1172
by William Grant
Let functions in ivle.date take datetimes, and deal in datetimes internally. |
90 |
if dt.year == now.year: |
91 |
return dt.strftime("%b %d") |
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
92 |
# Else, include the year (mmm dd, yyyy)
|
93 |
else: |
|
1172
by William Grant
Let functions in ivle.date take datetimes, and deal in datetimes internally. |
94 |
return dt.strftime("%b %d, %Y") |
1165.1.15
by William Grant
Add ivle.date.format_datetime_for_paragraph, a nice readable date formatter. |
95 |
|
96 |
def format_datetime_for_paragraph(datetime_or_seconds): |
|
97 |
"""Generate a compact representation of a datetime for use in a paragraph.
|
|
98 |
||
99 |
Given a datetime or number of seconds elapsed since the epoch, generates
|
|
100 |
a compact string representing the date and time in human-readable form.
|
|
101 |
||
102 |
Unlike make_date_nice_short, the time will always be included.
|
|
103 |
||
104 |
Also unlike make_date_nice_short, it is suitable for use in the middle of
|
|
105 |
a block of prose and properly handles timestamps in the future nicely.
|
|
1265
by William Grant
Add some doctests to ivle.date. |
106 |
|
107 |
>>> now = datetime.datetime.now()
|
|
108 |
>>> today = now.date()
|
|
109 |
>>> time = datetime.time(10, 35, 40)
|
|
110 |
>>> earlier = datetime.datetime.combine(today, time)
|
|
111 |
>>> date = datetime.datetime(2009, 5, 20, 21, 19, 53)
|
|
112 |
||
113 |
>>> format_datetime_for_paragraph(now)
|
|
114 |
'now'
|
|
115 |
||
116 |
# We can go backwards and forwards a little while and be pretty.
|
|
117 |
>>> format_datetime_for_paragraph(now - datetime.timedelta(0, 40))
|
|
118 |
'40 seconds ago'
|
|
119 |
>>> format_datetime_for_paragraph(now + datetime.timedelta(0, 30))
|
|
120 |
'in 29 seconds'
|
|
121 |
||
122 |
>>> format_datetime_for_paragraph(now - datetime.timedelta(0, 245))
|
|
123 |
'4 minutes ago'
|
|
124 |
>>> format_datetime_for_paragraph(now + datetime.timedelta(0, 3500))
|
|
125 |
'in 58 minutes'
|
|
126 |
||
127 |
# If we go back further, it gets a bit ugly.
|
|
128 |
>>> format_datetime_for_paragraph(earlier - datetime.timedelta(1))
|
|
129 |
'yesterday at 10:35am'
|
|
130 |
>>> format_datetime_for_paragraph(date)
|
|
131 |
'on 2009-05-20 at 9:19pm'
|
|
132 |
||
133 |
>>> format_datetime_for_paragraph(earlier + datetime.timedelta(1))
|
|
134 |
'tomorrow at 10:35am'
|
|
1165.1.15
by William Grant
Add ivle.date.format_datetime_for_paragraph, a nice readable date formatter. |
135 |
"""
|
136 |
||
137 |
dt = get_datetime(datetime_or_seconds) |
|
138 |
now = datetime.datetime.now() |
|
139 |
||
140 |
delta = dt - now |
|
141 |
||
142 |
# If the date is earlier than now, we want to either say something like
|
|
143 |
# '5 days ago' or '25 seconds ago', 'yesterday at 08:54' or
|
|
144 |
# 'on 2009-03-26 at 20:09'.
|
|
145 |
||
146 |
# If the time is within one hour of now, we show it nicely in either
|
|
147 |
# minutes or seconds.
|
|
148 |
||
149 |
if abs(delta).days == 0 and abs(delta).seconds <= 1: |
|
150 |
return 'now' |
|
151 |
||
152 |
if abs(delta).days == 0 and abs(delta).seconds < 60*60: |
|
153 |
if abs(delta) == delta: |
|
154 |
# It's in the future.
|
|
155 |
prefix = 'in ' |
|
156 |
suffix = '' |
|
157 |
else: |
|
158 |
prefix = '' |
|
159 |
suffix = ' ago' |
|
160 |
||
161 |
# Show the number of minutes unless we are within two minutes.
|
|
162 |
if abs(delta).seconds >= 120: |
|
163 |
return (prefix + '%d minutes' + suffix) % (abs(delta).seconds / 60) |
|
164 |
else: |
|
165 |
return (prefix + '%d seconds' + suffix) % (abs(delta).seconds) |
|
166 |
||
167 |
if dt < now: |
|
168 |
if dt.date() == now.date(): |
|
169 |
# Today.
|
|
1263
by William Grant
Drop leading 0s from pretty hours, and lowercase AM/PM. |
170 |
return dt.strftime('today at %l:%M%P') |
1165.1.15
by William Grant
Add ivle.date.format_datetime_for_paragraph, a nice readable date formatter. |
171 |
elif dt.date() == now.date() - datetime.timedelta(days=1): |
172 |
# Yesterday.
|
|
1263
by William Grant
Drop leading 0s from pretty hours, and lowercase AM/PM. |
173 |
return dt.strftime('yesterday at %l:%M%P') |
1165.1.15
by William Grant
Add ivle.date.format_datetime_for_paragraph, a nice readable date formatter. |
174 |
elif dt > now: |
175 |
if dt.date() == now.date(): |
|
176 |
# Today.
|
|
1263
by William Grant
Drop leading 0s from pretty hours, and lowercase AM/PM. |
177 |
return dt.strftime('today at %l:%M%P') |
1165.1.15
by William Grant
Add ivle.date.format_datetime_for_paragraph, a nice readable date formatter. |
178 |
elif dt.date() == now.date() + datetime.timedelta(days=1): |
179 |
# Tomorrow
|
|
1263
by William Grant
Drop leading 0s from pretty hours, and lowercase AM/PM. |
180 |
return dt.strftime('tomorrow at %l:%M%P') |
1165.1.15
by William Grant
Add ivle.date.format_datetime_for_paragraph, a nice readable date formatter. |
181 |
|
1263
by William Grant
Drop leading 0s from pretty hours, and lowercase AM/PM. |
182 |
return dt.strftime('on %Y-%m-%d at %l:%M%P') |