chronolizer 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chronolizer contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # Chronolizer
2
+
3
+ Chronolizer is an Effect v4 library for bidirectional natural-language date ranges.
4
+
5
+ It converts complete English and German date-range expressions to a small date-math format. It also renders supported ranges to canonical natural language and resolves the AST with an explicit Effect time zone.
6
+
7
+ ## Status
8
+
9
+ This package uses `effect@4.0.0-rc.112`. Its API can change while Effect v4 is in release-candidate status.
10
+
11
+ ## Main concepts
12
+
13
+ - Effect Schema owns the date AST, filter data, results, and errors.
14
+ - Complete periods are half-open: `[start, next period start)`.
15
+ - Open ranges are supported.
16
+ - Weeks always start on Monday.
17
+ - Parsing does not read the clock or host time zone.
18
+ - Resolution requires `DateTime.CurrentTimeZone`.
19
+ - Natural rendering is canonical. It does not reproduce the source wording.
20
+ - Typo correction is conservative and optional. It does not change numbers, ISO dates, or short ambiguous words.
21
+
22
+ ## Natural language to filter
23
+
24
+ ```ts
25
+ import { DefaultLanguageLayer, formatFilter, parseNatural } from "chronolizer";
26
+ import { Effect } from "effect";
27
+
28
+ const run = Effect.fn(function* () {
29
+ const result = yield* parseNatural("January of last year", {
30
+ locale: "en",
31
+ typoMode: "strict",
32
+ });
33
+
34
+ return formatFilter(result.range);
35
+ }, Effect.provide(DefaultLanguageLayer));
36
+
37
+ const program = run();
38
+
39
+ // { gte: "now-1y/y", lt: "now-1y/y+1M" }
40
+ ```
41
+
42
+ Other examples:
43
+
44
+ ```text
45
+ year to date -> { gte: "now/y", lte: "now" }
46
+ January 2025 -> { gte: "2025-01-01", lt: "2025-02-01" }
47
+ since January 2025 -> { gte: "2025-01-01" }
48
+ before January 2025 -> { lt: "2025-01-01" }
49
+ through January 2025 -> { lt: "2025-02-01" }
50
+ last 3 months -> { gte: "now-3M", lte: "now" }
51
+ 30 months ago -> { gte: "now-30M/M", lt: "now-30M/M+1M" }
52
+ 01 January 2025 - 31 January 2025
53
+ -> { gte: "2025-01-01", lt: "2025-02-01" }
54
+ Q1 2025 -> { gte: "2025-01-01", lt: "2025-04-01" }
55
+ Januar letzten Jahres -> { gte: "now-1y/y", lt: "now-1y/y+1M" }
56
+ die letzten 3 Monate -> { gte: "now-3M", lte: "now" }
57
+ seit Jahresbeginn -> { gte: "now/y", lte: "now" }
58
+ seit Januar 2025 -> { gte: "2025-01-01" }
59
+ ```
60
+
61
+ Chronolizer parses the complete input. It does not extract a date phrase from a larger sentence.
62
+
63
+ Supported families include named and abbreviated months, named dates, quarters, weekends, period starts and ends, past and future rolling windows, calendar offsets, open boundaries, `now`-bounded ranges, and explicit inclusive connectors. English and German use their own grammar and canonical forms.
64
+
65
+ ### Exclude positive relative ranges
66
+
67
+ Set `allowFuture: false` to reject expressions whose relative range extends after `now`:
68
+
69
+ ```ts
70
+ const program = parseNatural("next 3 months", {
71
+ locale: "en",
72
+ allowFuture: false,
73
+ });
74
+ ```
75
+
76
+ This option rejects relative forms such as `next month`, `this year`, and `in 3 years`. It does not classify fixed dates such as `January 2099`, because parsing does not read the clock.
77
+
78
+ ## Filter to natural language
79
+
80
+ ```ts
81
+ import { DefaultLanguageLayer, formatNatural, parseFilter } from "chronolizer";
82
+ import { Effect } from "effect";
83
+
84
+ const run = Effect.fn(function* () {
85
+ const range = yield* parseFilter({
86
+ gte: "2025-01-01",
87
+ lt: "2025-02-01",
88
+ });
89
+
90
+ return yield* formatNatural(range, { locale: "de" });
91
+ }, Effect.provide(DefaultLanguageLayer));
92
+
93
+ const program = run();
94
+
95
+ // "Januar 2025"
96
+ ```
97
+
98
+ Natural language is many-to-one. Chronolizer therefore guarantees semantic round trips, not the original words.
99
+
100
+ ## Validate external filters
101
+
102
+ `parseFilter` accepts a validated `DateFilter`. Decode external data with the exported Schema first:
103
+
104
+ ```ts
105
+ import { DateFilter, parseFilter } from "chronolizer";
106
+ import { Effect, Schema } from "effect";
107
+
108
+ const decodeDateFilter = Schema.decodeUnknownEffect(DateFilter);
109
+
110
+ const program = decodeDateFilter(externalInput).pipe(Effect.flatMap(parseFilter));
111
+ ```
112
+
113
+ A filter has at most one lower bound (`gt` or `gte`), at most one upper bound (`lt` or `lte`), and at least one bound.
114
+
115
+ ## Compact expression syntax
116
+
117
+ ```text
118
+ expression := anchor operation*
119
+ anchor := "now" | YYYY-MM-DD | YYYY-MM-DD "||"
120
+ operation := ("+" | "-") positiveInteger unit | "/" unit
121
+ unit := "d" | "w" | "M" | "q" | "y"
122
+ ```
123
+
124
+ Examples:
125
+
126
+ ```text
127
+ now/y
128
+ now-1y/y
129
+ now-1y/y+1M
130
+ 2025-01-01
131
+ 2025-01-01||+1M
132
+ ```
133
+
134
+ Operations run from left to right. `/unit` floors to the start of the calendar unit. Fixed dates require `||` before operations.
135
+
136
+ ## Resolve with an explicit time zone
137
+
138
+ ```ts
139
+ import { parseFilter, resolve } from "chronolizer";
140
+ import { DateTime, Effect } from "effect";
141
+
142
+ const run = Effect.fn(function* () {
143
+ const range = yield* parseFilter({ gte: "now/y", lte: "now" });
144
+ return yield* resolve(range);
145
+ }, DateTime.withCurrentZoneNamed("Europe/Berlin"));
146
+
147
+ const program = run();
148
+ ```
149
+
150
+ The resolver uses Effect Clock and `DateTime.CurrentTimeZone`. It never uses the host local time zone without an explicit caller decision. Named zones use the runtime ICU time-zone data.
151
+
152
+ ## Tolerant parsing
153
+
154
+ ```ts
155
+ const program = parseNatural("januray of last yaer", {
156
+ locale: "en",
157
+ typoMode: "tolerant",
158
+ });
159
+ ```
160
+
161
+ The result reports:
162
+
163
+ - `quality`: `exact`, `corrected`, or `ambiguous`;
164
+ - each correction and edit distance;
165
+ - semantic alternatives for equal-cost ties.
166
+
167
+ Strict mode never runs correction.
168
+
169
+ ## Language plugins
170
+
171
+ `LanguageRegistry` is an Effect service. A language is a scoped plugin contribution. `languagePluginsLayer` validates plugin identifiers, rejects conflicting base languages, applies deterministic extension order, and removes registrations when the Layer scope closes.
172
+
173
+ Language identifiers use canonical BCP 47 base tags. Lookup removes one subtag at a time. For example, `zh-Hant-TW` tries `zh-Hant-TW`, `zh-Hant`, and then `zh`.
174
+
175
+ Each base language owns:
176
+
177
+ - exact parsing and canonical rendering;
178
+ - optional text normalization;
179
+ - its typo-correction strategy, which can be disabled;
180
+ - vocabulary shared with registered language extensions.
181
+
182
+ `normalizeNaturalText` and `correctWhitespaceSeparatedText` are available for languages that use whitespace-separated words. A compact-script language can provide character, dictionary, or `Intl.Segmenter` based correction without changing Chronolizer core.
183
+
184
+ Built-in plugins:
185
+
186
+ - `EnglishLanguage`
187
+ - `GermanLanguage`
188
+ - `DefaultLanguageLayer`
189
+
190
+ Chinese and Japanese language packs are not included yet.
191
+
192
+ Business calendars, holidays, times of day, recurrence, and sentence extraction are outside v1. A future business-day feature will use an injected calendar service.
193
+
194
+ ## License
195
+
196
+ Chronolizer is available under the [MIT License](LICENSE).