@fhss-web-team/fuzzy-dates 1.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.
@@ -0,0 +1,31 @@
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
+ }
@@ -0,0 +1,2 @@
1
+ import { FuzzyDateModel } from '../types';
2
+ export declare function normalize(model: FuzzyDateModel): string;
@@ -0,0 +1,47 @@
1
+ import { SEASON_MONTH_MAP, isSeasonMonth, } from '../types';
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
+ }
@@ -0,0 +1,187 @@
1
+ import { NormalFormat, FuzzyDateValue } from '../types';
2
+ export declare function parse(input: string): {
3
+ ok: false;
4
+ error: "Year is required.";
5
+ } | {
6
+ ok: false;
7
+ error: "Unknown month.";
8
+ } | {
9
+ ok: false;
10
+ error: "Unknown date format.";
11
+ } | {
12
+ ok: true;
13
+ value: {
14
+ readonly original: string;
15
+ readonly modifier: "NONE";
16
+ readonly start: {
17
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
18
+ readonly minDate: Date;
19
+ readonly maxDate: Date;
20
+ };
21
+ readonly end: {
22
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
23
+ readonly minDate: Date;
24
+ readonly maxDate: Date;
25
+ };
26
+ };
27
+ } | {
28
+ ok: true;
29
+ value: {
30
+ readonly original: string;
31
+ readonly modifier: "BEFORE";
32
+ readonly start: {
33
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
34
+ readonly minDate: Date;
35
+ readonly maxDate: Date;
36
+ };
37
+ readonly end: {
38
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
39
+ readonly minDate: Date;
40
+ readonly maxDate: Date;
41
+ };
42
+ };
43
+ } | {
44
+ ok: true;
45
+ value: {
46
+ readonly original: string;
47
+ readonly modifier: "AFTER";
48
+ readonly start: {
49
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
50
+ readonly minDate: Date;
51
+ readonly maxDate: Date;
52
+ };
53
+ readonly end: {
54
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
55
+ readonly minDate: Date;
56
+ readonly maxDate: Date;
57
+ };
58
+ };
59
+ } | {
60
+ ok: true;
61
+ value: {
62
+ readonly original: string;
63
+ readonly modifier: "ABOUT";
64
+ readonly start: {
65
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
66
+ readonly minDate: Date;
67
+ readonly maxDate: Date;
68
+ };
69
+ readonly end: {
70
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
71
+ readonly minDate: Date;
72
+ readonly maxDate: Date;
73
+ };
74
+ };
75
+ } | {
76
+ ok: false;
77
+ error: "Invalid \"BETWEEN\" modifier.";
78
+ } | {
79
+ ok: true;
80
+ value: {
81
+ readonly original: string;
82
+ readonly modifier: "BETWEEN";
83
+ readonly start: {
84
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
85
+ readonly minDate: Date;
86
+ readonly maxDate: Date;
87
+ };
88
+ readonly end: {
89
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
90
+ readonly minDate: Date;
91
+ readonly maxDate: Date;
92
+ };
93
+ };
94
+ } | {
95
+ ok: false;
96
+ error: "Invalid \"FROM\" modifier.";
97
+ } | {
98
+ ok: true;
99
+ value: {
100
+ readonly original: string;
101
+ readonly modifier: "FROM";
102
+ readonly start: {
103
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
104
+ readonly minDate: Date;
105
+ readonly maxDate: Date;
106
+ };
107
+ readonly end: {
108
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
109
+ readonly minDate: Date;
110
+ readonly maxDate: Date;
111
+ };
112
+ };
113
+ } | {
114
+ ok: true;
115
+ value: {
116
+ readonly original: string;
117
+ readonly modifier: "EARLY";
118
+ readonly start: {
119
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
120
+ readonly minDate: Date;
121
+ readonly maxDate: Date;
122
+ };
123
+ readonly end: {
124
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
125
+ readonly minDate: Date;
126
+ readonly maxDate: Date;
127
+ };
128
+ };
129
+ } | {
130
+ ok: true;
131
+ value: {
132
+ readonly original: string;
133
+ readonly modifier: "MID";
134
+ readonly start: {
135
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
136
+ readonly minDate: Date;
137
+ readonly maxDate: Date;
138
+ };
139
+ readonly end: {
140
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
141
+ readonly minDate: Date;
142
+ readonly maxDate: Date;
143
+ };
144
+ };
145
+ } | {
146
+ ok: true;
147
+ value: {
148
+ readonly original: string;
149
+ readonly modifier: "LATE";
150
+ readonly start: {
151
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
152
+ readonly minDate: Date;
153
+ readonly maxDate: Date;
154
+ };
155
+ readonly end: {
156
+ readonly format: "YYYYs" | "YYYY" | "SEASON_YYYY" | "MMMM_YYYY" | "D_MMMM_YYYY";
157
+ readonly minDate: Date;
158
+ readonly maxDate: Date;
159
+ };
160
+ };
161
+ };
162
+ export declare function getTimes(date: FuzzyDateValue): {
163
+ start: number;
164
+ end: number;
165
+ half: number;
166
+ };
167
+ export declare function calculateMaxDate(start: Date, format: NormalFormat): Date;
168
+ export declare function parseDateGroups(groups: {
169
+ day?: string;
170
+ month?: string;
171
+ year?: string;
172
+ decade?: string;
173
+ }): {
174
+ ok: false;
175
+ error: "Year is required.";
176
+ } | {
177
+ ok: false;
178
+ error: "Unknown month.";
179
+ } | {
180
+ ok: false;
181
+ error: "Unknown date format.";
182
+ } | {
183
+ ok: true;
184
+ value: {
185
+ date: Date;
186
+ };
187
+ };
@@ -0,0 +1,91 @@
1
+ import { about, after, before, between, early, from, late, mid, none, } from './modifiers';
2
+ import { ok, err, MONTH_NAME_MAP, MONTH_SEASON_MAP, isMonth, isSeason, } from '../types';
3
+ // Main Parse Function
4
+ export function parse(input) {
5
+ const cleanedInput = input
6
+ .toLowerCase() // lowercases
7
+ .replace(/(\d+)\s*(st|nd|rd|th|of)\b(\s*of)?/gi, '$1') // removes ordinal suffixes and 'of'
8
+ .replace(/[.,/\-_]+/g, ' ') // replaces separators with spaces
9
+ .replace(/\s+/g, ' ') // collapses multiple spaces to a single space
10
+ .trim(); // Removes trailing and leading spaces
11
+ if (cleanedInput.startsWith('before '))
12
+ return before(cleanedInput, input);
13
+ if (cleanedInput.startsWith('after '))
14
+ return after(cleanedInput, input);
15
+ if (cleanedInput.startsWith('about '))
16
+ return about(cleanedInput, input);
17
+ if (cleanedInput.startsWith('between '))
18
+ return between(cleanedInput, input);
19
+ if (cleanedInput.startsWith('from '))
20
+ return from(cleanedInput, input);
21
+ if (cleanedInput.startsWith('early '))
22
+ return early(cleanedInput, input);
23
+ if (cleanedInput.startsWith('mid '))
24
+ return mid(cleanedInput, input);
25
+ if (cleanedInput.startsWith('late '))
26
+ return late(cleanedInput, input);
27
+ return none(cleanedInput, input);
28
+ }
29
+ // Helpers
30
+ export function getTimes(date) {
31
+ const start = date.minDate.getTime();
32
+ const end = date.maxDate.getTime() + 1; //Adds a milisecond for accurate date calculations
33
+ const half = (end - start) / 2;
34
+ return { start, end, half };
35
+ }
36
+ export function calculateMaxDate(start, format) {
37
+ const endDate = new Date(start);
38
+ if (format === 'MMMM_YYYY') {
39
+ endDate.setUTCMonth(endDate.getUTCMonth() + 1);
40
+ endDate.setMilliseconds(-1);
41
+ }
42
+ if (format === 'SEASON_YYYY') {
43
+ endDate.setUTCMonth(endDate.getUTCMonth() + 3);
44
+ endDate.setMilliseconds(-1);
45
+ }
46
+ if (format === 'YYYY') {
47
+ endDate.setUTCFullYear(endDate.getUTCFullYear() + 1);
48
+ endDate.setUTCMilliseconds(-1);
49
+ }
50
+ if (format === 'YYYYs') {
51
+ endDate.setUTCFullYear(endDate.getUTCFullYear() + 10);
52
+ endDate.setUTCMilliseconds(-1);
53
+ }
54
+ if (format === 'D_MMMM_YYYY') {
55
+ endDate.setUTCDate(endDate.getUTCDate() + 1);
56
+ endDate.setUTCMilliseconds(-1);
57
+ }
58
+ return endDate;
59
+ }
60
+ export function parseDateGroups(groups) {
61
+ if (!groups.year && !groups.decade)
62
+ return err('Year is required.');
63
+ const year = groups.decade
64
+ ? Number(groups.decade.replace(/(\d+)s$/, '$1')) // 'YYYYs' -> 'YYYY'
65
+ : Number(groups.year);
66
+ let month = 0; //default to January
67
+ if (groups.month) {
68
+ const result = resolveMonth(groups.month.toLowerCase());
69
+ if (!result.ok)
70
+ return result;
71
+ month = result.value.monthNumber;
72
+ }
73
+ let day = 1; //default to 1st day
74
+ if (groups.day) {
75
+ const dayToken = /^(?:0?[1-9]|[12][0-9]|3[01])$/.exec(groups.day)?.[0];
76
+ if (!dayToken)
77
+ return err('Unknown date format.');
78
+ day = Number(dayToken);
79
+ }
80
+ return ok({ date: new Date(Date.UTC(year, month, day)) });
81
+ }
82
+ function resolveMonth(rawMonth) {
83
+ const monthToken = /^(?:0?[1-9]|1[0-2])$/.exec(rawMonth)?.[0]; //Matches 01-09 or 1-9 or 10-12
84
+ if (monthToken)
85
+ return ok({ monthNumber: Number(monthToken) - 1 }); //months are zero-based
86
+ if (isMonth(rawMonth))
87
+ return ok({ monthNumber: MONTH_NAME_MAP[rawMonth] - 1 });
88
+ if (isSeason(rawMonth))
89
+ return ok({ monthNumber: MONTH_SEASON_MAP[rawMonth] - 1 });
90
+ return err('Unknown month.');
91
+ }
@@ -0,0 +1,204 @@
1
+ export declare const year: (rawDate: string) => {
2
+ ok: false;
3
+ error: "Year is required.";
4
+ } | {
5
+ ok: false;
6
+ error: "Unknown month.";
7
+ } | {
8
+ ok: false;
9
+ error: "Unknown date format.";
10
+ } | {
11
+ ok: true;
12
+ value: {
13
+ readonly format: "YYYY";
14
+ readonly minDate: Date;
15
+ readonly maxDate: Date;
16
+ };
17
+ } | null;
18
+ export declare const monthStringYear: (rawDate: string) => {
19
+ ok: false;
20
+ error: "Year is required.";
21
+ } | {
22
+ ok: false;
23
+ error: "Unknown month.";
24
+ } | {
25
+ ok: false;
26
+ error: "Unknown date format.";
27
+ } | {
28
+ ok: true;
29
+ value: {
30
+ readonly format: "SEASON_YYYY" | "MMMM_YYYY";
31
+ readonly minDate: Date;
32
+ readonly maxDate: Date;
33
+ };
34
+ } | null;
35
+ export declare const yearMonthString: (rawDate: string) => {
36
+ ok: false;
37
+ error: "Year is required.";
38
+ } | {
39
+ ok: false;
40
+ error: "Unknown month.";
41
+ } | {
42
+ ok: false;
43
+ error: "Unknown date format.";
44
+ } | {
45
+ ok: true;
46
+ value: {
47
+ readonly format: "SEASON_YYYY" | "MMMM_YYYY";
48
+ readonly minDate: Date;
49
+ readonly maxDate: Date;
50
+ };
51
+ } | null;
52
+ export declare const dayMonthStringYear: (rawDate: string) => {
53
+ ok: false;
54
+ error: "Year is required.";
55
+ } | {
56
+ ok: false;
57
+ error: "Unknown month.";
58
+ } | {
59
+ ok: false;
60
+ error: "Unknown date format.";
61
+ } | {
62
+ ok: true;
63
+ value: {
64
+ readonly format: "D_MMMM_YYYY";
65
+ readonly minDate: Date;
66
+ readonly maxDate: Date;
67
+ };
68
+ } | null;
69
+ export declare const monthStringDayYear: (rawDate: string) => {
70
+ ok: false;
71
+ error: "Year is required.";
72
+ } | {
73
+ ok: false;
74
+ error: "Unknown month.";
75
+ } | {
76
+ ok: false;
77
+ error: "Unknown date format.";
78
+ } | {
79
+ ok: true;
80
+ value: {
81
+ readonly format: "D_MMMM_YYYY";
82
+ readonly minDate: Date;
83
+ readonly maxDate: Date;
84
+ };
85
+ } | null;
86
+ export declare const yearMonthStringDay: (rawDate: string) => {
87
+ ok: false;
88
+ error: "Year is required.";
89
+ } | {
90
+ ok: false;
91
+ error: "Unknown month.";
92
+ } | {
93
+ ok: false;
94
+ error: "Unknown date format.";
95
+ } | {
96
+ ok: true;
97
+ value: {
98
+ readonly format: "D_MMMM_YYYY";
99
+ readonly minDate: Date;
100
+ readonly maxDate: Date;
101
+ };
102
+ } | null;
103
+ export declare const yearDayMonthString: (rawDate: string) => {
104
+ ok: false;
105
+ error: "Year is required.";
106
+ } | {
107
+ ok: false;
108
+ error: "Unknown month.";
109
+ } | {
110
+ ok: false;
111
+ error: "Unknown date format.";
112
+ } | {
113
+ ok: true;
114
+ value: {
115
+ readonly format: "D_MMMM_YYYY";
116
+ readonly minDate: Date;
117
+ readonly maxDate: Date;
118
+ };
119
+ } | null;
120
+ export declare const monthDigitYear: (rawDate: string) => {
121
+ ok: false;
122
+ error: "Year is required.";
123
+ } | {
124
+ ok: false;
125
+ error: "Unknown month.";
126
+ } | {
127
+ ok: false;
128
+ error: "Unknown date format.";
129
+ } | {
130
+ ok: true;
131
+ value: {
132
+ readonly format: "MMMM_YYYY";
133
+ readonly minDate: Date;
134
+ readonly maxDate: Date;
135
+ };
136
+ } | null;
137
+ export declare const yearMonthDigit: (rawDate: string) => {
138
+ ok: false;
139
+ error: "Year is required.";
140
+ } | {
141
+ ok: false;
142
+ error: "Unknown month.";
143
+ } | {
144
+ ok: false;
145
+ error: "Unknown date format.";
146
+ } | {
147
+ ok: true;
148
+ value: {
149
+ readonly format: "MMMM_YYYY";
150
+ readonly minDate: Date;
151
+ readonly maxDate: Date;
152
+ };
153
+ } | null;
154
+ export declare const dayMonthDigitYear: (rawDate: string) => {
155
+ ok: false;
156
+ error: "Year is required.";
157
+ } | {
158
+ ok: false;
159
+ error: "Unknown month.";
160
+ } | {
161
+ ok: false;
162
+ error: "Unknown date format.";
163
+ } | {
164
+ ok: true;
165
+ value: {
166
+ readonly format: "D_MMMM_YYYY";
167
+ readonly minDate: Date;
168
+ readonly maxDate: Date;
169
+ };
170
+ } | null;
171
+ export declare const yearMonthDigitDay: (rawDate: string) => {
172
+ ok: false;
173
+ error: "Year is required.";
174
+ } | {
175
+ ok: false;
176
+ error: "Unknown month.";
177
+ } | {
178
+ ok: false;
179
+ error: "Unknown date format.";
180
+ } | {
181
+ ok: true;
182
+ value: {
183
+ readonly format: "D_MMMM_YYYY";
184
+ readonly minDate: Date;
185
+ readonly maxDate: Date;
186
+ };
187
+ } | null;
188
+ export declare const decade: (rawDate: string) => {
189
+ ok: false;
190
+ error: "Year is required.";
191
+ } | {
192
+ ok: false;
193
+ error: "Unknown month.";
194
+ } | {
195
+ ok: false;
196
+ error: "Unknown date format.";
197
+ } | {
198
+ ok: true;
199
+ value: {
200
+ readonly format: "YYYYs";
201
+ readonly minDate: Date;
202
+ readonly maxDate: Date;
203
+ };
204
+ } | null;