How to Construct a Timedelta Object from a Simple String

How to construct a timedelta object from a simple string

For the first format (5hr34m56s), you should parse using regular expressions

Here is re-based solution:

import re
from datetime import timedelta

regex = re.compile(r'((?P<hours>\d+?)hr)?((?P<minutes>\d+?)m)?((?P<seconds>\d+?)s)?')

def parse_time(time_str):
parts = regex.match(time_str)
if not parts:
return
parts = parts.groupdict()
time_params = {}
for name, param in parts.items():
if param:
time_params[name] = int(param)
return timedelta(**time_params)

>>> from parse_time import parse_time
>>> parse_time('12hr')
datetime.timedelta(0, 43200)
>>> parse_time('12hr5m10s')
datetime.timedelta(0, 43510)
>>> parse_time('12hr10s')
datetime.timedelta(0, 43210)
>>> parse_time('10s')
datetime.timedelta(0, 10)
>>>

How to convert a timedelta to a string and back again

The module pytimeparse, which was inspired by How to construct a timedelta object from a simple string, seems to do the heavy lifting by returning the number of seconds. I just put a wrapper around it which returns a timedelta object with the same number of seconds:

#!/usr/bin/env python3.5
import datetime
import pytimeparse
import unittest

def reconstruct_timedelta(td_string):
seconds = pytimeparse.parse(td_string)
return datetime.timedelta(seconds=seconds)

class TestReconstruction(unittest.TestCase):
def test_reconstruct_timedelta_is_inverse_of_str(self):
td = datetime.timedelta(weeks=300, days=20, hours=3, minutes=4, milliseconds=254, microseconds=984)
td_reconstructed = reconstruct_timedelta(str(td))
self.assertTrue(td == td_reconstructed)

if __name__ == "__main__":
unittest.main()

As you can see from the test, the reconstructed timedelta object is the same as the original one, even when it is initialized with an arbitrary number if milliseconds and microseconds.

How to convert standard timedelta string to timedelta object

I cannot find a better way other than writing a parser myself. The code looks bulky but it is essentially parsing string into a dictionary which is useful not only to creating a timedelta object.

import re

def parse(s):
if 'day' in s:
m = re.match(r'(?P<days>[-\d]+) day[s]*, (?P<hours>\d+):(?P<minutes>\d+):(?P<seconds>\d[\.\d+]*)', s)
else:
m = re.match(r'(?P<hours>\d+):(?P<minutes>\d+):(?P<seconds>\d[\.\d+]*)', s)
return {key: float(val) for key, val in m.groupdict().iteritems()}

Test:

from datetime import timedelta

s1 = '1157 days, 9:46:39'
s2 = '12:00:01.824952'
s3 = '-1 day, 23:59:31.859767'
t1 = parse(s1)
t2 = parse(s2)
t3 = parse(s3)

timedelta(**t1) # datetime.timedelta(1157, 35199)
timedelta(**t2) # datetime.timedelta(0, 43201, 824952)
timedelta(**t3) # datetime.timedelta(-1, 86371, 859767)

Hope this can suit your purpose.

Turn a string back into a datetime timedelta

Use pd.to_timedelta

pd.to_timedelta(df.iloc[:, 0])

0 0 days 00:00:57.416000
1 0 days 00:00:12.036000
2 0 days 16:46:23.127000
3 49 days 00:09:30.813000
4 50 days 00:39:31.306000
5 55 days 12:39:32.269000
6 -1 days +22:03:05.256000
Name: 0, dtype: timedelta64[ns]

Formatting timedelta objects

But I was wondering if I can do it in a single line using any date time function like strftime.

As far as I can tell, there isn't a built-in method to timedelta that does that. If you're doing it often, you can create your own function, e.g.

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)

Usage:

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

If you want to use a string format closer to the one used by strftime we can employ 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)

Usage:

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

The totalSeconds value is shown as 13374 instead of 99774. I.e. it's ignoring the "day" value.

Note in the example above that you can use timedelta.days to get the "day" value.

Alternatively, from Python 2.7 onwards, timedelta has a total_seconds() method which return the total number of seconds contained in the duration.

Python timedelta object - strfdelta and deltafstr functions for conversion of timedelta 🠞 string 🠞 timedelta

After searching for such functions, and not being able to find one that converts back and forth, I wrote the following two functions and include them in a script. This is compatible with Python v2.6.6, which doesn't support some newer features such as timedelta.total_seconds():

#!/usr/bin/python

import re
import sys
import datetime

# String from Date/Time Delta:
# Takes a datetime.timedelta object, and converts the internal values
# to a dd:HH:mm:ss:ffffff string, prefixed with "-" if the delta is
# negative
def strfdelta(tdelta):

# Handle Negative time deltas
negativeSymbol = ""
if tdelta < datetime.timedelta(0):
negativeSymbol = "-"

