431

I'm having trouble formatting a datetime.timedelta object.

Here's what I'm trying to do: I have a list of objects and one of the members of the class of the object is a timedelta object that shows the duration of an event. I would like to display that duration in the format of hours:minutes.

I have tried a variety of methods for doing this and I'm having difficulty. My current approach is to add methods to the class for my objects that return hours and minutes. I can get the hours by dividing the timedelta.seconds by 3600 and rounding it. I'm having trouble with getting the remainder seconds and converting that to minutes.

By the way, I'm using Google AppEngine with Django Templates for presentation.

Ronan Boiteau
  • 9,608
  • 6
  • 34
  • 56
mawcs
  • 4,357
  • 2
  • 18
  • 7
  • 117
    Would be nice if timedelta had an equivalent of the strftime() method. – JS. Aug 22 '12 at 22:14
  • @JS. Well, you somewhat can if you use `datetime.utcfromtimestamp()`. See [my answer](http://stackoverflow.com/a/28242294/483113) below. – sjngm Jan 30 '15 at 18:26
  • @JS. - 100% agree. Then, `__str__` of `timedelta` is quite decent, as opposed to `__repr__` (that is - for humans!). For example: `datetime.timedelta(minutes=6, seconds=41) * 2618 / 48` gives `datetime.timedelta(seconds=21871, microseconds=208333)`, but `str(datetime.timedelta(minutes=6, seconds=41) * 2618 / 48)` gives `'6:04:31.208333'` which is fairly OK to read. – Tomasz Gandor Jan 14 '20 at 13:34
  • @JS. in python3 the datetime module is implemented in pure python in file /usr/lib/python3.7/datetime.py. At the end of this file an `import from _datetime` overrides the pure python implementation with a compiled one. But if you comment out the `import ` the module works and you can add a `datetime.timedelta.__format__` method either directly in said file or by monkey patching. – NameOfTheRose Dec 10 '20 at 10:05
  • 1
    Ofcourse [commenting out the `import`](https://stackoverflow.com/questions/538666/format-timedelta-to-string#comment115324020_538666), as I myself suggested, has implications: performance suffers ( strptime is 2x slower) , incompatibilities arise( timezone module crashes). – NameOfTheRose Dec 13 '20 at 09:48

34 Answers34

296

You can just convert the timedelta to a string with str(). Here's an example:

import datetime
start = datetime.datetime(2009,2,10,14,00)
end   = datetime.datetime(2009,2,10,16,00)
delta = end-start
print(str(delta))
# prints 2:00:00
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Parand
  • 102,950
  • 48
  • 151
  • 186
  • 13
    More like calling the str() method with timedelta as its argument. – joeforker Feb 12 '09 at 18:23
  • 16
    You don't need the str call there, it will be done automatically by print. – Zitrax Feb 14 '14 at 07:49
  • 5
    @Zitrax but it is necessary if you are going to concatenate the delta with another string. For instance `print 'some thing ' + str(delta)` – Plinio.Santos Feb 06 '15 at 13:33
  • 18
    And necessary when the data type is defined as 'datetime.timedelta' and you are using it like this `ConvertDuration=datetime.timedelta(milliseconds=int(254459))` then you just use split to get the microseconds out of play. From **0:03:43.765000** I can get **0:03:43** by simply running `TotalDuration=str(ConvertDuration).split('.', 2)[0]` – DarkXDroid Feb 08 '15 at 11:51
  • 40
    This might print the delta as a string, but it doesn't format it as the OP requested. – Dannid Feb 18 '15 at 22:16
  • 2
    The second parameter to split isn't needed: `TotalDuration=str(ConvertDuration).split('.')[0]` works just as well. – jbustamovej Feb 15 '17 at 08:18
  • @Zitrax when we export data frame in xlsx format we need to type cast to str explicitly. – Akarsh Jain Dec 23 '19 at 09:12
  • Let's say: `t=str(delta)`. If timedelta output is like: `'0 days 00:08:16.176470'` Then `hours, minutes, seconds = t.split(' ')[-1].split('.')[0].split(':')`. This also works for the above mentioned timedelta, delta, `str(delta)='2:00:00'`. – DisabledWhale Sep 22 '20 at 14:28
  • Really appreciate it, @Parand! – Sunghee Yun Dec 13 '20 at 03:44
260

As you know, you can get the total_seconds from a timedelta object by accessing the .seconds attribute.

Python provides the builtin function divmod() which allows for:

s = 13420
hours, remainder = divmod(s, 3600)
minutes, seconds = divmod(remainder, 60)
print('{:02}:{:02}:{:02}'.format(int(hours), int(minutes), int(seconds)))
# result: 03:43:40

or you can convert to hours and remainder by using a combination of modulo and subtraction:

# arbitrary number of seconds
s = 13420
# hours
hours = s // 3600 
# remaining seconds
s = s - (hours * 3600)
# minutes
minutes = s // 60
# remaining seconds
seconds = s - (minutes * 60)
# total time
print('{:02}:{:02}:{:02}'.format(int(hours), int(minutes), int(seconds)))
# result: 03:43:40
boxed
  • 3,895
  • 2
  • 24
  • 26
John Fouhy
  • 41,203
  • 19
  • 62
  • 77
82
>>> str(datetime.timedelta(hours=10.56))
10:33:36

>>> td = datetime.timedelta(hours=10.505) # any timedelta object
>>> ':'.join(str(td).split(':')[:2])
10:30

Passing the timedelta object to the str() function calls the same formatting code used if we simply type print td. Since you don't want the seconds, we can split the string by colons (3 parts) and put it back together with only the first 2 parts.

joeforker
  • 40,459
  • 37
  • 151
  • 246
64
def td_format(td_object):
    seconds = int(td_object.total_seconds())
    periods = [
        ('year',        60*60*24*365),
        ('month',       60*60*24*30),
        ('day',         60*60*24),
        ('hour',        60*60),
        ('minute',      60),
        ('second',      1)
    ]

    strings=[]
    for period_name, period_seconds in periods:
        if seconds > period_seconds:
            period_value , seconds = divmod(seconds, period_seconds)
            has_s = 's' if period_value > 1 else ''
            strings.append("%s %s%s" % (period_value, period_name, has_s))

    return ", ".join(strings)
Jean-Francois T.
  • 11,549
  • 7
  • 68
  • 107
  • 9
    Really nice, I would suggest changing `if seconds > period_seconds:` to `if seconds >= period_seconds:` however. – CBenni Jun 19 '15 at 19:04
  • 1
    This returns empty strings for negative timedeltas, not sure if this is intended? – Dirk Aug 22 '17 at 10:34
  • 2
    I have tried 5473 days and I got: "14 years, 12 months, 3 days". Why not "15 years and 3 days" instead? Furthermore, according to google, 5473 is "14.99452 calendar years" and "0.99451999988374 calendar years is 11.93422692000003593 months", and "0.93422692000003593193 months is 28.416099957565091216 days. Therefore, isn't the correct answer supposed to be: "14 years, 11 months, 28 days"? – Bruno Ambrozio Dec 15 '20 at 23:50
60

I personally use the humanize library for this:

>>> import datetime
>>> humanize.naturalday(datetime.datetime.now())
'today'
>>> humanize.naturalday(datetime.datetime.now() - datetime.timedelta(days=1))
'yesterday'
>>> humanize.naturalday(datetime.date(2007, 6, 5))
'Jun 05'
>>> humanize.naturaldate(datetime.date(2007, 6, 5))
'Jun 05 2007'
>>> humanize.naturaltime(datetime.datetime.now() - datetime.timedelta(seconds=1))
'a second ago'
>>> humanize.naturaltime(datetime.datetime.now() - datetime.timedelta(seconds=3600))
'an hour ago'

Of course, it doesn't give you exactly the answer you were looking for (which is, indeed, str(timeA - timeB), but I have found that once you go beyond a few hours, the display becomes quickly unreadable. humanize has support for much larger values that are human-readable, and is also well localized.

It's inspired by Django's contrib.humanize module, apparently, so since you are using Django, you should probably use that.

arogachev
  • 33,150
  • 7
  • 114
  • 117
anarcat
  • 5,605
  • 4
  • 32
  • 38
  • 3
    Nice,+1 Though: humanize.naturaldelta(pd.Timedelta('-10sec')) --> '10 seconds' :S... – ntg Dec 13 '16 at 10:49
  • 6
    well it *is* a 10 second *delta*. :) it you want the *time*, `naturaltime` is what you want to use. – anarcat Dec 14 '16 at 19:58
  • Kind a useful module but a weird result, diff became 'x minute ago' :) it should be just 'x minutes ' – ati ince Sep 26 '22 at 08:19
