Format timedelta to string
Asked Answered
F

35

448

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.

Fulviah answered 11/2, 2009 at 20:40 Comment(5)
Would be nice if timedelta had an equivalent of the strftime() method.Ardell
@Ardell Well, you somewhat can if you use datetime.utcfromtimestamp(). See my answer below.Fulk
@Ardell - 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.Olfactory
@Ardell 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.Gobelin
Ofcourse commenting out the import, as I myself suggested, has implications: performance suffers ( strptime is 2x slower) , incompatibilities arise( timezone module crashes).Gobelin
F
-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>
Fulviah answered 11/2, 2009 at 21:11 Comment(2)
It's a bit long. I would suggest: def hhmm(self): return ':'.join(str(td).split(':')[:2]) <td>{{ sum.1.hhmm }}</td>Armful
Just use divmod() as shown in the "Difference of Two Dates" example at docs.python.org/release/2.5.2/lib/datetime-timedelta.htmlMicronucleus
V
301

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
Verbalism answered 11/2, 2009 at 20:52 Comment(9)
More like calling the str() method with timedelta as its argument.Armful
You don't need the str call there, it will be done automatically by print.Echo
@Echo but it is necessary if you are going to concatenate the delta with another string. For instance print 'some thing ' + str(delta)Osteoblast
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]Itinerate
This might print the delta as a string, but it doesn't format it as the OP requested.Epenthesis
The second parameter to split isn't needed: TotalDuration=str(ConvertDuration).split('.')[0] works just as well.Hemichordate
@Echo when we export data frame in xlsx format we need to type cast to str explicitly.Norris
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'.Phytosociology
Really appreciate it, @Parand!Dott
R
270

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
Roily answered 11/2, 2009 at 23:34 Comment(6)
For negative timedeltas you should do evaluate the sign first and then do abs(s).Sweatbox
Note that you may actually want to use '%d:%02d:%02d' to have leading zeros in the output string.Dovetail
for python 2.7 and greater use .total_seconds() methodDepressomotor
Don't use .seconds if the difference can be negative or longer than 24 hours (.seconds attribute ignores .days). Use .total_seconds() or its analog instead.Caladium
For positive differences I'm re-implementing this from time to time. Thanks for just having to search this time :)Azoth
I took the liberty of editing a bit John, feel free to revert to the previous state if you feel it was better.Unbacked
A
85
>>> 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.

Armful answered 11/2, 2009 at 20:44 Comment(7)
Thanks for your answer joeforker, but I'm not sure I understand your response. I am getting a time delta by way of datetime - datetime. I don't know the hours. Plus, it looks like your example includes seconds, how would I remove that?Fulviah
Doesn't matter where you get the timedelta object, it will format the same.Armful
If it's longer than a day, it will format as e.g. "4 days, 8:00" after the split/join processing.Armful
str(my_timedelta) works poorly for negative numbersDavisson
Shows days too when >24 hours, e.g. '4 days, 18:48:22.330000'. Many methods advised here do not.Ruffi
@Armful Awesome solution. Thanks!Dott
What if I want only the last 2 parts? (ditching the hours part)Deedee
B
66

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.

Backman answered 17/11, 2016 at 16:30 Comment(3)
Nice,+1 Though: humanize.naturaldelta(pd.Timedelta('-10sec')) --> '10 seconds' :S...Specialistic
well it is a 10 second delta. :) it you want the time, naturaltime is what you want to use.Backman
Kind a useful module but a weird result, diff became 'x minute ago' :) it should be just 'x minutes <somethings>'Carson
F
65
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)
Farcical answered 7/12, 2012 at 2:24 Comment(3)
Really nice, I would suggest changing if seconds > period_seconds: to if seconds >= period_seconds: however.Gyrocompass
This returns empty strings for negative timedeltas, not sure if this is intended?Lyallpur
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"?Millikan
A
42

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
Apterygial answered 18/2, 2017 at 20:20 Comment(2)
Very nice, clean code! I wonder how this code can be expanded to handle the last fractional remainder seconds in the formatter...Hearthstone
@Hearthstone I modified this solution to include the fraction of a second https://mcmap.net/q/64455/-format-timedelta-to-stringJezabelle
M
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.

Martines answered 19/6, 2013 at 15:41 Comment(2)
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: gist.github.com/davidmankin/872388157e562dda8b90f631cc33657dVenusian
Try this with a time that has single-digit minutesCalaboose
F
33

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.

Fulk answered 30/1, 2015 at 18:24 Comment(7)
you should use print timedelta(seconds=end - begin) instead.Caladium
@J.F.Sebastian Then you'd have to pad the hours manually with leading zeroes.Fulk
I see nothing about padding in the question. Use .zfill(8) if you need it.Caladium
Needs a .total_seconds() call: >>> datetime.utcfromtimestamp((t2-t1).total_seconds()).strftime("%H:%M:%S") <<<>>> '00:00:05'Laplante
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)Premolar
If you have a timedelta object, you can use it directly: datetime.utcfromtimestamp(delta.total_seconds())Lory
DeprecationWarning: datetime.datetime.utcfromtimestamp() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.fromtimestamp(timestamp, datetime.UTC).Tear
D
21

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!

Dysplasia answered 25/2, 2015 at 22:53 Comment(1)
This does not cover daysSupercilious
F
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/

