[PER-10643] Add edtf date and time validation#1056
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1056 +/- ##
==========================================
+ Coverage 51.60% 51.98% +0.37%
==========================================
Files 354 354
Lines 12067 12127 +60
Branches 2172 2183 +11
==========================================
+ Hits 6227 6304 +77
+ Misses 5613 5599 -14
+ Partials 227 224 -3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
2a3db6f to
215f0c0
Compare
f3c2c87 to
374ded6
Compare
|
@cecilia-donnelly @slifty I have managed to rebase this one on the branch that's been checked out from too :D |
44786c5 to
6de3071
Compare
215f0c0 to
a71e024
Compare
6de3071 to
1c74479
Compare
|
Thanks for the branch management! For others reading this (QA), this needs a feature flag to be turned on: |
cecilia-donnelly
left a comment
There was a problem hiding this comment.
I love the way this is looking! The "sometime before"/"sometime after" language for an open ended range is very cool.
- I did see that if I enter February 29 in a non-leap-year, the error is "Day must be between 1 and 31" which is not exactly what we want to express there. I'm not sure what this should be, but maybe @omnignorant knows.
- If I leave both start and end dates empty and indicate that this is a range then I get "sometime before .." which isn't exactly right either
- If I have an open range (no start or end dates), then remove the "range" indicator, the new value is an empty string which gets an error from the backend. Maybe that's fine, but maybe we should instead send "Unknown"? QA/design question.
- the sidebar behaves slightly differently from the modal for invalid data. The sidebar allows you to click "save" with only a time filled (no date) while the modal blocks that path. The time-only value doesn't save, but in the sidebar the user gets a "We could not update" error in a toast instead of the nicer inline "A complete date is required when time is provided."
None of these are showstoppers, but if possible would be nice to fix!
a71e024 to
3876198
Compare
1c74479 to
fe7dd07
Compare
3876198 to
b6b99b9
Compare
|
When the date and time fields are undefined, we send null to the BE PATCH for the displayTime property and then we show the date field empty in the sidebar.
|
fe7dd07 to
57c3de4
Compare
slifty
left a comment
There was a problem hiding this comment.
I might be coming at this with the wrong design philosophy, so please forgive me for possibly questioning defined specification!
I think right now we have code that is silently attempting to make inferences about user intent which may or may not actually be correct.
I'd encourage we reconsider this approach, and instead simply convey "this isn't correct, you need to fix it by X" (or possibly offering a suggested correction).
All that said, if we do decide to keep attempted inference we should make sure that the logic for save / serialization matches the logic for formatting / rendering.
|
@aasandei-vsp and I talked about the null problem in More succinctly:
|
cecilia-donnelly
left a comment
There was a problem hiding this comment.
Officially requesting changes so this doesn't show up in my "review requested" list anymore. The specific change requested is to remove the fallback in the file list behind the feature flag.
I have created a commit specifically for this situation, making it easier to review. |
There are indeed a lot of scenarios and dealing with each one of them proved to be slightly complex. Your suggestions are great and correct, but at some point it was decided that we need to silently make some decisions for the user, as not everyone is savvy when it comes to edtf dates. I'll be addressing the rest of the comments one by one. |
57c3de4 to
67edbff
Compare
cecilia-donnelly
left a comment
There was a problem hiding this comment.
The code makes sense but there are a couple things I found while testing it out:
- Clear date doesn't work because somewhere we're sending an empty string instead of explicit null
- I can't enter a month value like "3" -- the sidebar simply doesn't show anything if I try. "03" works, but I should be able to enter a plain "3" (or any other single digit) and have it be padded with a zero afterward
- It looks like the same is happening for hours
- It lets me enter a value like "2026-XX-10" but then I can't set it to "uncertain" or "approximate." That might be an EDTF restriction, do you know?
I would go back through and do some testing again -- I know this has been waiting a while so maybe there are some odd interactions?
Also I see that you put the keyboard interactions back, disregard my inline comments! Thank you!
| } | ||
|
|
||
| if (message.includes('complete date is required')) { | ||
| return 'A complete date is required when time is provided.'; |
| [class.disabled]="disabled" | ||
| (click)="toggleDatepicker()" | ||
| (keydown.enter)="toggleDatepicker()" | ||
| (keydown.space)="toggleDatepicker()" |
There was a problem hiding this comment.
It's true that at some point in the past Claude removed them for some reason and I didn't notice and I'm glad you did! Now it's all in there, thank you for having such a thorough look!
| [class.disabled]="disabled" | ||
| (click)="toggleTimepicker()" | ||
| (keydown.enter)="toggleTimepicker()" | ||
| (keydown.space)="toggleTimepicker()" |
There was a problem hiding this comment.
Same question - why remove this? (Maybe this isn't acting as a button anymore?)
There was a problem hiding this comment.
It's true that at some point in the past Claude removed them for some reason and I didn't notice and I'm glad you did! Now it's all in there, thank you for having such a thorough look!
Clear date+only one digit Unspecified digits+qualifiers The library's grammar treats "unspecified digits" (X) and "qualification" ( ~ / ? / % ) as separate, non-combinable features, so it throws a syntax error on anything like 2026-XX-10~ — regardless of where the qualifier is placed. That error was being caught and surfaced as the generic "date is not valid" message, which is why you could enter 2026-XX-10 but not mark it approximate/uncertain. I verified the backend happily accepts and stores these strings (2026-XX-10~ and friends), so the restriction was purely FE. The fix works around the library by stripping the trailing qualifier before parsing/validating, then reattaching the flags to the model ourselves — so 2026-XX-10~ now serializes, saves, and parses back into the picker correctly, in both directions. One intentional boundary: a qualifier combined with a time (e.g. 2026-01-01T10:00:00~) is still rejected — the library doesn't support that combination either, and since an unspecified date and a time can't coexist anyway, we kept that blocked rather than silently allowing it. |
Left-pad single-digit month and day values with zero instead of
X-padding them, since a single 1-9 digit can only be a complete value
('5' -> '05', no longer '5X'). Require a complete date whenever a time
is provided and surface a dedicated friendly error message for that
case. Add a generic getSegmentError helper that the datepicker and
timepicker inputs use to build their inline field error messages
(invalid characters and out-of-range checks, skipped while the value
is still being typed).
Issue: PER-10643
Instead of silently rejecting invalid input, the year, month and day segments now emit whatever the user typed and surface a per-field error message below the input (invalid characters, out-of-range month or day). The day is re-validated when the month changes, and picking a date from the calendar clears all errors. The error styling lives in new shared input-error-state and input-error-message mixins. Issue: PER-10643
Instead of silently rejecting invalid input, the hours, minutes and seconds segments now emit whatever the user typed and surface a per-field error message below the input. The hour is re-validated when the AM/PM/24H format changes. Issue: PER-10643
The inline-error restructure dropped role="button", tabindex, and the keydown.enter/space handlers from the calendar and clock toggle buttons, leaving them mouse-only. Restore them and add keyboard-toggle tests. Issue: PER-10643
Persist null instead of '' when a date is cleared, and render an
explicitly-null displayTime as empty ("Click to add date"). Widen
displayTime to string | null and add specs.
Issue: PER-10643
With the flag on, displayTime is authoritative (the migration backfills it before the flip), so a missing value means the date was cleared and nothing is shown. With the flag off, the file list keeps its fallback so deployed code is unchanged. Issue: PER-10643
Move the picker's display formatting into EdtfService so the preview
and the serialized EDTF are built from the same padding helpers — a
single-digit month now reads as the month itself ("1" -> January /
1985-01) in both. A lone "0" month or day is rejected with a clear
range error instead of being silently serialized to "0X".
Issue: PER-10643
bad07a3 to
a3a7e5a
Compare
The edtf library rejects a qualifier (~/?/%) combined with unspecified (X) digits, so approximate/uncertain dates with a blank month or day (e.g. 2026-XX-10~) failed validation, even though the backend accepts them. Strip the qualifier before parsing/validating and reattach the flags ourselves so the combination round-trips both ways. Issue: PER-10643
a3a7e5a to
452da47
Compare
cecilia-donnelly
left a comment
There was a problem hiding this comment.
Thanks for this, @aasandei-vsp! I tested on dev this time to avoid any local environment oddities. I did find one edge case. When I put in "2021-2-29" (i.e., a leap day in a non-leap year) instead of erroring, the UI autocorrected to "2021-03-01" (no doubt a Date library fix). I think we want to give an error there. Final little things!

Manual test cases — Date/time input inline validation
Setup: log in, upload a file, click it.
EXPECTED: sidebar with a Date section. Open the edit modal via "More options" for each test.
Invalid characters
19abin the year, thenabin the hours.Out-of-range month
13in the month input.12.Out-of-range day
2026, month04, then type31in the day input.30.Out-of-range hours (AM/PM mode)
13in the hours input.10.Out-of-range minutes
60in the minutes input.59.Out-of-range seconds
60in the seconds input.59.No error while typing a single digit
5in the month or9in the day.2026-05, not2026-5X).Day re-validated when the month changes
2026/01/31(no error), then change the month to04.2025but not in2024.Hour re-validated when the format changes
15(no error), then toggle to AM.24.Calendar selection clears errors
Time without a complete date
1985, month05, no day, and a time of 10:30 AM.1985-05-20T10:30:00.