39

Here is a general purpose function for converting either a timedelta object or a regular number (in the form of seconds or minutes, etc.) to a nicely formatted string. I took mpounsett's fantastic answer on a duplicate question, made it a bit more flexible, improved readibility, and added documentation.

You will find that it is the most flexible answer here so far since it allows you to:

  1. Customize the string format on the fly instead of it being hard-coded.
  2. Leave out certain time intervals without a problem (see examples below).

Function:

from string import Formatter
from datetime import timedelta

def strfdelta(tdelta, fmt='{D:02}d {H:02}h {M:02}m {S:02}s', inputtype='timedelta'):
    """Convert a datetime.timedelta object or a regular number to a custom-
    formatted string, just like the stftime() method does for datetime.datetime
    objects.

    The fmt argument allows custom formatting to be specified.  Fields can 
    include seconds, minutes, hours, days, and weeks.  Each field is optional.

    Some examples:
        '{D:02}d {H:02}h {M:02}m {S:02}s' --> '05d 08h 04m 02s' (default)
        '{W}w {D}d {H}:{M:02}:{S:02}'     --> '4w 5d 8:04:02'
        '{D:2}d {H:2}:{M:02}:{S:02}'      --> ' 5d  8:04:02'
        '{H}h {S}s'                       --> '72h 800s'

    The inputtype argument allows tdelta to be a regular number instead of the  
    default, which is a datetime.timedelta object.  Valid inputtype strings: 
        's', 'seconds', 
        'm', 'minutes', 
        'h', 'hours', 
        'd', 'days', 
        'w', 'weeks'
    """

    # Convert tdelta to integer seconds.
    if inputtype == 'timedelta':
        remainder = int(tdelta.total_seconds())
    elif inputtype in ['s', 'seconds']:
        remainder = int(tdelta)
    elif inputtype in ['m', 'minutes']:
        remainder = int(tdelta)*60
    elif inputtype in ['h', 'hours']:
        remainder = int(tdelta)*3600
    elif inputtype in ['d', 'days']:
        remainder = int(tdelta)*86400
    elif inputtype in ['w', 'weeks']:
        remainder = int(tdelta)*604800

    f = Formatter()
    desired_fields = [field_tuple[1] for field_tuple in f.parse(fmt)]
    possible_fields = ('W', 'D', 'H', 'M', 'S')
    constants = {'W': 604800, 'D': 86400, 'H': 3600, 'M': 60, 'S': 1}
    values = {}
    for field in possible_fields:
        if field in desired_fields and field in constants:
            values[field], remainder = divmod(remainder, constants[field])
    return f.format(fmt, **values)

