web-dev-qa-db-ja.com

timedeltaオブジェクトのフォーマット

2つの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')

動作しません。

これを行う1つの方法は、秒に変換してから、必要なフォーマットを取得するためにdivmodingすることです。次のように:

totalSeconds = Turnaround_TimeObj.seconds
hours, remainder = divmod(totalSeconds, 3600)
minutes, seconds = divmod(remainder, 60)
print '%s:%s:%s' % (hours, minutes, seconds)

しかし、strftimeのような日付時刻関数を使用して1行で実行できるかどうか疑問に思っていました。

編集:実際に秒に変換することもできません。時間の差分「1日、3:42:54」を秒に変換すると、

totalSeconds = Turnaround_TimeObj.seconds

totalSeconds値は99774ではなく13374として表示されます。つまり、「day」値を無視します。

30
Rishav Sharan

しかし、strftimeのような日付時刻関数を使用して1行でそれを行うことができるかどうか疑問に思っていました。

私が知る限り、それを行う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値は、99774ではなく13374として表示されます。 「日」の値を無視します。

上記の例では、timedelta.daysを使用して「day」値を取得できることに注意してください。

または、Python 2.7以降、timedeltaには total_seconds() メソッドがあり、期間に含まれる合計秒数を返します。

48
Shawn Chin

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

Python2.6以前では、total_secondsを自分で計算できました。

total_seconds = turnaround.seconds + turnaround.days*24*60*60

(マイクロ秒を含むより一般的な式については、上記のリンクを参照してください)。

15
unutbu

Shawn Chinの答えは良いものですが、問題の1つは、フォーマットの特定の要素をスキップすると(日や秒ではなく時間と分のみを含む2番目の例のように)、その時間が結果から消えることです。修正してその問題を修正し、実際にフォーマット文字列に表示されるキーワードのみを処理することにより、より正確な結果を得ることができます。

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.Templateの代わりにstring.Formatterに適用し、さらに改善することができます。

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

Shawn Chinanswer -後続の issue によって提起された 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
12
gumption

より使いやすいrelativedeltaオブジェクトを持つdateutilモジュールを使用できます。

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)

7
dabhaid
def to_time(seconds):
  delta = datetime.timedelta(seconds=seconds)
  return str(delta.days) + 'd ' + (datetime.datetime.utcfromtimestamp(0) + delta).strftime('%H:%M')
3
Daniel F

上記の答えは、negativetimedelta値(UTCの「左」のタイムゾーンの pytz によって生成されるなど)を処理しないようです。 )。 ドキュメントごと のように、timedeltaオブジェクトは正規化され、負のtimedeltaは負のdayインスタンス属性によって表されます。ドキュメントから:

負の値の正規化は、最初は驚くかもしれません。

そしてそれ

Timedeltaオブジェクトの文字列表現は、内部表現と同様に正規化されます。これは、負の時間差に対してやや異常な結果をもたらします。

例えば:

>>> td = timedelta(seconds=-30)
>>> str(td)
'-1 day, 23:59:30'
>>> repr(td)
'datetime.timedelta(-1, 86370)'

この例のタイムデルタを考えると、 ショーンチンの受け入れられた答えガンプションの答え の両方が'23:59:30'、および mpounsettの回答'-1:59:30'

負のタイムデルタと正のタイムデルタの両方をより読みやすい方法で出力するには、タイムゾーンオブジェクトの符号と絶対値を明示的に処理する必要があると思います。

def strfdelta(td, fmt):

    # Get the timedelta’s 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 timedelta’s 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'
2
Jens