@samline/date 2.0.0 → 2.1.1

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/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # @samline/date
2
2
 
3
- Small date formatting package built on top of Day.js with a shared core API, framework wrappers, and browser usage.
3
+ Small date formatting package built on top of Day.js with strict parsing, locale-aware formatting, and a shared API for core, vanilla, React, Vue, Svelte, and browser usage.
4
4
 
5
- This package uses Day.js as its date engine. We are grateful for the existence of the package and will make good use of it in this project.
5
+ This package uses Day.js as its date engine. Thanks to the Day.js project for making that foundation available.
6
6
 
7
- Repository: https://github.com/iamkun/dayjs
7
+ Day.js repository: https://github.com/iamkun/dayjs
8
8
 
9
9
  ## Features
10
10
 
@@ -16,25 +16,86 @@ Repository: https://github.com/iamkun/dayjs
16
16
  - parse and validate dates with explicit result objects
17
17
  - use the same API from core, vanilla, React, Vue, Svelte, or browser global builds
18
18
 
19
+ ## Table of Contents
20
+
21
+ - [Installation](#installation)
22
+ - [Browser and CDN](#browser-and-cdn)
23
+ - [Entrypoints](#entrypoints)
24
+ - [Quick Start](#quick-start)
25
+ - [API](#api)
26
+ - [Supported Locales](#supported-locales)
27
+ - [Documentation](#documentation)
28
+ - [License](#license)
29
+
19
30
  ## Installation
20
31
 
32
+ Use the package manager that matches your project. The published package targets Node 20 or newer.
33
+
21
34
  ```bash
22
35
  npm install @samline/date
23
36
  ```
24
37
 
38
+ ```bash
39
+ pnpm add @samline/date
40
+ ```
41
+
42
+ ```bash
43
+ yarn add @samline/date
44
+ ```
45
+
46
+ ```bash
47
+ bun add @samline/date
48
+ ```
49
+
50
+ ## Browser and CDN
51
+
52
+ If your project does not use a bundler, load the browser build from a CDN. Prefer pinning a version instead of using `latest` in production.
53
+
54
+ ```html
55
+ <script type="module">
56
+ import { DateKit } from 'https://cdn.jsdelivr.net/npm/@samline/date@2.1.1/dist/browser/global.js'
57
+
58
+ const value = await DateKit.getDate({
59
+ date: '23/03/2026',
60
+ input: 'DD/MM/YYYY',
61
+ output: 'MMMM D, YYYY'
62
+ })
63
+
64
+ console.log(value)
65
+ console.log(window.DateKit.resolveLocale('en-us'))
66
+ </script>
67
+ ```
68
+
69
+ You can also use unpkg:
70
+
71
+ ```html
72
+ <script type="module">
73
+ import { DateKit } from 'https://unpkg.com/@samline/date@2.1.1/dist/browser/global.js'
74
+
75
+ console.log(await DateKit.isValidDate({
76
+ date: '23/03/2026',
77
+ input: 'DD/MM/YYYY'
78
+ }))
79
+ </script>
80
+ ```
81
+
82
+ The browser bundle exposes `window.DateKit` and the same shared helpers documented below.
83
+
25
84
  ## Entrypoints
26
85
 
27
- | Entrypoint | Purpose |
28
- | --- | --- |
29
- | `@samline/date` | shared core API |
30
- | `@samline/date/vanilla` | utility wrapper for plain TypeScript or JavaScript |
31
- | `@samline/date/react` | React hook |
32
- | `@samline/date/vue` | Vue composable |
33
- | `@samline/date/svelte` | Svelte store helpers |
34
- | `@samline/date/browser` | browser global build |
86
+ | Entrypoint | Main API | Purpose |
87
+ | --- | --- | --- |
88
+ | `@samline/date` | `createDateFormatter`, `getDate`, `parseDate`, `isValidDate` | shared core API |
89
+ | `@samline/date/vanilla` | same exports as root | utility wrapper for plain TypeScript or JavaScript |
90
+ | `@samline/date/react` | `useDateFormatter` | React hook with scoped formatter state |
91
+ | `@samline/date/vue` | `useDateFormatter` | Vue composable with reactive locale state |
92
+ | `@samline/date/svelte` | `createDateFormatterStore` | Svelte store-driven formatter API |
93
+ | `@samline/date/browser` | `DateKit` | browser global build for projects without a bundler |
35
94
 
36
95
  ## Quick Start
37
96
 
97
+ Use the one-shot helpers when you only need a single async operation.
98
+
38
99
  ```ts
39
100
  import { getDate } from '@samline/date'
40
101
 
@@ -45,7 +106,7 @@ const date = await getDate({
45
106
  })
46
107
  ```
47
108
 
48
- For repeated work with the same locale or invalid text, create one formatter instance and reuse it:
109
+ If you need repeated formatting, parsing, or locale changes, create one formatter instance and reuse it.
49
110
 
50
111
  ```ts
51
112
  import { createDateFormatter } from '@samline/date'
@@ -61,13 +122,42 @@ const date = formatter.getDate({
61
122
  })
62
123
  ```
63
124
 
125
+ You can also inspect the effective locale before formatting:
126
+
127
+ ```ts
128
+ import { getSupportedLocales, resolveLocale } from '@samline/date'
129
+
130
+ getSupportedLocales()
131
+ // ['en', 'es', 'es-mx', 'fr', 'pt', 'pt-br', 'de', 'it', 'ja']
132
+
133
+ resolveLocale('es-mx')
134
+ // es-mx
135
+
136
+ resolveLocale('en-us')
137
+ // en
138
+
139
+ resolveLocale('zz-zz')
140
+ // null
141
+ ```
142
+
64
143
  ## API
65
144
 
145
+ The shared root entrypoint exports:
146
+
147
+ - `createDateFormatter(config?)`
148
+ - `getDate(props?, config?)`
149
+ - `parseDate(props, config?)`
150
+ - `isValidDate(props, config?)`
151
+ - `getSupportedLocales()`
152
+ - `SUPPORTED_LOCALES`
153
+ - `resolveLocale(locale)`
154
+ - `isSupportedLocale(locale)`
155
+
66
156
  ### createDateFormatter
67
157
 
68
158
  ```ts
69
159
  createDateFormatter(config?: {
70
- locale?: SupportedLocale
160
+ locale?: LocaleInput
71
161
  strict?: boolean
72
162
  invalid?: string
73
163
  }): {
@@ -76,7 +166,7 @@ createDateFormatter(config?: {
76
166
  isValidDate(props: DateParsingOptions): boolean
77
167
  getSupportedLocales(): readonly SupportedLocale[]
78
168
  getCurrentLocale(): SupportedLocale
79
- setLocale(locale: SupportedLocale): Promise<void>
169
+ setLocale(locale: LocaleInput): Promise<void>
80
170
  ready: Promise<void>
81
171
  }
82
172
  ```
@@ -85,7 +175,9 @@ Creates a formatter instance with its own locale state. This avoids coupling fra
85
175
 
86
176
  `strict` is `true` by default, so parsing fails when the input does not match the provided format exactly. Use `strict: false` only when you explicitly want lenient parsing.
87
177
 
88
- If you override `locale` per call, make sure that locale was already loaded by a formatter instance.
178
+ If you override `locale` per call, make sure the effective locale was already loaded by a formatter instance.
179
+
180
+ Regional locale input falls back to the base locale when the exact variant is not supported by the package. For example, `en-us` resolves to `en`, while `es-mx` stays as `es-mx` because that locale is supported explicitly.
89
181
 
90
182
  The formatter instance exposes:
91
183
 
@@ -97,6 +189,23 @@ The formatter instance exposes:
97
189
  - `getSupportedLocales()`
98
190
  - `ready`
99
191
 
192
+ ### Locale helpers
193
+
194
+ ```ts
195
+ SUPPORTED_LOCALES: readonly SupportedLocale[]
196
+ getSupportedLocales(): readonly SupportedLocale[]
197
+ resolveLocale(locale: LocaleInput): SupportedLocale | null
198
+ isSupportedLocale(locale: string): boolean
199
+ ```
200
+
201
+ Use `SUPPORTED_LOCALES` or `getSupportedLocales()` when you need the list of locale keys exposed by the package.
202
+
203
+ Use `resolveLocale` when you want to know the effective locale before creating a formatter or calling a helper.
204
+
205
+ Use `isSupportedLocale` when you only need a boolean check after the package applies its exact-match and base-locale fallback rules.
206
+
207
+ These helpers are also available in the browser build through `DateKit.getSupportedLocales()`, `DateKit.resolveLocale(...)`, and `DateKit.isSupportedLocale(...)`.
208
+
100
209
  ### One-shot helpers
101
210
 
102
211
  ```ts
@@ -107,6 +216,8 @@ isValidDate(props: DateParsingOptions, config?: DateFormatterConfig): Promise<bo
107
216
 
108
217
  Use these helpers when you only need a single operation and do not want to create a formatter instance manually.
109
218
 
219
+ All three helpers are async because they can load locale data before running the operation.
220
+
110
221
  | Helper | Returns | Use it when you need |
111
222
  | --- | --- | --- |
112
223
  | `getDate(...)` | formatted string | a final display value |
@@ -135,6 +246,8 @@ const valid = await isValidDate({
135
246
 
136
247
  They load the requested locale automatically and also use `strict: true` by default.
137
248
 
249
+ If you call `getDate()` without props, it returns the current date formatted with the default formatter settings.
250
+
138
251
  ### parseDate
139
252
 
140
253
  ```ts
@@ -150,6 +263,8 @@ Returns a structured result.
150
263
  - Valid parse: `isValid`, `date`, `iso`, `timestamp`, `format(output?)`
151
264
  - Invalid parse: `isValid: false`, `error`, and null date fields
152
265
 
266
+ This makes `parseDate` the right choice when you need validation details, an ISO value, a timestamp, or deferred formatting from the same parsed input.
267
+
153
268
  ### isValidDate
154
269
 
155
270
  ```ts
@@ -164,7 +279,7 @@ Returns a boolean when you only need validation without formatting.
164
279
 
165
280
  ## Supported Locales
166
281
 
167
- The package ships helper support for these locale keys:
282
+ The package ships helper support for these locale keys through `SUPPORTED_LOCALES` and `getSupportedLocales()`:
168
283
 
169
284
  - `en`
170
285
  - `es`
@@ -176,15 +291,31 @@ The package ships helper support for these locale keys:
176
291
  - `it`
177
292
  - `ja`
178
293
 
179
- Use `createDateFormatter({ locale: 'es-mx' })` when you need a locale other than English.
294
+ You can also pass regional locale input such as `en-us`, `fr-ca`, or `pt-pt`.
295
+
296
+ The resolution rule is:
297
+
298
+ - if the exact locale exists, use it
299
+ - otherwise, try the base locale before the hyphen
300
+ - if neither exists, throw an unsupported locale error
301
+
302
+ Examples:
303
+
304
+ - `es-mx` -> `es-mx`
305
+ - `es-ar` -> `es`
306
+ - `en-us` -> `en`
307
+ - `pt-pt` -> `pt`
308
+ - `zz-zz` -> error
309
+
310
+ Use a simple locale like `fr` or `en` when the base language is enough. Use a regional locale like `es-mx` or `pt-br` when you need a supported country-specific variant.
180
311
 
181
312
  ## Documentation
182
313
 
183
- - [docs/vanilla.md](docs/vanilla.md)
184
- - [docs/browser.md](docs/browser.md)
185
- - [docs/react.md](docs/react.md)
186
- - [docs/vue.md](docs/vue.md)
187
- - [docs/svelte.md](docs/svelte.md)
314
+ - [docs/vanilla.md](docs/vanilla.md) for the shared API in plain TypeScript or JavaScript
315
+ - [docs/browser.md](docs/browser.md) for browser-only usage without a bundler
316
+ - [docs/react.md](docs/react.md) for the React hook entrypoint
317
+ - [docs/vue.md](docs/vue.md) for the Vue composable entrypoint
318
+ - [docs/svelte.md](docs/svelte.md) for the Svelte store entrypoint
188
319
 
189
320
  ## License
190
321
 
@@ -1,11 +1,13 @@
1
- import { DateFormatterConfig, DateFormatter, GetDateOptions, SupportedLocale, DateParsingOptions, ParseDateResult } from '../index.js';
1
+ import { DateFormatterConfig, DateFormatter, GetDateOptions, SupportedLocale, DateParsingOptions, ParseDateResult, LocaleInput } from '../index.js';
2
2
 
3
3
  declare const DateKit: {
4
4
  createDateFormatter: (config?: DateFormatterConfig) => DateFormatter;
5
5
  getDate: (props?: GetDateOptions, config?: DateFormatterConfig) => Promise<string>;
6
6
  getSupportedLocales: () => readonly SupportedLocale[];
7
+ isSupportedLocale: (locale: string) => boolean;
7
8
  parseDate: (props: DateParsingOptions, config?: DateFormatterConfig) => Promise<ParseDateResult>;
8
9
  isValidDate: (props: DateParsingOptions, config?: DateFormatterConfig) => Promise<boolean>;
10
+ resolveLocale: (locale: LocaleInput) => SupportedLocale | null;
9
11
  };
10
12
  declare global {
11
13
  interface Window {
@@ -25,8 +25,29 @@ var localeLoaders = {
25
25
  it: () => import("dayjs/locale/it.js"),
26
26
  ja: () => import("dayjs/locale/ja.js")
27
27
  };
28
+ var normalizeLocale = (locale) => {
29
+ return locale.trim().toLowerCase().replace(/_/g, "-");
30
+ };
31
+ var asSupportedLocale = (locale) => {
32
+ if (!SUPPORTED_LOCALES.includes(locale)) {
33
+ return null;
34
+ }
35
+ return locale;
36
+ };
28
37
  var isSupportedLocale = (locale) => {
29
- return SUPPORTED_LOCALES.includes(locale);
38
+ return resolveLocale(locale) !== null;
39
+ };
40
+ var resolveLocale = (locale) => {
41
+ const normalizedLocale = normalizeLocale(locale);
42
+ const exactLocale = asSupportedLocale(normalizedLocale);
43
+ if (exactLocale) {
44
+ return exactLocale;
45
+ }
46
+ const [baseLocale] = normalizedLocale.split("-");
47
+ if (!baseLocale) {
48
+ return null;
49
+ }
50
+ return asSupportedLocale(baseLocale);
30
51
  };
31
52
  var ensureLocaleLoaded = async (locale) => {
32
53
  const load = localeLoaders[locale];
@@ -52,10 +73,19 @@ var getInvalidDateText = (config, props) => {
52
73
  return props?.invalid ?? config?.invalid ?? DEFAULT_INVALID_DATE;
53
74
  };
54
75
  var getTargetLocale = (currentLocale, props) => {
55
- return props?.locale ?? currentLocale;
76
+ if (!props?.locale) {
77
+ return currentLocale;
78
+ }
79
+ return resolveLocaleOrThrow(props.locale);
56
80
  };
57
81
  var getHelperLocale = (config, props) => {
58
- return props?.locale ?? config?.locale ?? DEFAULT_LOCALE;
82
+ if (props?.locale) {
83
+ return resolveLocaleOrThrow(props.locale);
84
+ }
85
+ if (config?.locale) {
86
+ return resolveLocaleOrThrow(config.locale);
87
+ }
88
+ return DEFAULT_LOCALE;
59
89
  };
60
90
  var parseDateValue = (value, input, locale, strict) => {
61
91
  if (!input) {
@@ -118,10 +148,14 @@ var createFormatterGetDate = (getConfig) => {
118
148
  return parsed.format(output);
119
149
  };
120
150
  };
121
- function assertSupportedLocale(locale) {
122
- if (!isSupportedLocale(locale)) {
123
- throw new Error(`Unsupported locale: ${locale}`);
151
+ function resolveLocaleOrThrow(locale) {
152
+ const resolvedLocale = resolveLocale(locale);
153
+ if (!resolvedLocale) {
154
+ throw new Error(
155
+ `Unsupported locale: ${locale}. The package tries an exact locale match first and then falls back to the base locale.`
156
+ );
124
157
  }
158
+ return resolvedLocale;
125
159
  }
126
160
  var getSupportedLocales = () => SUPPORTED_LOCALES;
127
161
  var getDate = async (props, config) => {
@@ -143,7 +177,7 @@ var isValidDate = async (props, config) => {
143
177
  return formatter.isValidDate(props);
144
178
  };
145
179
  var createDateFormatter = (config) => {
146
- let currentLocale = config?.locale ?? DEFAULT_LOCALE;
180
+ let currentLocale = config?.locale ? resolveLocaleOrThrow(config.locale) : DEFAULT_LOCALE;
147
181
  const getConfig = () => createResolvedConfig(currentLocale, config);
148
182
  const ready = ensureLocaleLoaded(currentLocale);
149
183
  const parseDate2 = createFormatterParseDate(getConfig);
@@ -154,9 +188,9 @@ var createDateFormatter = (config) => {
154
188
  getSupportedLocales,
155
189
  getCurrentLocale: () => currentLocale,
156
190
  setLocale: async (locale) => {
157
- assertSupportedLocale(locale);
158
- await ensureLocaleLoaded(locale);
159
- currentLocale = locale;
191
+ const resolvedLocale = resolveLocaleOrThrow(locale);
192
+ await ensureLocaleLoaded(resolvedLocale);
193
+ currentLocale = resolvedLocale;
160
194
  },
161
195
  ready
162
196
  };
@@ -167,8 +201,10 @@ var DateKit = {
167
201
  createDateFormatter,
168
202
  getDate,
169
203
  getSupportedLocales,
204
+ isSupportedLocale,
170
205
  parseDate,
171
- isValidDate
206
+ isValidDate,
207
+ resolveLocale
172
208
  };
173
209
  if (typeof window !== "undefined") {
174
210
  window.DateKit = DateKit;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/date.ts","../../src/core/locales.ts","../../src/browser/global.ts"],"sourcesContent":["import customParseFormat from 'dayjs/plugin/customParseFormat.js'\nimport dayjs from 'dayjs'\nimport type { Dayjs } from 'dayjs'\n\nimport { ensureLocaleLoaded, isSupportedLocale, SUPPORTED_LOCALES, type SupportedLocale } from './locales.js'\n\ndayjs.extend(customParseFormat)\ndayjs.locale('en')\n\nexport type DateValue = string | number | Date\n\ntype DateInputOptions = {\n input?: string | readonly string[]\n locale?: SupportedLocale\n strict?: boolean\n}\n\nexport type DateParsingOptions = DateInputOptions & {\n date: DateValue\n}\n\nexport type GetDateOptions = DateInputOptions & {\n date?: DateValue\n output?: string\n invalid?: string\n}\n\nexport type DateFormatterConfig = {\n locale?: SupportedLocale\n strict?: boolean\n invalid?: string\n}\n\nexport type DateFormatter = {\n getDate: (props?: GetDateOptions) => string\n parseDate: (props: DateParsingOptions) => ParseDateResult\n isValidDate: (props: DateParsingOptions) => boolean\n getSupportedLocales: () => readonly SupportedLocale[]\n getCurrentLocale: () => SupportedLocale\n setLocale: (locale: SupportedLocale) => Promise<void>\n ready: Promise<void>\n}\n\nexport type ParseDateSuccess = {\n isValid: true\n locale: SupportedLocale\n date: Date\n iso: string\n timestamp: number\n format: (output?: string) => string\n}\n\nexport type ParseDateFailure = {\n isValid: false\n locale: SupportedLocale\n date: null\n iso: null\n timestamp: null\n error: string\n}\n\nexport type ParseDateResult = ParseDateSuccess | ParseDateFailure\n\nconst DEFAULT_FORMAT = 'YYYY-MM-DD'\nconst DEFAULT_LOCALE: SupportedLocale = 'en'\nconst DEFAULT_INVALID_DATE = 'Invalid Date'\nconst DEFAULT_STRICT = true\n\nconst createResolvedConfig = (\n locale: SupportedLocale,\n config?: DateFormatterConfig\n): Required<DateFormatterConfig> => ({\n locale,\n strict: config?.strict ?? DEFAULT_STRICT,\n invalid: config?.invalid ?? DEFAULT_INVALID_DATE\n})\n\nconst getInvalidDateText = (config?: DateFormatterConfig, props?: GetDateOptions): string => {\n return props?.invalid ?? config?.invalid ?? DEFAULT_INVALID_DATE\n}\n\nconst getTargetLocale = (currentLocale: SupportedLocale, props?: GetDateOptions): SupportedLocale => {\n return props?.locale ?? currentLocale\n}\n\nconst getHelperLocale = <T extends { locale?: SupportedLocale }>(\n config?: DateFormatterConfig,\n props?: T\n): SupportedLocale => {\n return props?.locale ?? config?.locale ?? DEFAULT_LOCALE\n}\n\nconst parseDateValue = (\n value: DateValue,\n input: DateParsingOptions['input'],\n locale: SupportedLocale,\n strict: boolean\n): Dayjs => {\n if (!input) {\n return dayjs(value).locale(locale)\n }\n\n if (typeof input === 'string') {\n return dayjs(value, input, locale, strict).locale(locale)\n }\n\n return dayjs(value, [...input], locale, strict).locale(locale)\n}\n\nconst createFormatterParseDate = (getConfig: () => Required<DateFormatterConfig>) => {\n return (props: DateParsingOptions): ParseDateResult => {\n const config = getConfig()\n const locale = getTargetLocale(config.locale, props)\n const parsed = parseDateValue(props.date, props.input, locale, props.strict ?? config.strict)\n\n if (!parsed.isValid()) {\n return {\n isValid: false,\n locale,\n date: null,\n iso: null,\n timestamp: null,\n error: getInvalidDateText(config, props)\n }\n }\n\n return {\n isValid: true,\n locale,\n date: parsed.toDate(),\n iso: parsed.toISOString(),\n timestamp: parsed.valueOf(),\n format: (output = DEFAULT_FORMAT) => parsed.format(output)\n }\n }\n}\n\nconst createFormatterIsValidDate = (parseDate: (props: DateParsingOptions) => ParseDateResult) => {\n return (props: DateParsingOptions): boolean => parseDate(props).isValid\n}\n\nconst createFormatterGetDate = (getConfig: () => Required<DateFormatterConfig>) => {\n const parseDate = createFormatterParseDate(getConfig)\n\n return (props?: GetDateOptions): string => {\n const config = getConfig()\n const locale = getTargetLocale(config.locale, props)\n const output = props?.output ?? DEFAULT_FORMAT\n\n if (!props) {\n return dayjs().locale(locale).format(DEFAULT_FORMAT)\n }\n\n if (props.date === undefined) {\n return dayjs().locale(locale).format(output)\n }\n\n const parsed = parseDate({\n date: props.date,\n input: props.input,\n locale: props.locale,\n strict: props.strict\n })\n\n if (!parsed.isValid) {\n return props.invalid ?? parsed.error\n }\n\n return parsed.format(output)\n }\n}\n\nfunction assertSupportedLocale(locale: string): asserts locale is SupportedLocale {\n if (!isSupportedLocale(locale)) {\n throw new Error(`Unsupported locale: ${locale}`)\n }\n}\n\nexport const getSupportedLocales = (): readonly SupportedLocale[] => SUPPORTED_LOCALES\n\nexport const getDate = async (props?: GetDateOptions, config?: DateFormatterConfig): Promise<string> => {\n const locale = getHelperLocale(config, props)\n const formatter = createDateFormatter({ ...config, locale })\n\n await formatter.ready\n\n return formatter.getDate(props)\n}\n\nexport const parseDate = async (\n props: DateParsingOptions,\n config?: DateFormatterConfig\n): Promise<ParseDateResult> => {\n const locale = getHelperLocale(config, props)\n const formatter = createDateFormatter({ ...config, locale })\n\n await formatter.ready\n\n return formatter.parseDate(props)\n}\n\nexport const isValidDate = async (\n props: DateParsingOptions,\n config?: DateFormatterConfig\n): Promise<boolean> => {\n const locale = getHelperLocale(config, props)\n const formatter = createDateFormatter({ ...config, locale })\n\n await formatter.ready\n\n return formatter.isValidDate(props)\n}\n\nexport const createDateFormatter = (config?: DateFormatterConfig): DateFormatter => {\n let currentLocale = config?.locale ?? DEFAULT_LOCALE\n\n const getConfig = (): Required<DateFormatterConfig> => createResolvedConfig(currentLocale, config)\n\n const ready = ensureLocaleLoaded(currentLocale)\n const parseDate = createFormatterParseDate(getConfig)\n\n return {\n getDate: createFormatterGetDate(getConfig),\n parseDate,\n isValidDate: createFormatterIsValidDate(parseDate),\n getSupportedLocales,\n getCurrentLocale: () => currentLocale,\n setLocale: async (locale: SupportedLocale) => {\n assertSupportedLocale(locale)\n await ensureLocaleLoaded(locale)\n currentLocale = locale\n },\n ready\n }\n}\n","export const SUPPORTED_LOCALES = [\n 'en',\n 'es',\n 'es-mx',\n 'fr',\n 'pt',\n 'pt-br',\n 'de',\n 'it',\n 'ja'\n] as const\n\nexport type SupportedLocale = (typeof SUPPORTED_LOCALES)[number]\n\nconst localeLoaders: Record<SupportedLocale, (() => Promise<unknown>) | null> = {\n en: null,\n es: () => import('dayjs/locale/es.js'),\n 'es-mx': () => import('dayjs/locale/es-mx.js'),\n fr: () => import('dayjs/locale/fr.js'),\n pt: () => import('dayjs/locale/pt.js'),\n 'pt-br': () => import('dayjs/locale/pt-br.js'),\n de: () => import('dayjs/locale/de.js'),\n it: () => import('dayjs/locale/it.js'),\n ja: () => import('dayjs/locale/ja.js')\n}\n\nexport const isSupportedLocale = (locale: string): locale is SupportedLocale => {\n return SUPPORTED_LOCALES.includes(locale as SupportedLocale)\n}\n\nexport const ensureLocaleLoaded = async (locale: SupportedLocale): Promise<void> => {\n const load = localeLoaders[locale]\n\n if (!load) {\n return\n }\n\n await load()\n}\n","import { createDateFormatter, getDate, getSupportedLocales, isValidDate, parseDate } from '../index.js'\n\nexport const DateKit = {\n createDateFormatter,\n getDate,\n getSupportedLocales\n ,\n parseDate,\n isValidDate\n}\n\ndeclare global {\n interface Window {\n DateKit: typeof DateKit\n }\n}\n\nif (typeof window !== 'undefined') {\n window.DateKit = DateKit\n}\n"],"mappings":"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","names":["parseDate"]}
1
+ {"version":3,"sources":["../../src/core/date.ts","../../src/core/locales.ts","../../src/browser/global.ts"],"sourcesContent":["import customParseFormat from 'dayjs/plugin/customParseFormat.js'\nimport dayjs from 'dayjs'\nimport type { Dayjs } from 'dayjs'\n\nimport { ensureLocaleLoaded, resolveLocale, SUPPORTED_LOCALES, type LocaleInput, type SupportedLocale } from './locales.js'\n\ndayjs.extend(customParseFormat)\ndayjs.locale('en')\n\nexport type DateValue = string | number | Date\n\ntype DateInputOptions = {\n input?: string | readonly string[]\n locale?: LocaleInput\n strict?: boolean\n}\n\nexport type DateParsingOptions = DateInputOptions & {\n date: DateValue\n}\n\nexport type GetDateOptions = DateInputOptions & {\n date?: DateValue\n output?: string\n invalid?: string\n}\n\nexport type DateFormatterConfig = {\n locale?: LocaleInput\n strict?: boolean\n invalid?: string\n}\n\ntype ResolvedDateFormatterConfig = {\n locale: SupportedLocale\n strict: boolean\n invalid: string\n}\n\nexport type DateFormatter = {\n getDate: (props?: GetDateOptions) => string\n parseDate: (props: DateParsingOptions) => ParseDateResult\n isValidDate: (props: DateParsingOptions) => boolean\n getSupportedLocales: () => readonly SupportedLocale[]\n getCurrentLocale: () => SupportedLocale\n setLocale: (locale: LocaleInput) => Promise<void>\n ready: Promise<void>\n}\n\nexport type ParseDateSuccess = {\n isValid: true\n locale: SupportedLocale\n date: Date\n iso: string\n timestamp: number\n format: (output?: string) => string\n}\n\nexport type ParseDateFailure = {\n isValid: false\n locale: SupportedLocale\n date: null\n iso: null\n timestamp: null\n error: string\n}\n\nexport type ParseDateResult = ParseDateSuccess | ParseDateFailure\n\nconst DEFAULT_FORMAT = 'YYYY-MM-DD'\nconst DEFAULT_LOCALE: SupportedLocale = 'en'\nconst DEFAULT_INVALID_DATE = 'Invalid Date'\nconst DEFAULT_STRICT = true\n\nconst createResolvedConfig = (\n locale: SupportedLocale,\n config?: DateFormatterConfig\n): ResolvedDateFormatterConfig => ({\n locale,\n strict: config?.strict ?? DEFAULT_STRICT,\n invalid: config?.invalid ?? DEFAULT_INVALID_DATE\n})\n\nconst getInvalidDateText = (config?: DateFormatterConfig, props?: GetDateOptions): string => {\n return props?.invalid ?? config?.invalid ?? DEFAULT_INVALID_DATE\n}\n\nconst getTargetLocale = (currentLocale: SupportedLocale, props?: GetDateOptions): SupportedLocale => {\n if (!props?.locale) {\n return currentLocale\n }\n\n return resolveLocaleOrThrow(props.locale)\n}\n\nconst getHelperLocale = <T extends { locale?: LocaleInput }>(\n config?: DateFormatterConfig,\n props?: T\n): SupportedLocale => {\n if (props?.locale) {\n return resolveLocaleOrThrow(props.locale)\n }\n\n if (config?.locale) {\n return resolveLocaleOrThrow(config.locale)\n }\n\n return DEFAULT_LOCALE\n}\n\nconst parseDateValue = (\n value: DateValue,\n input: DateParsingOptions['input'],\n locale: SupportedLocale,\n strict: boolean\n): Dayjs => {\n if (!input) {\n return dayjs(value).locale(locale)\n }\n\n if (typeof input === 'string') {\n return dayjs(value, input, locale, strict).locale(locale)\n }\n\n return dayjs(value, [...input], locale, strict).locale(locale)\n}\n\nconst createFormatterParseDate = (getConfig: () => ResolvedDateFormatterConfig) => {\n return (props: DateParsingOptions): ParseDateResult => {\n const config = getConfig()\n const locale = getTargetLocale(config.locale, props)\n const parsed = parseDateValue(props.date, props.input, locale, props.strict ?? config.strict)\n\n if (!parsed.isValid()) {\n return {\n isValid: false,\n locale,\n date: null,\n iso: null,\n timestamp: null,\n error: getInvalidDateText(config, props)\n }\n }\n\n return {\n isValid: true,\n locale,\n date: parsed.toDate(),\n iso: parsed.toISOString(),\n timestamp: parsed.valueOf(),\n format: (output = DEFAULT_FORMAT) => parsed.format(output)\n }\n }\n}\n\nconst createFormatterIsValidDate = (parseDate: (props: DateParsingOptions) => ParseDateResult) => {\n return (props: DateParsingOptions): boolean => parseDate(props).isValid\n}\n\nconst createFormatterGetDate = (getConfig: () => ResolvedDateFormatterConfig) => {\n const parseDate = createFormatterParseDate(getConfig)\n\n return (props?: GetDateOptions): string => {\n const config = getConfig()\n const locale = getTargetLocale(config.locale, props)\n const output = props?.output ?? DEFAULT_FORMAT\n\n if (!props) {\n return dayjs().locale(locale).format(DEFAULT_FORMAT)\n }\n\n if (props.date === undefined) {\n return dayjs().locale(locale).format(output)\n }\n\n const parsed = parseDate({\n date: props.date,\n input: props.input,\n locale: props.locale,\n strict: props.strict\n })\n\n if (!parsed.isValid) {\n return props.invalid ?? parsed.error\n }\n\n return parsed.format(output)\n }\n}\n\nfunction resolveLocaleOrThrow(locale: LocaleInput): SupportedLocale {\n const resolvedLocale = resolveLocale(locale)\n\n if (!resolvedLocale) {\n throw new Error(\n `Unsupported locale: ${locale}. The package tries an exact locale match first and then falls back to the base locale.`\n )\n }\n\n return resolvedLocale\n}\n\nexport const getSupportedLocales = (): readonly SupportedLocale[] => SUPPORTED_LOCALES\n\nexport const getDate = async (props?: GetDateOptions, config?: DateFormatterConfig): Promise<string> => {\n const locale = getHelperLocale(config, props)\n const formatter = createDateFormatter({ ...config, locale })\n\n await formatter.ready\n\n return formatter.getDate(props)\n}\n\nexport const parseDate = async (\n props: DateParsingOptions,\n config?: DateFormatterConfig\n): Promise<ParseDateResult> => {\n const locale = getHelperLocale(config, props)\n const formatter = createDateFormatter({ ...config, locale })\n\n await formatter.ready\n\n return formatter.parseDate(props)\n}\n\nexport const isValidDate = async (\n props: DateParsingOptions,\n config?: DateFormatterConfig\n): Promise<boolean> => {\n const locale = getHelperLocale(config, props)\n const formatter = createDateFormatter({ ...config, locale })\n\n await formatter.ready\n\n return formatter.isValidDate(props)\n}\n\nexport const createDateFormatter = (config?: DateFormatterConfig): DateFormatter => {\n let currentLocale = config?.locale ? resolveLocaleOrThrow(config.locale) : DEFAULT_LOCALE\n\n const getConfig = (): ResolvedDateFormatterConfig => createResolvedConfig(currentLocale, config)\n\n const ready = ensureLocaleLoaded(currentLocale)\n const parseDate = createFormatterParseDate(getConfig)\n\n return {\n getDate: createFormatterGetDate(getConfig),\n parseDate,\n isValidDate: createFormatterIsValidDate(parseDate),\n getSupportedLocales,\n getCurrentLocale: () => currentLocale,\n setLocale: async (locale: LocaleInput) => {\n const resolvedLocale = resolveLocaleOrThrow(locale)\n\n await ensureLocaleLoaded(resolvedLocale)\n currentLocale = resolvedLocale\n },\n ready\n }\n}\n","export const SUPPORTED_LOCALES = [\n 'en',\n 'es',\n 'es-mx',\n 'fr',\n 'pt',\n 'pt-br',\n 'de',\n 'it',\n 'ja'\n] as const\n\nexport type SupportedLocale = (typeof SUPPORTED_LOCALES)[number]\nexport type LocaleInput = string\n\nconst localeLoaders: Record<SupportedLocale, (() => Promise<unknown>) | null> = {\n en: null,\n es: () => import('dayjs/locale/es.js'),\n 'es-mx': () => import('dayjs/locale/es-mx.js'),\n fr: () => import('dayjs/locale/fr.js'),\n pt: () => import('dayjs/locale/pt.js'),\n 'pt-br': () => import('dayjs/locale/pt-br.js'),\n de: () => import('dayjs/locale/de.js'),\n it: () => import('dayjs/locale/it.js'),\n ja: () => import('dayjs/locale/ja.js')\n}\n\nconst normalizeLocale = (locale: string): string => {\n return locale.trim().toLowerCase().replace(/_/g, '-')\n}\n\nconst asSupportedLocale = (locale: string): SupportedLocale | null => {\n if (!SUPPORTED_LOCALES.includes(locale as SupportedLocale)) {\n return null\n }\n\n return locale as SupportedLocale\n}\n\nexport const isSupportedLocale = (locale: string): boolean => {\n return resolveLocale(locale) !== null\n}\n\nexport const resolveLocale = (locale: LocaleInput): SupportedLocale | null => {\n const normalizedLocale = normalizeLocale(locale)\n const exactLocale = asSupportedLocale(normalizedLocale)\n\n if (exactLocale) {\n return exactLocale\n }\n\n const [baseLocale] = normalizedLocale.split('-')\n\n if (!baseLocale) {\n return null\n }\n\n return asSupportedLocale(baseLocale)\n}\n\nexport const ensureLocaleLoaded = async (locale: SupportedLocale): Promise<void> => {\n const load = localeLoaders[locale]\n\n if (!load) {\n return\n }\n\n await load()\n}\n","import {\n createDateFormatter,\n getDate,\n getSupportedLocales,\n isSupportedLocale,\n isValidDate,\n parseDate,\n resolveLocale\n} from '../index.js'\n\nexport const DateKit = {\n createDateFormatter,\n getDate,\n getSupportedLocales,\n isSupportedLocale,\n parseDate,\n isValidDate,\n resolveLocale\n}\n\ndeclare global {\n interface Window {\n DateKit: typeof DateKit\n }\n}\n\nif (typeof window !== 'undefined') {\n window.DateKit = DateKit\n}\n"],"mappings":"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","names":["parseDate"]}
package/dist/index.d.ts CHANGED
@@ -1,10 +1,13 @@
1
1
  declare const SUPPORTED_LOCALES: readonly ["en", "es", "es-mx", "fr", "pt", "pt-br", "de", "it", "ja"];
2
2
  type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];
3
+ type LocaleInput = string;
4
+ declare const isSupportedLocale: (locale: string) => boolean;
5
+ declare const resolveLocale: (locale: LocaleInput) => SupportedLocale | null;
3
6
 
4
7
  type DateValue = string | number | Date;
5
8
  type DateInputOptions = {
6
9
  input?: string | readonly string[];
7
- locale?: SupportedLocale;
10
+ locale?: LocaleInput;
8
11
  strict?: boolean;
9
12
  };
10
13
  type DateParsingOptions = DateInputOptions & {
@@ -16,7 +19,7 @@ type GetDateOptions = DateInputOptions & {
16
19
  invalid?: string;
17
20
  };
18
21
  type DateFormatterConfig = {
19
- locale?: SupportedLocale;
22
+ locale?: LocaleInput;
20
23
  strict?: boolean;
21
24
  invalid?: string;
22
25
  };
@@ -26,7 +29,7 @@ type DateFormatter = {
26
29
  isValidDate: (props: DateParsingOptions) => boolean;
27
30
  getSupportedLocales: () => readonly SupportedLocale[];
28
31
  getCurrentLocale: () => SupportedLocale;
29
- setLocale: (locale: SupportedLocale) => Promise<void>;
32
+ setLocale: (locale: LocaleInput) => Promise<void>;
30
33
  ready: Promise<void>;
31
34
  };
32
35
  type ParseDateSuccess = {
@@ -52,4 +55,4 @@ declare const parseDate: (props: DateParsingOptions, config?: DateFormatterConfi
52
55
  declare const isValidDate: (props: DateParsingOptions, config?: DateFormatterConfig) => Promise<boolean>;
53
56
  declare const createDateFormatter: (config?: DateFormatterConfig) => DateFormatter;
54
57
 
55
- export { type DateFormatter, type DateFormatterConfig, type DateParsingOptions, type DateValue, type GetDateOptions, type ParseDateFailure, type ParseDateResult, type ParseDateSuccess, SUPPORTED_LOCALES, type SupportedLocale, createDateFormatter, getDate, getSupportedLocales, isValidDate, parseDate };
58
+ export { type DateFormatter, type DateFormatterConfig, type DateParsingOptions, type DateValue, type GetDateOptions, type LocaleInput, type ParseDateFailure, type ParseDateResult, type ParseDateSuccess, SUPPORTED_LOCALES, type SupportedLocale, createDateFormatter, getDate, getSupportedLocales, isSupportedLocale, isValidDate, parseDate, resolveLocale };
package/dist/index.js CHANGED
@@ -25,8 +25,29 @@ var localeLoaders = {
25
25
  it: () => import("dayjs/locale/it.js"),
26
26
  ja: () => import("dayjs/locale/ja.js")
27
27
  };
28
+ var normalizeLocale = (locale) => {
29
+ return locale.trim().toLowerCase().replace(/_/g, "-");
30
+ };
31
+ var asSupportedLocale = (locale) => {
32
+ if (!SUPPORTED_LOCALES.includes(locale)) {
33
+ return null;
34
+ }
35
+ return locale;
36
+ };
28
37
  var isSupportedLocale = (locale) => {
29
- return SUPPORTED_LOCALES.includes(locale);
38
+ return resolveLocale(locale) !== null;
39
+ };
40
+ var resolveLocale = (locale) => {
41
+ const normalizedLocale = normalizeLocale(locale);
42
+ const exactLocale = asSupportedLocale(normalizedLocale);
43
+ if (exactLocale) {
44
+ return exactLocale;
45
+ }
46
+ const [baseLocale] = normalizedLocale.split("-");
47
+ if (!baseLocale) {
48
+ return null;
49
+ }
50
+ return asSupportedLocale(baseLocale);
30
51
  };
31
52
  var ensureLocaleLoaded = async (locale) => {
32
53
  const load = localeLoaders[locale];
@@ -52,10 +73,19 @@ var getInvalidDateText = (config, props) => {
52
73
  return props?.invalid ?? config?.invalid ?? DEFAULT_INVALID_DATE;
53
74
  };
54
75
  var getTargetLocale = (currentLocale, props) => {
55
- return props?.locale ?? currentLocale;
76
+ if (!props?.locale) {
77
+ return currentLocale;
78
+ }
79
+ return resolveLocaleOrThrow(props.locale);
56
80
  };
57
81
  var getHelperLocale = (config, props) => {
58
- return props?.locale ?? config?.locale ?? DEFAULT_LOCALE;
82
+ if (props?.locale) {
83
+ return resolveLocaleOrThrow(props.locale);
84
+ }
85
+ if (config?.locale) {
86
+ return resolveLocaleOrThrow(config.locale);
87
+ }
88
+ return DEFAULT_LOCALE;
59
89
  };
60
90
  var parseDateValue = (value, input, locale, strict) => {
61
91
  if (!input) {
@@ -118,10 +148,14 @@ var createFormatterGetDate = (getConfig) => {
118
148
  return parsed.format(output);
119
149
  };
120
150
  };
121
- function assertSupportedLocale(locale) {
122
- if (!isSupportedLocale(locale)) {
123
- throw new Error(`Unsupported locale: ${locale}`);
151
+ function resolveLocaleOrThrow(locale) {
152
+ const resolvedLocale = resolveLocale(locale);
153
+ if (!resolvedLocale) {
154
+ throw new Error(
155
+ `Unsupported locale: ${locale}. The package tries an exact locale match first and then falls back to the base locale.`
156
+ );
124
157
  }
158
+ return resolvedLocale;
125
159
  }
126
160
  var getSupportedLocales = () => SUPPORTED_LOCALES;
127
161
  var getDate = async (props, config) => {
@@ -143,7 +177,7 @@ var isValidDate = async (props, config) => {
143
177
  return formatter.isValidDate(props);
144
178
  };
145
179
  var createDateFormatter = (config) => {
146
- let currentLocale = config?.locale ?? DEFAULT_LOCALE;
180
+ let currentLocale = config?.locale ? resolveLocaleOrThrow(config.locale) : DEFAULT_LOCALE;
147
181
  const getConfig = () => createResolvedConfig(currentLocale, config);
148
182
  const ready = ensureLocaleLoaded(currentLocale);
149
183
  const parseDate2 = createFormatterParseDate(getConfig);
@@ -154,9 +188,9 @@ var createDateFormatter = (config) => {
154
188
  getSupportedLocales,
155
189
  getCurrentLocale: () => currentLocale,
156
190
  setLocale: async (locale) => {
157
- assertSupportedLocale(locale);
158
- await ensureLocaleLoaded(locale);
159
- currentLocale = locale;
191
+ const resolvedLocale = resolveLocaleOrThrow(locale);
192
+ await ensureLocaleLoaded(resolvedLocale);
193
+ currentLocale = resolvedLocale;
160
194
  },
161
195
  ready
162
196
  };
@@ -166,7 +200,9 @@ export {
166
200
  createDateFormatter,
167
201
  getDate,
168
202
  getSupportedLocales,
203
+ isSupportedLocale,
169
204
  isValidDate,
170
- parseDate
205
+ parseDate,
206
+ resolveLocale
171
207
  };
172
208
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/date.ts","../src/core/locales.ts"],"sourcesContent":["import customParseFormat from 'dayjs/plugin/customParseFormat.js'\nimport dayjs from 'dayjs'\nimport type { Dayjs } from 'dayjs'\n\nimport { ensureLocaleLoaded, isSupportedLocale, SUPPORTED_LOCALES, type SupportedLocale } from './locales.js'\n\ndayjs.extend(customParseFormat)\ndayjs.locale('en')\n\nexport type DateValue = string | number | Date\n\ntype DateInputOptions = {\n input?: string | readonly string[]\n locale?: SupportedLocale\n strict?: boolean\n}\n\nexport type DateParsingOptions = DateInputOptions & {\n date: DateValue\n}\n\nexport type GetDateOptions = DateInputOptions & {\n date?: DateValue\n output?: string\n invalid?: string\n}\n\nexport type DateFormatterConfig = {\n locale?: SupportedLocale\n strict?: boolean\n invalid?: string\n}\n\nexport type DateFormatter = {\n getDate: (props?: GetDateOptions) => string\n parseDate: (props: DateParsingOptions) => ParseDateResult\n isValidDate: (props: DateParsingOptions) => boolean\n getSupportedLocales: () => readonly SupportedLocale[]\n getCurrentLocale: () => SupportedLocale\n setLocale: (locale: SupportedLocale) => Promise<void>\n ready: Promise<void>\n}\n\nexport type ParseDateSuccess = {\n isValid: true\n locale: SupportedLocale\n date: Date\n iso: string\n timestamp: number\n format: (output?: string) => string\n}\n\nexport type ParseDateFailure = {\n isValid: false\n locale: SupportedLocale\n date: null\n iso: null\n timestamp: null\n error: string\n}\n\nexport type ParseDateResult = ParseDateSuccess | ParseDateFailure\n\nconst DEFAULT_FORMAT = 'YYYY-MM-DD'\nconst DEFAULT_LOCALE: SupportedLocale = 'en'\nconst DEFAULT_INVALID_DATE = 'Invalid Date'\nconst DEFAULT_STRICT = true\n\nconst createResolvedConfig = (\n locale: SupportedLocale,\n config?: DateFormatterConfig\n): Required<DateFormatterConfig> => ({\n locale,\n strict: config?.strict ?? DEFAULT_STRICT,\n invalid: config?.invalid ?? DEFAULT_INVALID_DATE\n})\n\nconst getInvalidDateText = (config?: DateFormatterConfig, props?: GetDateOptions): string => {\n return props?.invalid ?? config?.invalid ?? DEFAULT_INVALID_DATE\n}\n\nconst getTargetLocale = (currentLocale: SupportedLocale, props?: GetDateOptions): SupportedLocale => {\n return props?.locale ?? currentLocale\n}\n\nconst getHelperLocale = <T extends { locale?: SupportedLocale }>(\n config?: DateFormatterConfig,\n props?: T\n): SupportedLocale => {\n return props?.locale ?? config?.locale ?? DEFAULT_LOCALE\n}\n\nconst parseDateValue = (\n value: DateValue,\n input: DateParsingOptions['input'],\n locale: SupportedLocale,\n strict: boolean\n): Dayjs => {\n if (!input) {\n return dayjs(value).locale(locale)\n }\n\n if (typeof input === 'string') {\n return dayjs(value, input, locale, strict).locale(locale)\n }\n\n return dayjs(value, [...input], locale, strict).locale(locale)\n}\n\nconst createFormatterParseDate = (getConfig: () => Required<DateFormatterConfig>) => {\n return (props: DateParsingOptions): ParseDateResult => {\n const config = getConfig()\n const locale = getTargetLocale(config.locale, props)\n const parsed = parseDateValue(props.date, props.input, locale, props.strict ?? config.strict)\n\n if (!parsed.isValid()) {\n return {\n isValid: false,\n locale,\n date: null,\n iso: null,\n timestamp: null,\n error: getInvalidDateText(config, props)\n }\n }\n\n return {\n isValid: true,\n locale,\n date: parsed.toDate(),\n iso: parsed.toISOString(),\n timestamp: parsed.valueOf(),\n format: (output = DEFAULT_FORMAT) => parsed.format(output)\n }\n }\n}\n\nconst createFormatterIsValidDate = (parseDate: (props: DateParsingOptions) => ParseDateResult) => {\n return (props: DateParsingOptions): boolean => parseDate(props).isValid\n}\n\nconst createFormatterGetDate = (getConfig: () => Required<DateFormatterConfig>) => {\n const parseDate = createFormatterParseDate(getConfig)\n\n return (props?: GetDateOptions): string => {\n const config = getConfig()\n const locale = getTargetLocale(config.locale, props)\n const output = props?.output ?? DEFAULT_FORMAT\n\n if (!props) {\n return dayjs().locale(locale).format(DEFAULT_FORMAT)\n }\n\n if (props.date === undefined) {\n return dayjs().locale(locale).format(output)\n }\n\n const parsed = parseDate({\n date: props.date,\n input: props.input,\n locale: props.locale,\n strict: props.strict\n })\n\n if (!parsed.isValid) {\n return props.invalid ?? parsed.error\n }\n\n return parsed.format(output)\n }\n}\n\nfunction assertSupportedLocale(locale: string): asserts locale is SupportedLocale {\n if (!isSupportedLocale(locale)) {\n throw new Error(`Unsupported locale: ${locale}`)\n }\n}\n\nexport const getSupportedLocales = (): readonly SupportedLocale[] => SUPPORTED_LOCALES\n\nexport const getDate = async (props?: GetDateOptions, config?: DateFormatterConfig): Promise<string> => {\n const locale = getHelperLocale(config, props)\n const formatter = createDateFormatter({ ...config, locale })\n\n await formatter.ready\n\n return formatter.getDate(props)\n}\n\nexport const parseDate = async (\n props: DateParsingOptions,\n config?: DateFormatterConfig\n): Promise<ParseDateResult> => {\n const locale = getHelperLocale(config, props)\n const formatter = createDateFormatter({ ...config, locale })\n\n await formatter.ready\n\n return formatter.parseDate(props)\n}\n\nexport const isValidDate = async (\n props: DateParsingOptions,\n config?: DateFormatterConfig\n): Promise<boolean> => {\n const locale = getHelperLocale(config, props)\n const formatter = createDateFormatter({ ...config, locale })\n\n await formatter.ready\n\n return formatter.isValidDate(props)\n}\n\nexport const createDateFormatter = (config?: DateFormatterConfig): DateFormatter => {\n let currentLocale = config?.locale ?? DEFAULT_LOCALE\n\n const getConfig = (): Required<DateFormatterConfig> => createResolvedConfig(currentLocale, config)\n\n const ready = ensureLocaleLoaded(currentLocale)\n const parseDate = createFormatterParseDate(getConfig)\n\n return {\n getDate: createFormatterGetDate(getConfig),\n parseDate,\n isValidDate: createFormatterIsValidDate(parseDate),\n getSupportedLocales,\n getCurrentLocale: () => currentLocale,\n setLocale: async (locale: SupportedLocale) => {\n assertSupportedLocale(locale)\n await ensureLocaleLoaded(locale)\n currentLocale = locale\n },\n ready\n }\n}\n","export const SUPPORTED_LOCALES = [\n 'en',\n 'es',\n 'es-mx',\n 'fr',\n 'pt',\n 'pt-br',\n 'de',\n 'it',\n 'ja'\n] as const\n\nexport type SupportedLocale = (typeof SUPPORTED_LOCALES)[number]\n\nconst localeLoaders: Record<SupportedLocale, (() => Promise<unknown>) | null> = {\n en: null,\n es: () => import('dayjs/locale/es.js'),\n 'es-mx': () => import('dayjs/locale/es-mx.js'),\n fr: () => import('dayjs/locale/fr.js'),\n pt: () => import('dayjs/locale/pt.js'),\n 'pt-br': () => import('dayjs/locale/pt-br.js'),\n de: () => import('dayjs/locale/de.js'),\n it: () => import('dayjs/locale/it.js'),\n ja: () => import('dayjs/locale/ja.js')\n}\n\nexport const isSupportedLocale = (locale: string): locale is SupportedLocale => {\n return SUPPORTED_LOCALES.includes(locale as SupportedLocale)\n}\n\nexport const ensureLocaleLoaded = async (locale: SupportedLocale): Promise<void> => {\n const load = localeLoaders[locale]\n\n if (!load) {\n return\n }\n\n await load()\n}\n"],"mappings":"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","names":["parseDate"]}
1
+ {"version":3,"sources":["../src/core/date.ts","../src/core/locales.ts"],"sourcesContent":["import customParseFormat from 'dayjs/plugin/customParseFormat.js'\nimport dayjs from 'dayjs'\nimport type { Dayjs } from 'dayjs'\n\nimport { ensureLocaleLoaded, resolveLocale, SUPPORTED_LOCALES, type LocaleInput, type SupportedLocale } from './locales.js'\n\ndayjs.extend(customParseFormat)\ndayjs.locale('en')\n\nexport type DateValue = string | number | Date\n\ntype DateInputOptions = {\n input?: string | readonly string[]\n locale?: LocaleInput\n strict?: boolean\n}\n\nexport type DateParsingOptions = DateInputOptions & {\n date: DateValue\n}\n\nexport type GetDateOptions = DateInputOptions & {\n date?: DateValue\n output?: string\n invalid?: string\n}\n\nexport type DateFormatterConfig = {\n locale?: LocaleInput\n strict?: boolean\n invalid?: string\n}\n\ntype ResolvedDateFormatterConfig = {\n locale: SupportedLocale\n strict: boolean\n invalid: string\n}\n\nexport type DateFormatter = {\n getDate: (props?: GetDateOptions) => string\n parseDate: (props: DateParsingOptions) => ParseDateResult\n isValidDate: (props: DateParsingOptions) => boolean\n getSupportedLocales: () => readonly SupportedLocale[]\n getCurrentLocale: () => SupportedLocale\n setLocale: (locale: LocaleInput) => Promise<void>\n ready: Promise<void>\n}\n\nexport type ParseDateSuccess = {\n isValid: true\n locale: SupportedLocale\n date: Date\n iso: string\n timestamp: number\n format: (output?: string) => string\n}\n\nexport type ParseDateFailure = {\n isValid: false\n locale: SupportedLocale\n date: null\n iso: null\n timestamp: null\n error: string\n}\n\nexport type ParseDateResult = ParseDateSuccess | ParseDateFailure\n\nconst DEFAULT_FORMAT = 'YYYY-MM-DD'\nconst DEFAULT_LOCALE: SupportedLocale = 'en'\nconst DEFAULT_INVALID_DATE = 'Invalid Date'\nconst DEFAULT_STRICT = true\n\nconst createResolvedConfig = (\n locale: SupportedLocale,\n config?: DateFormatterConfig\n): ResolvedDateFormatterConfig => ({\n locale,\n strict: config?.strict ?? DEFAULT_STRICT,\n invalid: config?.invalid ?? DEFAULT_INVALID_DATE\n})\n\nconst getInvalidDateText = (config?: DateFormatterConfig, props?: GetDateOptions): string => {\n return props?.invalid ?? config?.invalid ?? DEFAULT_INVALID_DATE\n}\n\nconst getTargetLocale = (currentLocale: SupportedLocale, props?: GetDateOptions): SupportedLocale => {\n if (!props?.locale) {\n return currentLocale\n }\n\n return resolveLocaleOrThrow(props.locale)\n}\n\nconst getHelperLocale = <T extends { locale?: LocaleInput }>(\n config?: DateFormatterConfig,\n props?: T\n): SupportedLocale => {\n if (props?.locale) {\n return resolveLocaleOrThrow(props.locale)\n }\n\n if (config?.locale) {\n return resolveLocaleOrThrow(config.locale)\n }\n\n return DEFAULT_LOCALE\n}\n\nconst parseDateValue = (\n value: DateValue,\n input: DateParsingOptions['input'],\n locale: SupportedLocale,\n strict: boolean\n): Dayjs => {\n if (!input) {\n return dayjs(value).locale(locale)\n }\n\n if (typeof input === 'string') {\n return dayjs(value, input, locale, strict).locale(locale)\n }\n\n return dayjs(value, [...input], locale, strict).locale(locale)\n}\n\nconst createFormatterParseDate = (getConfig: () => ResolvedDateFormatterConfig) => {\n return (props: DateParsingOptions): ParseDateResult => {\n const config = getConfig()\n const locale = getTargetLocale(config.locale, props)\n const parsed = parseDateValue(props.date, props.input, locale, props.strict ?? config.strict)\n\n if (!parsed.isValid()) {\n return {\n isValid: false,\n locale,\n date: null,\n iso: null,\n timestamp: null,\n error: getInvalidDateText(config, props)\n }\n }\n\n return {\n isValid: true,\n locale,\n date: parsed.toDate(),\n iso: parsed.toISOString(),\n timestamp: parsed.valueOf(),\n format: (output = DEFAULT_FORMAT) => parsed.format(output)\n }\n }\n}\n\nconst createFormatterIsValidDate = (parseDate: (props: DateParsingOptions) => ParseDateResult) => {\n return (props: DateParsingOptions): boolean => parseDate(props).isValid\n}\n\nconst createFormatterGetDate = (getConfig: () => ResolvedDateFormatterConfig) => {\n const parseDate = createFormatterParseDate(getConfig)\n\n return (props?: GetDateOptions): string => {\n const config = getConfig()\n const locale = getTargetLocale(config.locale, props)\n const output = props?.output ?? DEFAULT_FORMAT\n\n if (!props) {\n return dayjs().locale(locale).format(DEFAULT_FORMAT)\n }\n\n if (props.date === undefined) {\n return dayjs().locale(locale).format(output)\n }\n\n const parsed = parseDate({\n date: props.date,\n input: props.input,\n locale: props.locale,\n strict: props.strict\n })\n\n if (!parsed.isValid) {\n return props.invalid ?? parsed.error\n }\n\n return parsed.format(output)\n }\n}\n\nfunction resolveLocaleOrThrow(locale: LocaleInput): SupportedLocale {\n const resolvedLocale = resolveLocale(locale)\n\n if (!resolvedLocale) {\n throw new Error(\n `Unsupported locale: ${locale}. The package tries an exact locale match first and then falls back to the base locale.`\n )\n }\n\n return resolvedLocale\n}\n\nexport const getSupportedLocales = (): readonly SupportedLocale[] => SUPPORTED_LOCALES\n\nexport const getDate = async (props?: GetDateOptions, config?: DateFormatterConfig): Promise<string> => {\n const locale = getHelperLocale(config, props)\n const formatter = createDateFormatter({ ...config, locale })\n\n await formatter.ready\n\n return formatter.getDate(props)\n}\n\nexport const parseDate = async (\n props: DateParsingOptions,\n config?: DateFormatterConfig\n): Promise<ParseDateResult> => {\n const locale = getHelperLocale(config, props)\n const formatter = createDateFormatter({ ...config, locale })\n\n await formatter.ready\n\n return formatter.parseDate(props)\n}\n\nexport const isValidDate = async (\n props: DateParsingOptions,\n config?: DateFormatterConfig\n): Promise<boolean> => {\n const locale = getHelperLocale(config, props)\n const formatter = createDateFormatter({ ...config, locale })\n\n await formatter.ready\n\n return formatter.isValidDate(props)\n}\n\nexport const createDateFormatter = (config?: DateFormatterConfig): DateFormatter => {\n let currentLocale = config?.locale ? resolveLocaleOrThrow(config.locale) : DEFAULT_LOCALE\n\n const getConfig = (): ResolvedDateFormatterConfig => createResolvedConfig(currentLocale, config)\n\n const ready = ensureLocaleLoaded(currentLocale)\n const parseDate = createFormatterParseDate(getConfig)\n\n return {\n getDate: createFormatterGetDate(getConfig),\n parseDate,\n isValidDate: createFormatterIsValidDate(parseDate),\n getSupportedLocales,\n getCurrentLocale: () => currentLocale,\n setLocale: async (locale: LocaleInput) => {\n const resolvedLocale = resolveLocaleOrThrow(locale)\n\n await ensureLocaleLoaded(resolvedLocale)\n currentLocale = resolvedLocale\n },\n ready\n }\n}\n","export const SUPPORTED_LOCALES = [\n 'en',\n 'es',\n 'es-mx',\n 'fr',\n 'pt',\n 'pt-br',\n 'de',\n 'it',\n 'ja'\n] as const\n\nexport type SupportedLocale = (typeof SUPPORTED_LOCALES)[number]\nexport type LocaleInput = string\n\nconst localeLoaders: Record<SupportedLocale, (() => Promise<unknown>) | null> = {\n en: null,\n es: () => import('dayjs/locale/es.js'),\n 'es-mx': () => import('dayjs/locale/es-mx.js'),\n fr: () => import('dayjs/locale/fr.js'),\n pt: () => import('dayjs/locale/pt.js'),\n 'pt-br': () => import('dayjs/locale/pt-br.js'),\n de: () => import('dayjs/locale/de.js'),\n it: () => import('dayjs/locale/it.js'),\n ja: () => import('dayjs/locale/ja.js')\n}\n\nconst normalizeLocale = (locale: string): string => {\n return locale.trim().toLowerCase().replace(/_/g, '-')\n}\n\nconst asSupportedLocale = (locale: string): SupportedLocale | null => {\n if (!SUPPORTED_LOCALES.includes(locale as SupportedLocale)) {\n return null\n }\n\n return locale as SupportedLocale\n}\n\nexport const isSupportedLocale = (locale: string): boolean => {\n return resolveLocale(locale) !== null\n}\n\nexport const resolveLocale = (locale: LocaleInput): SupportedLocale | null => {\n const normalizedLocale = normalizeLocale(locale)\n const exactLocale = asSupportedLocale(normalizedLocale)\n\n if (exactLocale) {\n return exactLocale\n }\n\n const [baseLocale] = normalizedLocale.split('-')\n\n if (!baseLocale) {\n return null\n }\n\n return asSupportedLocale(baseLocale)\n}\n\nexport const ensureLocaleLoaded = async (locale: SupportedLocale): Promise<void> => {\n const load = localeLoaders[locale]\n\n if (!load) {\n return\n }\n\n await load()\n}\n"],"mappings":"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","names":["parseDate"]}