Demo:

>>> td = timedelta(days=2, hours=3, minutes=5, seconds=8, microseconds=340)

>>> print strfdelta(td)
02d 03h 05m 08s

>>> print strfdelta(td, '{D}d {H}:{M:02}:{S:02}')
2d 3:05:08

>>> print strfdelta(td, '{D:2}d {H:2}:{M:02}:{S:02}')
 2d  3:05:08

>>> print strfdelta(td, '{H}h {S}s')
51h 308s

>>> print strfdelta(12304, inputtype='s')
00d 03h 25m 04s

>>> print strfdelta(620, '{H}:{M:02}', 'm')
10:20

>>> print strfdelta(49, '{D}d {H}h', 'h')
2d 1h
Community
  • 1
  • 1
MarredCheese
  • 17,541
  • 8
  • 92
  • 91
  • 1
    Very nice, clean code! I wonder how this code can be expanded to handle the last fractional remainder seconds in the formatter... – kfmfe04 Feb 22 '20 at 07:06
  • 1
    @kfmfe04 I modified this solution to include the fraction of a second https://stackoverflow.com/a/63198084/11998874 – tomatoeshift Jul 31 '20 at 19:34
36

He already has a timedelta object so why not use its built-in method total_seconds() to convert it to seconds, then use divmod() to get hours and minutes?

hours, remainder = divmod(myTimeDelta.total_seconds(), 3600)
minutes, seconds = divmod(remainder, 60)

# Formatted only for hours and minutes as requested
print '%s:%s' % (hours, minutes)

This works regardless if the time delta has even days or years.

Markus Amalthea Magnuson
  • 8,415
  • 4
  • 41
  • 49
Bill Kidd
  • 1,130
  • 11
  • 13
  • In the spirit of Share Alike, I took this answer and used it to create a more compact string format that works in the past and future: https://gist.github.com/davidmankin/872388157e562dda8b90f631cc33657d – DaveMan Dec 16 '22 at 19:21
  • Try this with a time that has single-digit minutes – Konrad Rudolph Jun 01 '23 at 09:45
28

I know that this is an old answered question, but I use datetime.utcfromtimestamp() for this. It takes the number of seconds and returns a datetime that can be formatted like any other datetime.

duration = datetime.utcfromtimestamp((end - begin).total_seconds())
print(duration.strftime('%H:%M'))

As long as you stay in the legal ranges for the time parts this should work, i.e. it doesn't return 1234:35 as hours are <= 23.

Skippy le Grand Gourou
  • 6,976
  • 4
  • 60
  • 76
sjngm
  • 12,423
  • 14
  • 84
  • 114
  • 3
    you should use `print timedelta(seconds=end - begin)` instead. – jfs Jan 30 '15 at 20:37
  • @J.F.Sebastian Then you'd have to pad the hours manually with leading zeroes. – sjngm Jan 30 '15 at 20:45
  • I see nothing about padding in the question. Use `.zfill(8)` if you need it. – jfs Jan 30 '15 at 21:34
  • 3
    Needs a .total_seconds() call: >>> datetime.utcfromtimestamp((t2-t1).total_seconds()).strftime("%H:%M:%S") <<<>>> '00:00:05' – cagney May 03 '17 at 00:45
  • 3
    I much prefer this solution, it allows you to control the formatting. Note you can also use the str formatter directly like this: "{0:%H}:{0:%M}".format(duration) – toes Jul 26 '17 at 15:26
  • 5
    If you have a `timedelta` object, you can use it directly: `datetime.utcfromtimestamp(delta.total_seconds())` – squaregoldfish Aug 04 '20 at 18:16
