A CSV file is written correctly, sent to somebody, opened in a spreadsheet, and saved again. Nothing in that sequence looks like an edit. We put twenty-six values through it and eleven of them came back changed — eight of them visibly, and three in a way a file comparison cannot see.
What arrived and what came back
The source file was written with a standard library, so it is RFC 4180 correct: commas, quotes and newlines inside fields are all properly enclosed. Whatever happens next is the spreadsheet's decision, not a malformed file.
value in the file value in the cell afterwards
00123 123
018956 18956
+6591234567 6591234567
1234567890123456789 1.23456789012346E+018
1E5 100000
+1 1
=1+1 2
0.12345678901234567 0.123456789012346
Every one of those is type detection doing exactly what it was built to do. An internal record id, a Singapore postal code and a phone number in E.164 are all read as quantities, and a leading zero or a leading plus carries no meaning for a quantity, so it goes.
Two of those deserve a closer look. The decimal 0.12345678901234567 was not flagged or rejected; it was rounded to fifteen significant figures and written back looking like an ordinary decimal. And =1+1 was not imported as text. It became a formula, which the spreadsheet evaluated.
The three that a comparison certifies as clean
Fifteen values came through untouched and eight are in the table above. That leaves three, and they are the ones worth knowing about:
6591234567 re-exports identically — stored as a number
4111111111111111 re-exports identically — stored as a number
2026-03-04 re-exports identically — stored as 46085
Export the sheet back to CSV and all three come out byte-for-byte as they went in. Diff the two files and they match. Open the workbook itself and the third one is not a date string at all; it is the integer 46085 wearing a date format, and the string was reconstructed on the way out.
The distinction between the stored integer and the displayed date string holds only while the next step is another CSV export. Read the workbook directly, hand it to a different tool, or change the cell format, and the value is 46085.
Where the digits stop being exact
The nineteen-digit identifier failed loudly, in scientific notation. To find the point where a large integer stops being exact, we walked a ladder of ones from twelve digits to twenty:
12-16 digits exact
17 digits 11111111111111100
18-20 digits 1.11111111111111E+017 and up
Sixteen digits are exact. Eighteen and above are obviously wrong, because scientific notation announces itself. Seventeen is the one to worry about: the value is corrupted, the last two digits are now zeros, and it still looks like an ordinary integer.
A sixteen-digit payment card reference survives. A seventeen-digit order number does not, and nothing about the cell says so.
The fix is on the import, and it is complete
Import the same file with every column set to Text and all twenty-six values are preserved exactly — including =1+1, which stays four characters instead of becoming a formula.
default import 15 of 26 intact
all columns Text 26 of 26 intact
Not a partial improvement, and not a heuristic. The problem is type detection, and turning type detection off removes it completely. The cost is that genuine numbers arrive as text and must be converted deliberately. The trade is a good one: you convert what you intended to convert, rather than what a guess chose for you.
In LibreOffice this is the Text Import dialogue that appears when you open a .csv: select all columns in the preview pane and set Column type to Text. In Excel it is the Text Import Wizard, or Data → From Text/CSV with each column typed as Text before loading.
How this was measured, and what it does not cover
Twenty-six values in a UTF-8 CSV written by Python's csv module, converted to a workbook by LibreOffice 26.2.4.2 running headless with default import options, then read two ways: exported back to CSV, and read directly out of the workbook's XML so the cell type and stored value are the tool's own record rather than a display string.
This was tested in LibreOffice, not Excel. The two disagree on some conversions, which is the reason to test the tool you actually have. SEPT1 and MARCH1 came back unchanged here — the gene-symbol autocorrect this problem is famous for is an Excel behaviour, and reading about it does not tell you what your own tool will do.
Locale decides several of these. 03/04/2026 stayed text because the running locale did not claim that shape. On a machine configured differently it would have become a date, and which date depends on the setting rather than on the file.
Only the values we chose were tested. Twenty-six is a set of known-awkward cases, not a survey. It shows the categories of failure to look for, not how many of your own rows are affected.
What a file does to a spreadsheet is the other half of this, and the export side turns out to have a problem that writing the file correctly does not solve.