memories::match_filename_date only applies plausible_filename_date (year in 1995..=now+1) to the unix-timestamp pattern. The five wall-clock patterns (screenshot, screenshot_compact, dash, dash_compact, compact) build a NaiveDateTime and return it with no year check. Any filename with 8 digits, a separator, and 6 more digits gets accepted as a date, whatever the year.
Because the filename date class outranks EXIF (#116), the bogus date becomes the row's date of record.
Evidence (production, 2026-10-10)
Five rows have a capture year outside 1995–2027. All are Phone Backup videos named VID_<counter>_<HHMMSS>[_mmm] (looks like an app's sequence counter, not a date), all date_taken_tz_source = assumed_server:
The YYYY/MM/ parent folder of each matches its real capture month, so container metadata or fs time would give the right answer.
How it surfaced: the 2229 row was the only assumed_server row in the post-drain prod audit whose stored offset (−300) disagreed with Python's tz database for that instant (−240). The cause of that particular mismatch (far-future DST handling in chrono-tz vs. zoneinfo) is unverified, and moot once the date is fixed.
What to build
Apply the same year gate to the wall-clock patterns (check naive.year() before returning FilenameDate::Wall). On a miss, fall through to the next pattern / the rest of the waterfall, the same as the unix-timestamp branch does.
One gate function for both branches, so the bounds can't drift apart.
Re-resolve the existing affected rows. The drain only picks up date_utc IS NULL, so these already-converted rows need an explicit re-resolve (or a targeted reset), and the legacy date_taken for them should be revisited too.
Acceptance
match_filename_date("VID_22290524_080551_818.mp4") returns None (or a later, plausible pattern), with a unit test per wall-clock pattern for an out-of-range year
The filename audit utility (#117) no longer proposes these dates
After re-resolution, the 5 prod rows carry dates in their folder's year/month (exiftool container date or fs time)
## Problem
`memories::match_filename_date` only applies `plausible_filename_date` (year in `1995..=now+1`) to the **unix-timestamp** pattern. The five wall-clock patterns (`screenshot`, `screenshot_compact`, `dash`, `dash_compact`, `compact`) build a `NaiveDateTime` and return it with no year check. Any filename with 8 digits, a separator, and 6 more digits gets accepted as a date, whatever the year.
Because the filename date class outranks EXIF (#116), the bogus date becomes the row's date of record.
## Evidence (production, 2026-10-10)
Five rows have a capture year outside 1995–2027. All are Phone Backup videos named `VID_<counter>_<HHMMSS>[_mmm]` (looks like an app's sequence counter, not a date), all `date_taken_tz_source = assumed_server`:
| Year | Path |
|---|---|
| 2095 | `2016/07/VID_20950505_044049.mp4` |
| 2229 | `2017/07/VID_22290524_080551_818.mp4` |
| 8580 | `2017/11/VID_185800302_235056_842.mp4` (`screenshot_compact` matched `85800302_235056`) |
| 2044 | `2017/08/VID_20441022_013111_153.mp4` |
| 2049 | `2017/08/VID_20490313_205744_593.mp4` |
The `YYYY/MM/` parent folder of each matches its real capture month, so container metadata or fs time would give the right answer.
How it surfaced: the 2229 row was the only `assumed_server` row in the post-drain prod audit whose stored offset (−300) disagreed with Python's tz database for that instant (−240). The cause of that particular mismatch (far-future DST handling in chrono-tz vs. zoneinfo) is unverified, and moot once the date is fixed.
## What to build
- Apply the same year gate to the wall-clock patterns (check `naive.year()` before returning `FilenameDate::Wall`). On a miss, fall through to the next pattern / the rest of the waterfall, the same as the unix-timestamp branch does.
- One gate function for both branches, so the bounds can't drift apart.
- Re-resolve the existing affected rows. The drain only picks up `date_utc IS NULL`, so these already-converted rows need an explicit re-resolve (or a targeted reset), and the legacy `date_taken` for them should be revisited too.
## Acceptance
- [ ] `match_filename_date("VID_22290524_080551_818.mp4")` returns `None` (or a later, plausible pattern), with a unit test per wall-clock pattern for an out-of-range year
- [ ] The filename audit utility (#117) no longer proposes these dates
- [ ] After re-resolution, the 5 prod rows carry dates in their folder's year/month (exiftool container date or fs time)
Related: #116 (filename date class), #113.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Correction: 67 rows on prod, not 5. My first scan read /photos/exif with a date range ending around year 2255, so it missed every row dated further out. From an unfiltered dump (2026-10-10), these are all rows whose wall_clock year is outside 1995–2027:
library 2 (archive): 34
library 4 (Phone Backup): 33
years 267–9985; every one has date_taken_tz_source = assumed_server
Two filename shapes, both matched by screenshot_compact (YYYYMMDD[_-]HHMMSS):
33 VID_<counter>_<HHMMSS> videos, e.g. 1903/12/VID_121920715_134958_293.mp4 → 2192-07-15
34 Facebook downloads named <8 digits>_<id>_…, e.g. 10530519_10152700326093436_5479865808523667420_o.jpg → 1053-05-19 (10530519_101527 read as 1053-05-19 10:15:27)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
memories::match_filename_dateonly appliesplausible_filename_date(year in1995..=now+1) to the unix-timestamp pattern. The five wall-clock patterns (screenshot,screenshot_compact,dash,dash_compact,compact) build aNaiveDateTimeand return it with no year check. Any filename with 8 digits, a separator, and 6 more digits gets accepted as a date, whatever the year.Because the filename date class outranks EXIF (#116), the bogus date becomes the row's date of record.
Evidence (production, 2026-10-10)
Five rows have a capture year outside 1995–2027. All are Phone Backup videos named
VID_<counter>_<HHMMSS>[_mmm](looks like an app's sequence counter, not a date), alldate_taken_tz_source = assumed_server:2016/07/VID_20950505_044049.mp42017/07/VID_22290524_080551_818.mp42017/11/VID_185800302_235056_842.mp4(screenshot_compactmatched85800302_235056)2017/08/VID_20441022_013111_153.mp42017/08/VID_20490313_205744_593.mp4The
YYYY/MM/parent folder of each matches its real capture month, so container metadata or fs time would give the right answer.How it surfaced: the 2229 row was the only
assumed_serverrow in the post-drain prod audit whose stored offset (−300) disagreed with Python's tz database for that instant (−240). The cause of that particular mismatch (far-future DST handling in chrono-tz vs. zoneinfo) is unverified, and moot once the date is fixed.What to build
naive.year()before returningFilenameDate::Wall). On a miss, fall through to the next pattern / the rest of the waterfall, the same as the unix-timestamp branch does.date_utc IS NULL, so these already-converted rows need an explicit re-resolve (or a targeted reset), and the legacydate_takenfor them should be revisited too.Acceptance
match_filename_date("VID_22290524_080551_818.mp4")returnsNone(or a later, plausible pattern), with a unit test per wall-clock pattern for an out-of-range yearRelated: #116 (filename date class), #113.
🤖 Generated with Claude Code
Correction: 67 rows on prod, not 5. My first scan read
/photos/exifwith a date range ending around year 2255, so it missed every row dated further out. From an unfiltered dump (2026-10-10), these are all rows whosewall_clockyear is outside 1995–2027:date_taken_tz_source = assumed_serverTwo filename shapes, both matched by
screenshot_compact(YYYYMMDD[_-]HHMMSS):VID_<counter>_<HHMMSS>videos, e.g.1903/12/VID_121920715_134958_293.mp4→ 2192-07-15<8 digits>_<id>_…, e.g.10530519_10152700326093436_5479865808523667420_o.jpg→ 1053-05-19 (10530519_101527read as 1053-05-19 10:15:27)The year gate in the fix covers both. Full list:
67 rows
Oregon Crusaders 2014/OC14 Taffy Facebook/10530519_10152700326093436_5479865808523667420_o.jpgOregon Crusaders 2014/OC14 Taffy Facebook/10550106_10152700340603436_3250046659641924994_o.jpgOregon Crusaders 2014/OC14 Taffy Facebook/10550927_10152700362318436_1672926832434253065_n.jpgOregon Crusaders 2014/OC14 Taffy Facebook/10560526_10152700314898436_6264150702154094017_o.jpgOregon Crusaders 2014/OC14 Taffy Facebook/10580214_10152700335378436_5558696624760720358_n.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14 Prelims_files/10460624_10202578362324975_1067700837005629791_o.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14 Prelims_files/10471204_10202578431246698_760420543147129988_o.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14 Prelims_files/10530508_10202578372605232_6713470713824128510_o.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14 Prelims_files/10530522_10202578428126620_6852066874233908077_o.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14 Prelims_files/10580720_10202578455927315_8612701867203550491_o.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14 Prelims_files/10580830_10202578387965616_1297827824572926908_o.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14 Prelims_files/10580922_10202578371045193_2231403424437176102_o.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14 practice 8 6_files/10471204_10202578212641233_4340573659707815953_o.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14 practice 8 6_files/10520819_10202575681137947_6075784304107225357_o.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14 practice 8 6_files/10531227_10202578252322225_6485287216044517833_o.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14 practice 8 6_files/10580905_10202575687258100_1360774181272496115_o.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14_files/10150627_10202545744869559_916103811368534287_n.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14_files/10250204_10202558361944978_3623100149105833960_n.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14_files/10300305_10202545746309595_2622897866090891529_n.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14_files/10300305_10202557994655796_6066228824858451858_n.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14_files/10350428_10202545885633078_7405794514110724765_n.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14_files/10410612_10202558015656321_9031786313471104240_n.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14_files/10441019_10202557993975779_7146837669454293790_n.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14_files/10481005_10202557964415040_4943178209848477406_n.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14_files/10511201_10202545879752931_6814243381290013962_n.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14_files/10540412_10202545878672904_5770450943932492433_n.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14_files/10540817_10202545876912860_4362478334171100102_n.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14_files/10550820_10202545888193142_909847176937896079_n.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14_files/10551012_10202545831791732_1931544532953196079_n.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14_files/10570502_10202558001695972_6952786073970768131_n.jpgOregon Crusaders 2014/Oregon Crusaders 2014/David Kruse Photos - OC14/David Kruse-OC14_files/10590502_10202545880112940_2419709465924174738_n.jpgOregon Crusaders 2014/Oregon Crusaders 2014/July 1 - Aurora, OR/10470818_10154275243235162_3709987019130326233_7_1_2014.jpgOregon Crusaders 2014/Oregon Crusaders 2014/OC Instagram/10570129_1453550038243148_1870155583_a.jpgOregon Crusaders 2014/Oregon Crusaders 2014/OC Instagram/891457_272198722981017_1221264293_a.jpg1903/12/VID_111000513_045600_399.mp41903/12/VID_112610508_083230_684.mp41903/12/VID_112610510_071829_972.mp41903/12/VID_121920715_134958_293.mp41903/12/VID_128690224_095923_887.mp41903/12/VID_181071112_101639_621.mp41903/12/VID_245570918_065349_909.mp41903/12/VID_27420624_233241_796.mp41903/12/VID_302670327_110648_158.mp41903/12/VID_334371202_034827_125.mp41903/12/VID_334371202_164304_625.mp41903/12/VID_372440517_203122_156.mp41903/12/VID_42060928_104636_307.mp41903/12/VID_462400228_054900_354.mp41903/12/VID_55340410_012553_342.mp41903/12/VID_55670924_230759_801.mp41903/12/VID_55670927_044036_487.mp41903/12/VID_56460123_080213_506.mp41903/12/VID_699851203_194738_303.mp41903/12/VID_75610311_085844_982.mp42015/07/VID_28520508_235158.mp42015/09/VID_42930603_075439.mp42016/07/VID_20950505_044049.mp42017/01/VID_69080530_122844_305.mp42017/07/VID_22290524_080551_818.mp42017/08/VID_20441022_013111_153.mp42017/08/VID_20490313_205744_593.mp42017/08/VID_51461017_051005_798.mp42017/11/VID_185800302_235056_842.mp42017/11/VID_204150219_023828_195.mp42018/02/VID_117260731_050135_529.mp42018/02/VID_83740615_131840_968.mp42018/03/VID_317961211_064128_769.mp4