@fhss-web-team/fuzzy-dates 1.2.2 → 2.0.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 (48) hide show
  1. package/README.md +99 -36
  2. package/dist/index.d.ts +1 -3
  3. package/dist/index.js +1 -2
  4. package/dist/src/collation/collationKey.d.ts +2 -0
  5. package/dist/src/collation/collationKey.js +44 -0
  6. package/dist/{fuzzyDate → src}/fuzzyDate.d.ts +85 -69
  7. package/dist/src/fuzzyDate.js +238 -0
  8. package/dist/src/gedcomX/toGedcomX.js +28 -0
  9. package/dist/src/helpers/constants.d.ts +2 -0
  10. package/dist/src/helpers/constants.js +2 -0
  11. package/dist/src/helpers/types.d.ts +12 -0
  12. package/dist/src/helpers/types.js +3 -0
  13. package/dist/src/normalize/normalize.js +51 -0
  14. package/dist/src/parse/index.d.ts +47 -0
  15. package/dist/src/parse/index.js +41 -0
  16. package/dist/src/parse/modifiers.d.ts +182 -0
  17. package/dist/src/parse/modifiers.js +62 -0
  18. package/dist/{fuzzyDate/parse/inputDateFormats.d.ts → src/parse/simpleDate/formats.d.ts} +33 -50
  19. package/dist/{fuzzyDate/parse/inputDateFormats.js → src/parse/simpleDate/formats.js} +69 -88
  20. package/dist/src/parse/simpleDate/helpers.d.ts +21 -0
  21. package/dist/src/parse/simpleDate/helpers.js +58 -0
  22. package/dist/src/parse/simpleDate/parse.d.ts +31 -0
  23. package/dist/{fuzzyDate/parse/stringToDate.js → src/parse/simpleDate/parse.js} +4 -5
  24. package/package.json +1 -11
  25. package/dist/fuzzyDate/collate/collate.d.ts +0 -2
  26. package/dist/fuzzyDate/collate/collate.js +0 -15
  27. package/dist/fuzzyDate/fuzzyDate.js +0 -246
  28. package/dist/fuzzyDate/fuzzyDate.spec.d.ts +0 -1
  29. package/dist/fuzzyDate/fuzzyDate.spec.js +0 -158
  30. package/dist/fuzzyDate/gedcomX/toGedcomX.js +0 -31
  31. package/dist/fuzzyDate/helpers/constants.d.ts +0 -4
  32. package/dist/fuzzyDate/helpers/constants.js +0 -20
  33. package/dist/fuzzyDate/helpers/schemas.d.ts +0 -36
  34. package/dist/fuzzyDate/helpers/schemas.js +0 -12
  35. package/dist/fuzzyDate/helpers/types.d.ts +0 -16
  36. package/dist/fuzzyDate/helpers/types.js +0 -1
  37. package/dist/fuzzyDate/normalize/normalize.js +0 -47
  38. package/dist/fuzzyDate/parse/index.d.ts +0 -178
  39. package/dist/fuzzyDate/parse/index.js +0 -92
  40. package/dist/fuzzyDate/parse/modifiers.d.ts +0 -231
  41. package/dist/fuzzyDate/parse/modifiers.js +0 -185
  42. package/dist/fuzzyDate/parse/stringToDate.d.ts +0 -38
  43. /package/dist/{fuzzyDate → src}/gedcomX/toGedcomX.d.ts +0 -0
  44. /package/dist/{fuzzyDate → src}/helpers/result.d.ts +0 -0
  45. /package/dist/{fuzzyDate → src}/helpers/result.js +0 -0
  46. /package/dist/{fuzzyDate → src}/normalize/normalize.d.ts +0 -0
  47. /package/dist/{fuzzyDate/helpers → src/parse/simpleDate}/maps.d.ts +0 -0
  48. /package/dist/{fuzzyDate/helpers → src/parse/simpleDate}/maps.js +0 -0
