Skip to content

Commit 4605da1

Browse files
committed
ENH: Timedelta isoformat
1 parent 1f82f18 commit 4605da1

File tree

4 files changed

+109
-1
lines changed

4 files changed

+109
-1
lines changed

doc/source/timedeltas.rst

+10
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,16 @@ similarly to the ``Series``. These are the *displayed* values of the ``Timedelta
310310
td.dt.components
311311
td.dt.components.seconds
312312
313+
You can convert a ``Timedelta`` to an ISO 8601 Duration string with the
314+
``.isoformat`` method
315+
316+
.. ipython:: python
317+
pd.Timedelta(days=6, minutes=50, seconds=3,
318+
milliseconds=10, microseconds=10,
319+
nanoseconds=12).isoformat()
320+
321+
.. _ISO 8601 duration: https://en.wikipedia.org/wiki/ISO_8601#Durations
322+
313323
.. _timedeltas.index:
314324

315325
TimedeltaIndex

doc/source/whatsnew/v0.20.0.txt

+17-1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,22 @@ support for bz2 compression in the python 2 c-engine improved (:issue:`14874`).
9191
df = pd.read_table(url, compression='bz2') # explicitly specify compression
9292
df.head(2)
9393

94+
95+
.. _whatsnew_0200.enhancements.timedelta_isoformat:
96+
97+
Timedelta isoformat
98+
^^^^^^^^^^^^^^^^^^^
99+
100+
Added a Timedelta.isoformat method for formatting Timedeltas as an
101+
`ISO 8601 duration`_
102+
103+
.. _ISO 8601 duration: https://en.wikipedia.org/wiki/ISO_8601#Durations
104+
105+
.. ipython:: python
106+
td = pd.Timedelta(days=6, minutes=50, seconds=3,
107+
milliseconds=10, microseconds=10, nanoseconds=12)
108+
td.isoformat()
109+
94110
.. _whatsnew_0200.enhancements.other:
95111

96112
Other enhancements
@@ -369,4 +385,4 @@ Bug Fixes
369385

370386
- Bug in ``pd.read_csv()`` for the C engine where ``usecols`` were being indexed incorrectly with ``parse_dates`` (:issue:`14792`)
371387

372-
- Bug in ``Series.dt.round`` inconsistent behaviour on NAT's with different arguments (:issue:`14940`)
388+
- Bug in ``Series.dt.round`` inconsistent behaviour on NAT's with different arguments (:issue:`14940`)

pandas/tseries/tests/test_timedeltas.py

+34
Original file line numberDiff line numberDiff line change
@@ -1347,6 +1347,40 @@ def test_components(self):
13471347
self.assertFalse(result.iloc[0].isnull().all())
13481348
self.assertTrue(result.iloc[1].isnull().all())
13491349

1350+
def test_isoformat(self):
1351+
td = Timedelta(days=6, minutes=50, seconds=3,
1352+
milliseconds=10, microseconds=10, nanoseconds=12)
1353+
expected = 'P6DT0H50M3.010010012S'
1354+
result = td.isoformat()
1355+
self.assertEqual(result, expected)
1356+
1357+
td = Timedelta(days=4, hours=12, minutes=30, seconds=5)
1358+
result = td.isoformat()
1359+
expected = 'P4DT12H30M5S'
1360+
self.assertEqual(result, expected)
1361+
1362+
td = Timedelta(nanoseconds=123)
1363+
result = td.isoformat()
1364+
expected = 'P0DT0H0M0.000000123S'
1365+
self.assertEqual(result, expected)
1366+
1367+
# trim nano
1368+
td = Timedelta(microseconds=10)
1369+
result = td.isoformat()
1370+
expected = 'P0DT0H0M0.00001S'
1371+
self.assertEqual(result, expected)
1372+
1373+
# trim micro
1374+
td = Timedelta(milliseconds=1)
1375+
result = td.isoformat()
1376+
expected = 'P0DT0H0M0.001S'
1377+
self.assertEqual(result, expected)
1378+
1379+
# NaT
1380+
result = Timedelta('NaT').isoformat()
1381+
expected = 'NaT'
1382+
self.assertEqual(result, expected)
1383+
13501384
def test_constructor(self):
13511385
expected = TimedeltaIndex(['1 days', '1 days 00:00:05', '2 days',
13521386
'2 days 00:00:02', '0 days 00:00:03'])

pandas/tslib.pyx

+48
Original file line numberDiff line numberDiff line change
@@ -2966,6 +2966,54 @@ class Timedelta(_Timedelta):
29662966
"""
29672967
return 1e-9 *self.value
29682968

2969+
def isoformat(self):
2970+
"""
2971+
Format Timedelta as ISO 8601 Duration like
2972+
`P[n]Y[n]M[n]DT[n]H[n]M[n]S`, where the `[n]`s are replaced by the
2973+
values. See https://en.wikipedia.org/wiki/ISO_8601#Durations
2974+
2975+
Returns
2976+
-------
2977+
formatted : str
2978+
2979+
Notes
2980+
-----
2981+
The longest component is days, whose value may be larger than
2982+
365.
2983+
Every component is always included, even if its value is 0.
2984+
Pandas uses nanosecond precision, so up to 9 decimal places may
2985+
be included in the seconds component.
2986+
Trailing 0's are removed from the seconds component after the decimal.
2987+
We do not 0 pad components, so it's `...T5H...`, not `...T05H...`
2988+
2989+
Examples
2990+
--------
2991+
>>> td = pd.Timedelta(days=6, minutes=50, seconds=3,
2992+
... milliseconds=10, microseconds=10, nanoseconds=12)
2993+
>>> td.isoformat()
2994+
'P6DT0H50M3.010010012S'
2995+
>>> pd.Timedelta(hours=1, seconds=10).isoformat()
2996+
'P0DT0H0M10S'
2997+
>>> pd.Timedelta(hours=1, seconds=10).isoformat()
2998+
'P0DT0H0M10S'
2999+
>>> pd.Timedelta(days=500.5).isoformat()
3000+
'P500DT12H0MS'
3001+
3002+
See Also
3003+
--------
3004+
Timestamp.isoformat
3005+
"""
3006+
components = self.components
3007+
seconds = '{}.{:0>3}{:0>3}{:0>3}'.format(components.seconds,
3008+
components.milliseconds,
3009+
components.microseconds,
3010+
components.nanoseconds)
3011+
# Trim unnecessary 0s, 1.000000000 -> 1
3012+
seconds = seconds.rstrip('0').rstrip('.0')
3013+
tpl = 'P{td.days}DT{td.hours}H{td.minutes}M{seconds}S'.format(
3014+
td=components, seconds=seconds)
3015+
return tpl
3016+
29693017
def __setstate__(self, state):
29703018
(value) = state
29713019
self.value = value

0 commit comments

Comments
 (0)