Форматирование объектов 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'