20

I would seriously consider the Occam's Razor approach here:

td = str(timedelta).split('.')[0]

This returns a string without the microseconds

If you want to regenerate the datetime.timedelta object, just do this:

h,m,s = re.split(':', td)
new_delta = datetime.timedelta(hours=int(h),minutes=int(m),seconds=int(s))

2 years in, I love this language!

Pyrotherm
  • 293
  • 3
  • 8
18

I used the humanfriendly python library to do this, it works very well.

import humanfriendly
from datetime import timedelta
delta = timedelta(seconds = 321)
humanfriendly.format_timespan(delta)

'5 minutes and 21 seconds'

Available at https://pypi.org/project/humanfriendly/

Dhiraj Gupta
  • 9,704
  • 8
  • 49
  • 54
18

maybe:

>>> import datetime
>>> dt0 = datetime.datetime(1,1,1)
>>> td = datetime.timedelta(minutes=34, hours=12, seconds=56)
>>> (dt0+td).strftime('%X')
'12:34:56'
>>> (dt0+td).strftime('%M:%S')
'34:56'
>>> (dt0+td).strftime('%H:%M')
'12:34'
>>>
Mechanic
  • 319
  • 3
  • 8
  • 2
    `(datetime(1, 1, 1) + timedelta(seconds=delta)).strftime('%X')` Yes, will work, but if delta > 24 hours, result will be wrong. – socketpair Aug 31 '22 at 20:44
15

Questioner wants a nicer format than the typical:

  >>> import datetime
  >>> datetime.timedelta(seconds=41000)
  datetime.timedelta(0, 41000)
  >>> str(datetime.timedelta(seconds=41000))
  '11:23:20'
  >>> str(datetime.timedelta(seconds=4102.33))
  '1:08:22.330000'
  >>> str(datetime.timedelta(seconds=413302.33))
  '4 days, 18:48:22.330000'

So, really there's two formats, one where days are 0 and it's left out, and another where there's text "n days, h:m:s". But, the seconds may have fractions, and there's no leading zeroes in the printouts, so columns are messy.

Here's my routine, if you like it:

def printNiceTimeDelta(stime, etime):
    delay = datetime.timedelta(seconds=(etime - stime))
    if (delay.days > 0):
        out = str(delay).replace(" days, ", ":")
    else:
        out = "0:" + str(delay)
    outAr = out.split(':')
    outAr = ["%02d" % (int(float(x))) for x in outAr]
    out   = ":".join(outAr)
    return out

this returns output as dd:hh:mm:ss format:

00:00:00:15
00:00:00:19
02:01:31:40
02:01:32:22

I did think about adding years to this, but this is left as an exercise for the reader, since the output is safe at over 1 year:

>>> str(datetime.timedelta(seconds=99999999))
'1157 days, 9:46:39'
Kevin J. Rice
  • 3,337
  • 2
  • 24
  • 23
15

I have a function:

def period(delta, pattern):
    d = {'d': delta.days}
    d['h'], rem = divmod(delta.seconds, 3600)
    d['m'], d['s'] = divmod(rem, 60)
    return pattern.format(**d)

Examples:

>>> td = timedelta(seconds=123456789)
>>> period(td, "{d} days {h}:{m}:{s}")
'1428 days 21:33:9'
>>> period(td, "{h} hours, {m} minutes and {s} seconds, {d} days")
'21 hours, 33 minutes and 9 seconds, 1428 days'
GooDeeJAY
  • 1,681
  • 2
  • 20
  • 27
  • 3
    Very nice! Best answer!. I would modify slightly to include microseconds `d['f'] = delta.microseconds`, then use like `print(period(td,"{d} days {h}:{m}:{s}.{f}"))` – rayzinnz Jul 09 '21 at 20:24
14

My datetime.timedelta objects went greater than a day. So here is a further problem. All the discussion above assumes less than a day. A timedelta is actually a tuple of days, seconds and microseconds. The above discussion should use td.seconds as joe did, but if you have days it is NOT included in the seconds value.

I am getting a span of time between 2 datetimes and printing days and hours.

span = currentdt - previousdt
print '%d,%d\n' % (span.days,span.seconds/3600)
Alex L
  • 8,748
  • 5
  • 49
  • 75
7

Following Joe's example value above, I'd use the modulus arithmetic operator, thusly:

td = datetime.timedelta(hours=10.56)
td_str = "%d:%d" % (td.seconds/3600, td.seconds%3600/60)

Note that integer division in Python rounds down by default; if you want to be more explicit, use math.floor() or math.ceil() as appropriate.

UltraNurd
  • 1,314
  • 2
  • 14
  • 24
7

One liner. Since timedeltas do not offer datetime's strftime, bring the timedelta back to a datetime, and use stftime.

This can not only achieve the OP's requested format Hours:Minutes, now you can leverage the full formatting power of datetime's strftime, should your requirements change to another representation.

