Skip to content

Add support for writing fractional seconds#337

Merged
ofajardo merged 4 commits into
Roche:devfrom
belegdol:write_fractional_seconds
Jul 6, 2026
Merged

Add support for writing fractional seconds#337
ofajardo merged 4 commits into
Roche:devfrom
belegdol:write_fractional_seconds

Conversation

@belegdol

Copy link
Copy Markdown
Contributor

This is initial work towards implementing issue #336. The current status is:

  • pandas backend with pandas-3.0.3 passes tests
  • polars backend with pandas-3.0.3 fails the fractional seconds test because the datetime is still truncated to whole seconds. I need to find out why.
  • pandas-2.3.3 fails because it tries to use the vectorised function on the datetime object column which obviously fails. I am not sure why that is.

pandas with pandas-3.0.3 passes tests
Now "only" three out 100 test datetime values fail the fractional seconds xpt write test. All are beyond year 2250 and thus beyond the range in which microsecond precision is reliable using 64-bit float.
@ofajardo

ofajardo commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Regarding the problem with Polars, this fixes it for me (this is the git diff output on _readstat_writer.pyx, so - means I removed that line and + I added a line). Pandas tests also pass for pandas 3 (Have not checked the pandas 2 issue yet). Please give it a try.

import datetime
 #import calendar
 from datetime import timezone
-from decimal import Decimal
 #from datetime import timezone as _timezone
 #from libc.math cimport round, NAN

@@ -88,9 +87,11 @@ cdef object vectorized_convert_datetime_to_number(object df, dst_file_format fil
     df = df.with_columns(nw.nth(col_indxs).cast(nw.Int64))
     for col_indx in col_indxs:
         convfac = convfacs[pywriter_timeunits[col_indx]]
-        finfac = Decimal(mulfac) / Decimal(convfac)
+        total = nw.nth(col_indx) + offset_secs * convfac
+        whole = total // convfac
+        frac = total % convfac
         df = df.with_columns(nw.when(nw.nth(col_indx) != -9223372036854775808).then(
-            ((nw.nth(col_indx) + offset_secs * convfac) * finfac).cast(nw.Float64)))
+            (whole.cast(nw.Float64) * mulfac + frac.cast(nw.Float64) / convfac * mulfac)))
     return df

@ofajardo

ofajardo commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

and this one fixes the pandas 2.3.3 issue, both pandas and polars tests passing, also upgrading again to pandas 3 works: (again gitdiff on _readstat_writer.pyx)

@@ -87,9 +87,8 @@ cdef object vectorized_convert_datetime_to_number(object df, dst_file_format fil
     df = df.with_columns(nw.nth(col_indxs).cast(nw.Int64))
     for col_indx in col_indxs:
         convfac = convfacs[pywriter_timeunits[col_indx]]
-        total = nw.nth(col_indx) + offset_secs * convfac
-        whole = total // convfac
-        frac = total % convfac
+        whole = nw.nth(col_indx) // convfac + offset_secs
+        frac = nw.nth(col_indx) % convfac
         df = df.with_columns(nw.when(nw.nth(col_indx) != -9223372036854775808).then(
             (whole.cast(nw.Float64) * mulfac + frac.cast(nw.Float64) / convfac * mulfac)))
     return df

@belegdol

belegdol commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Nice! Now the tests pass with pandas-2.3.3, pandas-3.0.3 and polars. Would you like to commit the changes since it was your idea?

@ofajardo

ofajardo commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

to make it easy, if you have it in your fork and working, just go ahead and commit, or you can also add me as co-author:, something like that: https://gist.github.com/lisawolderiksen/f9747a3ae1e58e9daa7d176ab98f1bad
https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors

Co-authored-by: Otto Fajardo <otto.fajardob@gmail.com>
@belegdol belegdol marked this pull request as ready for review July 3, 2026 16:22
@belegdol belegdol changed the title Add initial support for writing fractional seconds Add support for writing fractional seconds Jul 3, 2026
@ofajardo

ofajardo commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Let me know once you think this is ready for merging

@belegdol

belegdol commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

I am not sure what else to test. I could try reading the generated xpt in sas and exporting it as csv to see that it matches the source csv.

@belegdol

belegdol commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

SAS seems to like the files too. I used the following code, once for pandas-3.x and once for polars:

%xpt2loc(FILESPEC='fractional_seconds.xpt');

proc datasets;
modify dataset;
	format date e8601da10. dtime e8601dt26.6 time e8601tm15.6;
run;

proc export data=dataset outfile="fractional_seconds.csv";
run;

The re-exported files match test_data/basic/fractional_seconds.csv exactly.
Unless you have ideas what else to test, I would say this is as ready for merging as it is going to get.

@ofajardo ofajardo merged commit b605fd1 into Roche:dev Jul 6, 2026
3 checks passed
@belegdol belegdol deleted the write_fractional_seconds branch July 6, 2026 09:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants