0001"""Date/Time Helpers"""
0002# Last synced with Rails copy at Revision 6080 on Feb 8th, 2007.
0003# Note that the select_ tags are purposely not ported as they're very totally useless
0004# and inefficient beyond comprehension.
0005
0006from datetime import datetime
0007import time
0008
0009DEFAULT_PREFIX = 'date'
0010
0011def distance_of_time_in_words(from_time, to_time=0, include_seconds=False):
0012    """
0013    Reports the approximate distance in time between two datetime objects or
0014    integers as seconds.
0015
0016    Set ``include_seconds`` to True for more more detailed approximations when
0017    distance < 1 min, 29 secs
0018
0019    Distances are reported based on the following table:
0020
0021    0 <-> 29 secs                                                           => less than a minute
0022    30 secs <-> 1 min, 29 secs                                              => 1 minute
0023    1 min, 30 secs <-> 44 mins, 29 secs                                     => [2..44] minutes
0024    44 mins, 30 secs <-> 89 mins, 29 secs                                   => about 1 hour
0025    89 mins, 29 secs <-> 23 hrs, 59 mins, 29 secs                           => about [2..24] hours
0026    23 hrs, 59 mins, 29 secs <-> 47 hrs, 59 mins, 29 secs                   => 1 day
0027    47 hrs, 59 mins, 29 secs <-> 29 days, 23 hrs, 59 mins, 29 secs          => [2..29] days
0028    29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs => about 1 month
0029    59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 31 secs                => [2..12] months
0030    1 yr minus 30 secs <-> 2 yrs minus 31 secs                              => about 1 year
0031    2 yrs minus 30 secs <-> max time or date                                => over [2..X] years
0032
0033    With ``include_seconds`` set to True and the difference < 1 minute 29
0034    seconds:
0035
0036    0-4   secs    => less than 5 seconds
0037    5-9   secs    => less than 10 seconds
0038    10-19 secs    => less than 20 seconds
0039    20-39 secs    => half a minute
0040    40-59 secs    => less than a minute
0041    60-89 secs    => 1 minute
0042
0043    Examples:
0044
0045        >>> from datetime import datetime, timedelta
0046        >>> from_time = datetime.now()
0047        >>> distance_of_time_in_words(from_time, from_time + timedelta(minutes=50))
0048        'about 1 hour'
0049        >>> distance_of_time_in_words(from_time, from_time + timedelta(seconds=15))
0050        'less than a minute'
0051        >>> distance_of_time_in_words(from_time, from_time + timedelta(seconds=15), include_seconds=True)
0052        'less than 20 seconds'
0053
0054    Note: ``distance_of_time_in_words`` calculates one year as 365.25 days.
0055    """
0056    if isinstance(from_time, int):
0057        from_time = time.time()+from_time
0058    else:
0059        from_time = time.mktime(from_time.timetuple())
0060    if isinstance(to_time, int):
0061        to_time = time.time()+to_time
0062    else:
0063        to_time = time.mktime(to_time.timetuple())
0064
0065    distance_in_minutes = int(round(abs(to_time-from_time)/60))
0066    distance_in_seconds = int(round(abs(to_time-from_time)))
0067
0068    if distance_in_minutes <= 1:
0069        if include_seconds:
0070            for remainder in [5, 10, 20]:
0071                if distance_in_seconds < remainder:
0072                    return "less than %s seconds" % remainder
0073            if distance_in_seconds < 40:
0074                return "half a minute"
0075            elif distance_in_seconds < 60:
0076                return "less than a minute"
0077            else:
0078                return "1 minute"
0079        else:
0080            if distance_in_minutes == 0:
0081                return "less than a minute"
0082            else:
0083                return "1 minute"
0084    elif distance_in_minutes < 45:
0085        return "%s minutes" % distance_in_minutes
0086    elif distance_in_minutes < 90:
0087        return "about 1 hour"
0088    elif distance_in_minutes < 1440:
0089        return "about %d hours" % (round(distance_in_minutes / 60.0))
0090    elif distance_in_minutes < 2880:
0091        return "1 day"
0092    elif distance_in_minutes < 43220:
0093        return "%d days" % (round(distance_in_minutes / 1440))
0094    elif distance_in_minutes < 86400:
0095        return "about 1 month"
0096    elif distance_in_minutes < 525600:
0097        return "%d months" % (round(distance_in_minutes / 43200))
0098    elif distance_in_minutes < 1051200:
0099        return "about 1 year"
0100    else:
0101        return "over %d years" % (round(distance_in_minutes / 525600))
0102
0103def time_ago_in_words(from_time, include_seconds=False):
0104    """
0105    Like distance_of_time_in_words, but where ``to_time`` is fixed to ``datetime.now()``.
0106    """
0107    return distance_of_time_in_words(from_time, datetime.now(), include_seconds)
0108
0109__all__ = ['distance_of_time_in_words', 'time_ago_in_words']