import datetime
td = datetime.timedelta(hours=2, minutes=10, seconds=5)
print(td)
print(datetime.datetime.strftime(datetime.datetime.strptime(str(td), "%H:%M:%S"), "%H:%M"))

Output:
2:10:05
02:10

This also solves the annoyance that timedeltas are formatted into strings as H:MM:SS rather than HH:MM:SS, which lead me to this problem, and the solution I've shared.

Bill Gale
  • 1,238
  • 1
  • 14
  • 14
6
import datetime
hours = datetime.timedelta(hours=16, minutes=30)
print((datetime.datetime(1,1,1) + hours).strftime('%H:%M'))
SleX
  • 139
  • 1
  • 4
  • 4
    in 3.7 I get AttributeError: 'datetime.timedelta' object has no attribute 'strftime' – qubodup Mar 14 '19 at 13:00
  • Now try this with `hours = datetime.timedelta(hours=25, minutes=30)` – gss Aug 27 '20 at 20:41
  • @qubodup the secret is to do an operation with a time field ;) when you add it to the team, you can use strftime – SleX Oct 01 '22 at 15:04
  • @gss ```(datetime.datetime(1,1,1) + datetime.timedelta(days=-1, hours=25, minutes=30)).strftime('%d %H:%M')``` – SleX Oct 01 '22 at 15:05
4
def seconds_to_time_left_string(total_seconds):
    s = int(total_seconds)
    years = s // 31104000
    if years > 1:
        return '%d years' % years
    s = s - (years * 31104000)
    months = s // 2592000
    if years == 1:
        r = 'one year'
        if months > 0:
            r += ' and %d months' % months
        return r
    if months > 1:
        return '%d months' % months
    s = s - (months * 2592000)
    days = s // 86400
    if months == 1:
        r = 'one month'
        if days > 0:
            r += ' and %d days' % days
        return r
    if days > 1:
        return '%d days' % days
    s = s - (days * 86400)
    hours = s // 3600
    if days == 1:
        r = 'one day'
        if hours > 0:
            r += ' and %d hours' % hours
        return r 
    s = s - (hours * 3600)
    minutes = s // 60
    seconds = s - (minutes * 60)
    if hours >= 6:
        return '%d hours' % hours
    if hours >= 1:
        r = '%d hours' % hours
        if hours == 1:
            r = 'one hour'
        if minutes > 0:
            r += ' and %d minutes' % minutes
        return r
    if minutes == 1:
        r = 'one minute'
        if seconds > 0:
            r += ' and %d seconds' % seconds
        return r
    if minutes == 0:
        return '%d seconds' % seconds
    if seconds == 0:
        return '%d minutes' % minutes
    return '%d minutes and %d seconds' % (minutes, seconds)

for i in range(10):
    print pow(8, i), seconds_to_time_left_string(pow(8, i))


Output:
1 1 seconds
8 8 seconds
64 one minute and 4 seconds
512 8 minutes and 32 seconds
4096 one hour and 8 minutes
32768 9 hours
262144 3 days
2097152 24 days
16777216 6 months
134217728 4 years
Veselin Penev
  • 77
  • 1
  • 5
4

I had a similar problem with the output of overtime calculation at work. The value should always show up in HH:MM, even when it is greater than one day and the value can get negative. I combined some of the shown solutions and maybe someone else find this solution useful. I realized that if the timedelta value is negative most of the shown solutions with the divmod method doesn't work out of the box:

def td2HHMMstr(td):
  '''Convert timedelta objects to a HH:MM string with (+/-) sign'''
  if td < datetime.timedelta(seconds=0):
    sign='-'
    td = -td
  else:
    sign = ''
  tdhours, rem = divmod(td.total_seconds(), 3600)
  tdminutes, rem = divmod(rem, 60)
  tdstr = '{}{:}:{:02d}'.format(sign, int(tdhours), int(tdminutes))
  return tdstr

timedelta to HH:MM string:

td2HHMMstr(datetime.timedelta(hours=1, minutes=45))
'1:54'

td2HHMMstr(datetime.timedelta(days=2, hours=3, minutes=2))
'51:02'

td2HHMMstr(datetime.timedelta(hours=-3, minutes=-2))
'-3:02'

td2HHMMstr(datetime.timedelta(days=-35, hours=-3, minutes=-2))
'-843:02'
nealtz
  • 361
  • 2
  • 7
4
from django.utils.translation import ngettext

def localize_timedelta(delta):
    ret = []
    num_years = int(delta.days / 365)
    if num_years > 0:
        delta -= timedelta(days=num_years * 365)
        ret.append(ngettext('%d year', '%d years', num_years) % num_years)

    if delta.days > 0:
        ret.append(ngettext('%d day', '%d days', delta.days) % delta.days)

    num_hours = int(delta.seconds / 3600)
    if num_hours > 0:
        delta -= timedelta(hours=num_hours)
        ret.append(ngettext('%d hour', '%d hours', num_hours) % num_hours)

    num_minutes = int(delta.seconds / 60)
    if num_minutes > 0:
        ret.append(ngettext('%d minute', '%d minutes', num_minutes) % num_minutes)

    return ' '.join(ret)

