Форматирование объектов timedelta
У меня есть два объекта datetime
. Мне нужно вычислить timedelta
между ними, а затем показать вывод в определенном формате.
Alpha_TimeObj = datetime.datetime(int(AlphaTime.strftime('%Y')), int(AlphaTime.strftime('%m')), int(AlphaTime.strftime('%d')), int(AlphaTime.strftime('%H')), int(AlphaTime.strftime('%M')), int(AlphaTime.strftime('%S')))
Beta_TimeObj = datetime.datetime(int(BetaTime.strftime('%Y')), int(BetaTime.strftime('%m')), int(BetaTime.strftime('%d')), int(BetaTime.strftime('%H')), int(BetaTime.strftime('%M')), int(BetaTime.strftime('%S')))
Turnaround_TimeObj = Beta_TimeObj - Alpha_TimeObj
Примером этой дельты времени Turnaround_TimeObj
является "2 дня, 22:13:45". Я хочу отформатировать вывод, но не могу этого сделать.
print Turnaround_TimeObj.strftime('%H hrs %M mins %S secs')
не работает.
Я знаю, что один из способов сделать это - преобразовать его в секунды, а затем разделить, чтобы получить необходимое форматирование.
Как в:
totalSeconds = Turnaround_TimeObj.seconds
hours, remainder = divmod(totalSeconds, 3600)
minutes, seconds = divmod(remainder, 60)
print '%s:%s:%s' % (hours, minutes, seconds)
Но мне было интересно, смогу ли я сделать это в одной строке, используя любую функцию даты и времени, например strftime
.
На самом деле преобразование в секунды тоже не работает. Если я преобразую дельту времени "1 день, 3:42:54" в секунды, используя:
totalSeconds = Turnaround_TimeObj.seconds
Значение totalSeconds
отображается как 13374 вместо 99774. Т.е. оно игнорирует значение "день".
Ответы
Ответ 1
Но мне было интересно, смогу ли я сделать это в одной строке, используя любую функцию даты и времени, например strftime
.
Насколько я могу судить, в timedelta
нет встроенного метода, который бы это делал. Если вы делаете это часто, вы можете создать свою собственную функцию, например,
def strfdelta(tdelta, fmt):
d = {"days": tdelta.days}
d["hours"], rem = divmod(tdelta.seconds, 3600)
d["minutes"], d["seconds"] = divmod(rem, 60)
return fmt.format(**d)
Использование:
>>> print strfdelta(delta_obj, "{days} days {hours}:{minutes}:{seconds}")
1 days 20:18:12
>>> print strfdelta(delta_obj, "{hours} hours and {minutes} to go")
20 hours and 18 to go
Если вы хотите использовать строковый формат ближе к тому, который используется в strftime
, мы можем использовать string.Template
:
from string import Template
class DeltaTemplate(Template):
delimiter = "%"
def strfdelta(tdelta, fmt):
d = {"D": tdelta.days}
d["H"], rem = divmod(tdelta.seconds, 3600)
d["M"], d["S"] = divmod(rem, 60)
t = DeltaTemplate(fmt)
return t.substitute(**d)
Использование:
>>> print strfdelta(delta_obj, "%D days %H:%M:%S")
1 days 20:18:12
>>> print strfdelta(delta_obj, "%H hours and %M to go")
20 hours and 18 to go
Значение totalSeconds
отображается как 13374 вместо 99774. Т.е. это игнорирует значение "день".
Обратите внимание, что в приведенном выше примере вы можете использовать timedelta.days
для получения значения "day".
В качестве альтернативы, начиная с Python 2.7, timedelta имеет метод total_seconds(), который возвращает общее количество секунд, содержащихся в продолжительности.
Ответ 2
В Python 2.7 или новее вы можете использовать метод total_seconds :
import datetime as dt
turnaround = dt.timedelta(days = 1, hours = 3, minutes = 42, seconds = 54)
total_seconds = int(turnaround.total_seconds())
hours, remainder = divmod(total_seconds,60*60)
minutes, seconds = divmod(remainder,60)
print('{} hrs {} mins {} secs'.format(hours,minutes,seconds))
дает
27 hrs 42 mins 54 secs
В Python 2.6 или старше вы можете сами вычислить total_seconds
:
total_seconds = turnaround.seconds + turnaround.days*24*60*60
(более общую формулу, включая микросекунды, смотрите по ссылке выше).
Ответ 3
Ответ шона Чина хороший, но одна проблема с ним заключается в том, что если вы пропустите определенный элемент в своем формате (как во втором примере, в котором есть только часы и минуты, а не дни или секунды), то это время исчезнет. из вашего результата. Вы можете изменить его, чтобы устранить эту проблему и получить более точные результаты, обрабатывая только те ключевые слова, которые фактически отображаются в строке формата.
class DeltaTemplate(Template):
delimiter = '%'
def strfdelta(tdelta, fmt):
d = {}
l = {'D': 86400, 'H': 3600, 'M': 60, 'S': 1}
rem = int(tdelta.total_seconds())
for k in ( 'D', 'H', 'M', 'S' ):
if "%{}".format(k) in fmt:
d[k], rem = divmod(rem, l[k])
t = DeltaTemplate(fmt)
return t.substitute(**d)
Использование:
>>> print strfdelta(delta_obj, "%D days %H:%M:%S")
1 days 20:18:12
>>> print strfdelta(delta_obj, "%H hours and %M to go")
44 hours and 18 to go
Тем не менее, это негибко в отношении форматирования, поскольку вы не можете применить какие-либо строки преобразования и получить такие уродливые вещи, как это:
>>> delta_obj = timedelta(minutes=5, seconds=2)
>>> print strfdelta(delta_obj, "%H:%M:%S")
0:5:2
Однако вы можете воспользоваться тем же подходом и применить его к string.Formatter вместо string.Template и получить что-то намного лучше.
from string import Formatter
def strfdelta(tdelta, fmt):
f = Formatter()
d = {}
l = {'D': 86400, 'H': 3600, 'M': 60, 'S': 1}
k = map( lambda x: x[1], list(f.parse(fmt)))
rem = int(tdelta.total_seconds())
for i in ('D', 'H', 'M', 'S'):
if i in k and i in l.keys():
d[i], rem = divmod(rem, l[i])
return f.format(fmt, **d)
Использование:
>>> delta_obj = timedelta(days=1, hours=20, minutes=18, seconds=12)
>>> print strfdelta(delta_obj, "{D} days {H}:{M}:{S}")
1 days 20:18:12
>>> print strfdelta(delta_obj, "{H} hours and {M} to go")
44 hours and 18 to go
>>> delta_obj = timedelta(minutes=5, seconds=2)
>>> print strfdelta(delta_obj, "{H:02}h{M:02}m{S:02}s")
00h05m02s
>>> print strfdelta(delta_obj, "{H:02}:{M:02}:{S:02}")
00:05:02
Ответ 4
Небольшой вариант ответа Шона Чина, который также решает последующую проблему, поднятую mpouncett, дополняет часы, минуты и секунды начальными нулями, чтобы гарантировать, что все 3 элемента используют 2 места (что больше соответствует спецификации для этих полей в strftime):
from string import Template
class DeltaTemplate(Template):
delimiter = "%"
def strfdelta(tdelta, fmt):
d = {"D": tdelta.days}
hours, rem = divmod(tdelta.seconds, 3600)
minutes, seconds = divmod(rem, 60)
d["H"] = '{:02d}'.format(hours)
d["M"] = '{:02d}'.format(minutes)
d["S"] = '{:02d}'.format(seconds)
t = DeltaTemplate(fmt)
return t.substitute(**d)
Вот тест для некоторых примеров значений:
from datetime import timedelta
for seconds in [0, 1, 59, 60, 61, 3599, 3600, 3601]:
print strfdelta(timedelta(0, seconds), '%H:%M:%S')
И вот вывод:
00:00:00
00:00:01
00:00:59
00:01:00
00:01:01
00:59:59
01:00:00
01:00:01
Ответ 5
Вы можете использовать модуль dateutil
, который имеет более дружественный объект relativedelta
:
import dateutil
import datetime
alpha = datetime.datetime(2012, 1, 16, 6, 0)
beta = datetime.datetime(2012, 1, 18, 10, 42, 57, 230301)
delta = dateutil.relativedelta(beta, alpha)
Это дает вам дельту объекта, которая выглядит следующим образом:
>>> delta
relativedelta(days=+2, hours=+4, minutes=+42, seconds=+57, microseconds=+230301)
Затем вы можете сделать
print('turnaround %i hrs %i mins %i secs' % (delta.days * 24 + delta.hours, delta.minutes, delta.seconds))
Ответ 6
def to_time(seconds):
delta = datetime.timedelta(seconds=seconds)
return str(delta.days) + 'd ' + (datetime.datetime.utcfromtimestamp(0) + delta).strftime('%H:%M')
Ответ 7
Предыдущие ответы, по-видимому, не обрабатывают отрицательные значения timedelta (как, например, с помощью pytz) для часовых поясов, "оставшихся" от UTC). Как указано в документации, объекты timedelta нормализованы, а отрицательная timedelta представлена отрицательным атрибутом экземпляра day
. Из документации:
Обратите внимание, что нормализация отрицательных значений может сначала удивить.
и это
Строковые представления объектов timedelta нормализуются аналогично их внутреннему представлению. Это приводит к несколько необычным результатам для отрицательных timedeltas.
Например:
>>> td = timedelta(seconds=-30)
>>> str(td)
'-1 day, 23:59:30'
>>> repr(td)
'datetime.timedelta(-1, 86370)'
Принимая во внимание этот пример timedelta, оба Шона Чинса приняли ответ и ответ на вопрос возвращают '23:59:30'
, а mpounsetts отвечают '-1:59:30'
.
Я думаю, что для того, чтобы печатать как отрицательные, так и положительные временные интервалы более читабельным образом, нам нужно явно обработать знак и абсолютное значение объекта часового пояса:
def strfdelta(td, fmt):
# Get the timedeltas sign and absolute number of seconds.
sign = "-" if td.days < 0 else "+"
secs = abs(td).total_seconds()
# Break the seconds into more readable quantities.
days, rem = divmod(secs, 86400) # Seconds per day: 24 * 60 * 60
hours, rem = divmod(rem, 3600) # Seconds per hour: 60 * 60
mins, secs = divmod(rem, 60)
# Format (as per above answers) and return the result string.
t = DeltaTemplate(fmt)
return t.substitute(
s=sign,
D="{:d}".format(int(days)),
H="{:02d}".format(int(hours)),
M="{:02d}".format(int(mins)),
S="{:02d}".format(int(secs)),
)
Эта функция возвращает более читаемое строковое представление:
>>> strfdelta(td, "%s%H:%M:%S") # Note that %s refers to the timedeltas sign.
'-00:00:30'
>>> strfdelta(timedelta(days=-1), "%s%D %H:%M:%S")
'-1 00:00:00'
>>> strfdelta(timedelta(days=-1, minutes=5), "%s%D %H:%M:%S")
'-0 23:55:00'
>>> strfdelta(timedelta(days=-1, minutes=-5), "%s%D %H:%M:%S")
'-1 00:05:00'
... или в более практичном контексте часовых поясов:
>>> import pytz
>>> import datetime
>>> td = pytz.timezone("Canada/Newfoundland").utcoffset(datetime.datetime.now())
>>> td
datetime.timedelta(-1, 77400)
>>> strfdelta(td, fmt="%s%H:%M")
'-02:30'
>>> td = pytz.timezone("Australia/Eucla").utcoffset(datetime.datetime.now())
>>> td
datetime.timedelta(0, 31500)
>>> strfdelta(td, fmt="%s%H:%M")
'+08:45'