From 32a8e0e7af0f0922fd4062e0d67f3fe2e42ea633 Mon Sep 17 00:00:00 2001 From: Michael McRae Date: Sat, 18 Jul 2026 10:13:47 +1000 Subject: [PATCH 1/2] kick-off v3.9.3 --- package-lock.json | 8 ++++---- package.json | 2 +- packages/library/package.json | 2 +- packages/tempo/package.json | 2 +- packages/tempo/src/tempo.version.ts | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8f82610c..17e35ee9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "tempo-monorepo", - "version": "3.9.1", + "version": "3.9.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tempo-monorepo", - "version": "3.9.1", + "version": "3.9.3", "workspaces": [ "packages/*", "packages/plugins/*" @@ -11049,7 +11049,7 @@ }, "packages/library": { "name": "@magmacomputing/library", - "version": "3.9.1", + "version": "3.9.3", "license": "MIT", "dependencies": { "tslib": "^2.8.1" @@ -11385,7 +11385,7 @@ }, "packages/tempo": { "name": "@magmacomputing/tempo", - "version": "3.9.1", + "version": "3.9.3", "license": "MIT", "dependencies": { "tslib": "^2.8.1" diff --git a/package.json b/package.json index d981b75c..2d91086e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tempo-monorepo", - "version": "3.9.1", + "version": "3.9.3", "private": true, "engines": { "node": ">=20.0.0" diff --git a/packages/library/package.json b/packages/library/package.json index 702667b2..7a441e35 100644 --- a/packages/library/package.json +++ b/packages/library/package.json @@ -1,6 +1,6 @@ { "name": "@magmacomputing/library", - "version": "3.9.1", + "version": "3.9.3", "description": "Shared utility library for Tempo", "author": "Magma Computing Solutions", "license": "MIT", diff --git a/packages/tempo/package.json b/packages/tempo/package.json index b9161d23..f12ee6df 100644 --- a/packages/tempo/package.json +++ b/packages/tempo/package.json @@ -1,6 +1,6 @@ { "name": "@magmacomputing/tempo", - "version": "3.9.1", + "version": "3.9.3", "engines": { "node": ">=20.0.0" }, diff --git a/packages/tempo/src/tempo.version.ts b/packages/tempo/src/tempo.version.ts index bcd131b2..2fea09c2 100644 --- a/packages/tempo/src/tempo.version.ts +++ b/packages/tempo/src/tempo.version.ts @@ -5,4 +5,4 @@ * ⚠️ This file is auto-updated by `npm run build:version` (see `bin/update-version.mjs`). * Do NOT edit manually β€” your changes will be overwritten on the next build. */ -export const TEMPO_VERSION = '3.9.1'; +export const TEMPO_VERSION = '3.9.2'; From 3f2e438f4477e69c15d2fa534de1f571ea785f61 Mon Sep 17 00:00:00 2001 From: Michael McRae Date: Sat, 18 Jul 2026 15:06:41 +1000 Subject: [PATCH 2/2] PR 3.9.3 --- packages/plugins/ticker/doc/index.md | 12 +-- packages/tempo/.vitepress/config.ts | 5 +- .../theme/components/CatalogList.vue | 2 +- packages/tempo/CHANGELOG.md | 13 +++ packages/tempo/README.md | 21 +++-- .../tempo/doc/2-core-concepts/tempo.mutate.md | 84 +++++++++++++++++++ .../tempo.layout.md | 0 .../tempo.registry.md | 0 packages/tempo/src/module/module.format.ts | 32 ++++--- packages/tempo/src/support/support.enum.ts | 26 ++---- packages/tempo/src/support/support.index.ts | 2 - packages/tempo/src/tempo.class.ts | 9 +- packages/tempo/src/tempo.type.ts | 7 +- packages/tempo/src/tempo.version.ts | 2 +- packages/tempo/test/discrete/format.test.ts | 16 ++-- .../tempo/test/discrete/standalone.test.ts | 18 ++-- .../tempo/test/engine/pattern.default.test.ts | 3 +- .../tempo/test/engine/pattern.weekday.test.ts | 2 +- .../test/instance/instance.format.test.ts | 12 +-- 19 files changed, 174 insertions(+), 92 deletions(-) create mode 100644 packages/tempo/doc/2-core-concepts/tempo.mutate.md rename packages/tempo/doc/{2-core-concepts => 3-extending-tempo}/tempo.layout.md (100%) rename packages/tempo/doc/{2-core-concepts => 3-extending-tempo}/tempo.registry.md (100%) diff --git a/packages/plugins/ticker/doc/index.md b/packages/plugins/ticker/doc/index.md index cb334701..6d03da70 100644 --- a/packages/plugins/ticker/doc/index.md +++ b/packages/plugins/ticker/doc/index.md @@ -34,7 +34,7 @@ Tempo.init({ plugins: [TickerPlugin] }); -// You can now access Ticker-based execution loops through the Tempo API: +// You can access Ticker-based execution loops through the Tempo API: const ticker = Tempo.ticker({ seconds: 1 }); ``` @@ -66,12 +66,14 @@ await using quarterly = Tempo.ticker({ '#quarter': 1 }); ``` ### 2. Term-Based Intervals -Ticker intervals can now be driven by any registered **Term**. This is powerful for syncing with business cycles or daily shifts. +Ticker intervals can be driven by any registered **Term**. This is powerful for syncing with business cycles or daily shifts. + +> **Snapping vs Shifting:** Use directional shorthands (like `>`) to snap pulses exactly to the **boundaries** of the term (e.g., the very start of the morning). Using numeric values (like `1`) performs a relative shift, which preserves your current time-offset into the next period (e.g. two hours into a time-period will always be two hours into the next time-period). ```typescript -// Pulse at the start of every 'morning', 'afternoon', etc. -using shiftTicker = Tempo.ticker({ '#period': 1 }, (t) => { - console.log(`New period started: ${t.term.per}`); +// Snap and pulse exactly at the start of every 'morning', 'afternoon', etc. +using shiftTicker = Tempo.ticker({ '#timeOfDay': '>' }, (t) => { + console.log(`New period started: ${t.term.tod}`); }); ``` diff --git a/packages/tempo/.vitepress/config.ts b/packages/tempo/.vitepress/config.ts index 84162fc7..6a02c545 100644 --- a/packages/tempo/.vitepress/config.ts +++ b/packages/tempo/.vitepress/config.ts @@ -46,12 +46,11 @@ export default defineConfig({ text: 'Core Concepts', items: [ { text: 'Configuration', link: '/doc/2-core-concepts/tempo.config' }, - { text: 'Registries', link: '/doc/2-core-concepts/tempo.registry' }, { text: 'Core Getters', link: '/doc/2-core-concepts/tempo.getters' }, { text: 'Smart Parsing', link: '/doc/2-core-concepts/tempo.parse' }, { text: 'Smart Formatting', link: '/doc/2-core-concepts/tempo.format' }, - { text: 'Layout Patterns', link: '/doc/2-core-concepts/tempo.layout' }, { text: 'Duration Logic', link: '/doc/2-core-concepts/tempo.duration' }, + { text: 'Mutation & Math', link: '/doc/2-core-concepts/tempo.mutate' }, { text: 'Intervals', link: '/doc/2-core-concepts/tempo.interval' } ] }, @@ -59,7 +58,9 @@ export default defineConfig({ text: 'Extending Tempo', items: [ { text: 'Modules', link: '/doc/3-extending-tempo/tempo.modularity' }, + { text: 'Registries', link: '/doc/3-extending-tempo/tempo.registry' }, { text: 'Plugins', link: '/doc/3-extending-tempo/tempo.plugin' }, + { text: 'Layout Patterns', link: '/doc/3-extending-tempo/tempo.layout' }, { text: 'Terms', link: '/doc/3-extending-tempo/tempo.term' }, { text: 'Namespaces', link: '/doc/3-extending-tempo/tempo.namespace' }, { text: 'Creating Custom Plugins', link: '/doc/3-extending-tempo/tempo.extension' }, diff --git a/packages/tempo/.vitepress/theme/components/CatalogList.vue b/packages/tempo/.vitepress/theme/components/CatalogList.vue index c0fda805..5f519a1e 100644 --- a/packages/tempo/.vitepress/theme/components/CatalogList.vue +++ b/packages/tempo/.vitepress/theme/components/CatalogList.vue @@ -214,7 +214,7 @@ const copyInstall = (pkgName: string) => { position: absolute; top: -10px; right: 15px; - background: var(--vp-c-brand); + background: var(--vp-c-danger-1, #ef4444); color: white; padding: 2px 8px; border-radius: 12px; diff --git a/packages/tempo/CHANGELOG.md b/packages/tempo/CHANGELOG.md index ddea1533..58a142d0 100644 --- a/packages/tempo/CHANGELOG.md +++ b/packages/tempo/CHANGELOG.md @@ -6,6 +6,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.9.3] - 2026-07-18 + +### Added +- **Core Concepts Documentation**: Added a new `Mutation & Math` (`tempo.mutate.md`) guide to the Core Concepts section to explicitly document `.add()` and `.set()` immutability, chainability, and the design decision to omit a dedicated `.subtract()` method. + +### Changed +- **Documentation Architecture**: Relocated `tempo.registry.md` and `tempo.layout.md` from Core Concepts to Extending Tempo to better reflect their advanced, extensibility-focused usage patterns. +- **README Updates**: Added `AstroPlugin` initialization examples, an "Ultra Lightweight" architecture bullet, and restructured the ecosystem markdown table for cleaner VitePress rendering. + +### Fixed +- **Format Leading Zeros**: Fixed a data-corruption bug in `Tempo.format()` where numeric-looking tokens (e.g., `{dd}`, `{mm}`) were implicitly cast to numbers, stripping their leading zeros. The `.format()` method now strictly adheres to a string-only return contract, ensuring zero-padded tokens retain their exact formatting. Removed `NumericPattern` and `BigIntPattern` complexity and simplified internal casting logic. +- **VitePress UI**: Fixed a CSS variable reference in the `CatalogList.vue` component, mapping the install badge background to `--vp-c-danger-1` instead of `--vp-c-brand`. + ## [3.9.1] - 2026-07-17 ### Added diff --git a/packages/tempo/README.md b/packages/tempo/README.md index 49d48ea9..b895c7fc 100644 --- a/packages/tempo/README.md +++ b/packages/tempo/README.md @@ -26,6 +26,13 @@ ## ⚑ Quick Start ```javascript import { Tempo } from '@magmacomputing/tempo'; +import { AstroPlugin } from '@magmacomputing/tempo-plugin-astro'; + +// πŸ”Œ Instantly Extensible (with deterministic defaults) +Tempo.init({ + plugins: [AstroPlugin], + timeZone: 'America/New_York' +}); // 🎯 Natural Language Parsing (Deterministic anchor) const event = new Tempo('next Friday 3pm', { anchor: '2026-10-15' }); @@ -39,6 +46,9 @@ console.log(diff.iso); // P2M2D // πŸ“ Beautiful Formatting console.log(event.format('{mon} {dd:ord}, {yyyy}')); // October 23rd, 2026 + +// 🌌 Domain Logic (via Plugin) +console.log(event.term.astronomy.season); // 'Autumn' ``` --- @@ -105,6 +115,7 @@ While the native Temporal API gives you perfect primitives (`ZonedDateTime`, `Pl * **⚑ Zero-Cost Parsing**: Lazy evaluation and smart matching ensure instantiation overhead is near-zero. * **πŸ›‘οΈ Monorepo Resilient**: Built for stability in complex environments with proxy-protected registries. * **πŸ“¦ Tree-Shakable**: Keep your bundle light. Only load what you needβ€”from Fiscal calendars to high-performance Tickers. +* **πŸͺΆ Ultra Lightweight**: Tempo itself is incredibly lean. While the required `Temporal` polyfill adds weight today, it can be dropped entirely the moment JavaScript environments natively adopt the Stage 4 standard. --- @@ -121,11 +132,11 @@ For a deeper dive into the API, architecture, and advanced features: Tempo is the core library, but the ecosystem extends further: -| Package | Description | -| :--- | :--- | -| **[`@magmacomputing/tempo`](https://www.npmjs.com/package/@magmacomputing/tempo)** | Core library β€” parsing, formatting, natural-language engine | -| **[`@magmacomputing/tempo-fns`](https://www.npmjs.com/package/@magmacomputing/tempo-fns)** | Pure functional utilities built on native Temporal & Tempo β€” tree-shakeable helpers for calendars, business logic, and scheduling   [![Docs](https://img.shields.io/badge/Docs-VitePress-brightgreen?logo=vitepress&style=flat-square)](https://magmacomputing.github.io/magma/functions/) | -| **[`@magmacomputing/tempo-plugin-*`](https://www.npmjs.com/search?q=%40magmacomputing%2Ftempo-plugin)** | Premium & community plugins β€” Ticker, Astro, Finance, Sync, Snap and more   [![Ecosystem](https://img.shields.io/badge/Browse-Plugin%20Ecosystem-blueviolet?logo=npm&style=flat-square)](https://magmacomputing.github.io/magma/doc/3-extending-tempo/ecosystem) | +| Package | Description | Resources | +| :--- | :--- | :--- | +| **[`@magmacomputing/tempo`](https://www.npmjs.com/package/@magmacomputing/tempo)** | Core library β€” parsing, formatting, natural-language engine | [![Docs](https://img.shields.io/badge/Docs-VitePress-brightgreen?logo=vitepress&style=flat-square)](https://magmacomputing.github.io/magma/) | +| **[`@magmacomputing/tempo-fns`](https://www.npmjs.com/package/@magmacomputing/tempo-fns)** | Pure functional utilities built on native Temporal & Tempo β€” tree-shakeable helpers | [![Docs](https://img.shields.io/badge/Docs-VitePress-brightgreen?logo=vitepress&style=flat-square)](https://magmacomputing.github.io/magma/functions/) | +| **[`@magmacomputing/tempo-plugin-*`](https://www.npmjs.com/search?q=%40magmacomputing%2Ftempo-plugin)** | Premium & community plugins β€” Ticker, Astro, Finance, Sync, Snap and more | [![Ecosystem](https://img.shields.io/badge/Browse-Plugin%20Ecosystem-blueviolet?logo=npm&style=flat-square)](https://magmacomputing.github.io/magma/doc/3-extending-tempo/ecosystem) | --- diff --git a/packages/tempo/doc/2-core-concepts/tempo.mutate.md b/packages/tempo/doc/2-core-concepts/tempo.mutate.md new file mode 100644 index 00000000..b88c4e4a --- /dev/null +++ b/packages/tempo/doc/2-core-concepts/tempo.mutate.md @@ -0,0 +1,84 @@ +# Mutation & Math + +Tempo's API for modifying instances is intentionally microscopic. Rather than bloating the prototype with dozens of distinct methods (`.add()`, `.subtract()`, `.startOf()`, etc.), Tempo provides unified utilities that natively support intuitive shorthand strings. + +> [!IMPORTANT] Immutability & Chainability +> All mutation methods in Tempo (`.add()`, `.set()`) are strictly **immutable**. They never modify the original instance. Instead, they evaluate the change and return a **new `Tempo` instance**, ensuring absolute safety and allowing for predictable method chaining. + +## The `.add()` Method + +The `.add()` method returns a new `Tempo` instance shifted by a specific amount. + +```typescript +const t = tempo(); +t.add({ days: 5 }); // Adds 5 days +``` + +### Where is `.subtract()`? + +> [!NOTE] Design Choice +> **Where is `.subtract()`?** +> Tempo keeps its core API intentionally microscopic. Because `.add()` natively supports negative durations and Tempo's Slick math provides directional operators (e.g., `t.add('<5d')` or `t.add({ days: -5 })`), a separate `.subtract()` method is mathematically redundant. We chose a smaller bundle size over duplicate methods. + +You can subtract time simply by using negative values: + +```typescript +t.add({ days: -5 }); // Subtracts 5 days +``` + +Or using **[Slick Math](../4-advanced-reference/tempo.shorthand.md)**: + +```typescript +t.add('>5d'); // Adds 5 days +t.add('<5d'); // Subtracts 5 days +``` + +## The `.set()` Method + +While `.add()` *shifts* a date, the `.set()` method *replaces* components. + +```typescript +t.set({ year: 2026, month: 1 }); // Sets to January 2026 +``` + +### Navigating to Boundaries + +[Slick Math](../4-advanced-reference/tempo.shorthand.md) also works inside `.set()` for boundary navigation: + +```typescript +t.set('start.month'); // Start of the current month +t.set('end.year'); // End of the current year +``` + +## Chainability + +Because all mutations return a new instance, you can safely chain `.add()` and `.set()` methods together to perform complex temporal logic in a single, readable line. + +```typescript +const endOfQ1 = t + .set('start.year') // Snap to January 1st + .add('>3mm') // Shift forward 3 months (to April 1st) + .set('end.month'); // Snap to April 30th at 23:59:59.999 +``` + +## Relational vs. Navigation Shifting + +When using custom terminology plugins (like Fiscal Quarters or Seasons), Tempo provides two distinct shorthand styles for mutation: + +### 1. Navigation Mode (String) +Use a string to **jump** to a specific boundary. This relies on chronological momentum. + +```typescript +t.set('#qtr.>q1'); // Snaps to the start of the next Q1 +t.add('#timeOfDay.>afternoon'); // Jumps to the start of the next Afternoon +``` + +### 2. Relational Mode (Object) +Use an object to **shift** by a specific semantic step while preserving your relative position in the cycle. + +```typescript +t.add({ '#qtr': 1 }); // Shift forward 1 quarter, preserving progress +``` +If you are 20 days into Q1, relational shifting will put you exactly 20 days into Q2. + +πŸ‘‰ **Learn More:** For deeper details on cycle preservation and directional operators, see the [Shorthand Engine Reference](../4-advanced-reference/tempo.shorthand.md). diff --git a/packages/tempo/doc/2-core-concepts/tempo.layout.md b/packages/tempo/doc/3-extending-tempo/tempo.layout.md similarity index 100% rename from packages/tempo/doc/2-core-concepts/tempo.layout.md rename to packages/tempo/doc/3-extending-tempo/tempo.layout.md diff --git a/packages/tempo/doc/2-core-concepts/tempo.registry.md b/packages/tempo/doc/3-extending-tempo/tempo.registry.md similarity index 100% rename from packages/tempo/doc/2-core-concepts/tempo.registry.md rename to packages/tempo/doc/3-extending-tempo/tempo.registry.md diff --git a/packages/tempo/src/module/module.format.ts b/packages/tempo/src/module/module.format.ts index d7907c39..66ba7dd8 100644 --- a/packages/tempo/src/module/module.format.ts +++ b/packages/tempo/src/module/module.format.ts @@ -2,36 +2,35 @@ import '#library/temporal.polyfill.js'; import { pad, toTitleCase } from '#library/string.library.js'; import { deepMerge } from '#library/object.library.js'; import { suffix } from '#library/number.library.js'; -import { ifNumeric } from '#library/coercion.library.js'; import { isString, isObject, isZonedDateTime, isInstant, isPlainDate, isPlainDateTime, isUndefined, isDefined, isFunction } from '#library/assertion.library.js'; import { formatDayPeriod, getDTF, getPR, getISOWeekOfYear } from '#library/international.library.js'; import { delegator } from '#library/proxy.library.js'; -import { isTempo, enums, Match, getRuntime, NumericPattern, BigIntPattern, hasOwn, $Internal } from '#tempo/support'; +import { isTempo, enums, Match, getRuntime, hasOwn, $Internal } from '#tempo/support'; import { defineInterpreterModule } from '../plugin/plugin.util.js'; import { findTermPlugin } from '../plugin/term/term.util.js'; +import type { FormatOptions, ValidateFormat, TempoFormatTokens } from '../tempo.type.js'; import type { Tempo } from '../tempo.class.js'; declare module '../tempo.class.js' { interface Tempo { - /** applies a format to the instance. */ format(options: import('../tempo.type.js').FormatOptions): string; - /** applies a format to the instance. */ format(fmt: BigIntPattern, options?: any): bigint; - /** applies a format to the instance. */ format(fmt: NumericPattern, options?: any): number; + /** applies a format to the instance. */ format(options: FormatOptions): string; + /** * Applies a format to the instance. * Format strings are validated at compile time β€” any unrecognised `{token}` * will produce an IDE error showing the bad token name. - * @see {@link import('../tempo.type.js').TempoFormatTokens} to extend the token set. + * @see {@link TempoFormatTokens} to extend the token set. */ format( fmt: string extends S - ? S // variable string β€” no validation, accept as-is - : string extends import('../tempo.type.js').ValidateFormat - ? S // ValidateFormat is `string` β†’ all tokens valid, accept - : import('../tempo.type.js').ValidateFormat, // ValidateFormat is an error literal β†’ mismatch forces IDE error + ? S // variable string β€” no validation, accept as-is + : string extends ValidateFormat + ? S // ValidateFormat is `string` β†’ all tokens valid, accept + : ValidateFormat, // ValidateFormat is an error literal β†’ mismatch forces IDE error options?: any - ): string | number | bigint; - /** applies a format to the instance (zero-argument β€” returns a pre-built format proxy). */ format(): string | number | bigint; + ): string; + /** applies a format to the instance (zero-argument β€” returns a pre-built format proxy). */ format(): string; } } @@ -48,9 +47,8 @@ declare module '../tempo.class.js' { * const stamp = format().logStamp; // defaults to 'Now' */ export function format(obj?: any): any; -export function format(obj: any, options: import('../tempo.type.js').FormatOptions): string; -export function format(obj: any, fmt: BigIntPattern, options?: any): bigint; -export function format(obj: any, fmt: NumericPattern, options?: any): number; +export function format(obj: any, options: FormatOptions): string; + export function format(obj: any, fmt: string | symbol, options?: any): string; export function format(obj?: any, fmt?: any, options?: any): any { const state = getRuntime().state; @@ -390,9 +388,7 @@ export function format(obj?: any, fmt?: any, options?: any): any { return res; }); - const tokens = template.match(new RegExp(Match.formatBraces, 'g')); - const isNumericOutput = BigIntPattern.includes(template as any) || NumericPattern.includes(template as any) || (tokens && tokens.length > 1 && /^[0-9]+$/.test(result)); - return (isNumericOutput ? ifNumeric(result, true) : result) as any; + return result as any; } /** diff --git a/packages/tempo/src/support/support.enum.ts b/packages/tempo/src/support/support.enum.ts index 9d644cb8..c861b343 100644 --- a/packages/tempo/src/support/support.enum.ts +++ b/packages/tempo/src/support/support.enum.ts @@ -177,33 +177,17 @@ export const DURATIONS = enumify(STATE.DURATIONS, false); export type DURATIONS = KeyOf /** common format aliases */ -export const FORMAT = looseIndex()(enumify(STATE.FORMAT, false)); +export const FORMAT = looseIndex()(enumify(STATE.FORMAT, false)); export type FORMAT = typeof FORMAT; export type Format = LooseUnion & string> -/** patterns that return a number */ -export const NumericPattern = ['{yyyy}{wy}', '{yyyy}{mm}', '{yyyy}{mm}{dd}', '{yywy}', '{yw}{wy}', '{yw}', '{ymd}', '{ymd6}', '{hms}', '{ff}', '{dmy}', '{dmy6}', '{mdy}', '{mdy6}'] as const; -export type NumericPattern = typeof NumericPattern[number] - -/** patterns that return a bigint */ -export const BigIntPattern = ['{nano}'] as const; -export type BigIntPattern = typeof BigIntPattern[number] - -/** pre-configured format strings */ -export type OwnFormat = Mutable> - -/** mapping of format names to instance-resolutions (string | number) */ -export type FormatType = K extends BigIntPattern ? bigint : K extends keyof OwnFormat - ? (OwnFormat[K] extends NumericPattern ? number : string) - : K extends NumericPattern ? number : string | number | bigint; - -/** mapping of format names to instance-resolutions (string | number) */ +/** mapping of format names to instance-resolutions */ export type Formats = { - [K in keyof OwnFormat]: FormatType; -} & Record; + [K in keyof OwnOf]: string; +} & Record; /** Enum registry of format strings */ -export type FormatEnum = Enum.wrap>; +export type FormatEnum = Enum.wrap; export const LIMIT = proxify(STATE.LIMIT, true, false); diff --git a/packages/tempo/src/support/support.index.ts b/packages/tempo/src/support/support.index.ts index 337990ae..365c8315 100644 --- a/packages/tempo/src/support/support.index.ts +++ b/packages/tempo/src/support/support.index.ts @@ -24,8 +24,6 @@ export { PARSE, MONTH_DAY, LICENSE, - NumericPattern, - BigIntPattern, } from './support.enum.js'; export { markConfig } from '#library/symbol.library.js'; diff --git a/packages/tempo/src/tempo.class.ts b/packages/tempo/src/tempo.class.ts index c9d2aae1..46edb227 100644 --- a/packages/tempo/src/tempo.class.ts +++ b/packages/tempo/src/tempo.class.ts @@ -59,8 +59,8 @@ namespace Internal { export interface PluginContainer extends TempoPlugin { } export type Fmt = { // used for the fmtTempo() shortcut - (fmt: F, tempo?: t.DateTime, options?: t.Options): t.FormatType; - (fmt: F, options: t.Options): t.FormatType; + (fmt: F, tempo?: t.DateTime, options?: t.Options): string; + (fmt: F, options: t.Options): string; (options: Intl.DateTimeFormatOptions & { timeZone?: string; calendar?: string }): string; } } @@ -1625,7 +1625,7 @@ export class Tempo { */ /** @internal */ get #Tempo() { return this.constructor as typeof Tempo; } - /** apply a custom format. */ format(fmt?: any, options?: any): string | number | bigint { return this.#resolve(() => interpret(this, 'FormatModule', () => `{${String(fmt)}}`, false, fmt, options)); } + /** apply a custom format. */ format(fmt?: any, options?: any): string { return this.#resolve(() => interpret(this, 'FormatModule', () => `{${String(fmt)}}`, false, fmt, options)) as string; } /** time duration until another date-time */ until(dateTimeOrOpts?: t.DateTime | t.Options, opts?: t.Options): t.Duration; until(unit: t.Unit, opts?: t.Options): number; @@ -1816,11 +1816,10 @@ export namespace Tempo { export type Set = t.MutateSet; export type Add = t.MutateAdd; - export type OwnFormat = t.OwnFormat; + export type Formats = t.Formats; export type Format = t.Format; export type FormatRegistry = t.FormatRegistry; - export type FormatType = t.FormatType; export type Modifier = t.Modifier; export type Relative = t.Relative; diff --git a/packages/tempo/src/tempo.type.ts b/packages/tempo/src/tempo.type.ts index 197df1a0..3397c312 100644 --- a/packages/tempo/src/tempo.type.ts +++ b/packages/tempo/src/tempo.type.ts @@ -175,17 +175,13 @@ export type Duration = NonOptional & Record<"iso", format(opts?: Intl.NumberFormatOptions & { locales?: string | string[] }): string; } -/** pre-configured format strings */ -export type OwnFormat = enums.OwnFormat; - -/** mapping of format names to instance-resolutions (string | number) */ +/** mapping of format names to instance-resolutions (string) */ export type Formats = enums.Formats; /** Union of all known format strings */ export type Format = enums.Format; /** Enum registry of format strings */ export type FormatRegistry = enums.FormatEnum; -export type FormatType = enums.FormatType; /** * Augment this interface in your plugin or app to register additional @@ -280,7 +276,6 @@ export type Month = enums.Month export type Element = enums.Element export type Number = enums.Number export type Mode = enums.MODE -export type NumericPattern = typeof enums.NumericPattern[number]; export interface RelativeTime { /** Pre-configured relative time formatter */ format?: Intl.RelativeTimeFormat; diff --git a/packages/tempo/src/tempo.version.ts b/packages/tempo/src/tempo.version.ts index 2fea09c2..61466281 100644 --- a/packages/tempo/src/tempo.version.ts +++ b/packages/tempo/src/tempo.version.ts @@ -5,4 +5,4 @@ * ⚠️ This file is auto-updated by `npm run build:version` (see `bin/update-version.mjs`). * Do NOT edit manually β€” your changes will be overwritten on the next build. */ -export const TEMPO_VERSION = '3.9.2'; +export const TEMPO_VERSION = '3.9.3'; diff --git a/packages/tempo/test/discrete/format.test.ts b/packages/tempo/test/discrete/format.test.ts index 57826c03..6a5154a6 100644 --- a/packages/tempo/test/discrete/format.test.ts +++ b/packages/tempo/test/discrete/format.test.ts @@ -3,16 +3,16 @@ import { Tempo } from '#tempo'; describe('Tempo.format() refinements', () => { const t = new Tempo('2024-05-20T10:00:00Z'); - it('should return a number for named numeric formats', () => { + it('should return a string for named numeric formats', () => { const yw = t.format('yearWeek'); - expect(typeof yw).toBe('number'); - expect(yw).toBe(202421); + expect(typeof yw).toBe('string'); + expect(yw).toBe('202421'); }) - it('should return a number for raw numeric patterns', () => { + it('should return a string for raw numeric patterns', () => { const val = t.format('{yyyy}{mm}{dd}'); - expect(typeof val).toBe('number'); - expect(val).toBe(20240520); + expect(typeof val).toBe('string'); + expect(val).toBe('20240520'); }) it('should return a string for formats with leading zeros (if not explicitly numeric)', () => { @@ -28,8 +28,8 @@ describe('Tempo.format() refinements', () => { it('should handle yw for ISO year', () => { const t3 = new Tempo('2024-12-30'); // Monday, Week 1 of 2025 expect(t3.yw).toBe(2025); - expect(t3.format('{yw}{ww}')).toBe(202501); - expect(typeof t3.format('{yw}{ww}')).toBe('number'); + expect(t3.format('{yw}{ww}')).toBe('202501'); + expect(typeof t3.format('{yw}{ww}')).toBe('string'); }) it('accepts an options object as the second argument to override configuration', () => { diff --git a/packages/tempo/test/discrete/standalone.test.ts b/packages/tempo/test/discrete/standalone.test.ts index 935358f0..de9d2ea2 100644 --- a/packages/tempo/test/discrete/standalone.test.ts +++ b/packages/tempo/test/discrete/standalone.test.ts @@ -27,16 +27,16 @@ describe('Tempo: Standalone Utilities', () => { expect(res).toBe('2026-04-22'); }); - it('should return a number for numeric-only patterns', () => { + it('should return a string for numeric-only patterns', () => { const res = format(zdt, '{yyyy}{mm}{dd}'); - expect(typeof res).toBe('number'); - expect(res).toBe(20260422); + expect(typeof res).toBe('string'); + expect(res).toBe('20260422'); }); - it('should return a number for ad-hoc numeric-only patterns (generic detection)', () => { + it('should return a string for ad-hoc numeric-only patterns (generic detection)', () => { const res = format(zdt, '{mm}{dd}'); - expect(typeof res).toBe('number'); - expect(res).toBe(422); // 0422 + expect(typeof res).toBe('string'); + expect(res).toBe('0422'); // 0422 }); it('should return a Queriable Proxy when called without a template', () => { @@ -83,9 +83,9 @@ describe('Tempo: Standalone Utilities', () => { it('should default to "Now" when called with no arguments', () => { const res = format(); - // Numeric return for yearMonthDay - expect(typeof res.yearMonthDay).toBe('number'); - expect(String(res.yearMonthDay)).toMatch(/^\d{8}$/); + // String return for yearMonthDay + expect(typeof res.yearMonthDay).toBe('string'); + expect(res.yearMonthDay).toMatch(/^\d{8}$/); }); }); diff --git a/packages/tempo/test/engine/pattern.default.test.ts b/packages/tempo/test/engine/pattern.default.test.ts index 1ad833d5..140d64a2 100644 --- a/packages/tempo/test/engine/pattern.default.test.ts +++ b/packages/tempo/test/engine/pattern.default.test.ts @@ -11,8 +11,7 @@ function yearMonthDay(date: Date) { year = date.getFullYear(), month = ('0' + (date.getMonth() + 1)).slice(-2), day = ('0' + date.getDate()).slice(-2) - - return Number(`${year}${month}${day}`); + return `${year}${month}${day}`; } /** diff --git a/packages/tempo/test/engine/pattern.weekday.test.ts b/packages/tempo/test/engine/pattern.weekday.test.ts index ea5cfc77..2b399c61 100644 --- a/packages/tempo/test/engine/pattern.weekday.test.ts +++ b/packages/tempo/test/engine/pattern.weekday.test.ts @@ -9,7 +9,7 @@ function formatDate(date: Date) { day = ('0' + date.getDate()).slice(-2), year = date.getFullYear(); - return Number(`${year}${month}${day}`); + return `${year}${month}${day}`; } /** diff --git a/packages/tempo/test/instance/instance.format.test.ts b/packages/tempo/test/instance/instance.format.test.ts index 00cfeb15..4765e248 100644 --- a/packages/tempo/test/instance/instance.format.test.ts +++ b/packages/tempo/test/instance/instance.format.test.ts @@ -45,11 +45,11 @@ describe(`${label} format method`, () => { expect(t.format('{www}')).toBe('Mon'); }); - test('formats {nano} as a BigInt', () => { + test('formats {nano} as a string', () => { const t = new Tempo('2024-05-20'); const nano = t.format('{nano}'); - expect(typeof nano).toBe('bigint'); - expect(nano).toBeGreaterThan(0n); + expect(typeof nano).toBe('string'); + expect(nano).toMatch(/^[0-9]+$/); }); test('delegates format(options) directly to native Intl and handles strict Temporal bounds', () => { @@ -119,15 +119,15 @@ describe(`${label} format method`, () => { test('formats compound tokens dmy, mdy, ymd with :yy or :year modifiers', () => { const t = new Tempo('2024-05-20'); - expect(t.format('{dmy}')).toBe(20052024); + expect(t.format('{dmy}')).toBe('20052024'); expect(t.format('{dmy:yy}')).toBe('200524'); expect(t.format('{dmy:year}')).toBe('200524'); - expect(t.format('{mdy}')).toBe(5202024); + expect(t.format('{mdy}')).toBe('05202024'); expect(t.format('{mdy:yy}')).toBe('052024'); expect(t.format('{mdy:year}')).toBe('052024'); - expect(t.format('{ymd}')).toBe(20240520); + expect(t.format('{ymd}')).toBe('20240520'); expect(t.format('{ymd:yy}')).toBe('240520'); expect(t.format('{ymd:year}')).toBe('240520'); });