331

I've got a timedelta. I want the days, hours and minutes from that - either as a tuple or a dictionary... I'm not fussed.

I must have done this a dozen times in a dozen languages over the years but Python usually has a simple answer to everything so I thought I'd ask here before busting out some nauseatingly simple (yet verbose) mathematics.

Mr Fooz raises a good point.

I'm dealing with "listings" (a bit like ebay listings) where each one has a duration. I'm trying to find the time left by doing when_added + duration - now

Am I right in saying that wouldn't account for DST? If not, what's the simplest way to add/subtract an hour?

Oli
  • 228,145
  • 62
  • 212
  • 294
  • 5
    If it is just to get it as a *string* in HH:mm:ss format, say *"0:19:37"* from the `timedelta` object *"datetime.timedelta(seconds=1177)"*: simply [use str()](https://stackoverflow.com/questions/14190045/how-to-convert-datetime-timedelta-to-minutes-hours-in-python/43965102#43965102) – Peter Mortensen Oct 24 '18 at 08:24

12 Answers12

464

If you have a datetime.timedelta value td, td.days already gives you the "days" you want. timedelta values keep fraction-of-day as seconds (not directly hours or minutes) so you'll indeed have to perform "nauseatingly simple mathematics", e.g.:

def days_hours_minutes(td):
    return td.days, td.seconds//3600, (td.seconds//60)%60
Kurt Peek
  • 43,920
  • 71
  • 247
  • 451
Alex Martelli
  • 811,175
  • 162
  • 1,198
  • 1,373
  • 3
    Keep in mind that if `td` is negative, `-1 // 3600` is `-1`. – jcrs Oct 15 '18 at 05:23
  • 9
    why // ? instead of / – Mohammed Shareef C Apr 01 '19 at 08:03
  • 24
    `//` is used instead of `/` to get an integer, not a float. `//` is a floor division operator: "division that results into whole number adjusted to the left in the number line", see [here](https://www.programiz.com/python-programming/operators). – Eerik Sven Puudist Apr 01 '19 at 19:51
  • 2
    @EerikSvenPuudist as `jcrs` pointed out, `-1//3600` is `-1`, so why not use `int(td.seconds/3600)` instead to turn `-1` second into `0` hours rather than `1` hour backwards? – simpleuser Sep 05 '19 at 20:19
  • 4
    how about adding the remaining seconds as well ? `days, hours, minutes = x.days, x.seconds // 3600, x.seconds %3600//60` and then `seconds = x.seconds - hours*3600 - minutes*60` – raphael Mar 24 '20 at 18:19
  • 23
    WHY are these not in the standard library? `days` and `seconds` are available but `hours` and `minutes` not? srsly? Some batteries were not included. `` – 3dGrabber Jan 28 '21 at 16:09
92

This is a bit more compact, you get the hours, minutes and seconds in two lines.

days = td.days
hours, remainder = divmod(td.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
# If you want to take into account fractions of a second
seconds += td.microseconds / 1e6
ryanjdillon
  • 15,701
  • 7
  • 81
  • 102
mberacochea
  • 1,604
  • 12
  • 18
  • @martineau hint: look at the dates on my comment and the answer's edit history. – jfs Jun 28 '21 at 18:29
  • @jfs: If your comment is obsolete (i.e. no longer needed) then you should delete it because nowadays it's confusing to folks who don't want to spend/waste their time carefully comparing command and edit historu timestamps. – martineau Jun 28 '21 at 18:36
  • @martineau I don't monitor edits to answers where I left comments. If you think the comment is obsolete, flag it. Perhaps, you should ask the person who made the edit that makes comments obsolete, so that they would notify/flag comments. – jfs Jun 30 '21 at 15:36
  • 1
    @jfs: Valid point, but again I'm not going to analyze the edit history in order to figure-out who's responsible. My asking you about your comment (obviously) was enough notice that it had become obsolete. BTW, I *did* flag it as no longer needed, but only after conferring with you about it. – martineau Jun 30 '21 at 16:31
36
days, hours, minutes = td.days, td.seconds // 3600, td.seconds // 60 % 60

As for DST, I think the best thing is to convert both datetime objects to seconds. This way the system calculates DST for you.

>>> m13 = datetime(2010, 3, 13, 8, 0, 0)  # 2010 March 13 8:00 AM
>>> m14 = datetime(2010, 3, 14, 8, 0, 0)  # DST starts on this day, in my time zone
>>> mktime(m14.timetuple()) - mktime(m13.timetuple())     # difference in seconds
82800.0
>>> _/3600                                                # convert to hours
23.0
Dav Clark
  • 1,360
  • 1
  • 12
  • 25
Jason Orendorff
  • 39,955
  • 4
  • 59
  • 96
  • 2
    `mktime()` may fail if the local timezone may have a different utc offset at different times (many do) -- you could use `pytz` module to get access to the tz database in a portable deteministic way. Also, local time may be ambiguous (50% chances of an error) -- you need some additional info to disambiguate e.g., often (not always) [dates in a log file are monotonous](http://stackoverflow.com/a/26221183/4279). See [How can I subtract a day from a python date?](http://stackoverflow.com/q/441147/4279) that may have to deal with similar issues. – jfs May 09 '15 at 10:34
9

For all coming along and searching for an implementation:

The above posts are related to datetime.timedelta, which is sadly not having properties for hours and seconds. So far it was not mentioned, that there is a package, which is having these. You can find it here:

Example - Calculation:

>>> import timedelta

>>> td = timedelta.Timedelta(days=2, hours=2)

# init from datetime.timedelta
>>> td = timedelta.Timedelta(datetime1 - datetime2)

Example - Properties:

>>> td = timedelta.Timedelta(days=2, hours=2)
>>> td.total.seconds
180000
>>> td.total.minutes
3000
>>> td.total.hours
50
>>> td.total.days
2

I hope this could help someone...

SiL3NC3
  • 578
  • 4
  • 24
  • 2
    This would also get around the limitation of timedelta, in that it depends on C. Thus, it cannot handle even moderately large times. It can only deal with day counts that are less than 1 billion, and if you use a value larger than +2G, you get a type conversion error as the underlying type appears to be just a 32-bit signed integer. – jakobengblom2 Sep 09 '21 at 08:57
9

I don't understand

days, hours, minutes = td.days, td.seconds // 3600, td.seconds // 60 % 60

how about this

days, hours, minutes = td.days, td.seconds // 3600, td.seconds % 3600 / 60.0

You get minutes and seconds of a minute as a float.

campbell
  • 147
  • 1
  • 8
4

I used the following:

delta = timedelta()
totalMinute, second = divmod(delta.seconds, 60)
hour, minute = divmod(totalMinute, 60)
print(f"{hour}h{minute:02}m{second:02}s")
Mathieu CAROFF
  • 890
  • 11
  • 17
3

I found the easiest way is using str(timedelta). It will return a sting formatted like 3 days, 21:06:40.001000, and you can parse hours and minutes using simple string operations or regular expression.

3

Here is a little function I put together to do this right down to microseconds:

def tdToDict(td:datetime.timedelta) -> dict:
    def __t(t, n):
        if t < n: return (t, 0)
        v = t//n
        return (t -  (v * n), v)
    (s, h) = __t(td.seconds, 3600)
    (s, m) = __t(s, 60)    
    (micS, milS) = __t(td.microseconds, 1000)

    return {
         'days': td.days
        ,'hours': h
        ,'minutes': m
        ,'seconds': s
        ,'milliseconds': milS
        ,'microseconds': micS
    }

Here is a version that returns a tuple:

# usage: (_d, _h, _m, _s, _mils, _mics) = tdTuple(td)
def tdTuple(td:datetime.timedelta) -> tuple:
    def _t(t, n):
        if t < n: return (t, 0)
        v = t//n
        return (t -  (v * n), v)
    (s, h) = _t(td.seconds, 3600)
    (s, m) = _t(s, 60)    
    (mics, mils) = _t(td.microseconds, 1000)
    return (td.days, h, m, s, mics, mils)
Timothy C. Quinn
  • 2,756
  • 1
  • 30
  • 39
1

While pandas.Timedelta does not provide these attributes directly, it indeed provide a method called total_seconds, based on which days, hours, and minutes can be easily derived:

import pandas as pd
td = pd.Timedelta("2 days 12:30:00")
minutes = td.total_seconds()/60
hours = minutes/60
days = hours/ 24
print(minutes, hours, days)
Fei Yao
  • 1,172
  • 8
  • 9
0

This is another possible approach, though a bit wordier than those already mentioned. It maybe isn't the best approach for this scenario but it is handy to be able to obtain your time duration in a specific unit that isn't stored within the object (weeks, hours, minutes, milliseconds) and without having to remember or calculate conversion factors.

from datetime import timedelta
one_hour = timedelta(hours=1)
one_minute = timedelta(minutes=1)
print(one_hour/one_minute)  # Yields 60.0

I've got a timedelta. I want the days, hours and minutes from that - either as a tuple or a dictionary... I'm not fussed.

in_time_delta = timedelta(days=2, hours=18, minutes=30)
td_d = timedelta(days=1)
td_h = timedelta(hours=1)
td_m = timedelta(minutes=1)
dmh_list = [in_time_delta.days,
            (in_time_delta%td_d)//td_h,
            (in_time_delta%td_h)//td_m]

Which should assign [2, 18, 30] to dmh_list

jslatane
  • 190
  • 3
  • 14
0

If using pandas (at least version >1.0), the Timedelta class has a components attribute that returns a named tuple with all the fields nicely laid out.

e.g.

import pandas as pd
delta = pd.Timestamp("today") - pd.Timestamp("2022-03-01")
print(delta.components)
Adrian
  • 594
  • 5
  • 14
-5

timedeltas have a days and seconds attribute .. you can convert them yourself with ease.

Jochen Ritzel
  • 99,912
  • 29
  • 194
  • 188