blob: 2b32e8c1a0919b8a63f2d161ebc59b1d67dca420 [file] [log] [blame]
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
"""Time-to-string and time-from-string routines."""
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import calendar
import datetime
import time
class Error(Exception):
"""Exception used to indicate problems with time routines."""
HTML_TIME_FMT = '%a, %d %b %Y %H:%M:%S GMT'
MONTH_DAY_FMT = '%b %d'
# We assume that all server clocks are synchronized within this amount.
def TimeForHTMLHeader(when=None):
"""Return the given time (or now) in HTML header format."""
if when is None:
when = int(time.time())
return time.strftime(HTML_TIME_FMT, time.gmtime(when))
def TimestampToDateWidgetStr(when):
"""Format a timestamp int for use by HTML <input type="date">."""
return time.strftime(HTML_DATE_WIDGET_FORMAT, time.gmtime(when))
def DateWidgetStrToTimestamp(val_str):
"""Parse the HTML <input type="date"> string into a timestamp int."""
return int(calendar.timegm(time.strptime(val_str, HTML_DATE_WIDGET_FORMAT)))
def FormatAbsoluteDate(
timestamp, clock=datetime.datetime.utcnow,
recent_format=MONTH_DAY_FMT, old_format=MONTH_YEAR_FMT):
"""Format timestamp like 'Sep 5', or 'Yesterday', or 'Today'.
timestamp: Seconds since the epoch in UTC.
clock: callable that returns a datetime.datetime object when called with no
arguments, giving the current time to use when computing what to display.
recent_format: Format string to pass to strftime to present dates between
six months ago and yesterday.
old_format: Format string to pass to strftime to present dates older than
six months or more than skew_tolerance in the future.
If timestamp's date is today, "Today". If timestamp's date is yesterday,
"Yesterday". If timestamp is within six months before today, return the
time as formatted by recent_format. Otherwise, return the time as formatted
by old_format.
ts = datetime.datetime.utcfromtimestamp(timestamp)
now = clock()
month_delta = 12 * now.year + now.month - (12 * ts.year + ts.month)
delta = now - ts
if ts > now:
# If the time is slightly in the future due to clock skew, treat as today.
skew_tolerance = datetime.timedelta(seconds=MAX_CLOCK_SKEW_SEC)
if -delta <= skew_tolerance:
return 'Today'
# Otherwise treat it like an old date.
fmt = old_format
elif month_delta > 6 or delta.days >= 365:
fmt = old_format
elif delta.days == 1:
return 'Yesterday'
elif delta.days == 0:
return 'Today'
fmt = recent_format
return time.strftime(fmt, time.gmtime(timestamp)).replace(' 0', ' ')
def FormatRelativeDate(timestamp, days_only=False, clock=None):
"""Return a short string that makes timestamp more meaningful to the user.
Describe the timestamp relative to the current time, e.g., '4
hours ago'. In cases where the timestamp is more than 6 days ago,
we return '' so that an alternative display can be used instead.
timestamp: Seconds since the epoch in UTC.
days_only: If True, return 'N days ago' even for more than 6 days.
clock: optional function to return an int time, like int(time.time()).
String describing relative time.
if clock:
now = clock()
now = int(time.time())
# TODO(jrobbins): i18n of date strings
delta = int(now - timestamp)
d_minutes = delta // 60
d_hours = d_minutes // 60
d_days = d_hours // 24
if days_only:
if d_days > 1:
return '%s days ago' % d_days
return ''
if d_days > 6:
return ''
if d_days > 1:
return '%s days ago' % d_days # starts at 2 days
if d_hours > 1:
return '%s hours ago' % d_hours # starts at 2 hours
if d_minutes > 1:
return '%s minutes ago' % d_minutes
if d_minutes > 0:
return '1 minute ago'
if delta > -MAX_CLOCK_SKEW_SEC:
return 'moments ago'
return ''
def GetHumanScaleDate(timestamp, now=None):
"""Formats a timestamp to a course-grained and fine-grained time phrase.
timestamp: Seconds since the epoch in UTC.
now: Current time in seconds since the epoch in UTC.
A pair (course_grain, fine_grain) where course_grain is a string
such as 'Today', 'Yesterday', etc.; and fine_grained is a string describing
relative hours for Today and Yesterday, or an exact date for longer ago.
if now is None:
now = int(time.time())
now_year = datetime.datetime.fromtimestamp(now).year
then_year = datetime.datetime.fromtimestamp(timestamp).year
delta = int(now - timestamp)
delta_minutes = delta // 60
delta_hours = delta_minutes // 60
delta_days = delta_hours // 24
if 0 <= delta_hours < 24:
if delta_hours > 1:
return 'Today', '%s hours ago' % delta_hours
if delta_minutes > 1:
return 'Today', '%s min ago' % delta_minutes
if delta_minutes > 0:
return 'Today', '1 min ago'
if delta > 0:
return 'Today', 'moments ago'
if 0 <= delta_hours < 48:
return 'Yesterday', '%s hours ago' % delta_hours
if 0 <= delta_days < 7:
return 'Last 7 days', time.strftime(
'%b %d, %Y', (time.localtime(timestamp)))
if 0 <= delta_days < 30:
return 'Last 30 days', time.strftime(
'%b %d, %Y', (time.localtime(timestamp)))
if delta > 0:
if now_year == then_year:
return 'Earlier this year', time.strftime(
'%b %d, %Y', (time.localtime(timestamp)))
return ('Before this year',
time.strftime('%b %d, %Y', (time.localtime(timestamp))))
if delta > -MAX_CLOCK_SKEW_SEC:
return 'Today', 'moments ago'
# Only say something is in the future if it is more than just clock skew.
return 'Future', 'Later'