将timedelta格式化为字符串


275

我在格式化datetime.timedelta对象时遇到问题。

这是我要执行的操作:我有一个对象列表,并且该对象类的成员之一是timedelta对象,该对象显示事件的持续时间。我想以小时:分钟的格式显示该持续时间。

我尝试了多种方法来执行此操作,但遇到了困难。我当前的方法是为返回小时和分钟的对象添加方法。我可以将timedelta.seconds除以3600并四舍五入来获得小时数。我在获取剩余秒数并将其转换为分钟时遇到麻烦。

顺便说一句,我将Google AppEngine与Django模板结合使用进行演示。


44
如果timedelta具有与strftime()方法等效的方法,那就更好了。
JS。

@JS。好吧,如果您使用,则可以datetime.utcfromtimestamp()。请参阅下面的答案
sjngm 2015年

@JS。-100%同意。然后,与(即-对于人类!)相反,__str__of timedelta是相当不错的__repr__。例如:datetime.timedelta(minutes=6, seconds=41) * 2618 / 48givens datetime.timedelta(seconds=21871, microseconds=208333),但是str(datetime.timedelta(minutes=6, seconds=41) * 2618 / 48)gives '6:04:31.208333'可以阅读。
Tomasz Gandor

Answers:


213

您可以使用str()将timedelta转换为字符串。这是一个例子:

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

13
更像是调用timedelta作为参数的str()方法。
joeforker

12
您在那里不需要str调用,它将通过打印自动完成。
Zitrax

5
@Zitrax,但是如果要将增量与另一个字符串连接起来,则很有必要。例如print 'some thing ' + str(delta)
Plinio.Santos,2015年

12
当数据类型定义为“ datetime.timedelta”并且像这样使用它时,这是必要的,ConvertDuration=datetime.timedelta(milliseconds=int(254459))然后只需使用split即可使微秒失效。从0:03:43.765000我可以通过简单地运行TotalDuration=str(ConvertDuration).split('.', 2)[0]
DarkXDroid

16
这可能会将增量打印为字符串,但不会按照OP的要求对其进行格式化。
丹妮德2015年

187

如您所知,您可以通过访问.seconds属性从timedelta对象获取total_seconds 。

Python提供了内置函数divmod(),该函数允许:

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

或者您可以将模和减相结合,转换为小时和余数:

# 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

5
对于负时间差,您应该先评估符号,然后再评估abs(s)
mbarkhau,2011年

27
注意,您实际上可能希望'%d:%02d:%02d'在输出字符串中使用前导零。
ShinNoNoir

12
对于python 2.7和更高版本,请使用.total_seconds()方法
sk8asd123

27
.seconds如果差异可能为负或超过24小时(.seconds属性忽略.days),则不要使用。使用.total_seconds()或其类似物代替。
jfs 2015年

为了实现积极的差异,我会不时重新实现此功能。感谢您这次必须进行搜索:)
Wolf

59
>>> 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

如果我们简单地输入,将timedelta对象传递给str()函数将调用相同的格式代码print td。由于您不需要秒数,因此我们可以用冒号(3个部分)分割字符串,然后仅将前2个部分放回去。


感谢您的回答joeforker,但我不确定我是否理解您的回复。我通过datetime-datetime获得时间增量。我不知道时间。另外,您的示例似乎包含几秒钟,我将如何删除呢?
mawcs

1
不管在哪里获取timedelta对象,它都会设置相同的格式。
joeforker

9
如果超过一天,它将在拆分/合并处理后格式化为“ 4天,8:00”。
joeforker

4
str(my_timedelta)对于负数,效果不佳
Catskul 2014年

2
如果> 24小时,也会显示天,例如'4 days,18:48:22.330000'。这里建议的许多方法都不是。
阿列克谢·马蒂安诺夫

47
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)

3
真的很好,我建议if seconds > period_seconds:改为if seconds >= period_seconds:
CBenni 2015年

1
这将为负时间增量返回空字符串,不确定是否要这样做吗?
德克(Dirk)

