@softure-ai/core 0.1.6 → 0.1.7
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/CHANGELOG.md +5 -0
- package/README.md +12 -2
- package/dist/calendar-day.d.ts +6 -0
- package/dist/calendar-day.d.ts.map +1 -0
- package/dist/calendar-day.js +32 -0
- package/dist/calendar-day.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/calendar-day.ts +38 -0
- package/src/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,11 @@ Newest first. Each version lists what changed for an app that uses `@softure-ai/
|
|
|
4
4
|
production, the version gets a line `verified in: <app>@<commit>` ([docs/05](../../docs/05-adoption-playbook.md),
|
|
5
5
|
"Definition of done"). Versions before the first one below are described in their GitHub Releases (`core@x.y.z`).
|
|
6
6
|
|
|
7
|
+
## 0.1.7
|
|
8
|
+
|
|
9
|
+
- `toCalendarDay(instant, timeZone)` and `getCalendarDay(clock, timeZone)`: the calendar day (`YYYY-MM-DD`) of an
|
|
10
|
+
instant, or today from a `Clock`, in an IANA time zone rather than the process zone (#251).
|
|
11
|
+
|
|
7
12
|
## 0.1.6
|
|
8
13
|
|
|
9
14
|
- `PublicError`, `isPublicError` and `getPublicMessage`: a message written for the user passes through, every other error's text stays out (`safeError` is unchanged).
|
package/README.md
CHANGED
|
@@ -249,7 +249,17 @@ if (plan.endedAt !== null) throw new PublicError("This plan has ended; pick anot
|
|
|
249
249
|
`systemClock` is the real one; `createTestClock(start)` has `advance(ms)` and `set(date)`.
|
|
250
250
|
A whole test run can also move to another day without code changes: `TEST_TODAY=YYYY-MM-DD` with the
|
|
251
251
|
`@softure-ai/testing/vitest-setup` setup file shifts the global `Date` (and so `systemClock`) to that
|
|
252
|
-
day while time keeps running ([`@softure-ai/testing`](../testing/README.md)).
|
|
252
|
+
day while time keeps running ([`@softure-ai/testing`](../testing/README.md)). The same setup file pins
|
|
253
|
+
the tests to a zone with a negative offset, so a date computed without an explicit zone fails there.
|
|
254
|
+
|
|
255
|
+
**Calendar days.** "Today" is a day in a zone, never in the process zone: on a server in UTC an evening
|
|
256
|
+
in New York is already tomorrow. `toCalendarDay(instant, timeZone)` returns the `YYYY-MM-DD` day of an
|
|
257
|
+
instant in an IANA zone, and `getCalendarDay(clock, timeZone)` today's day from a `Clock`; pass the
|
|
258
|
+
app's `timezone` from the config. Both throw a `RangeError` for an invalid date or an unknown zone.
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
getCalendarDay(context.clock, config.timezone); // "2026-10-08"
|
|
262
|
+
```
|
|
253
263
|
|
|
254
264
|
## 10. Hooks
|
|
255
265
|
|
|
@@ -275,4 +285,4 @@ manifest's `privacy.exports` / `privacy.deletes` flag is true. The `privacy` mod
|
|
|
275
285
|
are rejected.
|
|
276
286
|
- Route maps mix mounted paths and redirect targets, so two modules may share a path; mount
|
|
277
287
|
collisions are left to `softure doctor`.
|
|
278
|
-
- No date or number formatting helpers
|
|
288
|
+
- No date or number formatting helpers beyond the calendar day; apps use `Intl` with `config.locale` and `config.timezone`.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Clock } from "./clock.js";
|
|
2
|
+
/** The calendar day (`YYYY-MM-DD`) of `instant` in `timeZone`. Throws a `RangeError` for an invalid date or zone. */
|
|
3
|
+
export declare function toCalendarDay(instant: Date, timeZone: string): string;
|
|
4
|
+
/** Today's calendar day (`YYYY-MM-DD`) in `timeZone`, read from `clock`. */
|
|
5
|
+
export declare function getCalendarDay(clock: Clock, timeZone: string): string;
|
|
6
|
+
//# sourceMappingURL=calendar-day.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendar-day.d.ts","sourceRoot":"","sources":["../src/calendar-day.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAIxC,qHAAqH;AACrH,wBAAgB,aAAa,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQrE;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAErE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const formatters = new Map();
|
|
2
|
+
/** The calendar day (`YYYY-MM-DD`) of `instant` in `timeZone`. Throws a `RangeError` for an invalid date or zone. */
|
|
3
|
+
export function toCalendarDay(instant, timeZone) {
|
|
4
|
+
if (Number.isNaN(instant.getTime())) {
|
|
5
|
+
throw new RangeError("toCalendarDay: invalid date");
|
|
6
|
+
}
|
|
7
|
+
// Parts, not the formatted string: a locale's date pattern (`en-CA`) has changed between ICU versions.
|
|
8
|
+
const parts = getFormatter(timeZone).formatToParts(instant);
|
|
9
|
+
const readPart = (type) => parts.find((part) => part.type === type)?.value ?? "";
|
|
10
|
+
return `${readPart("year").padStart(4, "0")}-${readPart("month")}-${readPart("day")}`;
|
|
11
|
+
}
|
|
12
|
+
/** Today's calendar day (`YYYY-MM-DD`) in `timeZone`, read from `clock`. */
|
|
13
|
+
export function getCalendarDay(clock, timeZone) {
|
|
14
|
+
return toCalendarDay(clock.now(), timeZone);
|
|
15
|
+
}
|
|
16
|
+
function getFormatter(timeZone) {
|
|
17
|
+
let format = formatters.get(timeZone);
|
|
18
|
+
if (format === undefined) {
|
|
19
|
+
format = createFormatter(timeZone);
|
|
20
|
+
formatters.set(timeZone, format);
|
|
21
|
+
}
|
|
22
|
+
return format;
|
|
23
|
+
}
|
|
24
|
+
function createFormatter(timeZone) {
|
|
25
|
+
try {
|
|
26
|
+
return new Intl.DateTimeFormat("en-US", { timeZone, year: "numeric", month: "2-digit", day: "2-digit" });
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
throw new RangeError(`toCalendarDay: "${timeZone}" is not an IANA time zone`, { cause: error });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=calendar-day.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendar-day.js","sourceRoot":"","sources":["../src/calendar-day.ts"],"names":[],"mappings":"AAIA,MAAM,UAAU,GAAG,IAAI,GAAG,EAA+B,CAAC;AAE1D,qHAAqH;AACrH,MAAM,UAAU,aAAa,CAAC,OAAa,EAAE,QAAgB;IAC3D,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,UAAU,CAAC,6BAA6B,CAAC,CAAC;IACtD,CAAC;IACD,uGAAuG;IACvG,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAG,CAAC,IAAkC,EAAU,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;IACvH,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;AACxF,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,cAAc,CAAC,KAAY,EAAE,QAAgB;IAC3D,OAAO,aAAa,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,YAAY,CAAC,QAAgB;IACpC,IAAI,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;QACnC,UAAU,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,eAAe,CAAC,QAAgB;IACvC,IAAI,CAAC;QACH,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;IAC3G,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,UAAU,CAAC,mBAAmB,QAAQ,4BAA4B,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAClG,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { getCalendarDay, toCalendarDay } from "./calendar-day.js";
|
|
1
2
|
export { createTestClock, systemClock, type Clock, type TestClock } from "./clock.js";
|
|
2
3
|
export { formatMessage, getMessage, isLocale, LOCALES, mergeMessages, selectPlural, type DeepPartial, type Dictionaries, type Locale, type MessageOverrides, type MessageTree, type PluralForms, } from "./i18n.js";
|
|
3
4
|
export { coreMessages, type CoreMessages } from "./messages/index.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,KAAK,KAAK,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AACtF,OAAO,EACL,aAAa,EACb,UAAU,EACV,QAAQ,EACR,OAAO,EACP,aAAa,EACb,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,MAAM,EACX,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,WAAW,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,GAAG,EAAE,KAAK,SAAS,EAAE,KAAK,EAAE,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC7H,OAAO,EACL,mBAAmB,EACnB,SAAS,EACT,yBAAyB,EACzB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,0BAA0B,GAChC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACvG,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAE,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,KAAK,KAAK,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AACtF,OAAO,EACL,aAAa,EACb,UAAU,EACV,QAAQ,EACR,OAAO,EACP,aAAa,EACb,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,MAAM,EACX,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,WAAW,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,GAAG,EAAE,KAAK,SAAS,EAAE,KAAK,EAAE,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC7H,OAAO,EACL,mBAAmB,EACnB,SAAS,EACT,yBAAyB,EACzB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,0BAA0B,GAChC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACvG,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAE,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Public API of @softure-ai/core: the contract every SOFTURE module stands on
|
|
2
2
|
// (docs/02-module-standard.md). The Next.js registry is a separate entry: `@softure-ai/core/next`.
|
|
3
|
+
export { getCalendarDay, toCalendarDay } from "./calendar-day.js";
|
|
3
4
|
export { createTestClock, systemClock } from "./clock.js";
|
|
4
5
|
export { formatMessage, getMessage, isLocale, LOCALES, mergeMessages, selectPlural, } from "./i18n.js";
|
|
5
6
|
export { coreMessages } from "./messages/index.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,mGAAmG;AACnG,OAAO,EAAE,eAAe,EAAE,WAAW,EAA8B,MAAM,YAAY,CAAC;AACtF,OAAO,EACL,aAAa,EACb,UAAU,EACV,QAAQ,EACR,OAAO,EACP,aAAa,EACb,YAAY,GAOb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAqB,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAkD,MAAM,aAAa,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAsB,MAAM,iBAAiB,CAAC;AAC7H,OAAO,EACL,mBAAmB,EACnB,SAAS,EACT,yBAAyB,GAM1B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAuB,MAAM,eAAe,CAAC;AAC1E,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,YAAY,GAUb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAuC,MAAM,gBAAgB,CAAC;AACvG,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAyC,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAmC,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,mGAAmG;AACnG,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,WAAW,EAA8B,MAAM,YAAY,CAAC;AACtF,OAAO,EACL,aAAa,EACb,UAAU,EACV,QAAQ,EACR,OAAO,EACP,aAAa,EACb,YAAY,GAOb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAqB,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAkD,MAAM,aAAa,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAsB,MAAM,iBAAiB,CAAC;AAC7H,OAAO,EACL,mBAAmB,EACnB,SAAS,EACT,yBAAyB,GAM1B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAuB,MAAM,eAAe,CAAC;AAC1E,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,YAAY,GAUb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAuC,MAAM,gBAAgB,CAAC;AACvG,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAyC,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAmC,MAAM,oBAAoB,CAAC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// The calendar day of an instant in a given IANA time zone, as `YYYY-MM-DD`. "Today" computed in the
|
|
2
|
+
// process zone is a classic date bug: on a server in UTC, an evening in New York is already tomorrow.
|
|
3
|
+
import type { Clock } from "./clock.js";
|
|
4
|
+
|
|
5
|
+
const formatters = new Map<string, Intl.DateTimeFormat>();
|
|
6
|
+
|
|
7
|
+
/** The calendar day (`YYYY-MM-DD`) of `instant` in `timeZone`. Throws a `RangeError` for an invalid date or zone. */
|
|
8
|
+
export function toCalendarDay(instant: Date, timeZone: string): string {
|
|
9
|
+
if (Number.isNaN(instant.getTime())) {
|
|
10
|
+
throw new RangeError("toCalendarDay: invalid date");
|
|
11
|
+
}
|
|
12
|
+
// Parts, not the formatted string: a locale's date pattern (`en-CA`) has changed between ICU versions.
|
|
13
|
+
const parts = getFormatter(timeZone).formatToParts(instant);
|
|
14
|
+
const readPart = (type: Intl.DateTimeFormatPartTypes): string => parts.find((part) => part.type === type)?.value ?? "";
|
|
15
|
+
return `${readPart("year").padStart(4, "0")}-${readPart("month")}-${readPart("day")}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Today's calendar day (`YYYY-MM-DD`) in `timeZone`, read from `clock`. */
|
|
19
|
+
export function getCalendarDay(clock: Clock, timeZone: string): string {
|
|
20
|
+
return toCalendarDay(clock.now(), timeZone);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getFormatter(timeZone: string): Intl.DateTimeFormat {
|
|
24
|
+
let format = formatters.get(timeZone);
|
|
25
|
+
if (format === undefined) {
|
|
26
|
+
format = createFormatter(timeZone);
|
|
27
|
+
formatters.set(timeZone, format);
|
|
28
|
+
}
|
|
29
|
+
return format;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function createFormatter(timeZone: string): Intl.DateTimeFormat {
|
|
33
|
+
try {
|
|
34
|
+
return new Intl.DateTimeFormat("en-US", { timeZone, year: "numeric", month: "2-digit", day: "2-digit" });
|
|
35
|
+
} catch (error) {
|
|
36
|
+
throw new RangeError(`toCalendarDay: "${timeZone}" is not an IANA time zone`, { cause: error });
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Public API of @softure-ai/core: the contract every SOFTURE module stands on
|
|
2
2
|
// (docs/02-module-standard.md). The Next.js registry is a separate entry: `@softure-ai/core/next`.
|
|
3
|
+
export { getCalendarDay, toCalendarDay } from "./calendar-day.js";
|
|
3
4
|
export { createTestClock, systemClock, type Clock, type TestClock } from "./clock.js";
|
|
4
5
|
export {
|
|
5
6
|
formatMessage,
|