Skip to content

Commit 75b97a7

Browse files
nmartensenjreback
authored andcommitted
BUG: Fix TimeFormatter behavior with fractional seconds
closes #18478 Author: Nis Martensen <[email protected]> Closes #18552 from nmartensen/time_formatter and squashes the following commits: c1ace38 [Nis Martensen] TimeFormatter: Docstring: add parameters and returns sections 9b8bde4 [Nis Martensen] test_datetimelike::test_time: update expected results 75ae4f1 [Nis Martensen] test_datetimelike::test_time*: make testing tick labels actually work 8157c35 [Nis Martensen] test_converter::test_time_formatter: comment, formatting d7fa51f [Nis Martensen] TimeFormatter: Add __call__ docstring 1a11153 [Nis Martensen] simplify e7d5b0d [Nis Martensen] Add whatsnew entry 91bf9b2 [Nis Martensen] Add some TimeFormatter test comparisons c86238c [Nis Martensen] TimeFormatter: Only show seconds if non-zero 9a78cd7 [Nis Martensen] TimeFormatter: use round() for microseconds 9efa68c [Nis Martensen] Fix TimeFormatter for fractional seconds
1 parent 7a1b0ee commit 75b97a7

File tree

4 files changed

+77
-20
lines changed

4 files changed

+77
-20
lines changed

doc/source/whatsnew/v0.22.0.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ Plotting
316316
^^^^^^^^
317317

318318
- :func: `DataFrame.plot` now raises a ``ValueError`` when the ``x`` or ``y`` argument is improperly formed (:issue:`18671`)
319+
- Bug in formatting tick labels with ``datetime.time()`` and fractional seconds (:issue:`18478`).
319320
-
320321
-
321322

pandas/plotting/_converter.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,19 +190,39 @@ def __init__(self, locs):
190190
self.locs = locs
191191

192192
def __call__(self, x, pos=0):
193-
fmt = '%H:%M:%S'
193+
"""
194+
Return the time of day as a formatted string.
195+
196+
Parameters
197+
----------
198+
x : float
199+
The time of day specified as seconds since 00:00 (midnight),
200+
with upto microsecond precision.
201+
pos
202+
Unused
203+
204+
Returns
205+
-------
206+
str
207+
A string in HH:MM:SS.mmmuuu format. Microseconds,
208+
milliseconds and seconds are only displayed if non-zero.
209+
"""
210+
fmt = '%H:%M:%S.%f'
194211
s = int(x)
195-
ms = int((x - s) * 1e3)
196-
us = int((x - s) * 1e6 - ms)
212+
msus = int(round((x - s) * 1e6))
213+
ms = msus // 1000
214+
us = msus % 1000
197215
m, s = divmod(s, 60)
198216
h, m = divmod(m, 60)
199217
_, h = divmod(h, 24)
200218
if us != 0:
201-
fmt += '.%6f'
219+
return pydt.time(h, m, s, msus).strftime(fmt)
202220
elif ms != 0:
203-
fmt += '.%3f'
221+
return pydt.time(h, m, s, msus).strftime(fmt)[:-3]
222+
elif s != 0:
223+
return pydt.time(h, m, s).strftime('%H:%M:%S')
204224

205-
return pydt.time(h, m, s, us).strftime(fmt)
225+
return pydt.time(h, m).strftime('%H:%M')
206226

207227

208228
# Period Conversion

pandas/tests/plotting/test_converter.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,28 @@ def test_conversion_outofbounds_datetime(self):
236236
assert rs == xp
237237

238238
def test_time_formatter(self):
239-
self.tc(90000)
239+
# issue 18478
240+
241+
# time2num(datetime.time.min)
242+
rs = self.tc(0)
243+
xp = '00:00'
244+
assert rs == xp
245+
246+
# time2num(datetime.time.max)
247+
rs = self.tc(86399.999999)
248+
xp = '23:59:59.999999'
249+
assert rs == xp
250+
251+
# some other times
252+
rs = self.tc(90000)
253+
xp = '01:00'
254+
assert rs == xp
255+
rs = self.tc(3723)
256+
xp = '01:02:03'
257+
assert rs == xp
258+
rs = self.tc(39723.2)
259+
xp = '11:02:03.200'
260+
assert rs == xp
240261

