You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[$175] CSV import parses two-digit-year dates as year 2–25 AD #102517
When you import transactions from a CSV or spreadsheet, dates with a two-digit year now get the wrong year. Some also swap the month and day. The app shows no error, and the wrong date is saved on the expense.
CSV value
Before
Now
01/20/24
2024-01-20
0024-01-20
11/2/25
2025-11-02
0025-11-02
11-02-25
2025-11-02
0011-02-25
Nov 2, 25
2025-11-02
0025-11-02
2 Nov 25
2025-11-02
0025-11-02
m/d/yy is Excel's default short-date format on US locales, so many bank and card exports use it.
The yyyy token in date-fns matches one to four digits, not exactly four. So 01/20/24 matches MM/dd/yyyy as year 24. 11-02-25 matches the first entry, yyyy-MM-dd, as year 11, month 02, day 25. Before that PR, these strings went to new Date() first, which maps two-digit years to 19xx or 20xx.
The full analysis, including a suggested fix, is in this comment.
Steps to reproduce
Go to Account > Wallet > Import transactions.
Upload a CSV whose date column uses a two-digit year, for example 01/20/24.
Finish the column mapping and import.
Open an imported expense.
Expected: The date is 2024-01-20. Actual: The date is 0024-01-20.
Suggested fix
Handle bare yyyy-MM-dd strings explicitly by building the date in local time. Keep the original order for everything else: new Date() first, then the format loop. Add a unit test such as expect(parseCSVDate('01/20/24')).toBe('2024-01-20').
This is a regression from PR 100612, which reached production on 2026-09-24. Its 7-day regression period is still running, so the author of that PR is assigned.
changed the title [-]CSV import parses two-digit-year dates as year 2–25 AD[/-][+][$175] CSV import parses two-digit-year dates as year 2–25 AD[/+]on Sep 29, 2026
It restores the original parsing order, new Date() first and then the date-fns formats, so 01/20/24 parses as 2024-01-20 again. Bare yyyy-MM-dd values are now built in local time, which keeps the time zone fix from the earlier PR.
What changed and how it was checked
src/libs/CSVDateUtils.ts: adds parseISODateOnly. It accepts months 01–12 and days 01–31, the same ranges new Date() accepts. So 2026-02-30 still rolls over to 2026-03-02, and 2026-13-01 and 2026-01-00 still return null. It also runs on the first 10 characters of longer values.
tests/unit/CSVDateUtils.test.ts: adds tests for every two-digit-year example in this issue, ISO rollover and rejection, and the 10-character fallback.
117 tests across the 7 related suites pass, including CSVDateUtils, ImportTransactions, and the CompanyCards CSV import tests. ESLint, typecheck, spellcheck, and oxfmt pass too.
Not covered: the Jest suite runs with TZ=utc, so no automated test checks a zone west of UTC. I also didn't run an end-to-end import, because the web automation can't open the native file picker.
Known gap: 20/01/24 (day first, two-digit year) still parses as year 24. This already happened before the earlier PR, so this PR doesn't change it.
In this checkout, the new Date call sits at the top of the function. The issue links the pre-fix lines at 41d52ca and says the two blocks were swapped there, so I could not confirm the swap from this checkout alone.
What I did confirm is the shape of the code. The format list starts with yyyy-MM-dd, MM/dd/yyyy, and dd/MM/yyyy:
Leave the Excel serial branch as it is. Serials are plain digits, so new Date('45000') is either invalid or a far-off year. Keep the /^\d+$/ check ahead of the generic native parse if Read XLSX date cells as dates and parse Excel serial numbers correctly #100612 relies on it for serials such as 45000, because new Date with 45000 can parse as year 45000 in V8. Check that case in the test.
2025-11-02 returns 2025-11-02, with no timezone shift.
An Excel serial such as 45000 still returns its serial date.
Alternatives
Require exactly four digits by adding a regex guard to the four-digit formats and adding yy variants. This is more code, and it changes which ambiguous strings win.
Swap yyyy for y. This does not help because y is just as permissive.
I would not take either option. Handling ISO strings explicitly and restoring the old order is the smallest change and puts back the behavior users had before the regression.
Activity
melvin-bot commentedon Sep 29, 2026
Current assignee @rojiphil is eligible for the External assigner, not assigning anyone new.
[-]CSV import parses two-digit-year dates as year 2–25 AD[/-][+][$175] CSV import parses two-digit-year dates as year 2–25 AD[/+]melvin-bot commentedon Sep 29, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022104858883737977987
dominictb commentedon Sep 29, 2026
Assigned @rojiphil since he authored #100612 which is from #100317
melvin-bot commentedon Oct 2, 2026
@rojiphil Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
melvin-bot commentedon Oct 3, 2026
@rojiphil Whoops! This issue is 2 days overdue. Let's get this updated quick!
rojiphil commentedon Oct 4, 2026
@MelvinBot Implement to create a draft PR with the suggested fix as mentioned here
MelvinBot commentedon Oct 4, 2026
🤖 Draft PR created: Fix CSV import parsing two-digit-year dates as year 2-25 AD
Please mark it as "Ready for review" when it is ready for review.
It restores the original parsing order,
new Date()first and then thedate-fnsformats, so01/20/24parses as2024-01-20again. Bareyyyy-MM-ddvalues are now built in local time, which keeps the time zone fix from the earlier PR.What changed and how it was checked
src/libs/CSVDateUtils.ts: addsparseISODateOnly. It accepts months 01–12 and days 01–31, the same rangesnew Date()accepts. So2026-02-30still rolls over to2026-03-02, and2026-13-01and2026-01-00still returnnull. It also runs on the first 10 characters of longer values.tests/unit/CSVDateUtils.test.ts: adds tests for every two-digit-year example in this issue, ISO rollover and rejection, and the 10-character fallback.CSVDateUtils,ImportTransactions, and theCompanyCardsCSV import tests. ESLint, typecheck, spellcheck, and oxfmt pass too.TZ=utc, so no automated test checks a zone west of UTC. I also didn't run an end-to-end import, because the web automation can't open the native file picker.20/01/24(day first, two-digit year) still parses as year 24. This already happened before the earlier PR, so this PR doesn't change it.yuvrajangadsingh commentedon Oct 7, 2026
Proposal
Re-state problem
After PR #100612, CSV import turns two-digit-year dates into the wrong year:
There is no error, and the wrong date is saved on the expense.
Root cause
#100612 put the date-fns format loop ahead of native
new Date()inparseCSVDate:App/src/libs/CSVDateUtils.ts
Lines 26 to 45 in fe288f3
In this checkout, the new Date call sits at the top of the function. The issue links the pre-fix lines at 41d52ca and says the two blocks were swapped there, so I could not confirm the swap from this checkout alone.
What I did confirm is the shape of the code. The format list starts with yyyy-MM-dd, MM/dd/yyyy, and dd/MM/yyyy:
App/src/libs/CSVDateUtils.ts
Lines 7 to 21 in fe288f3
In date-fns, yyyy accepts one to four digits and does not require four. Therefore:
Whenever the loop runs before new Date, these strings never reach the native parser, which maps two-digit years to 19xx or 20xx.
Changes to make
This is a bug, so I would keep the fix small and limited to
src/libs/CSVDateUtils.ts.Handle a bare yyyy-MM-dd string on its own, before anything else.
/^(\d{4})-(\d{2})-(\d{2})$/and build the date withnew Date(y, m - 1, d)in local time.new Date('2025-11-02')can produce. That is why yyyy-MM-dd no longer needs to be the first format.Restore the original order for everything else: new Date first, then the CSV_DATE_FORMATS loop. Apply the same order in the shortInput fallback. This brings back the two-digit-year behavior from before Read XLSX date cells as dates and parse Excel serial numbers correctly #100612.
Leave the Excel serial branch as it is. Serials are plain digits, so
new Date('45000')is either invalid or a far-off year. Keep the/^\d+$/check ahead of the generic native parse if Read XLSX date cells as dates and parse Excel serial numbers correctly #100612 relies on it for serials such as 45000, because new Date with 45000 can parse as year 45000 in V8. Check that case in the test.Tests, in
tests/unit/CSVDateUtilsTest.ts(add the file if it does not exist):expect(parseCSVDate('01/20/24')).toBe('2024-01-20')Alternatives
I would not take either option. Handling ISO strings explicitly and restoring the old order is the smallest change and puts back the behavior users had before the regression.
melvin-bot commentedon Oct 7, 2026
@rojiphil Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
melvin-bot commentedon Oct 9, 2026
@rojiphil Eep! 4 days overdue now. Issues have feelings too...
rojiphil commentedon Oct 12, 2026
Closing this issue as the issue is already addressed in another PR at #103201
102517-resolved.mp4