This will produce:

>>> from datetime import timedelta
>>> localize_timedelta(timedelta(days=3660, minutes=500))
'10 years 10 days 8 hours 20 minutes'
Artem Vasilev
  • 139
  • 1
  • 3
4

A straight forward template filter for this problem. The built-in function int() never rounds up. F-Strings (i.e. f'') require python 3.6.

@app_template_filter()
def diffTime(end, start):
    diff = (end - start).total_seconds()
    d = int(diff / 86400)
    h = int((diff - (d * 86400)) / 3600)
    m = int((diff - (d * 86400 + h * 3600)) / 60)
    s = int((diff - (d * 86400 + h * 3600 + m *60)))
    if d > 0:
        fdiff = f'{d}d {h}h {m}m {s}s'
    elif h > 0:
        fdiff = f'{h}h {m}m {s}s'
    elif m > 0:
        fdiff = f'{m}m {s}s'
    else:
        fdiff = f'{s}s'
    return fdiff
Patrick
  • 407
  • 4
  • 7
4

I continued from MarredCheese's answer and added year, month, millicesond and microsecond

all numbers are formatted to integer except for second, thus the fraction of a second can be customized.

@kfmfe04 asked for fraction of a second so I posted this solution

In the main there are some examples.

from string import Formatter
from datetime import timedelta

def strfdelta(tdelta, fmt='{D:02}d {H:02}h {M:02}m {S:02.0f}s', inputtype='timedelta'):
    """Convert a datetime.timedelta object or a regular number to a custom-
    formatted string, just like the stftime() method does for datetime.datetime
    objects.

    The fmt argument allows custom formatting to be specified.  Fields can 
    include seconds, minutes, hours, days, and weeks.  Each field is optional.

    Some examples:
        '{D:02}d {H:02}h {M:02}m {S:02.0f}s' --> '05d 08h 04m 02s' (default)
        '{W}w {D}d {H}:{M:02}:{S:02.0f}'     --> '4w 5d 8:04:02'
        '{D:2}d {H:2}:{M:02}:{S:02.0f}'      --> ' 5d  8:04:02'
        '{H}h {S:.0f}s'                       --> '72h 800s'

    The inputtype argument allows tdelta to be a regular number instead of the  
    default, which is a datetime.timedelta object.  Valid inputtype strings: 
        's', 'seconds', 
        'm', 'minutes', 
        'h', 'hours', 
        'd', 'days', 
        'w', 'weeks'
    """

    # Convert tdelta to integer seconds.
    if inputtype == 'timedelta':
        remainder = tdelta.total_seconds()
    elif inputtype in ['s', 'seconds']:
        remainder = float(tdelta)
    elif inputtype in ['m', 'minutes']:
        remainder = float(tdelta)*60
    elif inputtype in ['h', 'hours']:
        remainder = float(tdelta)*3600
    elif inputtype in ['d', 'days']:
        remainder = float(tdelta)*86400
    elif inputtype in ['w', 'weeks']:
        remainder = float(tdelta)*604800

    f = Formatter()
    desired_fields = [field_tuple[1] for field_tuple in f.parse(fmt)]
    possible_fields = ('Y','m','W', 'D', 'H', 'M', 'S', 'mS', 'µS')
    constants = {'Y':86400*365.24,'m': 86400*30.44 ,'W': 604800, 'D': 86400, 'H': 3600, 'M': 60, 'S': 1, 'mS': 1/pow(10,3) , 'µS':1/pow(10,6)}
    values = {}
    for field in possible_fields:
        if field in desired_fields and field in constants:
            Quotient, remainder = divmod(remainder, constants[field])
            values[field] = int(Quotient) if field != 'S' else Quotient + remainder
    return f.format(fmt, **values)

if __name__ == "__main__":
    td = timedelta(days=717, hours=3, minutes=5, seconds=8, microseconds=3549)
    print(strfdelta(td,'{Y} years {m} months {W} weeks {D} days {H:02}:{M:02}:{S:02}'))  
    print(strfdelta(td,'{m} months {W} weeks {D} days {H:02}:{M:02}:{S:02.4f}'))  
    td = timedelta( seconds=8, microseconds=8549)
    print(strfdelta(td,'{S} seconds {mS} milliseconds {µS} microseconds'))  
    print(strfdelta(td,'{S:.0f} seconds {mS} milliseconds {µS} microseconds'))  
    print(strfdelta(pow(10,7),inputtype='s'))

Output:

1 years 11 months 2 weeks 3 days 01:09:56.00354900211096
23 months 2 weeks 3 days 00:12:20.0035
8.008549 seconds 8 milliseconds 549 microseconds
8 seconds 8 milliseconds 549 microseconds
115d 17h 46m 40s
tomatoeshift
  • 465
  • 7
  • 23
  • Thanks! But your floating point format is incorrect for seconds < 10.0. You need `{S:07.4f}` if you want 9.0035 to show as 09.0035. – b-jazz Jan 31 '21 at 06:13
3

If you happen to have IPython in your packages (you should), it has (up to now, anyway) a very nice formatter for durations (in float seconds). That is used in various places, for example by the %%time cell magic. I like the format it produces for short durations:

>>> from IPython.core.magics.execution import _format_time
>>> 
>>> for v in range(-9, 10, 2):
...     dt = 1.25 * 10**v
...     print(_format_time(dt))

1.25 ns
125 ns
12.5 µs
1.25 ms
125 ms
12.5 s
20min 50s
1d 10h 43min 20s
144d 16h 13min 20s
14467d 14h 13min 20s
Pierre D
  • 24,012
  • 7
  • 60
  • 96
2

Here's a function to stringify timedelta.total_seconds(). It works in python 2 and 3.

def strf_interval(seconds):
    days, remainder = divmod(seconds, 86400)
    hours, remainder = divmod(remainder, 3600)
    minutes, seconds = divmod(remainder, 60)
    return '{} {} {} {}'.format(
            "" if int(days) == 0 else str(int(days)) + ' days',
            "" if int(hours) == 0 else str(int(hours)) + ' hours',
            "" if int(minutes) == 0 else str(int(minutes))  + ' mins',
            "" if int(seconds) == 0 else str(int(seconds))  + ' secs'
        )

Example output:

>>> print(strf_interval(1))
   1 secs
>>> print(strf_interval(100))
  1 mins 40 secs
>>> print(strf_interval(1000))
  16 mins 40 secs
>>> print(strf_interval(10000))
 2 hours 46 mins 40 secs
>>> print(strf_interval(100000))
1 days 3 hours 46 mins 40 secs
2

timedelta to string, use for print running time info.

def strfdelta_round(tdelta, round_period='second'):
  """timedelta to string,  use for measure running time
  attend period from days downto smaller period, round to minimum period
  omit zero value period  
  """
  period_names = ('day', 'hour', 'minute', 'second', 'millisecond')
  if round_period not in period_names:
    raise Exception(f'round_period "{round_period}" invalid, should be one of {",".join(period_names)}')
  period_seconds = (86400, 3600, 60, 1, 1/pow(10,3))
  period_desc = ('days', 'hours', 'mins', 'secs', 'msecs')
  round_i = period_names.index(round_period)
  
  s = ''
  remainder = tdelta.total_seconds()
  for i in range(len(period_names)):
    q, remainder = divmod(remainder, period_seconds[i])
    if int(q)>0:
      if not len(s)==0:
        s += ' '
      s += f'{q:.0f} {period_desc[i]}'
    if i==round_i:
      break
    if i==round_i+1:
      s += f'{remainder} {period_desc[round_i]}'
      break
    
  return s

e.g. auto omit zero leading period:

>>> td = timedelta(days=0, hours=2, minutes=5, seconds=8, microseconds=3549)
>>> strfdelta_round(td, 'second')
'2 hours 5 mins 8 secs'

or omit middle zero period:

>>> td = timedelta(days=2, hours=0, minutes=5, seconds=8, microseconds=3549)
>>> strfdelta_round(td, 'millisecond')
'2 days 5 mins 8 secs 3 msecs'

or round to minutes, omit below minutes:

>>> td = timedelta(days=1, hours=2, minutes=5, seconds=8, microseconds=3549)
>>> strfdelta_round(td, 'minute')
'1 days 2 hours 5 mins'
Florian Castellane
  • 1,197
  • 2
  • 14
  • 38
raidsan
  • 777
  • 1
  • 7
  • 11
1

Please check this function - it converts timedelta object into string 'HH:MM:SS'

def format_timedelta(td):
    hours, remainder = divmod(td.total_seconds(), 3600)
    minutes, seconds = divmod(remainder, 60)
    hours, minutes, seconds = int(hours), int(minutes), int(seconds)
    if hours < 10:
        hours = '0%s' % int(hours)
    if minutes < 10:
        minutes = '0%s' % minutes
    if seconds < 10:
        seconds = '0%s' % seconds
    return '%s:%s:%s' % (hours, minutes, seconds)
MobilePro.pl
  • 331
  • 1
  • 8
1

