Skip to content

Commit 2541d96

Browse files
committed
[JS] Make the date parser less strict
and display the expected date formt as a tooltip.
1 parent 5653458 commit 2541d96

6 files changed

Lines changed: 81 additions & 47 deletions

File tree

src/core/annotation.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import {
6161
parseAppearanceStream,
6262
parseDefaultAppearance,
6363
} from "./default_appearance.js";
64+
import { DateFormats, TimeFormats } from "../shared/scripting_utils.js";
6465
import { Dict, isName, isRefsEqual, Name, Ref, RefSet } from "./primitives.js";
6566
import { Stream, StringStream } from "./stream.js";
6667
import { BaseStream } from "./base_stream.js";
@@ -2780,6 +2781,25 @@ class TextWidgetAnnotation extends WidgetAnnotation {
27802781
!this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) &&
27812782
this.data.maxLen !== 0;
27822783
this.data.doNotScroll = this.hasFieldFlag(AnnotationFieldFlag.DONOTSCROLL);
2784+
2785+
// Check if we have a date or time.
2786+
const {
2787+
data: { actions },
2788+
} = this;
2789+
for (const keystrokeAction of actions?.Keystroke || []) {
2790+
const m = keystrokeAction
2791+
.trim()
2792+
.match(/^AF(Date|Time)_Keystroke(?:Ex)?\(['"]?([^'"]+)['"]?\);$/);
2793+
if (m) {
2794+
let format = m[2];
2795+
const num = parseInt(format, 10);
2796+
if (!isNaN(num) && Math.floor(Math.log10(num)) + 1 === m[2].length) {
2797+
format = (m[1] === "Date" ? DateFormats : TimeFormats)[num] ?? format;
2798+
}
2799+
this.data[m[1] === "Date" ? "dateFormat" : "timeFormat"] = format;
2800+
break;
2801+
}
2802+
}
27832803
}
27842804

