@ciwergrp/nuxid 1.9.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/runtime/helper/date/format.d.ts +2 -29
- package/dist/runtime/helper/date/format.js +1 -29
- package/dist/runtime/helper/date/parse.js +1 -2
- package/dist/runtime/validator.d.ts +1 -1
- package/dist/runtime/validator.js +98 -8
- package/docs/.docs/form-validation/create-validation-rules.md +11 -0
- package/docs/.docs/form-validation/quick-start.md +14 -0
- package/docs/.docs/form-validation/rules.md +42 -4
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -1,33 +1,7 @@
|
|
|
1
1
|
import type { FormatDistanceOptions, FormatDistanceToNowOptions, FormatISOOptions, FormatOptions, FormatRFC3339Options } from 'date-fns';
|
|
2
2
|
import type { DateInput } from './input.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
readonly 'YYYY-MM-DD HH:mm': "yyyy-MM-dd HH:mm";
|
|
6
|
-
readonly 'YYYY-MM-DD HH:mm:ss': "yyyy-MM-dd HH:mm:ss";
|
|
7
|
-
readonly 'YYYY-MM-DD HH:mm:ss.SSS': "yyyy-MM-dd HH:mm:ss.SSS";
|
|
8
|
-
readonly 'YYYY-MM-DD HH:mm:ssZ': "yyyy-MM-dd HH:mm:ssXXX";
|
|
9
|
-
readonly 'YYYY-MM-DD HH:mm:ss.SSSZ': "yyyy-MM-dd HH:mm:ss.SSSXXX";
|
|
10
|
-
readonly 'YYYY/MM/DD': "yyyy/MM/dd";
|
|
11
|
-
readonly 'YYYY/MM/DD HH:mm': "yyyy/MM/dd HH:mm";
|
|
12
|
-
readonly 'YYYY/MM/DD HH:mm:ss': "yyyy/MM/dd HH:mm:ss";
|
|
13
|
-
readonly 'DD/MM/YYYY': "dd/MM/yyyy";
|
|
14
|
-
readonly 'MM/DD/YYYY': "MM/dd/yyyy";
|
|
15
|
-
readonly 'DD-MM-YYYY': "dd-MM-yyyy";
|
|
16
|
-
readonly 'MM-DD-YYYY': "MM-dd-yyyy";
|
|
17
|
-
readonly 'YYYY-MM': "yyyy-MM";
|
|
18
|
-
readonly 'YYYY/MM': "yyyy/MM";
|
|
19
|
-
readonly YYYY: "yyyy";
|
|
20
|
-
readonly 'DD MMM YYYY': "dd MMM yyyy";
|
|
21
|
-
readonly 'MMM DD, YYYY': "MMM dd, yyyy";
|
|
22
|
-
readonly 'HH:mm': "HH:mm";
|
|
23
|
-
readonly 'HH:mm:ss': "HH:mm:ss";
|
|
24
|
-
readonly 'HH:mm:ss.SSS': "HH:mm:ss.SSS";
|
|
25
|
-
readonly 'hh:mm A': "hh:mm a";
|
|
26
|
-
readonly 'hh:mm:ss A': "hh:mm:ss a";
|
|
27
|
-
};
|
|
28
|
-
export type DateFormatPreset = keyof typeof formatAliases;
|
|
29
|
-
export type DateFormatInput = DateFormatPreset | (string & {});
|
|
30
|
-
export declare function resolveFormatString(formatString: DateFormatInput): "yyyy-MM-dd" | "yyyy-MM-dd HH:mm" | "yyyy-MM-dd HH:mm:ss" | "yyyy-MM-dd HH:mm:ss.SSS" | "yyyy-MM-dd HH:mm:ssXXX" | "yyyy-MM-dd HH:mm:ss.SSSXXX" | "yyyy/MM/dd" | "yyyy/MM/dd HH:mm" | "yyyy/MM/dd HH:mm:ss" | "dd/MM/yyyy" | "MM/dd/yyyy" | "dd-MM-yyyy" | "MM-dd-yyyy" | "yyyy-MM" | "yyyy/MM" | "yyyy" | "dd MMM yyyy" | "MMM dd, yyyy" | "HH:mm" | "HH:mm:ss" | "HH:mm:ss.SSS" | "hh:mm a" | "hh:mm:ss a";
|
|
3
|
+
export type DateFormatPreset = string;
|
|
4
|
+
export type DateFormatInput = string;
|
|
31
5
|
export declare function format(date: DateInput, formatString: DateFormatInput, options?: FormatOptions): string;
|
|
32
6
|
export declare function formatISO(date: DateInput, options?: FormatISOOptions): string;
|
|
33
7
|
export declare function formatRFC3339(date: DateInput, options?: FormatRFC3339Options): string;
|
|
@@ -40,4 +14,3 @@ export declare function toDateTimeString(date: DateInput): string;
|
|
|
40
14
|
export declare function toIsoString(date: DateInput): string;
|
|
41
15
|
export declare function toRfc3339String(date: DateInput): string;
|
|
42
16
|
export declare function toRfc7231String(date: DateInput): string;
|
|
43
|
-
export {};
|
|
@@ -7,36 +7,8 @@ import {
|
|
|
7
7
|
formatRFC7231 as formatRFC7231Date
|
|
8
8
|
} from "date-fns";
|
|
9
9
|
import { toDate } from "./input.js";
|
|
10
|
-
const formatAliases = {
|
|
11
|
-
"YYYY-MM-DD": "yyyy-MM-dd",
|
|
12
|
-
"YYYY-MM-DD HH:mm": "yyyy-MM-dd HH:mm",
|
|
13
|
-
"YYYY-MM-DD HH:mm:ss": "yyyy-MM-dd HH:mm:ss",
|
|
14
|
-
"YYYY-MM-DD HH:mm:ss.SSS": "yyyy-MM-dd HH:mm:ss.SSS",
|
|
15
|
-
"YYYY-MM-DD HH:mm:ssZ": "yyyy-MM-dd HH:mm:ssXXX",
|
|
16
|
-
"YYYY-MM-DD HH:mm:ss.SSSZ": "yyyy-MM-dd HH:mm:ss.SSSXXX",
|
|
17
|
-
"YYYY/MM/DD": "yyyy/MM/dd",
|
|
18
|
-
"YYYY/MM/DD HH:mm": "yyyy/MM/dd HH:mm",
|
|
19
|
-
"YYYY/MM/DD HH:mm:ss": "yyyy/MM/dd HH:mm:ss",
|
|
20
|
-
"DD/MM/YYYY": "dd/MM/yyyy",
|
|
21
|
-
"MM/DD/YYYY": "MM/dd/yyyy",
|
|
22
|
-
"DD-MM-YYYY": "dd-MM-yyyy",
|
|
23
|
-
"MM-DD-YYYY": "MM-dd-yyyy",
|
|
24
|
-
"YYYY-MM": "yyyy-MM",
|
|
25
|
-
"YYYY/MM": "yyyy/MM",
|
|
26
|
-
"YYYY": "yyyy",
|
|
27
|
-
"DD MMM YYYY": "dd MMM yyyy",
|
|
28
|
-
"MMM DD, YYYY": "MMM dd, yyyy",
|
|
29
|
-
"HH:mm": "HH:mm",
|
|
30
|
-
"HH:mm:ss": "HH:mm:ss",
|
|
31
|
-
"HH:mm:ss.SSS": "HH:mm:ss.SSS",
|
|
32
|
-
"hh:mm A": "hh:mm a",
|
|
33
|
-
"hh:mm:ss A": "hh:mm:ss a"
|
|
34
|
-
};
|
|
35
|
-
export function resolveFormatString(formatString) {
|
|
36
|
-
return formatAliases[formatString] ?? formatString;
|
|
37
|
-
}
|
|
38
10
|
export function format(date, formatString, options) {
|
|
39
|
-
return formatDate(toDate(date),
|
|
11
|
+
return formatDate(toDate(date), formatString, options);
|
|
40
12
|
}
|
|
41
13
|
export function formatISO(date, options) {
|
|
42
14
|
return formatISODate(toDate(date), options);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { parse as parseDate, parseISO as parseISODate } from "date-fns";
|
|
2
|
-
import { resolveFormatString } from "./format.js";
|
|
3
2
|
import { toDate } from "./input.js";
|
|
4
3
|
export function parse(value, formatString, baseDate = /* @__PURE__ */ new Date(), options) {
|
|
5
|
-
return parseDate(value,
|
|
4
|
+
return parseDate(value, formatString, toDate(baseDate), options);
|
|
6
5
|
}
|
|
7
6
|
export function parseISO(value) {
|
|
8
7
|
return parseISODate(value);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FormRules } from 'element-plus';
|
|
2
|
-
export type ValidationRule = 'required' | 'time' | 'string' | 'nullable' | 'email' | 'accepted' | `accepted_if:${string}` | 'boolean' | 'filled' | 'present' | `required_if:${string}` | `required_unless:${string}` | `required_with:${string}` | `required_with_all:${string}` | `required_without:${string}` | `required_without_all:${string}` | 'prohibited' | `prohibited_if:${string}` | `prohibited_unless:${string}` | `prohibits:${string}` | `min:${number}` | `max:${number}` | `between:${number},${number}` | `size:${number}` | `same:${string}` | 'alpha' | 'alpha_num' | 'alpha_dash' | 'alphanumeric_space' | 'alpha_space' | 'numeric' | 'integer' | 'confirmed' | `digits:${number}` | `digits_between:${number},${number}` | 'bail' | `different:${string}` | `gt:${string}` | `gte:${string}` | `lt:${string}` | `lte:${string}` | `starts_with:${string}` | `ends_with:${string}` | `regex:${string}` | `date_format:${string}` | 'date' | `before:${string}` | `after:${string}` | `before_or_equal:${string}` | `after_or_equal:${string}` | 'array' | 'distinct' | `in_array:${string}` | `required_array_keys:${string}` | 'list' | 'url' | 'ip' | 'hex_color' | `in:${string}` | `multiple_of:${number}` | `decimal:${number}` | `decimal:${number},${number}` | 'lowercase' | 'uppercase' | 'uuid' | 'ulid' | `timezone:${string}` | 'timezone';
|
|
2
|
+
export type ValidationRule = 'required' | 'time' | 'datetime' | 'string' | 'nullable' | 'email' | 'accepted' | `accepted_if:${string}` | 'boolean' | 'filled' | 'present' | `required_if:${string}` | `required_unless:${string}` | `required_with:${string}` | `required_with_all:${string}` | `required_without:${string}` | `required_without_all:${string}` | 'prohibited' | `prohibited_if:${string}` | `prohibited_unless:${string}` | `prohibits:${string}` | `min:${number}` | `max:${number}` | `between:${number},${number}` | `size:${number}` | `same:${string}` | 'alpha' | 'alpha_num' | 'alpha_dash' | 'alphanumeric_space' | 'alpha_space' | 'numeric' | 'integer' | 'confirmed' | `digits:${number}` | `digits_between:${number},${number}` | 'bail' | `different:${string}` | `gt:${string}` | `gte:${string}` | `lt:${string}` | `lte:${string}` | `starts_with:${string}` | `ends_with:${string}` | `regex:${string}` | `date_format:${string}` | 'date' | `before:${string}` | `after:${string}` | `before_or_equal:${string}` | `after_or_equal:${string}` | 'array' | 'distinct' | `in_array:${string}` | `required_array_keys:${string}` | 'list' | 'url' | 'ip' | 'hex_color' | `in:${string}` | `multiple_of:${number}` | `decimal:${number}` | `decimal:${number},${number}` | 'lowercase' | 'uppercase' | 'uuid' | 'ulid' | `timezone:${string}` | 'timezone';
|
|
3
3
|
export interface ValidationOptions {
|
|
4
4
|
messages?: Record<string, string>;
|
|
5
5
|
attributes?: Record<string, string>;
|
|
@@ -84,17 +84,43 @@ const isAcceptedValue = (value) => {
|
|
|
84
84
|
const isBooleanValue = (value) => {
|
|
85
85
|
return value === true || value === false || value === 0 || value === 1 || value === "0" || value === "1";
|
|
86
86
|
};
|
|
87
|
-
const
|
|
87
|
+
const parseTimeToComparable = (value) => {
|
|
88
88
|
if (typeof value !== "string") {
|
|
89
89
|
return null;
|
|
90
90
|
}
|
|
91
|
-
const match = value.match(
|
|
91
|
+
const match = value.match(
|
|
92
|
+
/^([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d)(?:\.(\d{1,3}))?)?(?:(z)|([+-])([01]\d|2[0-3]):?([0-5]\d))?$/i
|
|
93
|
+
);
|
|
92
94
|
if (!match) {
|
|
93
95
|
return null;
|
|
94
96
|
}
|
|
95
97
|
const hours = Number(match[1]);
|
|
96
98
|
const minutes = Number(match[2]);
|
|
97
|
-
|
|
99
|
+
const seconds = match[3] ? Number(match[3]) : 0;
|
|
100
|
+
const milliseconds = match[4] ? Number(match[4].padEnd(3, "0")) : 0;
|
|
101
|
+
const hasZuluOffset = Boolean(match[5]);
|
|
102
|
+
const offsetSign = match[6];
|
|
103
|
+
const offsetHours = match[7] ? Number(match[7]) : 0;
|
|
104
|
+
const offsetMinutes = match[8] ? Number(match[8]) : 0;
|
|
105
|
+
const localComparableValue = hours * 36e5 + minutes * 6e4 + seconds * 1e3 + milliseconds;
|
|
106
|
+
if (hasZuluOffset) {
|
|
107
|
+
return {
|
|
108
|
+
comparableValue: localComparableValue,
|
|
109
|
+
hasOffset: true
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (offsetSign) {
|
|
113
|
+
const offsetComparable = (offsetHours * 60 + offsetMinutes) * 6e4;
|
|
114
|
+
const direction = offsetSign === "+" ? 1 : -1;
|
|
115
|
+
return {
|
|
116
|
+
comparableValue: localComparableValue - direction * offsetComparable,
|
|
117
|
+
hasOffset: true
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
comparableValue: localComparableValue,
|
|
122
|
+
hasOffset: false
|
|
123
|
+
};
|
|
98
124
|
};
|
|
99
125
|
const parseDateToTimestamp = (value) => {
|
|
100
126
|
if (typeof value !== "string") {
|
|
@@ -109,14 +135,53 @@ const parseDateToTimestamp = (value) => {
|
|
|
109
135
|
}
|
|
110
136
|
return date.getTime();
|
|
111
137
|
};
|
|
138
|
+
const parseDateTimeToTimestamp = (value) => {
|
|
139
|
+
if (typeof value !== "string") {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const match = value.match(
|
|
143
|
+
/^(\d{4})-(\d{2})-(\d{2})[T ]([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d)(?:\.(\d{1,3}))?)?(?:([zZ])|([+-])([01]\d|2[0-3]):?([0-5]\d))?$/
|
|
144
|
+
);
|
|
145
|
+
if (!match) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const year = Number(match[1]);
|
|
149
|
+
const month = Number(match[2]);
|
|
150
|
+
const day = Number(match[3]);
|
|
151
|
+
const hours = Number(match[4]);
|
|
152
|
+
const minutes = Number(match[5]);
|
|
153
|
+
const seconds = match[6] ? Number(match[6]) : 0;
|
|
154
|
+
const milliseconds = match[7] ? Number(match[7].padEnd(3, "0")) : 0;
|
|
155
|
+
const utcCandidate = Date.UTC(year, month - 1, day, hours, minutes, seconds, milliseconds);
|
|
156
|
+
const utcDate = new Date(utcCandidate);
|
|
157
|
+
if (utcDate.getUTCFullYear() !== year || utcDate.getUTCMonth() !== month - 1 || utcDate.getUTCDate() !== day || utcDate.getUTCHours() !== hours || utcDate.getUTCMinutes() !== minutes || utcDate.getUTCSeconds() !== seconds || utcDate.getUTCMilliseconds() !== milliseconds) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const hasZuluOffset = Boolean(match[8]);
|
|
161
|
+
const offsetSign = match[9];
|
|
162
|
+
const offsetHour = match[10] ? Number(match[10]) : 0;
|
|
163
|
+
const offsetMinute = match[11] ? Number(match[11]) : 0;
|
|
164
|
+
if (hasZuluOffset) {
|
|
165
|
+
return utcCandidate;
|
|
166
|
+
}
|
|
167
|
+
if (offsetSign) {
|
|
168
|
+
const totalOffsetMinutes = offsetHour * 60 + offsetMinute;
|
|
169
|
+
const direction = offsetSign === "+" ? 1 : -1;
|
|
170
|
+
return utcCandidate - direction * totalOffsetMinutes * 6e4;
|
|
171
|
+
}
|
|
172
|
+
return utcCandidate;
|
|
173
|
+
};
|
|
112
174
|
const compareFieldValues = (left, right, compare) => {
|
|
113
|
-
const leftTime =
|
|
114
|
-
const rightTime =
|
|
175
|
+
const leftTime = parseTimeToComparable(left);
|
|
176
|
+
const rightTime = parseTimeToComparable(right);
|
|
115
177
|
if (leftTime !== null || rightTime !== null) {
|
|
116
178
|
if (leftTime === null || rightTime === null) {
|
|
117
179
|
return false;
|
|
118
180
|
}
|
|
119
|
-
|
|
181
|
+
if (leftTime.hasOffset !== rightTime.hasOffset) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
return compare(leftTime.comparableValue, rightTime.comparableValue);
|
|
120
185
|
}
|
|
121
186
|
const leftDate = parseDateToTimestamp(left);
|
|
122
187
|
const rightDate = parseDateToTimestamp(right);
|
|
@@ -126,6 +191,14 @@ const compareFieldValues = (left, right, compare) => {
|
|
|
126
191
|
}
|
|
127
192
|
return compare(leftDate, rightDate);
|
|
128
193
|
}
|
|
194
|
+
const leftDateTime = parseDateTimeToTimestamp(left);
|
|
195
|
+
const rightDateTime = parseDateTimeToTimestamp(right);
|
|
196
|
+
if (leftDateTime !== null || rightDateTime !== null) {
|
|
197
|
+
if (leftDateTime === null || rightDateTime === null) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
return compare(leftDateTime, rightDateTime);
|
|
201
|
+
}
|
|
129
202
|
const leftNumber = Number(left);
|
|
130
203
|
const rightNumber = Number(right);
|
|
131
204
|
if (Number.isNaN(leftNumber) || Number.isNaN(rightNumber)) {
|
|
@@ -173,7 +246,7 @@ const createTimeValidator = (message) => {
|
|
|
173
246
|
if (isEmptyValue(value)) {
|
|
174
247
|
return callback();
|
|
175
248
|
}
|
|
176
|
-
if (
|
|
249
|
+
if (parseTimeToComparable(value) === null) {
|
|
177
250
|
return callback(new Error(message));
|
|
178
251
|
}
|
|
179
252
|
callback();
|
|
@@ -232,6 +305,17 @@ const createDateValidator = (message) => {
|
|
|
232
305
|
callback();
|
|
233
306
|
};
|
|
234
307
|
};
|
|
308
|
+
const createDateTimeValidator = (message) => {
|
|
309
|
+
return (_rule, value, callback) => {
|
|
310
|
+
if (isEmptyValue(value)) {
|
|
311
|
+
return callback();
|
|
312
|
+
}
|
|
313
|
+
if (parseDateTimeToTimestamp(value) === null) {
|
|
314
|
+
return callback(new Error(message));
|
|
315
|
+
}
|
|
316
|
+
callback();
|
|
317
|
+
};
|
|
318
|
+
};
|
|
235
319
|
const createDateComparisonValidator = (compareDate, message, compare) => {
|
|
236
320
|
return (_rule, value, callback) => {
|
|
237
321
|
if (!value) {
|
|
@@ -665,7 +749,7 @@ export const createValidationRules = (definitions, formState, options = {}) => {
|
|
|
665
749
|
break;
|
|
666
750
|
case "time":
|
|
667
751
|
fieldRules.push({
|
|
668
|
-
validator: createTimeValidator(getMessage(fieldName, "time", `${attributeName} must be a valid time
|
|
752
|
+
validator: createTimeValidator(getMessage(fieldName, "time", `${attributeName} must be a valid time`)),
|
|
669
753
|
trigger: pickTrigger(fieldName, "blur")
|
|
670
754
|
});
|
|
671
755
|
break;
|
|
@@ -784,6 +868,12 @@ export const createValidationRules = (definitions, formState, options = {}) => {
|
|
|
784
868
|
trigger: pickTrigger(fieldName, "blur")
|
|
785
869
|
});
|
|
786
870
|
break;
|
|
871
|
+
case "datetime":
|
|
872
|
+
fieldRules.push({
|
|
873
|
+
validator: createDateTimeValidator(getMessage(fieldName, "datetime", `${attributeName} must be a valid datetime`)),
|
|
874
|
+
trigger: pickTrigger(fieldName, "blur")
|
|
875
|
+
});
|
|
876
|
+
break;
|
|
787
877
|
case "before":
|
|
788
878
|
if (!firstParam) {
|
|
789
879
|
return;
|
|
@@ -62,6 +62,17 @@ const definitions = {
|
|
|
62
62
|
|
|
63
63
|
Optional configuration for messages, attributes, and hooks.
|
|
64
64
|
|
|
65
|
+
## Date and Time Comparison Behavior
|
|
66
|
+
|
|
67
|
+
Rules `gt`, `gte`, `lt`, and `lte` use type-aware parsing:
|
|
68
|
+
|
|
69
|
+
- `time` (supports `HH:mm`, optional seconds/milliseconds, optional `Z`/offset)
|
|
70
|
+
- `date` (`YYYY-MM-DD`)
|
|
71
|
+
- `datetime` (date + time, optional `Z`/offset)
|
|
72
|
+
- numeric fallback
|
|
73
|
+
|
|
74
|
+
For `time`, offset-aware and non-offset values cannot be mixed in one comparison.
|
|
75
|
+
|
|
65
76
|
### options.messages
|
|
66
77
|
- Summary: Custom error messages for specific rule keys.
|
|
67
78
|
- Technique: Lookup key format is `{field}.{rule}`.
|
|
@@ -64,4 +64,18 @@ const rules = createValidationRules(
|
|
|
64
64
|
)
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
## Time and Datetime Example
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const rules = createValidationRules(
|
|
71
|
+
{
|
|
72
|
+
start_time: ['required', 'time'],
|
|
73
|
+
end_time: ['required', 'time', 'gte:start_time'],
|
|
74
|
+
starts_at: ['required', 'datetime'],
|
|
75
|
+
ends_at: ['required', 'datetime', 'gte:starts_at'],
|
|
76
|
+
},
|
|
77
|
+
form,
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
67
81
|
See the full list of supported rules on the [Available Rules](/form-validation/rules) page.
|
|
@@ -237,6 +237,42 @@ Notes:
|
|
|
237
237
|
- Use: `'digits_between:6,8'`
|
|
238
238
|
- Example: `['digits_between:6,8']`
|
|
239
239
|
|
|
240
|
+
### time
|
|
241
|
+
- Summary: Must be a valid time string.
|
|
242
|
+
- Technique: Supports strict `HH:mm`, optional seconds/milliseconds, and optional timezone offset.
|
|
243
|
+
- Use: `'time'`
|
|
244
|
+
- Example: `['time']`
|
|
245
|
+
- Supported formats:
|
|
246
|
+
- `HH:mm`
|
|
247
|
+
- `HH:mm:ss`
|
|
248
|
+
- `HH:mm:ss.SSS`
|
|
249
|
+
- `HH:mmZ`
|
|
250
|
+
- `HH:mm:ssZ`
|
|
251
|
+
- `HH:mm:ss.SSSZ`
|
|
252
|
+
- `HH:mm±HHmm`
|
|
253
|
+
- `HH:mm:ss±HHmm`
|
|
254
|
+
- `HH:mm:ss.SSS±HHmm`
|
|
255
|
+
- Offset notes:
|
|
256
|
+
- `Z` means UTC (example: `09:30Z`).
|
|
257
|
+
- `±HH:MM` and `±HHmm` are both accepted (examples: `+07:00`, `-0530`).
|
|
258
|
+
|
|
259
|
+
### datetime
|
|
260
|
+
- Summary: Must be a valid datetime string.
|
|
261
|
+
- Technique: Strict date + time parser with optional timezone offset.
|
|
262
|
+
- Use: `'datetime'`
|
|
263
|
+
- Example: `['datetime']`
|
|
264
|
+
- Supported formats:
|
|
265
|
+
- `YYYY-MM-DDTHH:mm`
|
|
266
|
+
- `YYYY-MM-DD HH:mm`
|
|
267
|
+
- `YYYY-MM-DDTHH:mm:ss`
|
|
268
|
+
- `YYYY-MM-DD HH:mm:ss`
|
|
269
|
+
- `YYYY-MM-DDTHH:mm:ss.SSS`
|
|
270
|
+
- `YYYY-MM-DD HH:mm:ss.SSS`
|
|
271
|
+
- Datetime timezone suffix (optional for each format):
|
|
272
|
+
- `Z`
|
|
273
|
+
- `±HH:MM`
|
|
274
|
+
- `±HHmm`
|
|
275
|
+
|
|
240
276
|
### bail
|
|
241
277
|
- Summary: Placeholder (no-op).
|
|
242
278
|
- Technique: No behavior; included for parity.
|
|
@@ -245,27 +281,29 @@ Notes:
|
|
|
245
281
|
|
|
246
282
|
### gt:field
|
|
247
283
|
- Summary: Must be greater than another field.
|
|
248
|
-
- Technique:
|
|
284
|
+
- Technique: Type-aware comparison in this order: `time` -> `date` (`YYYY-MM-DD`) -> `datetime` -> numeric.
|
|
249
285
|
- Use: `'gt:other'`
|
|
250
286
|
- Example: `['gt:other']`
|
|
251
287
|
|
|
252
288
|
### gte:field
|
|
253
289
|
- Summary: Must be greater than or equal to another field.
|
|
254
|
-
- Technique:
|
|
290
|
+
- Technique: Type-aware comparison in this order: `time` -> `date` (`YYYY-MM-DD`) -> `datetime` -> numeric.
|
|
255
291
|
- Use: `'gte:other'`
|
|
256
292
|
- Example: `['gte:other']`
|
|
257
293
|
|
|
258
294
|
### lt:field
|
|
259
295
|
- Summary: Must be less than another field.
|
|
260
|
-
- Technique:
|
|
296
|
+
- Technique: Type-aware comparison in this order: `time` -> `date` (`YYYY-MM-DD`) -> `datetime` -> numeric.
|
|
261
297
|
- Use: `'lt:other'`
|
|
262
298
|
- Example: `['lt:other']`
|
|
263
299
|
|
|
264
300
|
### lte:field
|
|
265
301
|
- Summary: Must be less than or equal to another field.
|
|
266
|
-
- Technique:
|
|
302
|
+
- Technique: Type-aware comparison in this order: `time` -> `date` (`YYYY-MM-DD`) -> `datetime` -> numeric.
|
|
267
303
|
- Use: `'lte:other'`
|
|
268
304
|
- Example: `['lte:other']`
|
|
305
|
+
- Comparison notes:
|
|
306
|
+
- For `time`, comparing offset-aware values against non-offset values fails (both sides must either include offset or not).
|
|
269
307
|
|
|
270
308
|
### starts_with:a,b,c
|
|
271
309
|
- Summary: Must start with any of the given prefixes.
|