PyPlot plot_date issue (year 3990) - Solved

Recently I installed Julia on a new computer and using code I knew that works, PyPlot displayed dates far in the future. I was plotting data from the previous week (August 2021) and plot_date was displaying the dates in the year 3990. Previously I would just convert the dates myself.

Dates.value.(timestamp)/1000/60/60/24

The reason for the change, as far as I can tell, is that matplotlib added an epoch setting to the rcParams (matplotlib settings). The matplotlib conversion function can handle the change but is very slow.

julia> x = collect(DateTime(2021,8,1):Millisecond(1):DateTime(2021,8,2));

julia> @time y = PyPlot.matplotlib.dates.date2num(x);
270.131231 seconds (87.19 M allocations: 1.978 GiB, 1.29% gc time, 0.12% compilation time)

julia> @time y = PyPlot.matplotlib.dates.date2num(x);
265.707832 seconds (86.40 M allocations: 1.931 GiB, 0.74% gc time)

My solution proved to be much faster.

function datetime2matplotlib(timestamp::AbstractVector{DateTime})
	epoch = DateTime(get(matplotlib.rcParams,"date.epoch","0000-01-01T00:00:00"),Dates.dateformat"yyyy-mm-ddTHH:MM:SS") # Check matplotlib epoch
	if epoch == DateTime(0,1,1,0,0,0)
		return Dates.value.(timestamp)/1000/60/60/24
	else
		return Dates.value.(timestamp .- epoch)/1000/60/60/24
	end
end

It correctly handles both new and old versions of matplotlib. I tried a version which converted a single value and then broadcasted it but it was also very slow.

julia> x = collect(DateTime(2021,8,1):Millisecond(1):DateTime(2021,8,2));

julia> @time z = datetime2matplotlib(x);
  3.003491 seconds (1.75 M allocations: 3.319 GiB, 12.45% gc time, 24.36% compilation time)

julia> @time z = datetime2matplotlib(x);
  1.988773 seconds (5.64 k allocations: 3.219 GiB, 5.11% gc time)

julia> @time z = datetime2matplotlib(x);
  2.094877 seconds (5.64 k allocations: 3.219 GiB, 9.69% gc time)

Old test setup:

julia> versioninfo()
Julia Version 1.6.0
Commit f9720dc2eb (2021-03-24 12:55 UTC)
Platform Info:
  OS: Linux (x86_64-pc-linux-gnu)
  CPU: Intel(R) Core(TM) i5-4570 CPU @ 3.20GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-11.0.1 (ORCJIT, haswell)

(@v1.6) pkg> status
  [d330b81b] PyPlot v2.9.0

julia> PyPlot.matplotlib.__version__
"3.2.2"

New test setup:

julia> versioninfo()
Julia Version 1.6.2
Commit 1b93d53fc4 (2021-07-14 15:36 UTC)
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-11.0.1 (ORCJIT, skylake)
Environment:
  JULIA_NUM_THREADS = 7

(@v1.6) pkg> status
  [d330b81b] PyPlot v2.9.0

julia> PyPlot.matplotlib.__version__
"3.4.2"

Other functions if needed:

function datetime2matplotlib(timestamp::DateTime)
	epoch = DateTime(get(matplotlib.rcParams,"date.epoch","0000-01-01T00:00:00"),Dates.dateformat"yyyy-mm-ddTHH:MM:SS") # Check matplotlib epoch
	if epoch == DateTime(0,1,1,0,0,0)
		return Dates.value(timestamp)/1000/60/60/24
	else
		return Dates.value(timestamp - epoch)/1000/60/60/24
	end
end

function matplotlib2datetime(timestamp::Float64)
	epoch = DateTime(get(matplotlib.rcParams,"date.epoch","0000-01-01T00:00:00"),Dates.dateformat"yyyy-mm-ddTHH:MM:SS") # Check matplotlib epoch
	if epoch == DateTime(0,1,1,0,0,0)
		return DateTime(1) - Day(1) + Millisecond(floor(timestamp*24*60*60*1000))
	else
		return Millisecond(round(timestamp*24*60*60*1000)) + epoch
	end
end

function matplotlib2datetime(timestamp::AbstractVector{Float64})
	epoch = DateTime(get(matplotlib.rcParams,"date.epoch","0000-01-01T00:00:00"),Dates.dateformat"yyyy-mm-ddTHH:MM:SS") # Check matplotlib epoch
	if epoch == DateTime(0,1,1,0,0,0)
		return DateTime(1) .- Day(1) .+ Millisecond.(floor.(timestamp .* 24*60*60*1000))
	else
		return Millisecond.(round.(timestamp .* 24*60*60*1000)) .+ epoch
	end
end