@fieldia/core 0.23.0 → 0.25.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.
Files changed (221) hide show
  1. package/README.md +211 -3
  2. package/package.json +1 -1
  3. package/schema/page.schema.json +1051 -325
  4. package/src/index.d.mts +9 -2
  5. package/src/index.d.ts +9 -2
  6. package/src/index.js +31 -1
  7. package/src/index.js.map +1 -1
  8. package/src/index.mjs +12 -2
  9. package/src/index.mjs.map +1 -1
  10. package/src/lib/format/actions.d.mts +31 -1
  11. package/src/lib/format/actions.d.ts +31 -1
  12. package/src/lib/format/actions.js +1 -0
  13. package/src/lib/format/actions.js.map +1 -1
  14. package/src/lib/format/actions.mjs +1 -0
  15. package/src/lib/format/actions.mjs.map +1 -1
  16. package/src/lib/format/autofill-group.d.mts +12 -0
  17. package/src/lib/format/autofill-group.d.ts +12 -0
  18. package/src/lib/format/autofill-group.js +23 -0
  19. package/src/lib/format/autofill-group.js.map +1 -0
  20. package/src/lib/format/autofill-group.mjs +19 -0
  21. package/src/lib/format/autofill-group.mjs.map +1 -0
  22. package/src/lib/format/autofill.d.mts +14 -0
  23. package/src/lib/format/autofill.d.ts +14 -0
  24. package/src/lib/format/autofill.js +54 -0
  25. package/src/lib/format/autofill.js.map +1 -0
  26. package/src/lib/format/autofill.mjs +48 -0
  27. package/src/lib/format/autofill.mjs.map +1 -0
  28. package/src/lib/format/bulk-edit.d.mts +18 -0
  29. package/src/lib/format/bulk-edit.d.ts +18 -0
  30. package/src/lib/format/bulk-edit.js +26 -0
  31. package/src/lib/format/bulk-edit.js.map +1 -0
  32. package/src/lib/format/bulk-edit.mjs +21 -0
  33. package/src/lib/format/bulk-edit.mjs.map +1 -0
  34. package/src/lib/format/duplicate-kinds.d.mts +10 -0
  35. package/src/lib/format/duplicate-kinds.d.ts +10 -0
  36. package/src/lib/format/duplicate-kinds.js +14 -0
  37. package/src/lib/format/duplicate-kinds.js.map +1 -0
  38. package/src/lib/format/duplicate-kinds.mjs +10 -0
  39. package/src/lib/format/duplicate-kinds.mjs.map +1 -0
  40. package/src/lib/format/duplicates.d.mts +22 -0
  41. package/src/lib/format/duplicates.d.ts +22 -0
  42. package/src/lib/format/duplicates.js +24 -0
  43. package/src/lib/format/duplicates.js.map +1 -0
  44. package/src/lib/format/duplicates.mjs +21 -0
  45. package/src/lib/format/duplicates.mjs.map +1 -0
  46. package/src/lib/format/field.d.mts +291 -34
  47. package/src/lib/format/field.d.ts +291 -34
  48. package/src/lib/format/field.js +19 -4
  49. package/src/lib/format/field.js.map +1 -1
  50. package/src/lib/format/field.mjs +17 -2
  51. package/src/lib/format/field.mjs.map +1 -1
  52. package/src/lib/format/import-fields.d.mts +18 -0
  53. package/src/lib/format/import-fields.d.ts +18 -0
  54. package/src/lib/format/import-fields.js +26 -0
  55. package/src/lib/format/import-fields.js.map +1 -0
  56. package/src/lib/format/import-fields.mjs +21 -0
  57. package/src/lib/format/import-fields.mjs.map +1 -0
  58. package/src/lib/format/layout.d.mts +7357 -3127
  59. package/src/lib/format/layout.d.ts +7357 -3127
  60. package/src/lib/format/layout.js +108 -32
  61. package/src/lib/format/layout.js.map +1 -1
  62. package/src/lib/format/layout.mjs +107 -32
  63. package/src/lib/format/layout.mjs.map +1 -1
  64. package/src/lib/format/layout.types.d.mts +5 -2
  65. package/src/lib/format/layout.types.d.ts +5 -2
  66. package/src/lib/format/page.d.mts +1080 -106
  67. package/src/lib/format/page.d.ts +1080 -106
  68. package/src/lib/format/page.js +25 -0
  69. package/src/lib/format/page.js.map +1 -1
  70. package/src/lib/format/page.mjs +26 -1
  71. package/src/lib/format/page.mjs.map +1 -1
  72. package/src/lib/format/phone.d.mts +27 -0
  73. package/src/lib/format/phone.d.ts +27 -0
  74. package/src/lib/format/phone.js +43 -0
  75. package/src/lib/format/phone.js.map +1 -0
  76. package/src/lib/format/phone.mjs +39 -0
  77. package/src/lib/format/phone.mjs.map +1 -0
  78. package/src/lib/format/references.d.mts +39 -0
  79. package/src/lib/format/references.d.ts +39 -0
  80. package/src/lib/format/references.js +263 -20
  81. package/src/lib/format/references.js.map +1 -1
  82. package/src/lib/format/references.mjs +263 -20
  83. package/src/lib/format/references.mjs.map +1 -1
  84. package/src/lib/format/slots-options.d.mts +26 -0
  85. package/src/lib/format/slots-options.d.ts +26 -0
  86. package/src/lib/format/slots-options.js +35 -0
  87. package/src/lib/format/slots-options.js.map +1 -0
  88. package/src/lib/format/slots-options.mjs +31 -0
  89. package/src/lib/format/slots-options.mjs.map +1 -0
  90. package/src/lib/format/translate.js +8 -4
  91. package/src/lib/format/translate.js.map +1 -1
  92. package/src/lib/format/translate.mjs +8 -4
  93. package/src/lib/format/translate.mjs.map +1 -1
  94. package/src/lib/format/upgrade.d.mts +8 -3
  95. package/src/lib/format/upgrade.d.ts +8 -3
  96. package/src/lib/format/upgrade.js +8 -3
  97. package/src/lib/format/upgrade.js.map +1 -1
  98. package/src/lib/format/upgrade.mjs +8 -3
  99. package/src/lib/format/upgrade.mjs.map +1 -1
  100. package/src/lib/format/version.d.mts +2 -2
  101. package/src/lib/format/version.d.ts +2 -2
  102. package/src/lib/format/version.js +2 -2
  103. package/src/lib/format/version.js.map +1 -1
  104. package/src/lib/format/version.mjs +2 -2
  105. package/src/lib/format/version.mjs.map +1 -1
  106. package/src/lib/format/zoned.d.mts +8 -0
  107. package/src/lib/format/zoned.d.ts +8 -0
  108. package/src/lib/format/zoned.js +36 -0
  109. package/src/lib/format/zoned.js.map +1 -0
  110. package/src/lib/format/zoned.mjs +33 -0
  111. package/src/lib/format/zoned.mjs.map +1 -0
  112. package/src/lib/record/check.d.mts +6 -1
  113. package/src/lib/record/check.d.ts +6 -1
  114. package/src/lib/record/check.js +12 -4
  115. package/src/lib/record/check.js.map +1 -1
  116. package/src/lib/record/check.mjs +12 -4
  117. package/src/lib/record/check.mjs.map +1 -1
  118. package/src/lib/record/data-source.d.mts +309 -3
  119. package/src/lib/record/data-source.d.ts +309 -3
  120. package/src/lib/record/data-source.js +16 -0
  121. package/src/lib/record/data-source.js.map +1 -1
  122. package/src/lib/record/data-source.mjs +15 -0
  123. package/src/lib/record/data-source.mjs.map +1 -1
  124. package/src/lib/record/form.d.mts +116 -25
  125. package/src/lib/record/form.d.ts +116 -25
  126. package/src/lib/record/form.js +457 -78
  127. package/src/lib/record/form.js.map +1 -1
  128. package/src/lib/record/form.mjs +457 -78
  129. package/src/lib/record/form.mjs.map +1 -1
  130. package/src/lib/record/input-rules.d.mts +51 -9
  131. package/src/lib/record/input-rules.d.ts +51 -9
  132. package/src/lib/record/input-rules.js +119 -20
  133. package/src/lib/record/input-rules.js.map +1 -1
  134. package/src/lib/record/input-rules.mjs +118 -19
  135. package/src/lib/record/input-rules.mjs.map +1 -1
  136. package/src/lib/record/inputs.js +4 -2
  137. package/src/lib/record/inputs.js.map +1 -1
  138. package/src/lib/record/inputs.mjs +4 -2
  139. package/src/lib/record/inputs.mjs.map +1 -1
  140. package/src/lib/record/limits.d.mts +1 -1
  141. package/src/lib/record/limits.d.ts +1 -1
  142. package/src/lib/record/limits.js +4 -2
  143. package/src/lib/record/limits.js.map +1 -1
  144. package/src/lib/record/limits.mjs +4 -2
  145. package/src/lib/record/limits.mjs.map +1 -1
  146. package/src/lib/record/locales/ar.js +3 -2
  147. package/src/lib/record/locales/ar.js.map +1 -1
  148. package/src/lib/record/locales/ar.mjs +3 -2
  149. package/src/lib/record/locales/ar.mjs.map +1 -1
  150. package/src/lib/record/locales/de.js +3 -2
  151. package/src/lib/record/locales/de.js.map +1 -1
  152. package/src/lib/record/locales/de.mjs +3 -2
  153. package/src/lib/record/locales/de.mjs.map +1 -1
  154. package/src/lib/record/locales/fr.js +3 -2
  155. package/src/lib/record/locales/fr.js.map +1 -1
  156. package/src/lib/record/locales/fr.mjs +3 -2
  157. package/src/lib/record/locales/fr.mjs.map +1 -1
  158. package/src/lib/record/memory-data-source.d.mts +20 -2
  159. package/src/lib/record/memory-data-source.d.ts +20 -2
  160. package/src/lib/record/memory-data-source.js +153 -35
  161. package/src/lib/record/memory-data-source.js.map +1 -1
  162. package/src/lib/record/memory-data-source.mjs +154 -36
  163. package/src/lib/record/memory-data-source.mjs.map +1 -1
  164. package/src/lib/record/memory-duplicates.d.mts +19 -0
  165. package/src/lib/record/memory-duplicates.d.ts +19 -0
  166. package/src/lib/record/memory-duplicates.js +78 -0
  167. package/src/lib/record/memory-duplicates.js.map +1 -0
  168. package/src/lib/record/memory-duplicates.mjs +74 -0
  169. package/src/lib/record/memory-duplicates.mjs.map +1 -0
  170. package/src/lib/record/memory-schedules.d.mts +45 -0
  171. package/src/lib/record/memory-schedules.d.ts +45 -0
  172. package/src/lib/record/memory-schedules.js +81 -0
  173. package/src/lib/record/memory-schedules.js.map +1 -0
  174. package/src/lib/record/memory-schedules.mjs +78 -0
  175. package/src/lib/record/memory-schedules.mjs.map +1 -0
  176. package/src/lib/record/messages.d.mts +3 -1
  177. package/src/lib/record/messages.d.ts +3 -1
  178. package/src/lib/record/messages.js +3 -2
  179. package/src/lib/record/messages.js.map +1 -1
  180. package/src/lib/record/messages.mjs +3 -2
  181. package/src/lib/record/messages.mjs.map +1 -1
  182. package/src/lib/record/periods.d.mts +47 -0
  183. package/src/lib/record/periods.d.ts +47 -0
  184. package/src/lib/record/periods.js +86 -0
  185. package/src/lib/record/periods.js.map +1 -0
  186. package/src/lib/record/periods.mjs +79 -0
  187. package/src/lib/record/periods.mjs.map +1 -0
  188. package/src/lib/record/rules.js +5 -6
  189. package/src/lib/record/rules.js.map +1 -1
  190. package/src/lib/record/rules.mjs +5 -6
  191. package/src/lib/record/rules.mjs.map +1 -1
  192. package/src/lib/record/run.d.mts +75 -9
  193. package/src/lib/record/run.d.ts +75 -9
  194. package/src/lib/record/run.js +169 -26
  195. package/src/lib/record/run.js.map +1 -1
  196. package/src/lib/record/run.mjs +170 -27
  197. package/src/lib/record/run.mjs.map +1 -1
  198. package/src/lib/record/search.d.mts +13 -3
  199. package/src/lib/record/search.d.ts +13 -3
  200. package/src/lib/record/search.js +11 -4
  201. package/src/lib/record/search.js.map +1 -1
  202. package/src/lib/record/search.mjs +11 -4
  203. package/src/lib/record/search.mjs.map +1 -1
  204. package/src/lib/record/waits.d.mts +32 -0
  205. package/src/lib/record/waits.d.ts +32 -0
  206. package/src/lib/record/waits.js +89 -0
  207. package/src/lib/record/waits.js.map +1 -0
  208. package/src/lib/record/waits.mjs +85 -0
  209. package/src/lib/record/waits.mjs.map +1 -0
  210. package/src/lib/record/zones.d.mts +57 -0
  211. package/src/lib/record/zones.d.ts +57 -0
  212. package/src/lib/record/zones.js +147 -0
  213. package/src/lib/record/zones.js.map +1 -0
  214. package/src/lib/record/zones.mjs +132 -0
  215. package/src/lib/record/zones.mjs.map +1 -0
  216. package/src/runtime.d.mts +19 -9
  217. package/src/runtime.d.ts +19 -9
  218. package/src/runtime.js +37 -2
  219. package/src/runtime.js.map +1 -1
  220. package/src/runtime.mjs +9 -2
  221. package/src/runtime.mjs.map +1 -1