Foreignborn answered 21/6, 2018 at 5:49 Comment(0)
G
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'
>>>
Goodnight answered 3/12, 2020 at 21:22 Comment(1)
(datetime(1, 1, 1) + timedelta(seconds=delta)).strftime('%X') Yes, will work, but if delta > 24 hours, result will be wrong.Marcomarconi
I
16

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'
Irrepressible answered 3/7, 2020 at 16:20 Comment(1)
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}"))Delanos
A
15

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)
Ahern answered 29/10, 2009 at 14:23 Comment(1)
This is the future proof solution.Westbrooks
B
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'
Brickbat answered 9/1, 2013 at 22:16 Comment(0)
D
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.

Dieter answered 11/2, 2009 at 20:52 Comment(7)
timedelta already knows how to format itself, as in 'print some_timedelta'.Armful
Yeah, but it can't accept an arbitrary format string, which is what Michael was asking. Although now that I think about it 3600 division mod makes the hours-seconds assumption which causes problems at leap seconds.Dieter
Yeah, but he doesn't want an arbitrary format string, he wants almost exactly the default behaviour.Armful
Don't forget // for truncating division in Python 3000Armful
Ah, I've only be using up through 2.6.Dieter
@zekel: // was introduced in 2.2. Yup, it's that old. I didn't even remember it being that old. :DLocular
+1, but why don't you edit the answer to use //? I'd also suggest using td.total_seconds() instead of .seconds to make it work for intervals > 1 day.Drover
A
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.

Airdry answered 26/4, 2020 at 6:53 Comment(0)
S
6
import datetime
hours = datetime.timedelta(hours=16, minutes=30)
print((datetime.datetime(1,1,1) + hours).strftime('%H:%M'))
Scaliger answered 11/5, 2018 at 15:37 Comment(4)
in 3.7 I get AttributeError: 'datetime.timedelta' object has no attribute 'strftime'Hollenbeck
Now try this with hours = datetime.timedelta(hours=25, minutes=30)Kith
@Hollenbeck the secret is to do an operation with a time field ;) when you add it to the team, you can use strftimeScaliger
@Kith (datetime.datetime(1,1,1) + datetime.timedelta(days=-1, hours=25, minutes=30)).strftime('%d %H:%M')Scaliger
B
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
Bronson answered 29/9, 2013 at 5:21 Comment(5)
Did you write this one? How much did you test it?Udo
I am using this code in my project called datahaven.net. It works pretty fine. Did you see any errors?Bronson
It's always nice if you can provide a bit of information with such a code heavy answer :) Like an example of how it works, possible strengths and weaknesses.Udo
Oh. Sure!. Added an example for you. :-)Bronson
Super :) Also notice that the timedelta object has the fields days, seconds and microseconds by the documentation.Udo
S
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'
Simulacrum answered 10/5, 2014 at 14:21 Comment(0)
A
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'
Annelid answered 26/9, 2016 at 13:41 Comment(1)
Neatest one in my opinion, in terms of length and of what it coversFakir
A
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
Alarice answered 9/10, 2019 at 1:23 Comment(0)
J
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
Jezabelle answered 31/7, 2020 at 19:33 Comment(1)
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.Looby
P
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
Pictish answered 18/5, 2020 at 21:14 Comment(0)
A
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
Arms answered 30/6, 2020 at 13:48 Comment(0)
F
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'
Frow answered 8/10, 2020 at 7:37 Comment(0)
K
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)
Kommunarsk answered 3/3, 2016 at 8:34 Comment(0)
S
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
Slang answered 17/2, 2021 at 20:27 Comment(0)
E
1

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.

Environ answered 7/3, 2022 at 11:54 Comment(0)
B
1

Here's my simple solution that accounts for days and padding

def td_format(td, pad=True):
    try:
        _days, parsed = str(td).split(",")
    except ValueError:
        hours, minutes, seconds = str(td).split(":")
    else:
        days = _days.split(" ")[0]
        _hours, minutes, seconds = parsed.split(":")
        hours = int(_hours) + int(days) * 24

    if pad:
        hours = hours.zfill(2)
        
    return f"{hours}:{minutes}:{seconds}"
>>> from datetime import timedelta
>>>
>>> td_format(timedelta(minutes=600))
'10:00:00'
>>> td_format(timedelta(hours=25))
'25:00:00'
>>> td_format(timedelta(minutes=144))
'02:24:00'
>>> td_format(timedelta(hours=2), pad=False)
'2:00:00'
Benilda answered 10/11, 2023 at 17:7 Comment(0)
M
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]
Mcclish answered 21/11, 2017 at 5:24 Comment(1)
The -3 doesn't work, better use print t.split('.')[0]Heap
D
-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")
Dott answered 13/12, 2020 at 3:57 Comment(0)
L
-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("")
Lysenko answered 8/9, 2021 at 7:55 Comment(0)
K
-1

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"

Koah answered 9/7, 2023 at 12:22 Comment(1)
'datetime.timedelta' object has no attribute 'components'Faience
B
-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'
Basinger answered 21/7, 2013 at 13:1 Comment(0)
F
-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>
Fulviah answered 11/2, 2009 at 21:11 Comment(2)
It's a bit long. I would suggest: def hhmm(self): return ':'.join(str(td).split(':')[:2]) <td>{{ sum.1.hhmm }}</td>Armful
Just use divmod() as shown in the "Difference of Two Dates" example at docs.python.org/release/2.5.2/lib/datetime-timedelta.htmlMicronucleus

© 2022 - 2024 — McMap. All rights reserved.