What is it about?
- I need to say someting like 1 day ago, 5 days ago, 2 weeks ago, ...
- I can control to have it with/without milliseconds and microseconds.
- I can use it automatically with current date and time or with a provide one.
Why?
- I need it for next revision of my recipe 578111.
- I found recipes here and there but often it is always assumed that a month has 30 days and that a year has 365 days; this is not true. That's why I've left away months and years.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | """
@author Thomas Lehmann
@file dateBack.py
@brief provides a human readable format for a time delta
"""
from datetime import datetime , timedelta
def dateBack(theDateAndTime, precise=False, fromDate=None):
""" provides a human readable format for a time delta
@param theDateAndTime this is time equal or older than now or the date in 'fromDate'
@param precise when true then milliseconds and microseconds are included
@param fromDate when None the 'now' is used otherwise a concrete date is expected
@return the time delta as text
@note I don't calculate months and years because those varies (28,29,30 or 31 days a month
and 365 or 366 days the year depending on leap years). In addition please refer
to the documentation for timedelta limitations.
"""
if not fromDate:
fromDate = datetime.now()
if theDateAndTime > fromDate: return None
elif theDateAndTime == fromDate: return "now"
delta = fromDate - theDateAndTime
# the timedelta structure does not have all units; bigger units are converted
# into given smaller ones (hours -> seconds, minutes -> seconds, weeks > days, ...)
# but we need all units:
deltaMinutes = delta.seconds // 60
deltaHours = delta.seconds // 3600
deltaMinutes -= deltaHours * 60
deltaWeeks = delta.days // 7
deltaSeconds = delta.seconds - deltaMinutes * 60 - deltaHours * 3600
deltaDays = delta.days - deltaWeeks * 7
deltaMilliSeconds = delta.microseconds // 1000
deltaMicroSeconds = delta.microseconds - deltaMilliSeconds * 1000
valuesAndNames =[ (deltaWeeks ,"week" ), (deltaDays ,"day" ),
(deltaHours ,"hour" ), (deltaMinutes,"minute"),
(deltaSeconds,"second") ]
if precise:
valuesAndNames.append((deltaMilliSeconds, "millisecond"))
valuesAndNames.append((deltaMicroSeconds, "microsecond"))
text =""
for value, name in valuesAndNames:
if value > 0:
text += len(text) and ", " or ""
text += "%d %s" % (value, name)
text += (value > 1) and "s" or ""
# replacing last occurrence of a comma by an 'and'
if text.find(",") > 0:
text = " and ".join(text.rsplit(", ",1))
return text
def test():
""" testing function "dateBack" """
# we need a date to rely on for testing concrete deltas
fromDate = datetime(year=2012, month=4, day=26, hour=8, minute=40, second=45)
testCases = [
("1 second" , fromDate-timedelta(seconds=1), False),
("5 seconds" , fromDate-timedelta(seconds=5), False),
("1 minute" , fromDate-timedelta(minutes=1), False),
("5 minutes" , fromDate-timedelta(minutes=5), False),
("1 minute and 10 seconds" , fromDate-timedelta(minutes=1, seconds=10), False),
("1 hour" , fromDate-timedelta(hours= 1), False),
("1 hour and 1 second" , fromDate-timedelta(hours=1, seconds=1), False),
("1 hour and 1 minute" , fromDate-timedelta(hours=1, minutes=1), False),
("1 hour, 1 minute and 1 second" , fromDate-timedelta(hours=1, minutes=1, seconds=1), False),
("1 week" , fromDate-timedelta(weeks=1), False),
("2 weeks" , fromDate-timedelta(weeks=2), False),
("1 week and 1 second" , fromDate-timedelta(weeks=1, seconds=1), False),
("1 week and 1 minute" , fromDate-timedelta(weeks=1, minutes=1), False),
("1 week and 1 hour" , fromDate-timedelta(weeks=1, hours=1), False),
("1 week, 1 hour, 1 minute and 1 second", fromDate-timedelta(weeks=1, hours=1, minutes=1, seconds=1), False),
("1 millisecond" , fromDate-timedelta(milliseconds=1),True),
("2 milliseconds" , fromDate-timedelta(milliseconds=2),True),
("1 microsecond" , fromDate-timedelta(microseconds=1),True),
("2 microseconds" , fromDate-timedelta(microseconds=2),True),
("1 millisecond and 1 microsecond" , fromDate-timedelta(milliseconds=1, microseconds=1),True) ]
for expectedResult, testDate, precise in testCases:
print("test case for '%s'" % expectedResult)
calculatedResult = dateBack(testDate, precise=precise, fromDate=fromDate)
try: assert expectedResult == calculatedResult
except: print(" -> error: wrong value: '%s'" % calculatedResult)
# future date in relation to 'fromDate' (1 hour)
futureDate = fromDate + timedelta(hours=1)
expectedResult = None
assert expectedResult == dateBack(futureDate, precise=False, fromDate=fromDate)
if __name__ == "__main__":
test()
|
A few notes
There's this code and some of you might wonder because it's not really pythonic:
text += len(text) and ", " or ""
The point is I tried this variant...
text += ", " if len(text)
...but this variant was not accepted by the Python interpeter. Somwhere I've been reading that this is since 2.5 available; I tried Jyton 2.5.3 and Python 3.1.2 but without success
And why not this?:
The syntax you want is
i.e. you need the
else
clause.Why not use relative delta ??
from dateutil.relativedelta import relativedelta
Example usage:
Since I really like Sharoon Thomas' solution, I want to share a couple modifications:
I use this to find how long ago something was modified. If there was no difference in time, the original function returned an empty list. By adding "attr == attrs[-1]", I ensure that I return a value of ['0 seconds'].
I also added 'ago' to the string, since that is how I'm using it.
The last item I added was the test for whether the time is plural or not:
We say '0 seconds' not '0 second'. I also accidentally tested a negative value and found '-2 minute' should be '-2 minutes'. The modified version says 1 is singular, everything else is plural.
Thanks for this function.