| Index: appengine/monorail/framework/timestr.py
|
| diff --git a/appengine/monorail/framework/timestr.py b/appengine/monorail/framework/timestr.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..5ec1ee8a69eebb8112e00ff35c4057a35159ae43
|
| --- /dev/null
|
| +++ b/appengine/monorail/framework/timestr.py
|
| @@ -0,0 +1,184 @@
|
| +# Copyright 2016 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is govered by a BSD-style
|
| +# license that can be found in the LICENSE file or at
|
| +# https://developers.google.com/open-source/licenses/bsd
|
| +
|
| +"""Time-to-string and time-from-string routines."""
|
| +
|
| +import datetime
|
| +import time
|
| +
|
| +
|
| +class Error(Exception):
|
| + """Exception used to indicate problems with time routines."""
|
| + pass
|
| +
|
| +
|
| +HTML_TIME_FMT = '%a, %d %b %Y %H:%M:%S GMT'
|
| +
|
| +MONTH_YEAR_FMT = '%b %Y'
|
| +MONTH_DAY_FMT = '%b %d'
|
| +MONTH_DAY_YEAR_FMT = '%b %d %Y'
|
| +
|
| +# We assume that all server clocks are synchronized within this amount.
|
| +MAX_CLOCK_SKEW_SEC = 30
|
| +
|
| +
|
| +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 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'.
|
| +
|
| + Args:
|
| + 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.
|
| +
|
| + Returns:
|
| + 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.fromtimestamp(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.
|
| + else:
|
| + 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'
|
| + else:
|
| + fmt = recent_format
|
| +
|
| + return time.strftime(fmt, time.localtime(timestamp)).replace(' 0', ' ')
|
| +
|
| +
|
| +def FormatRelativeDate(timestamp, recent_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 simply show the year, so that the combined absolute and
|
| + relative parts look like 'Sep 05, 2005'.
|
| +
|
| + Args:
|
| + timestamp: Seconds since the epoch in UTC.
|
| + recent_only: If True, only return a description of recent relative
|
| + dates. Do not return the year, and do not put results inside parentheses.
|
| + clock: optional function to return an int time, like int(time.time()).
|
| +
|
| + Returns:
|
| + String describing relative time.
|
| + """
|
| + if clock:
|
| + now = clock()
|
| + else:
|
| + 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 recent_only:
|
| + 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 ''
|
| + else:
|
| + if d_days > 6:
|
| + return ', %s' % (time.localtime(timestamp))[0]
|
| + 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)'
|
| + # Only say something is in the future if it is more than just clock skew.
|
| + return ' (in the future)'
|
| +
|
| +
|
| +def GetHumanScaleDate(timestamp, now=None):
|
| + """Formats a timestamp to a course-grained and fine-grained time phrase.
|
| +
|
| + Args:
|
| + timestamp: Seconds since the epoch in UTC.
|
| + now: Current time in seconds since the epoch in UTC.
|
| +
|
| + Returns:
|
| + 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 'Older', 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'
|
|
|