@@ -73,6 +73,11 @@ export interface NumberReadOptions {
73
73
  */
74
74
  ungrouped?: boolean;
75
75
  }
76
+ /**
77
+ * Text as every reader sees it: no direction marks, digits in any script as 0-9, and the marks a full-width keyboard
78
+ * types — "15/10/2026 14:30" — as their plain kind. For a box that judges keys as the readers read them.
79
+ */
80
+ export declare const foldTyped: (text: string) => string;
76
81
  /**
77
82
  * Typed or pasted text as a number, in the page's language (`locale`).
78
83
  *
@@ -121,6 +126,11 @@ export type NumberRefusal = {
121
126
  } | {
122
127
  problem: 'digits';
123
128
  n: number;
129
+ }
130
+ /** More digits before the decimal mark than a field of `digits` [precision, scale] keeps there: `n`, precision less scale. */
131
+ | {
132
+ problem: 'wholeDigits';
133
+ n: number;
124
134
  };
125
135
  /** The most significant digits a number keeps exactly. */
126
136
  export declare const MOST_DIGITS = 15;
@@ -162,15 +172,18 @@ export type PastedNumber = {
162
172
  /**
163
173
  * Pasted (or dropped) text as the plain number a box takes, in the page's
164
174
  * way: direction marks, the box's own currency (its symbol, narrow symbol,
165
- * code or Arabic symbol), grouping, % and spaces taken out, and an
166
- * accounting negative read — (1,200.00) and 1,200- are -1200. Another
167
- * currency is named; a tab or a line break inside is several values; a
168
- * single line break at the end, as a copied cell brings, is let go.
175
+ * code or Arabic symbol, in any case, as Intl writes it or as people write it
176
+ * — "LE 1,500", "JD 12.345", "12 usd"), the box's own unit ("12 kg" where
177
+ * its box says kg), grouping, % and spaces taken out, and an accounting
178
+ * negative read — (1,200.00) and 1,200- are -1200. Another currency is
179
+ * named; a tab or a line break inside is several values; a single line break
180
+ * at the end, as a copied cell brings, is let go.
169
181
  */
170
182
  export declare function cleanPastedNumber(text: string, locale: string, options?: {
171
183
  currency?: string | null;
172
184
  percent?: boolean;
173
185
  ungrouped?: boolean;
186
+ units?: readonly string[];
174
187
  }): PastedNumber;
175
188
  /** Why typed hours do not read. */
176
189
  export type DurationProblem = 'unfinished' | 'minutes' | 'duration' | 'numbersOnly' | 'minusFirst' | 'severalValues';
@@ -198,13 +211,36 @@ export type DurationRead = {
198
211
  export declare function readDuration(text: string, locale: string): DurationRead;
199
212
  /** The order a page types a date's day, month and year in. */
200
213
  export type DateOrder = 'dmy' | 'mdy' | 'ymd';
201
- export type DateProblem = 'unfinished' | 'noSuchMonth' | 'yearDigits' | 'time';
214
+ /** Why a date does not read; 'order', written in another order than the page's ("15/10/2026" where dates go year first). */
215
+ export type DateProblem = 'unfinished' | 'noSuchMonth' | 'yearDigits' | 'time' | 'order';
202
216
  export type DateRead =
203
- /** Read; `timeLeftOut`, the time (HH:MM, here) an instant had, where a day alone is kept: said, never dropped in silence. */
217
+ /**
218
+ * Read; `timeLeftOut`, the time (HH:MM, here) an instant had, where a day alone is kept: said, never dropped in
219
+ * silence. `at`, in milliseconds, the instant a date and time written with its own zone or its offset from UTC is:
220
+ * its time on the clock of the zone it is read in may not tell it apart (the hour the clocks repeat).
221
+ */
204
222
  {
205
223
  ok: true;
206
224
  value: string | null;
207
225
  timeLeftOut?: string;
226
+ at?: number;
227
+ }
228
+ /** A date and time the clocks skip where it is read (going forward for summer time): they go from `from` to `to`. */
229
+ | {
230
+ ok: false;
231
+ problem: 'skippedTime';
232
+ date: string;
233
+ time: string;
234
+ from: string;
235
+ to: string;
236
+ }
237
+ /** A date and time the clocks pass twice where it is read (going back): the offsets from UTC it could have, earliest first. */
238
+ | {
239
+ ok: false;
240
+ problem: 'repeatedTime';
241
+ date: string;
242
+ time: string;
243
+ offsets: [string, string];
208
244
  } | {
209
245
  ok: false;
210
246
  problem: DateProblem;
@@ -242,14 +278,20 @@ export declare function couldBeMonth(word: string, locale?: string): boolean;
242
278
  * numbers beside a month in words are its day and its year of four digits,
243
279
  * in either order. With `time`, a time may follow ("13/10/2026 14:30", "…,
244
280
  * 2:30 pm", "… ٢:٣٠ م"), and the value is yyyy-mm-ddTHH:MM. An instant with
245
- * its zone (2026-10-15T14:30:00Z) is the local day and time it is — with
246
- * `time`, its seconds :00, never dropped. Unfinished or impossible dates say
247
- * why (31/02 → 'noSuchDay' with the days that month has).
281
+ * its zone (2026-10-15T14:30:00Z), or a date and time typed with its offset
282
+ * ("… 02:30 +01:00"), is the day and time it is where it is read (`zone`, a
283
+ * time zone's name; the computer's own when left out) — with `time`, its
284
+ * seconds :00, never dropped — and gives its moment (`at`). Unfinished or
285
+ * impossible dates say why (31/02 → 'noSuchDay' with the days that month
286
+ * has, as an instant's 2026-02-31 does). A date and time is one moment where
287
+ * it is read: one the clocks skip ('skippedTime') or pass twice
288
+ * ('repeatedTime') as summer time comes or goes is said, never moved.
248
289
  */
249
290
  export declare function readDate(text: string, options?: {
250
291
  order?: DateOrder;
251
292
  time?: boolean;
252
293
  locale?: string;
294
+ zone?: string;
253
295
  }): DateRead;
254
296
  export type TimeRead = {
255
297
  ok: true;
@@ -73,6 +73,11 @@ export interface NumberReadOptions {
73
73
  */
74
74
  ungrouped?: boolean;
75
75
  }
76
+ /**
77
+ * Text as every reader sees it: no direction marks, digits in any script as 0-9, and the marks a full-width keyboard
78
+ * types — "15/10/2026 14:30" — as their plain kind. For a box that judges keys as the readers read them.
79
+ */
80
+ export declare const foldTyped: (text: string) => string;
76
81
  /**
77
82
  * Typed or pasted text as a number, in the page's language (`locale`).
78
83
  *
@@ -121,6 +126,11 @@ export type NumberRefusal = {
121
126
  } | {
122
127
  problem: 'digits';
123
128
  n: number;
129
+ }
130
+ /** More digits before the decimal mark than a field of `digits` [precision, scale] keeps there: `n`, precision less scale. */
131
+ | {
132
+ problem: 'wholeDigits';
133
+ n: number;
124
134
  };
125
135
  /** The most significant digits a number keeps exactly. */
126
136
  export declare const MOST_DIGITS = 15;
@@ -162,15 +172,18 @@ export type PastedNumber = {
162
172
  /**
163
173
  * Pasted (or dropped) text as the plain number a box takes, in the page's
164
174
  * way: direction marks, the box's own currency (its symbol, narrow symbol,
165
- * code or Arabic symbol), grouping, % and spaces taken out, and an
166
- * accounting negative read — (1,200.00) and 1,200- are -1200. Another
167
- * currency is named; a tab or a line break inside is several values; a
168
- * single line break at the end, as a copied cell brings, is let go.
175
+ * code or Arabic symbol, in any case, as Intl writes it or as people write it
176
+ * — "LE 1,500", "JD 12.345", "12 usd"), the box's own unit ("12 kg" where
177
+ * its box says kg), grouping, % and spaces taken out, and an accounting
178
+ * negative read — (1,200.00) and 1,200- are -1200. Another currency is
179
+ * named; a tab or a line break inside is several values; a single line break
180
+ * at the end, as a copied cell brings, is let go.
169
181
  */
170
182
  export declare function cleanPastedNumber(text: string, locale: string, options?: {
171
183
  currency?: string | null;
172
184
  percent?: boolean;
173
185
  ungrouped?: boolean;
186
+ units?: readonly string[];
174
187
  }): PastedNumber;
175
188
  /** Why typed hours do not read. */
176
189
  export type DurationProblem = 'unfinished' | 'minutes' | 'duration' | 'numbersOnly' | 'minusFirst' | 'severalValues';
@@ -198,13 +211,36 @@ export type DurationRead = {
198
211
  export declare function readDuration(text: string, locale: string): DurationRead;
199
212
  /** The order a page types a date's day, month and year in. */
200
213
  export type DateOrder = 'dmy' | 'mdy' | 'ymd';
201
- export type DateProblem = 'unfinished' | 'noSuchMonth' | 'yearDigits' | 'time';
214
+ /** Why a date does not read; 'order', written in another order than the page's ("15/10/2026" where dates go year first). */
215
+ export type DateProblem = 'unfinished' | 'noSuchMonth' | 'yearDigits' | 'time' | 'order';
202
216
  export type DateRead =
203
- /** Read; `timeLeftOut`, the time (HH:MM, here) an instant had, where a day alone is kept: said, never dropped in silence. */
217
+ /**
218
+ * Read; `timeLeftOut`, the time (HH:MM, here) an instant had, where a day alone is kept: said, never dropped in
219
+ * silence. `at`, in milliseconds, the instant a date and time written with its own zone or its offset from UTC is:
220
+ * its time on the clock of the zone it is read in may not tell it apart (the hour the clocks repeat).
221
+ */
204
222
  {
205
223
  ok: true;
206
224
  value: string | null;
207
225
  timeLeftOut?: string;
226
+ at?: number;
227
+ }
228
+ /** A date and time the clocks skip where it is read (going forward for summer time): they go from `from` to `to`. */
229
+ | {
230
+ ok: false;
231
+ problem: 'skippedTime';
232
+ date: string;
233
+ time: string;
234
+ from: string;
235
+ to: string;
236
+ }
237
+ /** A date and time the clocks pass twice where it is read (going back): the offsets from UTC it could have, earliest first. */
238
+ | {
239
+ ok: false;
240
+ problem: 'repeatedTime';
241
+ date: string;
242
+ time: string;
243
+ offsets: [string, string];
208
244
  } | {
209
245
  ok: false;
210
246
  problem: DateProblem;
@@ -242,14 +278,20 @@ export declare function couldBeMonth(word: string, locale?: string): boolean;
242
278
  * numbers beside a month in words are its day and its year of four digits,
243
279
  * in either order. With `time`, a time may follow ("13/10/2026 14:30", "…,
244
280
  * 2:30 pm", "… ٢:٣٠ م"), and the value is yyyy-mm-ddTHH:MM. An instant with
245
- * its zone (2026-10-15T14:30:00Z) is the local day and time it is — with
246
- * `time`, its seconds :00, never dropped. Unfinished or impossible dates say
247
- * why (31/02 → 'noSuchDay' with the days that month has).
281
+ * its zone (2026-10-15T14:30:00Z), or a date and time typed with its offset
282
+ * ("… 02:30 +01:00"), is the day and time it is where it is read (`zone`, a
283
+ * time zone's name; the computer's own when left out) — with `time`, its
284
+ * seconds :00, never dropped — and gives its moment (`at`). Unfinished or
285
+ * impossible dates say why (31/02 → 'noSuchDay' with the days that month
286
+ * has, as an instant's 2026-02-31 does). A date and time is one moment where
287
+ * it is read: one the clocks skip ('skippedTime') or pass twice
288
+ * ('repeatedTime') as summer time comes or goes is said, never moved.
248
289
  */
249
290
  export declare function readDate(text: string, options?: {
250
291
  order?: DateOrder;
251
292
  time?: boolean;
252
293
  locale?: string;
294
+ zone?: string;
253
295
  }): DateRead;
254
296
  export type TimeRead = {
255
297
  ok: true;
@@ -12,7 +12,7 @@
12
12
  * copying brings along, are never part of what is read.
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.MOST_DIGITS = void 0;
15
+ exports.MOST_DIGITS = exports.foldTyped = void 0;
16
16
  exports.stripMarks = stripMarks;
17
17
  exports.foldDigits = foldDigits;
18
18
  exports.oneLine = oneLine;
@@ -29,6 +29,7 @@ exports.readDate = readDate;
29
29
  exports.readTime = readTime;
30
30
  exports.placesOfCurrency = placesOfCurrency;
31
31
  exports.placesOf = placesOf;
32
+ const zones_1 = require("./zones");
32
33
  /** Invisible direction marks: LRM, RLM, ALM, the embeddings and overrides, the isolates, and a byte-order mark. */
33
34
  const MARKS = /[\u200e\u200f\u061c\u202a-\u202e\u2066-\u2069\ufeff]/g;
34
35
  const MARKS_AT_ENDS = /^[\u200e\u200f\u061c\u202a-\u202e\u2066-\u2069\ufeff]+|[\u200e\u200f\u061c\u202a-\u202e\u2066-\u2069\ufeff]+$/g;
@@ -114,10 +115,16 @@ const MINUS = '-\u2212\ufe63\uff0d';
114
115
  const IS_MINUS = new RegExp(`[${MINUS}]`);
115
116
  const LINE_BREAKS = /[\t\n\r\v\f\u2028\u2029]/;
116
117
  const GROUP_SPACE = /[ \u00a0\u2007\u2009\u202f]/;
117
- /** Fullwidth marks a Japanese or Chinese keyboard types, as their plain kind. */
118
- const fullwidth = (text) => text.replace(/[\uff0b\uff0c\uff0e\uff05]/g, (c) => ({ '\uff0b': '+', '\uff0c': ',', '\uff0e': '.', '\uff05': '%' })[c]);
118
+ /** Fullwidth marks a Japanese or Chinese keyboard types, as their plain kind: a number's + , . %, a date's /, a time's :, its space. */
119
+ const FULLWIDTH = { '\uff0b': '+', '\uff0c': ',', '\uff0e': '.', '\uff05': '%', '\uff0f': '/', '\uff1a': ':', '\u3000': ' ' };
120
+ const fullwidth = (text) => text.replace(/[\uff0b\uff0c\uff0e\uff05\uff0f\uff1a\u3000]/g, (c) => FULLWIDTH[c]);
119
121
  /** Text as the readers see it: no direction marks, digits and fullwidth marks folded. */
120
122
  const folded = (text) => fullwidth(foldDigits(stripMarks(text)));
123
+ /**
124
+ * Text as every reader sees it: no direction marks, digits in any script as 0-9, and the marks a full-width keyboard
125
+ * types — "15/10/2026 14:30" — as their plain kind. For a box that judges keys as the readers read them.
126
+ */
127
+ exports.foldTyped = folded;
121
128
  function kindOf(char) {
122
129
  if (char === '.')
123
130
  return 'dot';
@@ -281,6 +288,8 @@ function readNumber(text, locale, options = {}) {
281
288
  }
282
289
  /** The most significant digits a number keeps exactly. */
283
290
  exports.MOST_DIGITS = 15;
291
+ /** The most digits a field keeps before its decimal mark, where its limits say its places and its digits (precision less scale). */
292
+ const wholeDigits = (limits) => (limits.digits !== undefined && limits.places !== undefined && !limits.percent ? Math.max(1, limits.digits - limits.places) : undefined);
284
293
  /**
285
294
  * Whether the decimal mark a text reads with may still turn out to group as
286
295
  * more is typed: a lone . or , (or ،), last, with one to three digits before
@@ -349,6 +358,11 @@ function couldBeNumber(text, locale, limits = {}) {
349
358
  return { problem: 'decimals', places: limits.places };
350
359
  const all = [...sign.body].filter((c) => c >= '0' && c <= '9').join('');
351
360
  const whole = body.decimal === -1 ? all : all.slice(0, all.length - fraction.length);
361
+ // A field's precision keeps its scale's places after the mark: before it, the rest (Floor area [10, 1]: 9), as its
362
+ // check counts them — never a digit taken here that the check then refuses.
363
+ const wholeMost = wholeDigits(limits);
364
+ if (wholeMost !== undefined && whole.replace(/^0+/, '').length > wholeMost)
365
+ return { problem: 'wholeDigits', n: wholeMost };
352
366
  const significant = (whole + places).replace(/^0+/, '').length;
353
367
  const digits = whole.replace(/^0+/, '').length + places.length;
354
368
  const most = Math.min(exports.MOST_DIGITS, limits.digits ?? exports.MOST_DIGITS);
@@ -373,6 +387,9 @@ function numberMisfit(value, limits) {
373
387
  return { problem: 'decimals', places: limits.places };
374
388
  const integerPart = Math.trunc(Math.abs(value));
375
389
  const whole = integerPart === 0 ? 0 : integerPart < 1e21 ? String(integerPart).length : 22;
390
+ const wholeMost = wholeDigits(limits);
391
+ if (wholeMost !== undefined && whole <= exports.MOST_DIGITS && whole > wholeMost)
392
+ return { problem: 'wholeDigits', n: wholeMost };
376
393
  const most = Math.min(exports.MOST_DIGITS, limits.digits ?? exports.MOST_DIGITS);
377
394
  if (whole > exports.MOST_DIGITS || whole + places > most)
378
395
  return { problem: 'digits', n: most };
@@ -410,19 +427,42 @@ function formsOf(currency, locale) {
410
427
  }
411
428
  return found;
412
429
  }
430
+ /**
431
+ * How people in the region write their own currency beside an amount, where Intl's symbols are not how they write it:
432
+ * Egypt's LE, L.E. and ج.م, Jordan's JD, the Gulf's KD, BD, RO, QR, SR and Dhs. (Arabic letters made from their code
433
+ * points: the one-tag script carries no Arabic words.)
434
+ */
435
+ const WRITTEN = {
436
+ EGP: ['LE', `${letter(0x62c)}.${letter(0x645)}`],
437
+ JOD: ['JD'],
438
+ KWD: ['KD'],
439
+ BHD: ['BD'],
440
+ OMR: ['RO'],
441
+ QAR: ['QR'],
442
+ SAR: ['SR'],
443
+ AED: ['Dhs', 'Dh'],
444
+ };
445
+ /** A currency's or a unit's form as it is matched: its case, its Arabic letters' forms, its dots and spaces let go. */
446
+ const formKey = (form) => foldForSearch(form).replace(/[.\s]/g, '');
413
447
  /**
414
448
  * Pasted (or dropped) text as the plain number a box takes, in the page's
415
449
  * way: direction marks, the box's own currency (its symbol, narrow symbol,
416
- * code or Arabic symbol), grouping, % and spaces taken out, and an
417
- * accounting negative read — (1,200.00) and 1,200- are -1200. Another
418
- * currency is named; a tab or a line break inside is several values; a
419
- * single line break at the end, as a copied cell brings, is let go.
450
+ * code or Arabic symbol, in any case, as Intl writes it or as people write it
451
+ * — "LE 1,500", "JD 12.345", "12 usd"), the box's own unit ("12 kg" where
452
+ * its box says kg), grouping, % and spaces taken out, and an accounting
453
+ * negative read — (1,200.00) and 1,200- are -1200. Another currency is
454
+ * named; a tab or a line break inside is several values; a single line break
455
+ * at the end, as a copied cell brings, is let go.
420
456
  */
421
457
  function cleanPastedNumber(text, locale, options = {}) {
422
458
  let t = folded(text).replace(/(\r\n|[\r\n])$/, '');
423
459
  if (LINE_BREAKS.test(t.trim()))
424
460
  return { ok: false, problem: 'severalValues' };
425
461
  t = t.trim();
462
+ // The box's own currency and unit, however they are written: a word or a sign taken out whole where it is one of them.
463
+ const own = new Set([...(options.currency ? [...formsOf(options.currency, locale), ...(WRITTEN[options.currency] ?? [])] : []), ...(options.units ?? [])].map(formKey).filter(Boolean));
464
+ if (own.size)
465
+ t = t.replace(/[\p{L}\p{Sc}][\p{L}\p{Sc}\p{M}.]*/gu, (token) => (own.has(formKey(token)) ? ' ' : token));
426
466
  if (options.currency)
427
467
  for (const form of formsOf(options.currency, locale))
428
468
  t = t.split(form).join(' ');
@@ -498,10 +538,12 @@ function readDuration(text, locale) {
498
538
  return { ok: false, problem: 'duration' };
499
539
  return { ok: true, value: signed(Number(h) + Number(m) / 60) };
500
540
  }
501
- if (/[:hH]/.test(body))
541
+ // Hours with a decimal and their h after them, as "6h" may have one: "6.5h" is six and a half.
542
+ const decimal = /^(\d+[.,\u060c\u066b]\d+)\s*[hH]$/.exec(body);
543
+ if (/[:hH]/.test(body) && !decimal)
502
544
  return { ok: false, problem: 'duration' };
503
545
  // Hours as a number, in the page's way: "6.5", "6,5".
504
- const read = readNumber(body, locale);
546
+ const read = readNumber(decimal ? decimal[1] : body, locale);
505
547
  if (read.ok)
506
548
  return { ok: true, value: read.value === null ? null : signed(read.value) };
507
549
  if (read.problem === 'unfinished')
@@ -510,8 +552,25 @@ function readDuration(text, locale) {
510
552
  }
511
553
  const daysIn = (year, month) => [31, year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1];
512
554
  const pad2 = (n) => String(n).padStart(2, '0');
513
- /** A moment as the local day and time it is, as the readers give a date and time: yyyy-mm-ddTHH:MM. */
514
- const localOf = (d) => `${String(d.getFullYear()).padStart(4, '0')}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}T${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
555
+ // ---------------------------------------------------------------------------
556
+ // Wall clocks and their zones: zones.ts's, the one set every reader and writer counts by.
557
+ /** A moment as the wall clock reads it in `zone` (a time zone's name; where the computer is when left out): yyyy-mm-ddTHH:MM. */
558
+ const wallAt = (at, zone) => (0, zones_1.wallText)((0, zones_1.wallTime)(at, zone));
559
+ /** An offset from UTC as people write it after a time: +02:00, -05:00. */
560
+ const offsetText = (minutes) => `${minutes < 0 ? '-' : '+'}${pad2(Math.floor(Math.abs(minutes) / 60))}:${pad2(Math.abs(minutes) % 60)}`;
561
+ /** Why a date and time typed (yyyy-mm-ddTHH:MM) is not one moment in `zone`, or null when it is: skipped or passed twice. */
562
+ function clocksMove(local, zone) {
563
+ const wall = (0, zones_1.wallOf)(local);
564
+ const moments = (0, zones_1.instantsAt)(wall, zone);
565
+ const [date, time] = [local.slice(0, 10), local.slice(11)];
566
+ if (moments.length === 2)
567
+ return { ok: false, problem: 'repeatedTime', date, time, offsets: [offsetText((0, zones_1.zoneOffset)(moments[0], zone)), offsetText((0, zones_1.zoneOffset)(moments[1], zone))] };
568
+ if (moments.length)
569
+ return null;
570
+ // Skipped: the clocks moved forward across it, reading `from` just before they moved and `to` from then on.
571
+ const skip = (0, zones_1.clocksSkip)(wall, zone);
572
+ return { ok: false, problem: 'skippedTime', date, time, from: skip ? (0, zones_1.wallClock)(skip.from) : time, to: skip ? (0, zones_1.wallClock)(skip.to) : time };
573
+ }
515
574
  /** An instant as a server or a spreadsheet writes one, with its zone: 2026-10-15T14:30:00Z, 2026-10-15T16:30:00.000+02:00. */
516
575
  const INSTANT = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?\s*(Z|[+-]\d{2}(?::?\d{2})?)$/i;
517
576
  /** The languages whose month names a date may be written with, whatever the page's: the four Fieldia speaks. */
@@ -575,29 +634,66 @@ function couldBeMonth(word, locale) {
575
634
  * numbers beside a month in words are its day and its year of four digits,
576
635
  * in either order. With `time`, a time may follow ("13/10/2026 14:30", "…,
577
636
  * 2:30 pm", "… ٢:٣٠ م"), and the value is yyyy-mm-ddTHH:MM. An instant with
578
- * its zone (2026-10-15T14:30:00Z) is the local day and time it is — with
579
- * `time`, its seconds :00, never dropped. Unfinished or impossible dates say
580
- * why (31/02 → 'noSuchDay' with the days that month has).
637
+ * its zone (2026-10-15T14:30:00Z), or a date and time typed with its offset
638
+ * ("… 02:30 +01:00"), is the day and time it is where it is read (`zone`, a
639
+ * time zone's name; the computer's own when left out) — with `time`, its
640
+ * seconds :00, never dropped — and gives its moment (`at`). Unfinished or
641
+ * impossible dates say why (31/02 → 'noSuchDay' with the days that month
642
+ * has, as an instant's 2026-02-31 does). A date and time is one moment where
643
+ * it is read: one the clocks skip ('skippedTime') or pass twice
644
+ * ('repeatedTime') as summer time comes or goes is said, never moved.
581
645
  */
582
646
  function readDate(text, options = {}) {
583
- let t = folded(text).trim();
647
+ const t = folded(text).trim();
584
648
  if (!t)
585
649
  return { ok: true, value: null };
586
650
  const instant = INSTANT.exec(t);
587
651
  if (instant) {
588
652
  const [, y, mo, d, h, mi, seconds = '0', fraction = '0', zone] = instant;
653
+ // A day its month does not have is refused as typed, never rolled into the next month (31 February, 3 March).
654
+ if (Number(mo) < 1 || Number(mo) > 12)
655
+ return { ok: false, problem: 'noSuchMonth' };
656
+ const days = daysIn(Number(y), Number(mo));
657
+ if (Number(d) < 1 || Number(d) > days)
658
+ return { ok: false, problem: 'noSuchDay', year: Number(y), month: Number(mo), days };
589
659
  // A time is kept to the minute: seconds are never dropped in silence. A date's box takes the instant's day alone.
590
660
  if (options.time && (Number(seconds) !== 0 || Number(fraction) !== 0))
591
661
  return { ok: false, problem: 'time' };
592
662
  const offset = /^z$/i.test(zone) ? 'Z' : zone.length === 3 ? `${zone}:00` : `${zone.slice(0, 3)}:${zone.slice(-2)}`;
593
- const at = new Date(`${y}-${mo}-${d}T${h}:${mi}:00${offset}`);
594
- if (Number.isNaN(at.getTime()))
663
+ const at = new Date(`${y}-${mo}-${d}T${h}:${mi}:00${offset}`).getTime();
664
+ if (Number.isNaN(at))
595
665
  return { ok: false, problem: 'unfinished' };
596
- const local = localOf(at);
597
- return options.time ? { ok: true, value: local } : { ok: true, value: local.slice(0, 10), timeLeftOut: local.slice(11) };
666
+ // On the clock of the zone it is read in (the computer's own without one): the instant itself said too.
667
+ const local = wallAt(at, options.zone);
668
+ return options.time ? { ok: true, value: local, at } : { ok: true, value: local.slice(0, 10), timeLeftOut: local.slice(11) };
669
+ }
670
+ if (options.time) {
671
+ // A date and time typed with its offset from UTC ("25/10/2026 02:30 +01:00"): the moment it names, the wall clock it
672
+ // is where it is read — the way to write a time the clocks pass twice.
673
+ const zoned = OFFSET_AFTER_TIME.exec(t);
674
+ if (zoned) {
675
+ const [, written, sign, hours, minutes = '00'] = zoned;
676
+ const read = readWritten(written, options);
677
+ if (!read.ok || read.value === null)
678
+ return read;
679
+ const offset = (Number(hours) * 60 + Number(minutes)) * (sign === '+' ? 1 : -1);
680
+ if (Number(hours) > 14 || Number(minutes) > 59)
681
+ return { ok: false, problem: 'time' };
682
+ const wall = (0, zones_1.wallOf)(read.value);
683
+ const at = Date.UTC(wall.year, wall.month - 1, wall.day, wall.hour, wall.minute) - offset * 60000;
684
+ return { ok: true, value: wallAt(at, options.zone), at };
685
+ }
598
686
  }
687
+ const read = readWritten(t, options);
688
+ // A wall clock's time is one moment where it is read: one the clocks skip or pass twice is said, never moved.
689
+ return read.ok && read.value !== null && options.time ? (clocksMove(read.value, options.zone) ?? read) : read;
690
+ }
691
+ /** A time after a date, and an offset from UTC after it: "… 02:30 +01:00", "… 2:30 pm UTC-5". */
692
+ const OFFSET_AFTER_TIME = new RegExp(`^(.*\\d{1,2}\\s*[:hH.]\\s*\\d{2}(?:\\s*(?:${HALF}))?)\\s*(?:UTC|GMT)?\\s*([+\\-\\u2212])(\\d{1,2})(?::?(\\d{2}))?$`, 'i');
693
+ /** A date, and with `time` its time, as written — in words or numbers, in the page's order — as a wall clock reads it. */
694
+ function readWritten(text, options) {
599
695
  // A comma before a space sets a date's parts apart ("Oct 15, 2026") or its time ("15 Oct 2026, 14:30"): no part of either.
600
- t = t.replace(/\s*[,\u060c](?=\s)/g, '');
696
+ const t = text.replace(/\s*[,\u060c](?=\s)/g, '');
601
697
  let datePart = t;
602
698
  let timePart = '';
603
699
  if (options.time) {
@@ -629,6 +725,9 @@ function readDate(text, options = {}) {
629
725
  else {
630
726
  // ISO, whatever the page's order: a four-digit year first.
631
727
  const order = pieces[0].length === 4 ? 'ymd' : (options.order ?? 'dmy');
728
+ // Its year in full last where the page writes it first: written another way than the page's, never read as year 15.
729
+ if (order === 'ymd' && pieces[2].length === 4 && pieces[0].length <= 2)
730
+ return { ok: false, problem: 'order' };
632
731
  [yearText, monthText, dayText] = [pieces[order.indexOf('y')], pieces[order.indexOf('m')], pieces[order.indexOf('d')]];
633
732
  month = Number(monthText);
634
733
  }