31

我个人使用该humanize库:

>>> 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'

当然,它并不能完全给您为您的答案(的确是,str(timeA - timeB)但是我发现,一旦您超过几个小时,显示就会迅速变得不可读。humanize它支持更大的值易于阅读,并且位置很好。

contrib.humanize显然,它是受Django 模块启发的,因此,由于您使用的是Django,您应该使用它。


2
不错,+ 1虽然:humanize.naturaldelta(pd.Timedelta('-10sec'))-> '10秒':S ...
ntg

2
以及它 10秒增量。:)您想要的时间naturaltime就是您想要使用的。
anarcat '16

28

他已经有一个timedelta对象,所以为什么不使用其内置方法total_seconds()将其转换为秒,然后使用divmod()获得小时和分钟呢?

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)

无论时间增量是偶数天还是数年,这都有效。


23

这是一个通用函数,用于将timedelta对象或常规数字(以秒或分钟等形式)转换为格式正确的字符串。我对一个重复的问题做了mpounsett的出色回答,使其更加灵活,可读性更高,并增加了文档。

您会发现,到目前为止,这是最灵活的答案,因为它可以使您:

  1. 即时自定义字符串格式,而不是对其进行硬编码。
  2. 留出一定的时间间隔没有问题(请参见下面的示例)。

功能:

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)

演示:

>>> 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

非常好,干净的代码!我想知道如何扩展该代码以处理格式化程序中的最后小数秒...
kfmfe04

17

我知道这是一个古老的已回答问题,但我datetime.utcfromtimestamp()为此使用了。它需要秒数并返回datetime可以像其他格式一样设置的datetime

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

只要您停留在合法的时间范围内,它就应该起作用,即,当小时数小于等于23时,它不会返回1234:35。


2
您应该print timedelta(seconds=end - begin)改用。
jfs 2015年

@JFSebastian然后,您必须用前导零手动填充小时。
sjngm 2015年

我对问题的填充一无所知。.zfill(8)如果需要,请使用。
jfs 2015年

1
需要.total_seconds()调用:>>> datetime.utcfromtimestamp((t2-t1).total_seconds())。strftime(“%H:%M:%S”)<<< >>> '00:00: 05'
卡格尼

2
我非常喜欢这种解决方案,它可以让您控制格式。请注意,您也可以像这样直接使用str格式程序:“ {0:%H}:{0:%M}”。format(duration)
脚趾

15

发问者想要比典型的更好的格式:

  >>> 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'

因此,实际上有两种格式,一种格式的天数为0,而被忽略,另一种格式的文本为“ n days,h:m:s”。但是,秒可能会有分数,并且打印输出中没有前导零,所以列很乱。

如果您喜欢,这是我的日常工作:

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

这将以dd:hh:mm:ss格式返回输出:

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

我确实考虑过要增加几年,但这留给读者练习,因为输出在1年以上是安全的:

>>> str(datetime.timedelta(seconds=99999999))
'1157 days, 9:46:39'

15

我会在这里认真考虑Occam的Razor方法:

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

这将返回不带微秒的字符串

如果要重新生成datetime.timedelta对象,请执行以下操作:

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

2年过去了,我喜欢这种语言!


5
这不包括工作日
Waschbaer

13

我的datetime.timedelta物品超过一天。因此,这是另一个问题。以上所有讨论都假设不到一天。A timedelta实际上是天,秒和微秒的元组。上面的讨论应该使用td.seconds像joe一样,但是如果您有工作日,则它不包括在seconds值中。

我得到了2个日期时间与打印天数和小时之间的时间跨度。

span = currentdt - previousdt
print '%d,%d\n' % (span.days,span.seconds/3600)

这是面向未来的解决方案。
吉宾


7

遵循上面Joe的示例值,因此,我将使用模数算术运算符:

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

注意,Python中的整数除法默认舍入;如果要更明确,请适当使用math.floor()或math.ceil()。


timedelta已经知道如何格式化自己,就像在'print some_timedelta'中一样。
joeforker

