Take two datetime objects and return the time between d and now as a nicely formatted string, e.g. "10 minutes". If d occurs after now, return "0 minutes". Units used are years, months, weeks, days, hours, and minutes. Seconds and microseconds are ignored. The algorithm ta
(d, now=None, reversed=False, time_strings=None, depth=2)
| 26 | |
| 27 | |
| 28 | def timesince(d, now=None, reversed=False, time_strings=None, depth=2): |
| 29 | """ |
| 30 | Take two datetime objects and return the time between d and now as a nicely |
| 31 | formatted string, e.g. "10 minutes". If d occurs after now, return |
| 32 | "0 minutes". |
| 33 | |
| 34 | Units used are years, months, weeks, days, hours, and minutes. |
| 35 | Seconds and microseconds are ignored. |
| 36 | |
| 37 | The algorithm takes into account the varying duration of years and months. |
| 38 | There is exactly "1 year, 1 month" between 2013/02/10 and 2014/03/10, |
| 39 | but also between 2007/08/10 and 2008/09/10 despite the delta being 393 days |
| 40 | in the former case and 397 in the latter. |
| 41 | |
| 42 | Up to `depth` adjacent units will be displayed. For example, |
| 43 | "2 weeks, 3 days" and "1 year, 3 months" are possible outputs, but |
| 44 | "2 weeks, 3 hours" and "1 year, 5 days" are not. |
| 45 | |
| 46 | `time_strings` is an optional dict of strings to replace the default |
| 47 | TIME_STRINGS dict. |
| 48 | |
| 49 | `depth` is an optional integer to control the number of adjacent time |
| 50 | units returned. |
| 51 | |
| 52 | Originally adapted from |
| 53 | https://web.archive.org/web/20060617175230/http://blog.natbat.co.uk/archive/2003/Jun/14/time_since |
| 54 | Modified to improve results for years and months. |
| 55 | """ |
| 56 | if time_strings is None: |
| 57 | time_strings = TIME_STRINGS |
| 58 | if depth <= 0: |
| 59 | raise ValueError("depth must be greater than 0.") |
| 60 | # Convert datetime.date to datetime.datetime for comparison. |
| 61 | if not isinstance(d, datetime.datetime): |
| 62 | d = datetime.datetime(d.year, d.month, d.day) |
| 63 | if now and not isinstance(now, datetime.datetime): |
| 64 | now = datetime.datetime(now.year, now.month, now.day) |
| 65 | |
| 66 | # Compared datetimes must be in the same time zone. |
| 67 | if not now: |
| 68 | now = datetime.datetime.now(d.tzinfo if is_aware(d) else None) |
| 69 | elif is_aware(now) and is_aware(d): |
| 70 | now = now.astimezone(d.tzinfo) |
| 71 | |
| 72 | if reversed: |
| 73 | d, now = now, d |
| 74 | delta = now - d |
| 75 | |
| 76 | # Ignore microseconds. |
| 77 | since = delta.days * 24 * 60 * 60 + delta.seconds |
| 78 | if since <= 0: |
| 79 | # d is in the future compared to now, stop processing. |
| 80 | return avoid_wrapping(time_strings["minute"] % {"num": 0}) |
| 81 | |
| 82 | # Get years and months. |
| 83 | total_months = (now.year - d.year) * 12 + (now.month - d.month) |
| 84 | if d.day > now.day or (d.day == now.day and d.time() > now.time()): |
| 85 | total_months -= 1 |