@@ -1,246 +0,0 @@
1
- import { parse } from './parse/index';
2
- import { normalize } from './normalize/normalize';
3
- import { collate } from './collate/collate';
4
- import { toGedcomX } from './gedcomX/toGedcomX';
5
- import { DATE_NEG_INFINITY, DATE_POS_INFINITY } from './helpers/constants';
6
- import { ok, err } from './helpers/result';
7
- import { fuzzyDateJsonSchema } from './helpers/schemas';
8
- /**
9
- * Represents an immutable, parsed fuzzy date suitable for genealogy and
10
- * historical data contexts.
11
- *
12
- * A `FuzzyDate` encapsulates a human-entered date expression
13
- * (e.g. `"before 1900"`, `"between 1 Jan 1900 and 31 Dec 1901"`,
14
- * `"March 1890"`) and exposes multiple *derived representations* optimized
15
- * for different use cases:
16
- *
17
- * - A canonical JSON model for storage and transport
18
- * - Query bounds for database filtering
19
- * - A collation key for stable chronological ordering
20
- * - A normalized, human-readable string form
21
- * - A GEDCOM X formal date representation
22
- *
23
- * ---
24
- *
25
- * ### Design principles
26
- *
27
- * - **Immutable**: Instances cannot be mutated after construction.
28
- * - **Explicit construction**: Instances must be created via `parse()` or
29
- * `fromJSON()`. Direct construction via `new` is intentionally disallowed.
30
- * - **Derived projections**: Query bounds, collation keys, and serializations
31
- * are all derived from a single internal model to guarantee consistency.
32
- * - **UTC semantics**: All date calculations and comparisons are performed
33
- * using UTC to avoid timezone-related ambiguity.
34
- *
35
- * ---
36
- *
37
- * ### Intended usage
38
- *
39
- * - Use `parse()` for untrusted, human-entered input.
40
- * - Use `fromJSON()` when hydrating from a trusted serialized model
41
- * (e.g. database JSON).
42
- * - Store the JSON model as the canonical representation.
43
- * - Store `lowerBound`, `upperBound`, and `collationKey` separately for
44
- * efficient querying and ordering.
45
- */
46
- export class FuzzyDate {
47
- /**
48
- * Canonical internal model representing the parsed fuzzy date.
49
- *
50
- * This model is considered the single source of truth; all other
51
- * representations are derived from it.
52
- */
53
- _model;
54
- /**
55
- * Private constructor to enforce factory-based creation.
56
- *
57
- * Consumers must use {@link FuzzyDate.parse} or {@link FuzzyDate.fromJSON}.
58
- */
59
- constructor(model) {
60
- this._model = model;
61
- }
62
- /**
63
- * GEDCOM X formal date representation of this fuzzy date.
64
- *
65
- * This value is suitable for interoperability with genealogy systems
66
- * that support the GEDCOM X date specification.
67
- *
68
- * @see https://github.com/FamilySearch/gedcomx/blob/master/specifications/date-format-specification.md
69
- *
70
- * @remarks
71
- * This representation is derived and may evolve as GEDCOM X mapping
72
- * rules are refined.
73
- */
74
- get formal() {
75
- return toGedcomX(this._model);
76
- }
77
- /**
78
- * Normalized, human-readable representation of the fuzzy date.
79
- *
80
- * This format represents the interpreted meaning of the original input
81
- * using a standardized vocabulary and ordering.
82
- *
83
- * Example outputs:
84
- * - `"before 1900"`
85
- * - `"March 1890"`
86
- * - `"between 1 Jan 1900 and 31 Dec 1901"`
87
- */
88
- get normalized() {
89
- return normalize(this._model);
90
- }
91
- /**
92
- * The inclusive lower bound of this fuzzy date's possible interval (UTC).
93
- *
94
- * This bound is intended for **endpoint-in-window** searching:
95
- * a fuzzy date matches an inclusive search range `[searchStart, searchEnd]`
96
- * if **either** its `lowerBound` **or** its `upperBound` falls within the
97
- * search range.
98
- *
99
- * Formally (inclusive):
100
- *
101
- * ```text
102
- * (searchStart <= lowerBound <= searchEnd) OR (searchStart <= upperBound <= searchEnd)
103
- * ```
104
- *
105
- * ## Unbounded intervals
106
- *
107
- * Open-ended intervals are represented with `null`:
108
- * - `lowerBound === null` means unbounded in the past (−∞)
109
- * - `upperBound === null` means unbounded in the future (+∞)
110
- *
111
- * When using the predicate above, treat a `null` bound as not satisfying
112
- * the endpoint check (i.e., it is not “within” any finite window).
113
- *
114
- * @remarks
115
- * - Derived from the canonical model.
116
- * - Always interpreted as UTC.
117
- */
118
- get lowerBound() {
119
- return this._model.start.minDate === DATE_NEG_INFINITY
120
- ? null
121
- : this._model.start.minDate;
122
- }
123
- /**
124
- * The inclusive upper bound of this fuzzy date's possible interval (UTC).
125
- *
126
- * This bound is intended for **endpoint-in-window** searching:
127
- * a fuzzy date matches an inclusive search range `[searchStart, searchEnd]`
128
- * if **either** its `lowerBound` **or** its `upperBound` falls within the
129
- * search range.
130
- *
131
- * Formally (inclusive):
132
- *
133
- * ```text
134
- * (searchStart <= lowerBound <= searchEnd) OR (searchStart <= upperBound <= searchEnd)
135
- * ```
136
- *
137
- * ## Unbounded intervals
138
- *
139
- * Open-ended intervals are represented with `null`:
140
- * - `lowerBound === null` means unbounded in the past (−∞)
141
- * - `upperBound === null` means unbounded in the future (+∞)
142
- *
143
- * When using the predicate above, treat a `null` bound as not satisfying
144
- * the endpoint check (i.e., it is not “within” any finite window).
145
- *
146
- * @remarks
147
- * - Derived from the canonical model.
148
- * - Always interpreted as UTC.
149
- */
150
- get upperBound() {
151
- return this._model.start.maxDate === DATE_POS_INFINITY
152
- ? null
153
- : this._model.start.maxDate;
154
- }
155
- /**
156
- * Collation key used for chronological ordering.
157
- *
158
- * The collation key is a derived string whose lexicographic ordering
159
- * is guaranteed to match the semantic chronological ordering of fuzzy dates.
160
- *
161
- * ```text
162
- * a occurs before b ⇔ a.collationKey < b.collationKey
163
- * ```
164
- *
165
- * This property is intended for:
166
- * - database indexing
167
- * - `ORDER BY` clauses
168
- * - stable sorting without parsing
169
- *
170
- * @remarks
171
- * - Not human-readable
172
- * - Not intended for round-tripping
173
- * - Derived entirely from the canonical model
174
- */
175
- get collationKey() {
176
- return collate(this._model);
177
- }
178
- /**
179
- * Serializes this fuzzy date to its canonical JSON representation.
180
- *
181
- * The returned object is suitable for long-term storage,
182
- * transport, and later reconstruction via {@link fromJSON}.
183
- */
184
- toJSON() {
185
- return {
186
- modifier: this._model.modifier,
187
- start: {
188
- format: this._model.start.format,
189
- minDate: this._model.start.minDate.toISOString(),
190
- maxDate: this._model.start.maxDate.toISOString(),
191
- },
192
- end: {
193
- format: this._model.end.format,
194
- minDate: this._model.end.minDate.toISOString(),
195
- maxDate: this._model.end.maxDate.toISOString(),
196
- },
197
- };
198
- }
199
- /**
200
- * Reconstructs a `FuzzyDate` from a previously serialized canonical model.
201
- *
202
- * @param data A trusted `FuzzyDateModel`, typically loaded from storage.
203
- */
204
- static fromJSON(data) {
205
- try {
206
- const json = typeof data === 'string' ? JSON.parse(data) : data;
207
- const safeJson = fuzzyDateJsonSchema.parse(json);
208
- const model = {
209
- modifier: safeJson.modifier,
210
- start: {
211
- format: safeJson.start.format,
212
- minDate: new Date(safeJson.start.minDate),
213
- maxDate: new Date(safeJson.start.maxDate),
214
- },
215
- end: {
216
- format: safeJson.end.format,
217
- minDate: new Date(safeJson.end.minDate),
218
- maxDate: new Date(safeJson.end.maxDate),
219
- },
220
- };
221
- return ok(new FuzzyDate(model));
222
- }
223
- catch {
224
- return err('Invalid JSON data.');
225
- }
226
- }
227
- /**
228
- * Parses a human-readable date string into a `FuzzyDate`.
229
- *
230
- * This method should be used for all untrusted or user-provided input.
231
- *
232
- * @param input Human-readable date expression.
233
- * @returns A result object containing either a `FuzzyDate` or parse issues.
234
- *
235
- * @remarks
236
- * - Parsing is non-throwing; errors are returned as structured issues.
237
- * - Successful results are guaranteed to produce a valid canonical model.
238
- */
239
- static parse(input) {
240
- const result = parse(input);
241
- if (!result.ok)
242
- return result;
243
- const date = new FuzzyDate(result.value);
244
- return ok(date);
245
- }
246
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,158 +0,0 @@
1
- import { assert, describe, expect, it } from 'vitest';
2
- import { FuzzyDate } from './fuzzyDate';
3
- describe('Fuzzy Date', () => {
4
- it('errs if it parses an invalid date', () => {
5
- const unknownDate = FuzzyDate.parse('a');
6
- expect(unknownDate).toStrictEqual({
7
- ok: false,
8
- error: 'Unknown date format.',
9
- });
10
- const invalidBetween = FuzzyDate.parse('between 1900');
11
- expect(invalidBetween).toStrictEqual({
12
- ok: false,
13
- error: 'Invalid "BETWEEN" modifier.',
14
- });
15
- const invalidFrom = FuzzyDate.parse('from 1900');
16
- expect(invalidFrom).toStrictEqual({
17
- ok: false,
18
- error: 'Invalid "FROM" modifier.',
19
- });
20
- const invalidMonth = FuzzyDate.parse('jam 1900');
21
- expect(invalidMonth).toStrictEqual({
22
- ok: false,
23
- error: 'Unknown month.',
24
- });
25
- });
26
- it('parses date inputs', () => {
27
- const formats = {
28
- '1800s': '1800s',
29
- '1800': '1800',
30
- 'Winter 1800': 'winter 1800',
31
- '1800 Winter': 'winter 1800',
32
- 'Jan 1800': 'January 1800',
33
- 'January 1800': 'January 1800',
34
- '1800 Jan': 'January 1800',
35
- '1800 January': 'January 1800',
36
- '1 Jan 1800': '1 January 1800',
37
- '1 1800': 'January 1800',
38
- '01 1800': 'January 1800',
39
- '1800 1': 'January 1800',
40
- '1800 01': 'January 1800',
41
- '1 January 1800': '1 January 1800',
42
- '01 Jan 1800': '1 January 1800',
43
- '01 January 1800': '1 January 1800',
44
- 'Jan 1 1800': '1 January 1800',
45
- 'January 1 1800': '1 January 1800',
46
- 'Jan 01 1800': '1 January 1800',
47
- 'January 01 1800': '1 January 1800',
48
- '1800 Jan 1': '1 January 1800',
49
- '1800 January 1': '1 January 1800',
50
- '1800 Jan 01': '1 January 1800',
51
- '1800 January 01': '1 January 1800',
52
- '1800 1 Jan': '1 January 1800',
53
- '1800 1 January': '1 January 1800',
54
- '1800 01 Jan': '1 January 1800',
55
- '1800 01 January': '1 January 1800',
56
- '1 1 1800': '1 January 1800',
57
- '01 1 1800': '1 January 1800',
58
- '1 01 1800': '1 January 1800',
59
- '1800 1 1': '1 January 1800',
60
- '1800 01 1': '1 January 1800',
61
- '1800 1 01': '1 January 1800',
62
- ' 1st of February 1900 ': '1 February 1900',
63
- 'feb 1st, 1900': '1 February 1900',
64
- 'February 1st, 1900': '1 February 1900',
65
- ' ---___////,,,, sEpTeMbEr 30 th ,,, ---// 1984': '30 September 1984',
66
- };
67
- for (const [key, value] of Object.entries(formats)) {
68
- const result = FuzzyDate.parse(key);
69
- let normal;
70
- if (result.ok) {
71
- normal = result.value.normalized;
72
- }
73
- else {
74
- normal = result.error;
75
- }
76
- expect(normal).toStrictEqual(value);
77
- }
78
- });
79
- it('test modifiers parsing', () => {
80
- const modifiers = [
81
- '',
82
- 'before',
83
- 'after',
84
- 'about',
85
- 'between',
86
- 'from',
87
- 'early',
88
- 'mid',
89
- 'late',
90
- 'or',
91
- ];
92
- const parse = {
93
- 'before 1st of February 1900': 'before 1 February 1900',
94
- 'after 1st of February 1900': 'after 1 February 1900',
95
- 'about 1st of February 1900': 'about 1 February 1900',
96
- 'between 1st of February 1900 and 18/03/1905': 'between 1 February 1900 and 18 March 1905',
97
- 'from 1st of February 1900 to 18/03/1905': 'from 1 February 1900 to 18 March 1905',
98
- };
99
- Object.entries(parse).forEach(([input, expected]) => {
100
- const result = FuzzyDate.parse(input);
101
- let normal;
102
- if (result.ok) {
103
- normal = result.value.normalized;
104
- }
105
- else {
106
- normal = result.error;
107
- }
108
- expect(normal).toStrictEqual(expected);
109
- });
110
- });
111
- it('orders serialized dates in chronological order', () => {
112
- const order = [
113
- 'before 2000',
114
- 'before jan 2000',
115
- 'before jan 1 2000',
116
- 'about 2000',
117
- 'about jan 2000',
118
- 'about jan 1 2000',
119
- '2000',
120
- 'jan 2000',
121
- 'jan 1 2000',
122
- 'from 2000 to 2001',
123
- 'from 2000 to jan 2001',
124
- 'from 2000 to 1 jan 2001',
125
- 'from jan 2000 to 2001',
126
- 'from jan 2000 to jan 2001',
127
- 'from jan 2000 to 1 jan 2001',
128
- 'from 1 jan 2000 to 2001',
129
- 'from 1 jan 2000 to jan 2001',
130
- 'from 1 jan 2000 to 1 jan 2001',
131
- 'between 2000 and 2001',
132
- 'between 2000 and jan 2001',
133
- 'between 2000 and 1 jan 2001',
134
- 'between jan 2000 and 2001',
135
- 'between jan 2000 and jan 2001',
136
- 'between jan 2000 and 1 jan 2001',
137
- 'between 1 jan 2000 and 2001',
138
- 'between 1 jan 2000 and jan 2001',
139
- 'between 1 jan 2000 and 1 jan 2001',
140
- 'after 2000',
141
- 'after jan 2000',
142
- 'after jan 1 2000',
143
- 'jan 2 2000',
144
- 'dec 31 2000',
145
- '',
146
- ];
147
- for (let i = 1; i < order.length - 1; i++) {
148
- const previous = FuzzyDate.parse(order[i - 1]);
149
- const current = FuzzyDate.parse(order[i]);
150
- if (previous.ok && current.ok) {
151
- expect(previous.value.collationKey < current.value.collationKey).toBeTruthy();
152
- }
153
- else {
154
- assert.fail('failed to parse input');
155
- }
156
- }
157
- });
158
- });
@@ -1,31 +0,0 @@
1
- export function toGedcomX(model) {
2
- const startDate = toSimpleDate(model.start);
3
- const endDate = toSimpleDate(model.end);
4
- const gedcomModifierMap = {
5
- NONE: startDate,
6
- BEFORE: `/${endDate}`,
7
- AFTER: `${startDate}/`,
8
- EARLY: `${startDate}/${endDate}`,
9
- MID: `${startDate}/${endDate}`,
10
- LATE: `${startDate}/${endDate}`,
11
- FROM: `${startDate}/${endDate}`,
12
- BETWEEN: `${startDate}/${endDate}`,
13
- ABOUT: `A${startDate}`,
14
- };
15
- return gedcomModifierMap[model.modifier];
16
- }
17
- function toSimpleDate(dateValue) {
18
- const year = dateValue.minDate.getUTCFullYear();
19
- const month = dateValue.minDate.getUTCMonth() + 1;
20
- const day = dateValue.minDate.getUTCDate();
21
- const sign = year >= 0 ? '+' : '-';
22
- const yearAbs = Math.abs(year);
23
- const yyyy = String(yearAbs).padStart(4, '0');
24
- if (dateValue.format === 'YYYY' || dateValue.format === 'YYYYs')
25
- return `${sign}${yyyy}`;
26
- const mm = String(month).padStart(2, '0');
27
- if (dateValue.format === 'MMMM_YYYY' || dateValue.format === 'SEASON_YYYY')
28
- return `${sign}${yyyy}-${mm}`;
29
- const dd = String(day).padStart(2, '0');
30
- return `${sign}${yyyy}-${mm}-${dd}`;
31
- }
@@ -1,4 +0,0 @@
1
- export declare const DATE_NEG_INFINITY: Date;
2
- export declare const DATE_POS_INFINITY: Date;
3
- export declare const FORMAT_ORDER: readonly ["YYYYs", "YYYY", "SEASON_YYYY", "MMMM_YYYY", "D_MMMM_YYYY"];
4
- export declare const MODIFIER_ORDER: readonly ["BEFORE", "ABOUT", "NONE", "EARLY", "MID", "LATE", "FROM", "BETWEEN", "AFTER"];
@@ -1,20 +0,0 @@
1
- export const DATE_NEG_INFINITY = new Date(-8640000000000000);
2
- export const DATE_POS_INFINITY = new Date(8640000000000000);
3
- export const FORMAT_ORDER = [
4
- 'YYYYs',
5
- 'YYYY',
6
- 'SEASON_YYYY',
7
- 'MMMM_YYYY',
8
- 'D_MMMM_YYYY',
9
- ];
10
- export const MODIFIER_ORDER = [
11
- 'BEFORE',
12
- 'ABOUT',
13
- 'NONE',
14
- 'EARLY',
15
- 'MID',
16
- 'LATE',
17
- 'FROM',
18
- 'BETWEEN',
19
- 'AFTER',
20
- ];
@@ -1,36 +0,0 @@
1
- import z from 'zod';
2
- export declare const fuzzyDateJsonSchema: z.ZodObject<{
3
- modifier: z.ZodEnum<{
4
- BEFORE: "BEFORE";
5
- ABOUT: "ABOUT";
6
- NONE: "NONE";
7
- EARLY: "EARLY";
8
- MID: "MID";
9
- LATE: "LATE";
10
- FROM: "FROM";
11
- BETWEEN: "BETWEEN";
12
- AFTER: "AFTER";
13
- }>;
14
- start: z.ZodObject<{
15
- format: z.ZodEnum<{
16
- YYYYs: "YYYYs";
17
- YYYY: "YYYY";
18
- SEASON_YYYY: "SEASON_YYYY";
19
- MMMM_YYYY: "MMMM_YYYY";
20
- D_MMMM_YYYY: "D_MMMM_YYYY";
21
- }>;
22
- minDate: z.ZodISODateTime;
23
- maxDate: z.ZodISODateTime;
24
- }, z.core.$strip>;
25
- end: z.ZodObject<{
26
- format: z.ZodEnum<{
27
- YYYYs: "YYYYs";
28
- YYYY: "YYYY";
29
- SEASON_YYYY: "SEASON_YYYY";
30
- MMMM_YYYY: "MMMM_YYYY";
31
- D_MMMM_YYYY: "D_MMMM_YYYY";
32
- }>;
33
- minDate: z.ZodISODateTime;
34
- maxDate: z.ZodISODateTime;
35
- }, z.core.$strip>;
36
- }, z.core.$strip>;
@@ -1,12 +0,0 @@
1
- import z from 'zod';
2
- import { FORMAT_ORDER, MODIFIER_ORDER } from './constants';
3
- const fuzzyDateValueSchema = z.object({
4
- format: z.enum(FORMAT_ORDER),
5
- minDate: z.iso.datetime(),
6
- maxDate: z.iso.datetime(),
7
- });
8
- export const fuzzyDateJsonSchema = z.object({
9
- modifier: z.enum(MODIFIER_ORDER),
10
- start: fuzzyDateValueSchema,
11
- end: fuzzyDateValueSchema,
12
- });
@@ -1,16 +0,0 @@
1
- import z from 'zod';
2
- import { FORMAT_ORDER, MODIFIER_ORDER } from './constants';
3
- import { fuzzyDateJsonSchema } from './schemas';
4
- export type FuzzyDateFormat = (typeof FORMAT_ORDER)[number];
5
- export type FuzzyDate = (typeof MODIFIER_ORDER)[number];
6
- export type FuzzyDateValue = {
7
- format: FuzzyDateFormat;
8
- minDate: Date;
9
- maxDate: Date;
10
- };
11
- export type FuzzyDateModel = {
12
- modifier: FuzzyDate;
13
- start: FuzzyDateValue;
14
- end: FuzzyDateValue;
15
- };
16
- export type FuzzyDateJson = z.infer<typeof fuzzyDateJsonSchema>;
@@ -1 +0,0 @@
1
- export {};
@@ -1,47 +0,0 @@
1
- import { isSeasonMonth, SEASON_MONTH_MAP } from '../helpers/maps';
2
- // Main function
3
- export function normalize(model) {
4
- const startDate = normalizeDate(model.start.minDate, model.start.format);
5
- const endDate = normalizeDate(model.end.minDate, model.end.format);
6
- const normalModifierMap = {
7
- NONE: startDate,
8
- BEFORE: `before ${endDate}`,
9
- AFTER: `after ${startDate}`,
10
- EARLY: `early ${startDate}`,
11
- MID: `mid ${startDate}`,
12
- LATE: `late ${startDate}`,
13
- FROM: `from ${startDate} to ${endDate}`,
14
- BETWEEN: `between ${startDate} and ${endDate}`,
15
- ABOUT: `about ${startDate}`,
16
- };
17
- return normalModifierMap[model.modifier];
18
- }
19
- // Helpers
20
- function normalizeDate(date, format) {
21
- const options = { timeZone: 'UTC' };
22
- switch (format) {
23
- case 'YYYY':
24
- options.year = 'numeric';
25
- break;
26
- case 'MMMM_YYYY':
27
- options.month = 'long';
28
- options.year = 'numeric';
29
- break;
30
- case 'D_MMMM_YYYY':
31
- options.day = 'numeric';
32
- options.month = 'long';
33
- options.year = 'numeric';
34
- break;
35
- case 'SEASON_YYYY': {
36
- const month = date.getUTCMonth() + 1;
37
- if (!isSeasonMonth(month))
38
- return ''; // month always returns 1 - 12 so this should never happen
39
- return `${SEASON_MONTH_MAP[month]} ${date.getUTCFullYear()}`;
40
- }
41
- case 'YYYYs': {
42
- const year = date.getUTCFullYear();
43
- return `${year}s`;
44
- }
45
- }
46
- return date.toLocaleDateString('en-GB', options);
47
- }