是的,但是它不能接受任意格式的字符串,这就是Michael的要求。虽然现在我考虑到它,但3600 Division Mod仍以小时为单位进行假设,这会在leap秒时引起问题。
UltraNurd

是的,但是他不想使用任意格式的字符串,他几乎想要的是默认行为。
joeforker

2
别忘了// //在Python 3000中进行截断
运算

3
+1,但是为什么不编辑答案以使用//?我也建议您使用td.total_seconds()而不是.seconds使其间隔> 1天。
2014年

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

你写这个吗?您测试了多少?
Thomas Ahle 2013年

我在名为datahaven.net的项目中使用此代码。它工作正常。看到错误了吗?
Veselin Penev 2013年

如果您可以提供一些信息,并且提供如此繁琐的代码,那总是很高兴的:)像一个示例,说明如何工作,可能有优点也可能有缺点。
Thomas Ahle 2013年

1
哦。当然!。为您添加了一个示例。:-)
Veselin Penev

超级:)还要注意的是,timedelta对象具有字段dayssecondsmicroseconds通过文档。
Thomas Ahle 2014年

4

我在工作中加班计算的输出也有类似的问题。该值应始终以HH:MM显示,即使该值大于一天并且该值可能会变为负数。我合并了一些所示的解决方案,也许其他人认为此解决方案很有用。我意识到,如果timedelta值为负,则大多数使用divmod方法显示的解决方案都无法立即使用:

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到HH:MM字符串:

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'

4
import datetime
hours = datetime.timedelta(hours=16, minutes=30)
print((datetime.datetime(1,1,1) + hours).strftime('%H:%M'))

2
在3.7中,我得到AttributeError:'datetime.timedelta'对象没有属性'strftime'–
qubodup

4

直接解决此问题的模板过滤器。内置函数int()从不舍入。F字符串(即f'')需要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

3
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)

这将产生:

>>> from datetime import timedelta
>>> localize_timedelta(timedelta(days=3660, minutes=500))
'10 years 10 days 8 hours 20 minutes'

村子距离最近的一个在我看来,在长度和条件是什么它涵盖
帝赐

1

请检查此功能-它会将timedelta对象转换为字符串'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)

1

一支班轮。由于timedelta不提供datetime的strftime,因此请将timedelta带回datetime,然后使用stftime。

这不仅可以实现OP要求的格式Hours:Minutes,现在,如果您的需求更改为其他表示形式,则可以利用datetime的strftime的完整格式化功能。

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

这也解决了将时间增量格式化为H:MM:SS而不是HH:MM:SS的麻烦,这导致了我遇到这个问题,并且分享了我的解决方案。


0

如果您已经有一个timedelta obj,则只需将该obj转换为字符串即可。删除字符串的最后3个字符并打印。这将截断秒数部分,并以小时:分钟的格式打印其余部分。

t = str(timedeltaobj) 

print t[:-3]

-3不起作用,请更好地使用print t.split('.')[0]
EvgenyKolyakov

0

如果您恰好IPython在包装中(应该如此),则它具有(到目前为止,无论如何)持续时间非常长的格式化程序(以秒为单位)。那是在各个地方使用的,例如%%time细胞魔术。我喜欢它能短期产生的格式:

>>> 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

-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)

因此对于:

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

返回:

'0:03:47'

-10

感谢大家的帮助。我采纳了您的许多想法并将它们组合在一起,让我知道您的想法。

我向此类添加了两个方法:

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

在我的django中,我使用了这个(sum是对象,它在字典中):

<td>{{ sum.0 }}</td>    
<td>{{ sum.1.hours|stringformat:"d" }}:{{ sum.1.minutes|stringformat:"#02.0d" }}</td>

有点长 我建议:def hhmm(self):return':'。join(str(td).split(':')[:2])<td> {{sum.1.hhmm}} </ td>
joeforker 09年

1
只需使用docs.python.org/release/2.5.2/lib/datetime-timedelta.html的“两个日期的区别”示例中所示的divmod()
汤姆
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.