blob: b768c733fc972391f86052b99d50bfbc0405d546 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# Copyright 2016 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Copybara854996b2021-09-07 19:36:02 +00004
5"""Time-to-string and time-from-string routines."""
6from __future__ import print_function
7from __future__ import division
8from __future__ import absolute_import
9
10import calendar
11import datetime
12import time
13
14
15class Error(Exception):
16 """Exception used to indicate problems with time routines."""
17 pass
18
19
20HTML_TIME_FMT = '%a, %d %b %Y %H:%M:%S GMT'
21HTML_DATE_WIDGET_FORMAT = '%Y-%m-%d'
22
23MONTH_YEAR_FMT = '%b %Y'
24MONTH_DAY_FMT = '%b %d'
25MONTH_DAY_YEAR_FMT = '%b %d %Y'
26
27# We assume that all server clocks are synchronized within this amount.
28MAX_CLOCK_SKEW_SEC = 30
29
30
31def TimeForHTMLHeader(when=None):
32 """Return the given time (or now) in HTML header format."""
33 if when is None:
34 when = int(time.time())
35 return time.strftime(HTML_TIME_FMT, time.gmtime(when))
36
37
38def TimestampToDateWidgetStr(when):
39 """Format a timestamp int for use by HTML <input type="date">."""
40 return time.strftime(HTML_DATE_WIDGET_FORMAT, time.gmtime(when))
41
42
43def DateWidgetStrToTimestamp(val_str):
44 """Parse the HTML <input type="date"> string into a timestamp int."""
45 return int(calendar.timegm(time.strptime(val_str, HTML_DATE_WIDGET_FORMAT)))
46
47
48def FormatAbsoluteDate(
49 timestamp, clock=datetime.datetime.utcnow,
50 recent_format=MONTH_DAY_FMT, old_format=MONTH_YEAR_FMT):
51 """Format timestamp like 'Sep 5', or 'Yesterday', or 'Today'.
52
53 Args:
54 timestamp: Seconds since the epoch in UTC.
55 clock: callable that returns a datetime.datetime object when called with no
56 arguments, giving the current time to use when computing what to display.
57 recent_format: Format string to pass to strftime to present dates between
58 six months ago and yesterday.
59 old_format: Format string to pass to strftime to present dates older than
60 six months or more than skew_tolerance in the future.
61
62 Returns:
63 If timestamp's date is today, "Today". If timestamp's date is yesterday,
64 "Yesterday". If timestamp is within six months before today, return the
65 time as formatted by recent_format. Otherwise, return the time as formatted
66 by old_format.
67 """
68 ts = datetime.datetime.utcfromtimestamp(timestamp)
69 now = clock()
70 month_delta = 12 * now.year + now.month - (12 * ts.year + ts.month)
71 delta = now - ts
72
73 if ts > now:
74 # If the time is slightly in the future due to clock skew, treat as today.
75 skew_tolerance = datetime.timedelta(seconds=MAX_CLOCK_SKEW_SEC)
76 if -delta <= skew_tolerance:
77 return 'Today'
78 # Otherwise treat it like an old date.
79 else:
80 fmt = old_format
81 elif month_delta > 6 or delta.days >= 365:
82 fmt = old_format
83 elif delta.days == 1:
84 return 'Yesterday'
85 elif delta.days == 0:
86 return 'Today'
87 else:
88 fmt = recent_format
89
90 return time.strftime(fmt, time.gmtime(timestamp)).replace(' 0', ' ')
91
92
93def FormatRelativeDate(timestamp, days_only=False, clock=None):
94 """Return a short string that makes timestamp more meaningful to the user.
95
96 Describe the timestamp relative to the current time, e.g., '4
97 hours ago'. In cases where the timestamp is more than 6 days ago,
98 we return '' so that an alternative display can be used instead.
99
100 Args:
101 timestamp: Seconds since the epoch in UTC.
102 days_only: If True, return 'N days ago' even for more than 6 days.
103 clock: optional function to return an int time, like int(time.time()).
104
105 Returns:
106 String describing relative time.
107 """
108 if clock:
109 now = clock()
110 else:
111 now = int(time.time())
112
113 # TODO(jrobbins): i18n of date strings
114 delta = int(now - timestamp)
115 d_minutes = delta // 60
116 d_hours = d_minutes // 60
117 d_days = d_hours // 24
118 if days_only:
119 if d_days > 1:
120 return '%s days ago' % d_days
121 else:
122 return ''
123
124 if d_days > 6:
125 return ''
126 if d_days > 1:
127 return '%s days ago' % d_days # starts at 2 days
128 if d_hours > 1:
129 return '%s hours ago' % d_hours # starts at 2 hours
130 if d_minutes > 1:
131 return '%s minutes ago' % d_minutes
132 if d_minutes > 0:
133 return '1 minute ago'
134 if delta > -MAX_CLOCK_SKEW_SEC:
135 return 'moments ago'
136 return ''
137
138
139def GetHumanScaleDate(timestamp, now=None):
140 """Formats a timestamp to a course-grained and fine-grained time phrase.
141
142 Args:
143 timestamp: Seconds since the epoch in UTC.
144 now: Current time in seconds since the epoch in UTC.
145
146 Returns:
147 A pair (course_grain, fine_grain) where course_grain is a string
148 such as 'Today', 'Yesterday', etc.; and fine_grained is a string describing
149 relative hours for Today and Yesterday, or an exact date for longer ago.
150 """
151 if now is None:
152 now = int(time.time())
153
154 now_year = datetime.datetime.fromtimestamp(now).year
155 then_year = datetime.datetime.fromtimestamp(timestamp).year
156 delta = int(now - timestamp)
157 delta_minutes = delta // 60
158 delta_hours = delta_minutes // 60
159 delta_days = delta_hours // 24
160
161 if 0 <= delta_hours < 24:
162 if delta_hours > 1:
163 return 'Today', '%s hours ago' % delta_hours
164 if delta_minutes > 1:
165 return 'Today', '%s min ago' % delta_minutes
166 if delta_minutes > 0:
167 return 'Today', '1 min ago'
168 if delta > 0:
169 return 'Today', 'moments ago'
170 if 0 <= delta_hours < 48:
171 return 'Yesterday', '%s hours ago' % delta_hours
172 if 0 <= delta_days < 7:
173 return 'Last 7 days', time.strftime(
174 '%b %d, %Y', (time.localtime(timestamp)))
175 if 0 <= delta_days < 30:
176 return 'Last 30 days', time.strftime(
177 '%b %d, %Y', (time.localtime(timestamp)))
178 if delta > 0:
179 if now_year == then_year:
180 return 'Earlier this year', time.strftime(
181 '%b %d, %Y', (time.localtime(timestamp)))
182 return ('Before this year',
183 time.strftime('%b %d, %Y', (time.localtime(timestamp))))
184 if delta > -MAX_CLOCK_SKEW_SEC:
185 return 'Today', 'moments ago'
186 # Only say something is in the future if it is more than just clock skew.
187 return 'Future', 'Later'