27852805
get hasTextContent() {

src/display/annotation_layer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,6 +1293,10 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
12931293
element.disabled = this.data.readOnly;
12941294
element.name = this.data.fieldName;
12951295
element.tabIndex = 0;
1296+
const format = this.data.dateFormat || this.data.timeFormat;
1297+
if (format) {
1298+
element.title = format;
1299+
}
12961300

12971301
this._setRequired(element, this.data.required);
12981302

src/scripting_api/aform.js

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* limitations under the License.
1414
*/
1515

16+
import { DateFormats, TimeFormats } from "../shared/scripting_utils.js";
1617
import { GlobalConstants } from "./constants.js";
1718

1819
class AForm {
@@ -21,23 +22,6 @@ class AForm {
2122
this._app = app;
2223
this._util = util;
2324
this._color = color;
24-
this._dateFormats = [
25-
"m/d",
26-
"m/d/yy",
27-
"mm/dd/yy",
28-
"mm/yy",
29-
"d-mmm",
30-
"d-mmm-yy",
31-
"dd-mmm-yy",
32-
"yy-mm-dd",
33-
"mmm-yy",
34-
"mmmm-yy",
35-
"mmm d, yyyy",
36-
"mmmm d, yyyy",
37-
"m/d/yy h:MM tt",
38-
"m/d/yy HH:MM",
39-
];
40-
this._timeFormats = ["HH:MM", "h:MM tt", "HH:MM:ss", "h:MM:ss tt"];
4125

4226
// The e-mail address regex below originates from:
4327
// https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
@@ -52,17 +36,14 @@ class AForm {
5236
return event.target ? `[ ${event.target.name} ]` : "";
5337
}
5438

55-
_parseDate(cFormat, cDate, strict = false) {
39+
_parseDate(cFormat, cDate) {
5640
let date = null;
5741
try {
58-
date = this._util._scand(cFormat, cDate, strict);
42+
date = this._util._scand(cFormat, cDate, /* strict = */ false);
5943
} catch {}
6044
if (date) {
6145
return date;
6246
}
63-
if (strict) {
64-
return null;
65-
}
6647

6748
date = Date.parse(cDate);
6849
return isNaN(date) ? null : new Date(date);
@@ -277,9 +258,7 @@ class AForm {
277258
}
278259

279260
AFDate_Format(pdf) {
280-
if (pdf >= 0 && pdf < this._dateFormats.length) {
281-
this.AFDate_FormatEx(this._dateFormats[pdf]);
282-
}
261+
this.AFDate_FormatEx(DateFormats[pdf] ?? pdf);
283262
}
284263

285264
AFDate_KeystrokeEx(cFormat) {
@@ -293,7 +272,7 @@ class AForm {
293272
return;
294273
}
295274

296-
if (this._parseDate(cFormat, value, /* strict = */ true) === null) {
275+
if (this._parseDate(cFormat, value) === null) {
297276
const invalid = GlobalConstants.IDS_INVALID_DATE;
298277
const invalid2 = GlobalConstants.IDS_INVALID_DATE2;
299278
const err = `${invalid} ${this._mkTargetName(
@@ -305,8 +284,8 @@ class AForm {
305284
}
306285

307286
AFDate_Keystroke(pdf) {
308-
if (pdf >= 0 && pdf < this._dateFormats.length) {
309-
this.AFDate_KeystrokeEx(this._dateFormats[pdf]);
287+
if (pdf >= 0 && pdf < DateFormats.length) {
288+
this.AFDate_KeystrokeEx(DateFormats[pdf]);
310289
}
311290
}
312291

@@ -617,18 +596,16 @@ class AForm {
617596
}
618597

619598
AFTime_Format(pdf) {
620-
if (pdf >= 0 && pdf < this._timeFormats.length) {
621-
this.AFDate_FormatEx(this._timeFormats[pdf]);
622-
}
599+
this.AFDate_FormatEx(TimeFormats[pdf] ?? pdf);
623600
}
624601

625602
AFTime_KeystrokeEx(cFormat) {
626603
this.AFDate_KeystrokeEx(cFormat);
627604
}
628605

629606
AFTime_Keystroke(pdf) {
630-
if (pdf >= 0 && pdf < this._timeFormats.length) {
631-
this.AFDate_KeystrokeEx(this._timeFormats[pdf]);
607+
if (pdf >= 0 && pdf < TimeFormats.length) {
608+
this.AFDate_KeystrokeEx(TimeFormats[pdf]);
632609
}
633610
}
634611

src/scripting_api/util.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ class Util extends PDFObject {
227227
ddd: data => this._days[data.dayOfWeek].substring(0, 3),
228228
dd: data => data.day.toString().padStart(2, "0"),
229229
d: data => data.day.toString(),
230-
yyyy: data => data.year.toString(),
230+
yyyy: data => data.year.toString().padStart(4, "0"),
231231
yy: data => (data.year % 100).toString().padStart(2, "0"),
232232
HH: data => data.hours.toString().padStart(2, "0"),
233233
H: data => data.hours.toString(),

src/shared/scripting_utils.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,22 @@ class ColorConverters {
105105
}
106106
}
107107

108-
export { ColorConverters };
108+
const DateFormats = [
109+
"m/d",
110+
"m/d/yy",
111+
"mm/dd/yy",
112+
"mm/yy",
113+
"d-mmm",
114+
"d-mmm-yy",
115+
"dd-mmm-yy",
116+
"yy-mm-dd",
117+
"mmm-yy",
118+
"mmmm-yy",
119+
"mmm d, yyyy",
120+
"mmmm d, yyyy",
121+
"m/d/yy h:MM tt",
122+
"m/d/yy HH:MM",
123+
];
124+
const TimeFormats = ["HH:MM", "h:MM tt", "HH:MM:ss", "h:MM:ss tt"];
125+
126+
export { ColorConverters, DateFormats, TimeFormats };

test/unit/scripting_spec.js

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,8 +1065,8 @@ describe("Scripting", function () {
10651065
id: refId,
10661066
value: "",
10671067
actions: {
1068-
Format: [`AFDate_FormatEx("mmddyyyy");`],
1069-
Keystroke: [`AFDate_KeystrokeEx("mmddyyyy");`],
1068+
Format: [`AFDate_FormatEx("mm.dd.yyyy");`],
1069+
Keystroke: [`AFDate_KeystrokeEx("mm.dd.yyyy");`],
10701070
},
10711071
type: "text",
10721072
},
@@ -1080,48 +1080,63 @@ describe("Scripting", function () {
10801080
sandbox.createSandbox(data);
10811081
await sandbox.dispatchEventInSandbox({
10821082
id: refId,
1083-
value: "12062023",
1083+
value: "12.06.2023",
10841084
name: "Keystroke",
10851085
willCommit: true,
10861086
});
10871087
expect(send_queue.has(refId)).toEqual(true);
10881088
expect(send_queue.get(refId)).toEqual({
10891089
id: refId,
10901090
siblings: null,
1091-
value: "12062023",
1092-
formattedValue: "12062023",
1091+
value: "12.06.2023",
1092+
formattedValue: "12.06.2023",
10931093
});
10941094
send_queue.delete(refId);
10951095

10961096
await sandbox.dispatchEventInSandbox({
10971097
id: refId,
1098-
value: "1206202",
1098+
value: "12.06.202",
10991099
name: "Keystroke",
11001100
willCommit: true,
11011101
});
11021102
expect(send_queue.has(refId)).toEqual(true);
11031103
expect(send_queue.get(refId)).toEqual({
11041104
id: refId,
11051105
siblings: null,
1106-
value: "",
1107-
formattedValue: null,
1108-
selRange: [0, 0],
1106+
value: "12.06.202",
1107+
formattedValue: "12.06.0202",
1108+
});
1109+
send_queue.delete(refId);
1110+
1111+
sandbox.createSandbox(data);
1112+
await sandbox.dispatchEventInSandbox({
1113+
id: refId,
1114+
value: "02.06.2023",
1115+
name: "Keystroke",
1116+
willCommit: true,
1117+
});
1118+
expect(send_queue.has(refId)).toEqual(true);
1119+
expect(send_queue.get(refId)).toEqual({
1120+
id: refId,
1121+
siblings: null,
1122+
value: "02.06.2023",
1123+
formattedValue: "02.06.2023",
11091124
});
11101125
send_queue.delete(refId);
11111126

11121127
sandbox.createSandbox(data);
11131128
await sandbox.dispatchEventInSandbox({
11141129
id: refId,
1115-
value: "02062023",
1130+
value: "2.6.2023",
11161131
name: "Keystroke",
11171132
willCommit: true,
11181133
});
11191134
expect(send_queue.has(refId)).toEqual(true);
11201135
expect(send_queue.get(refId)).toEqual({
11211136
id: refId,
11221137
siblings: null,
1123-
value: "02062023",
1124-
formattedValue: "02062023",
1138+
value: "2.6.2023",
1139+
formattedValue: "02.06.2023",
11251140
});
11261141
send_queue.delete(refId);
11271142
});

0 commit comments

Comments
 (0)