241262
def test_dateindex_conversion(self):
242263
decimals = 9

pandas/tests/plotting/test_datetimelike.py

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,32 +1032,40 @@ def test_time(self):
10321032
df = DataFrame({'a': np.random.randn(len(ts)),
10331033
'b': np.random.randn(len(ts))},
10341034
index=ts)
1035-
_, ax = self.plt.subplots()
1035+
fig, ax = self.plt.subplots()
10361036
df.plot(ax=ax)
10371037

10381038
# verify tick labels
1039+
fig.canvas.draw()
10391040
ticks = ax.get_xticks()
10401041
labels = ax.get_xticklabels()
10411042
for t, l in zip(ticks, labels):
10421043
m, s = divmod(int(t), 60)
10431044
h, m = divmod(m, 60)
1044-
xp = l.get_text()
1045-
if len(xp) > 0:
1046-
rs = time(h, m, s).strftime('%H:%M:%S')
1045+
rs = l.get_text()
1046+
if len(rs) > 0:
1047+
if s != 0:
1048+
xp = time(h, m, s).strftime('%H:%M:%S')
1049+
else:
1050+
xp = time(h, m, s).strftime('%H:%M')
10471051
assert xp == rs
10481052

10491053
# change xlim
10501054
ax.set_xlim('1:30', '5:00')
10511055

10521056
# check tick labels again
1057+
fig.canvas.draw()
10531058
ticks = ax.get_xticks()
10541059
labels = ax.get_xticklabels()
10551060
for t, l in zip(ticks, labels):
10561061
m, s = divmod(int(t), 60)
10571062
h, m = divmod(m, 60)
1058-
xp = l.get_text()
1059-
if len(xp) > 0:
1060-
rs = time(h, m, s).strftime('%H:%M:%S')
1063+
rs = l.get_text()
1064+
if len(rs) > 0:
1065+
if s != 0:
1066+
xp = time(h, m, s).strftime('%H:%M:%S')
1067+
else:
1068+
xp = time(h, m, s).strftime('%H:%M')
10611069
assert xp == rs
10621070

10631071
@pytest.mark.slow
@@ -1069,22 +1077,29 @@ def test_time_musec(self):
10691077
df = DataFrame({'a': np.random.randn(len(ts)),
10701078
'b': np.random.randn(len(ts))},
10711079
index=ts)
1072-
_, ax = self.plt.subplots()
1080+
fig, ax = self.plt.subplots()
10731081
ax = df.plot(ax=ax)
10741082

10751083
# verify tick labels
1084+
fig.canvas.draw()
10761085
ticks = ax.get_xticks()
10771086
labels = ax.get_xticklabels()
10781087
for t, l in zip(ticks, labels):
10791088
m, s = divmod(int(t), 60)
10801089

1081-
# TODO: unused?
1082-
# us = int((t - int(t)) * 1e6)
1090+
us = int(round((t - int(t)) * 1e6))
10831091

10841092
h, m = divmod(m, 60)
1085-
xp = l.get_text()
1086-
if len(xp) > 0:
1087-
rs = time(h, m, s).strftime('%H:%M:%S.%f')
1093+
rs = l.get_text()
1094+
if len(rs) > 0:
1095+
if (us % 1000) != 0:
1096+
xp = time(h, m, s, us).strftime('%H:%M:%S.%f')
1097+
elif (us // 1000) != 0:
1098+
xp = time(h, m, s, us).strftime('%H:%M:%S.%f')[:-3]
1099+
elif s != 0:
1100+
xp = time(h, m, s, us).strftime('%H:%M:%S')
1101+
else:
1102+
xp = time(h, m, s, us).strftime('%H:%M')
10881103
assert xp == rs
10891104

10901105
@pytest.mark.slow

0 commit comments

Comments
 (0)