I wanted to do this so wrote a simple function. It works great for me and is quite versatile (supports years to microseconds, and any granularity level, e.g. you can pick between '2 days, 4 hours, 48 minutes' and '2 days, 4 hours' and '2 days, 4.8 hours', etc.

def pretty_print_timedelta(t, max_components=None, max_decimal_places=2):
''' 
Print a pretty string for a timedelta. 
For example datetime.timedelta(days=2, seconds=17280) will be printed as '2 days, 4 hours, 48 minutes'. Setting max_components to e.g. 1 will change this to '2.2 days', where the 
number of decimal points can also be set. 
'''
time_scales = [timedelta(days=365), timedelta(days=1), timedelta(hours=1), timedelta(minutes=1), timedelta(seconds=1), timedelta(microseconds=1000), timedelta(microseconds=1)]
time_scale_names_dict = {timedelta(days=365): 'year',  
                         timedelta(days=1): 'day', 
                         timedelta(hours=1): 'hour', 
                         timedelta(minutes=1): 'minute', 
                         timedelta(seconds=1): 'second', 
                         timedelta(microseconds=1000): 'millisecond', 
                         timedelta(microseconds=1): 'microsecond'}
count = 0
txt = ''
first = True
for scale in time_scales:
    if t >= scale: 
        count += 1
        if count == max_components:
            n = t / scale
        else:
            n = int(t / scale)
            
        t -= n*scale
        
        n_txt = str(round(n, max_decimal_places))
        if n_txt[-2:]=='.0': n_txt = n_txt[:-2]
        txt += '{}{} {}{}'.format('' if first else ', ', n_txt, time_scale_names_dict[scale], 's' if n>1 else '', )
        if first:
            first = False
        
        
if len(txt) == 0: 
    txt = 'none'
return txt
0

If you already have a timedelta obj then just convert that obj into string. Remove the last 3 characters of the string and print. This will truncate the seconds part and print the rest of it in the format Hours:Minutes.

t = str(timedeltaobj) 

print t[:-3]
Sima
  • 39
  • 2
0

I had the same problem and I am using pandas Timedeltas, didn't want to bring in additional dependencies (another answer mentions humanfriendly) so I wrote this small function to print out only the relevant information:

def format_timedelta(td: pd.Timedelta) -> str:
    if pd.isnull(td):
        return str(td)
    else:
        c = td.components._asdict()
        return ", ".join(f"{n} {unit}" for unit, n in c.items() if n)

For example, pd.Timedelta(hours=3, seconds=12) would print as 3 hours, 12 seconds.

ilCatania
  • 187
  • 1
  • 1
  • 11
0

if you have a timedelta like this: '1 days 21:13:38.953000' then you can simply use this code for your purpose:

string = f'{timedelta.components.hours}:{timedelta.components.minutes}'

It will show the timedelta like this : "21:13"

M.K
  • 1
  • 1
-1

I suggest the following method so that we can utilize the standard formatting function, pandas.Timestamp.strftime!

from pandas import Timestamp, Timedelta

(Timedelta("2 hours 30 min") + Timestamp("00:00:00")).strftime("%H:%M")
Sunghee Yun
  • 722
  • 1
  • 6
  • 11
-1
# Format seconds to days, hours, minutes and seconds string
def ptime(seconds):
if(seconds >= 86400):
    d = seconds // 86400 # // floor division
    return (f"{round(d)}d") + ptime(seconds - d * 86400)
else:
    if(seconds >= 3600):
        h = seconds // 3600 
        return (f"{round(h)}h") + ptime(seconds - h * 3600)
    else:
        if(seconds >= 60):
            m = seconds // 60
            return(f"{round(m)}m" + ptime(seconds - m * 60))
        else:
            if (seconds > 0):
                return(f"{round(seconds)}s")
            else:
                return("")
Manuel Alves
  • 3,885
  • 2
  • 30
  • 24
-3
t1 = datetime.datetime.strptime(StartTime, "%H:%M:%S %d-%m-%y")

t2 = datetime.datetime.strptime(EndTime, "%H:%M:%S %d-%m-%y")

return str(t2-t1)

So for:

StartTime = '15:28:53 21-07-13'
EndTime = '15:32:40 21-07-13'

returns:

'0:03:47'
Aviad
  • 343
  • 1
  • 7
-14

Thanks everyone for your help. I took many of your ideas and put them together, let me know what you think.

I added two methods to the class like this:

def hours(self):
    retval = ""
    if self.totalTime:
        hoursfloat = self.totalTime.seconds / 3600
        retval = round(hoursfloat)
    return retval

def minutes(self):
    retval = ""
    if self.totalTime:
        minutesfloat = self.totalTime.seconds / 60
        hoursAsMinutes = self.hours() * 60
        retval = round(minutesfloat - hoursAsMinutes)
    return retval

In my django I used this (sum is the object and it is in a dictionary):

<td>{{ sum.0 }}</td>    
<td>{{ sum.1.hours|stringformat:"d" }}:{{ sum.1.minutes|stringformat:"#02.0d" }}</td>
mawcs
  • 4,357
  • 2
  • 18
  • 7
  • It's a bit long. I would suggest: def hhmm(self): return ':'.join(str(td).split(':')[:2]) {{ sum.1.hhmm }} – joeforker Feb 12 '09 at 13:57
  • 1
    Just use divmod() as shown in the "Difference of Two Dates" example at http://docs.python.org/release/2.5.2/lib/datetime-timedelta.html – Tom Nov 17 '10 at 16:01