fast_excel
fast_excel copied to clipboard
Fix timestamps with timezones
What problem does this address?
Using fast_excel
to write Time
or DateTime
to XLSX corrupts the result when the timezone is not +00:00
.
While exposing the issue, this was the result of converting various input values via FastExcel::WorksheetExt#write_value
:
Equivalent Check Input Result
√ DateTime +00:00 2017-03-03T03:03:03+00:00 2017-03-03T03:03:03+00:00
x DateTime +01:00 2017-01-01T01:01:01+01:00 2017-01-01T01:01:01+00:00
x DateTime CET 2017-01-01T01:01:01+01:00 2017-01-01T01:01:01+00:00
x DateTime CEST 2017-07-07T07:07:07+02:00 2017-07-07T07:07:07+00:00
√ Time +00:00 2017-03-03 03:03:03 +0000 2017-03-03T03:03:03+00:00
x Time +01:00 2017-01-01 01:01:01 +0100 2017-01-01T00:01:01+00:00
x Time +02:00 2017-07-07 07:07:07 +0200 2017-07-07T05:07:07+00:00
√ Date 2017-03-01 2017-03-01
Only the +00:00
offsets work as expected, which are the only use cases originally tested.
What have I done to solve this?
I've expanded the test cases to expose the problem and then applied a fix that ensures DateTime
and Time
use non-destructive calls to convert timestamps to be in +00:00
before writing them to XLSX.
I've tested it locally on Ruby 2.7.5. If older Rubies break, CI should show us.
Refactoring
Because of the (now) large number of test cases that duplicate the logic for writing a value to XLSX and reading it back, I've added a helper method to the test file to encapsulate this. The test cases are now much easier to read.
I think excel doesn't know about timezones, and internally it stores dates as number of days since 1900. And excel file is usually for people to see, so it should display local time.
When we write date or time, this library will call FastExcel.date_num(...)
https://github.com/Paxa/fast_excel/blob/788c94231405e80ce307ac29ce2c85d37f2a41fa/lib/fast_excel.rb#L92
And inside date_num
it will try to read zone offset of parameter or Time.zone
(for rails), if none then use UTC.
I think it's correct behaviour, if we call SpecialEvent.all()
and print it in logs or in HTML, the dates will be in local timezone (zone of application or Time.zone inside rails http request), same time also shown in excel file
If you want always UTC then do something like
worksheet.write_number(1, 1, FastExcel.date_num(value, 0), nil) # 0 = offset in seconds