1

Trying out the sample code for an XY plot in pygal involving datetime or date, any dates prior to 1970 cause this traceback:

Traceback (most recent call last):
  File "C:/Users/***/dt_test.py", line 30, in <module>
    datetimeline.render()
  File "C:\Python\Python36\lib\site-packages\pygal\graph\public.py", line 52, 
in render
    self.setup(**kwargs)
  File "C:\Python\Python36\lib\site-packages\pygal\graph\base.py", line 217, 
in setup
self._draw()
  File "C:\Python\Python36\lib\site-packages\pygal\graph\graph.py", line 924, 
in _draw
    self._compute_x_labels()
  File "C:\Python\Python36\lib\site-packages\pygal\graph\dual.py", line 61, 
in _compute_x_labels
    self._x_labels = list(zip(map(self._x_format, x_pos), x_pos))
  File "C:\Python\Python36\lib\site-packages\pygal\graph\time.py", line 103, 
in datetime_to_str
    dt = datetime.utcfromtimestamp(x)
OSError: [Errno 22] Invalid argument

Does anyone else get this behavior? (I'm using PyCharm.) lPerhaps the 'millennium' is returning an unexpected negative number?

(Edit) I used the code under "date", running in PyCharm:

from datetime import datetime
datetimeline = pygal.DateTimeLine(
    x_label_rotation=35, truncate_label=-1,
    x_value_formatter=lambda dt: dt.strftime('%d, %b %Y at %I:%M:%S %p'))
datetimeline.add("Serie", [
    (datetime(2013, 1, 2, 12, 0), 300),
    (datetime(2013, 1, 12, 14, 30, 45), 412),
    (datetime(2013, 2, 2, 6), 823),
    (datetime(2013, 2, 22, 9, 45), 672)
])
datetimeline.render()

... when I change the '2013' to '1969,' I get the Traceback shown above.

Patrick Dennis
  • 323
  • 1
  • 7

1 Answers1

1

This is caused by limitations in the underlying C functions that python uses for some date and time handling. These functions are implemented differently on different platforms, which is why it doesn't affect everybody (I had to borrow a Windows box to replicate the error).

The docs for datetime.utcfromtimestamp mention these limitations:

This may raise OverflowError, if the timestamp is out of the range of values supported by the platform C gmtime() function, and OSError on gmtime() failure. It’s common for this to be restricted to years in 1970 through 2038.

Fortunately the same docs suggest a workaround, and, as the function in the DateTimeLine class that contains the troublesome line is so short we can easily create our own class that inherits from DateTimeLine and over-rides the function.

import pygal
from datetime import datetime, timedelta, timezone

class MyDateTimeLine(pygal.DateTimeLine):

    @property
    def _x_format(self):
        def datetime_to_str(x):
            dt = datetime(1970, 1, 1, tzinfo=timezone.utc) + timedelta(seconds=x)
            return self.x_value_formatter(dt)
        return datetime_to_str

This is pretty much just copied from the source code for DateTimeLine. The only change is to the line assigning a value to dt.

We can now use this class as a drop-in replacement for DateTimeLine to plot dates in 2013 or 1969:

formatter = lambda dt: dt.strftime("%d, %b %Y at %I:%M:%S %p")

chart = MyDateTimeLine(x_label_rotation=35, truncate_label=-1,
                       x_value_formatter=formatter, width=640, height=300)
chart.add("2013", [(datetime(2013, 1, 2, 12, 0), 300),
                   (datetime(2013, 1, 12, 14, 30, 45), 412),
                   (datetime(2013, 2, 2, 6), 823),
                   (datetime(2013, 2, 22, 9, 45), 672)])
chart.render_to_png("chart2013.png")

chart = MyDateTimeLine(x_label_rotation=35, truncate_label=-1,
                       x_value_formatter=formatter, width=640, height=300)
chart.add("1969", [(datetime(1969, 1, 2, 12, 0), 300),
                   (datetime(1969, 1, 12, 14, 30, 45), 412),
                   (datetime(1969, 2, 2, 6), 823),
                   (datetime(1969, 2, 22, 9, 45), 672)])
chart.render_to_png("chart1969.png")

Plotting dates in 2013 Plotting dates in 1969

mostlyoxygen
  • 981
  • 5
  • 14