Skip to content
Closed
1 change: 1 addition & 0 deletions doc/source/whatsnew/v0.22.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ Plotting
^^^^^^^^

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

Expand Down
32 changes: 26 additions & 6 deletions pandas/plotting/_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,19 +190,39 @@ def __init__(self, locs):
self.locs = locs

def __call__(self, x, pos=0):
fmt = '%H:%M:%S'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a doc-string here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cam you add a Parameters sections

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and a Returns

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

"""
Return the time of day as a formatted string.

Parameters
----------
x : float
The time of day specified as seconds since 00:00 (midnight),
with upto microsecond precision.
pos
Unused

Returns
-------
str
A string in HH:MM:SS.mmmuuu format. Microseconds,
milliseconds and seconds are only displayed if non-zero.
"""
fmt = '%H:%M:%S.%f'
s = int(x)
ms = int((x - s) * 1e3)
us = int((x - s) * 1e6 - ms)
msus = int(round((x - s) * 1e6))
ms = msus // 1000
us = msus % 1000
m, s = divmod(s, 60)
h, m = divmod(m, 60)
_, h = divmod(h, 24)
if us != 0:
fmt += '.%6f'
return pydt.time(h, m, s, msus).strftime(fmt)
elif ms != 0:
fmt += '.%3f'
return pydt.time(h, m, s, msus).strftime(fmt)[:-3]
elif s != 0:
return pydt.time(h, m, s).strftime('%H:%M:%S')

return pydt.time(h, m, s, us).strftime(fmt)
return pydt.time(h, m).strftime('%H:%M')


# Period Conversion
Expand Down
23 changes: 22 additions & 1 deletion pandas/tests/plotting/test_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,28 @@ def test_conversion_outofbounds_datetime(self):
assert rs == xp

def test_time_formatter(self):
self.tc(90000)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add the issue number here as a comment

# issue 18478

# time2num(datetime.time.min)
rs = self.tc(0)
xp = '00:00'
assert rs == xp

# time2num(datetime.time.max)
rs = self.tc(86399.999999)
xp = '23:59:59.999999'
assert rs == xp

# some other times
rs = self.tc(90000)
xp = '01:00'
assert rs == xp
rs = self.tc(3723)
xp = '01:02:03'
assert rs == xp
rs = self.tc(39723.2)
xp = '11:02:03.200'
assert rs == xp

def test_dateindex_conversion(self):
decimals = 9
Expand Down
41 changes: 28 additions & 13 deletions pandas/tests/plotting/test_datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -1032,32 +1032,40 @@ def test_time(self):
df = DataFrame({'a': np.random.randn(len(ts)),
'b': np.random.randn(len(ts))},
index=ts)
_, ax = self.plt.subplots()
fig, ax = self.plt.subplots()
df.plot(ax=ax)

# verify tick labels
fig.canvas.draw()
ticks = ax.get_xticks()
labels = ax.get_xticklabels()
for t, l in zip(ticks, labels):
m, s = divmod(int(t), 60)
h, m = divmod(m, 60)
xp = l.get_text()
if len(xp) > 0:
rs = time(h, m, s).strftime('%H:%M:%S')
rs = l.get_text()
if len(rs) > 0:
if s != 0:
xp = time(h, m, s).strftime('%H:%M:%S')
else:
xp = time(h, m, s).strftime('%H:%M')
assert xp == rs

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

# check tick labels again
fig.canvas.draw()
ticks = ax.get_xticks()
labels = ax.get_xticklabels()
for t, l in zip(ticks, labels):
m, s = divmod(int(t), 60)
h, m = divmod(m, 60)
xp = l.get_text()
if len(xp) > 0:
rs = time(h, m, s).strftime('%H:%M:%S')
rs = l.get_text()
if len(rs) > 0:
if s != 0:
xp = time(h, m, s).strftime('%H:%M:%S')
else:
xp = time(h, m, s).strftime('%H:%M')
assert xp == rs

@pytest.mark.slow
Expand All @@ -1069,22 +1077,29 @@ def test_time_musec(self):
df = DataFrame({'a': np.random.randn(len(ts)),
'b': np.random.randn(len(ts))},
index=ts)
_, ax = self.plt.subplots()
fig, ax = self.plt.subplots()
ax = df.plot(ax=ax)

# verify tick labels
fig.canvas.draw()
ticks = ax.get_xticks()
labels = ax.get_xticklabels()
for t, l in zip(ticks, labels):
m, s = divmod(int(t), 60)

# TODO: unused?
# us = int((t - int(t)) * 1e6)
us = int(round((t - int(t)) * 1e6))

h, m = divmod(m, 60)
xp = l.get_text()
if len(xp) > 0:
rs = time(h, m, s).strftime('%H:%M:%S.%f')
rs = l.get_text()
if len(rs) > 0:
if (us % 1000) != 0:
xp = time(h, m, s, us).strftime('%H:%M:%S.%f')
elif (us // 1000) != 0:
xp = time(h, m, s, us).strftime('%H:%M:%S.%f')[:-3]
elif s != 0:
xp = time(h, m, s, us).strftime('%H:%M:%S')
else:
xp = time(h, m, s, us).strftime('%H:%M')
assert xp == rs

@pytest.mark.slow
Expand Down