Skip to content

Commit 6c11b91

Browse files
TomAugspurgerjreback
authored andcommitted
ENH: Timedelta isoformat
Author: Tom Augspurger <[email protected]> Closes pandas-dev#15136 from TomAugspurger/timedelta-isoformat and squashes the following commits: 9fda713 [Tom Augspurger] ENH: Timedelta isoformat
1 parent 14d3a98 commit 6c11b91

File tree

4 files changed

+106
-0
lines changed

4 files changed

+106
-0
lines changed

doc/source/timedeltas.rst

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

315329
TimedeltaIndex

doc/source/whatsnew/v0.20.0.txt

+3
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,14 @@ Other enhancements
133133
- The ``skiprows`` argument in ``pd.read_csv`` now accepts a callable function as a value (:issue:`10882`)
134134
- ``pd.DataFrame.plot`` now prints a title above each subplot if ``suplots=True`` and ``title`` is a list of strings (:issue:`14753`)
135135
- ``pd.Series.interpolate`` now supports timedelta as an index type with ``method='time'`` (:issue:`6424`)
136+
- ``Timedelta.isoformat`` method added for formatting Timedeltas as an `ISO 8601 duration`_. See the :ref:`Timedelta docs <timedeltas.isoformat>` (:issue:`15136`)
136137
- ``pandas.io.json.json_normalize()`` gained the option ``errors='ignore'|'raise'``; the default is ``errors='raise'`` which is backward compatible. (:issue:`14583`)
137138

138139
- ``.select_dtypes()`` now allows the string 'datetimetz' to generically select datetimes with tz (:issue:`14910`)
139140
- ``pd.merge_asof()`` gained the option ``direction='backward'|'forward'|'nearest'`` (:issue:`14887`)
140141

142+
.. _ISO 8601 duration: https://en.wikipedia.org/wiki/ISO_8601#Durations
143+
141144

142145
.. _whatsnew_0200.api_breaking:
143146

pandas/tseries/tests/test_timedeltas.py

+39
Original file line numberDiff line numberDiff line change
@@ -1347,6 +1347,45 @@ 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+
1384+
# don't strip every 0
1385+
result = Timedelta(minutes=1).isoformat()
1386+
expected = 'P0DT0H1M0S'
1387+
self.assertEqual(result, expected)
1388+
13501389
def test_constructor(self):
13511390
expected = TimedeltaIndex(['1 days', '1 days 00:00:05', '2 days',
13521391
'2 days 00:00:02', '0 days 00:00:03'])

pandas/tslib.pyx

+50
Original file line numberDiff line numberDiff line change
@@ -2966,6 +2966,56 @@ 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+
.. versionadded:: 0.20.0
2976+
2977+
Returns
2978+
-------
2979+
formatted : str
2980+
2981+
Notes
2982+
-----
2983+
The longest component is days, whose value may be larger than
2984+
365.
2985+
Every component is always included, even if its value is 0.
2986+
Pandas uses nanosecond precision, so up to 9 decimal places may
2987+
be included in the seconds component.
2988+
Trailing 0's are removed from the seconds component after the decimal.
2989+
We do not 0 pad components, so it's `...T5H...`, not `...T05H...`
2990+
2991+
Examples
2992+
--------
2993+
>>> td = pd.Timedelta(days=6, minutes=50, seconds=3,
2994+
... milliseconds=10, microseconds=10, nanoseconds=12)
2995+
>>> td.isoformat()
2996+
'P6DT0H50M3.010010012S'
2997+
>>> pd.Timedelta(hours=1, seconds=10).isoformat()
2998+
'P0DT0H0M10S'
2999+
>>> pd.Timedelta(hours=1, seconds=10).isoformat()
3000+
'P0DT0H0M10S'
3001+
>>> pd.Timedelta(days=500.5).isoformat()
3002+
'P500DT12H0MS'
3003+
3004+
See Also
3005+
--------
3006+
Timestamp.isoformat
3007+
"""
3008+
components = self.components
3009+
seconds = '{}.{:0>3}{:0>3}{:0>3}'.format(components.seconds,
3010+
components.milliseconds,
3011+
components.microseconds,
3012+
components.nanoseconds)
3013+
# Trim unnecessary 0s, 1.000000000 -> 1
3014+
seconds = seconds.rstrip('0').rstrip('.')
3015+
tpl = 'P{td.days}DT{td.hours}H{td.minutes}M{seconds}S'.format(
3016+
td=components, seconds=seconds)
3017+
return tpl
3018+
29693019
def __setstate__(self, state):
29703020
(value) = state
29713021
self.value = value

0 commit comments

Comments
 (0)