# Convert days to seconds, as individual components could
# possibly both be negative
tdSeconds = (tdelta.seconds) + (tdelta.days * 86400)

# Capture +/- state of seconds for later user with milliseonds calculation
secsNegMultiplier = 1
if tdSeconds < 0:
secsNegMultiplier = -1

# Extract minutes from seconds
tdMinutes, tdSeconds = divmod(abs(tdSeconds), 60)

# Extract hours from minutes
tdHours, tdMinutes = divmod(tdMinutes, 60)
# Extract days from hours
tdDays, tdHours = divmod(tdHours, 24)

# Convert seconds to microseconds, as individual components
# could possibly both be negative
tdMicroseconds = (tdelta.microseconds) + (tdSeconds * 1000000 * secsNegMultiplier)

# Get seconds and microsecond components
tdSeconds, tdMicroseconds = divmod( abs(tdMicroseconds), 1000000)

return "{negSymbol}{days}:{hours:02d}:{minutes:02d}:{seconds:02d}:{microseconds:06d}".format(
negSymbol=negativeSymbol,
days=tdDays,
hours=tdHours,
minutes=tdMinutes,
seconds=tdSeconds,
microseconds=tdMicroseconds)

# Date/Time delta from string
# Example: -1:23:32:59:020030 (negative sign optional)
def deltafstr(stringDelta):

# Regular expression to capture status change events, with groups for date/time,
# instrument ID and state
regex = re.compile("^(-?)(\d{1,6}):([01]?\d|2[0-3]):([0-5][0-9]):([0-5][0-9]):(\d{6})$",re.UNICODE)
matchObj = regex.search(stringDelta)

# If this line doesn't match, return None
if(matchObj is None):
return None;

# Debug - Capture date-time from regular expression
# for g in range(0, 7):
# print "Grp {grp}: ".format(grp=g) + str(matchObj.group(g))

# Get Seconds multiplier (-ve sign at start)
secsNegMultiplier = 1
if matchObj.group(1):
secsNegMultiplier = -1

# Get time components
tdDays = int(matchObj.group(2)) * secsNegMultiplier
tdHours = int(matchObj.group(3)) * secsNegMultiplier
tdMinutes = int(matchObj.group(4)) * secsNegMultiplier
tdSeconds = int(matchObj.group(5)) * secsNegMultiplier
tdMicroseconds = int(matchObj.group(6)) * secsNegMultiplier

# Prepare return timedelta
retTimedelta = datetime.timedelta(
days=tdDays,
hours=tdHours,
minutes=tdMinutes,
seconds=tdSeconds,
microseconds=tdMicroseconds)

return retTimedelta;

Here is some code that tests going back and forward between the two formats. The constructor arguments for the timedelta object may be changed to test different scenarios:

# Testing (change the constructor for timedelta to test other cases)
firstDelta = datetime.timedelta(seconds=-1,microseconds=999999, days=-1)
print "--------"
print firstDelta
firstDeltaStr = strfdelta(firstDelta)
print "--------"
print firstDeltaStr;
secondDelta = deltafstr(firstDeltaStr)
print "--------"
print secondDelta
secondDeltaStr = strfdelta(secondDelta)
print "--------"
print secondDelta
print "--------"

Convert pandas freq string to timedelta

In many cases, you can use the to_timedelta function for this. It will convert a string to a timedelta:

In [9]: pd.to_timedelta('30min')
Out[9]: Timedelta('0 days 00:30:00')

In [10]: pd.to_timedelta('2S')
Out[10]: Timedelta('0 days 00:00:02')

However, it seems that pd.to_timedelta('30T') does not work, but this can maybe be regarded as a missing feature.

But, for this one it does work if you first convert it to a frequency object and then supply it to to_timedelta:

In [19]: from pandas.tseries.frequencies import to_offset

In [20]: pd.to_timedelta(to_offset('30T'))
Out[20]: Timedelta('0 days 00:30:00')

If you start from a freqstr from a DatetimeIndex, the second approach will always work. But, you can also use freq instead of freqstr, which returns a frequency object directly:

In [34]: dtidx = pd.date_range('2012-01-01', periods=5, freq='30min')

In [35]: pd.to_timedelta(dtidx.freq)
Out[35]: Timedelta('0 days 00:30:00')

Best way to convert time string `1'29.30` to time format

You can use pandas.to_datetime with a custom format '%M\'%S.%f"', then keep only the time part (without the date part)

import pandas as pd

data = ['1\'29.30"', '1\'29.36"', '1\'29.54"', '1\'29.93"',
'1\'30.62"', '1\'30.80"', '1\'30.83"']

df = pd.DataFrame(data, columns=['time'])
df['time'] = pd.to_datetime(df['time'], format='%M\'%S.%f"').dt.time


Related Topics



Leave a reply



Submit