From f939b0d199f32281066ceb1cf3ae0631ba990fcd Mon Sep 17 00:00:00 2001 From: Michael McRae Date: Wed, 1 Jul 2026 09:42:01 +1000 Subject: [PATCH 1/4] tz format modifiers --- .editorconfig | 14 ++++++++++ packages/tempo/doc/tempo.format.md | 7 ++++- packages/tempo/src/module/module.format.ts | 32 +++++++++++++++++++--- 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3096861 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +indent_style = tab +indent_size = 2 + +[*.js] +indent_style = tab +indent_size = 2 diff --git a/packages/tempo/doc/tempo.format.md b/packages/tempo/doc/tempo.format.md index 8efc29d..fd0f25e 100644 --- a/packages/tempo/doc/tempo.format.md +++ b/packages/tempo/doc/tempo.format.md @@ -128,6 +128,11 @@ You can append modifiers to any token using a colon (`:`) to transform its outpu | `:title` | String | Converts to titlecase | `{mon:locale:title}` → `Octobre` | | `:locale` | String | Resolves term via localization dictionary | `{mon:locale}` → `octobre` | | `:yy` | Compound Date | Truncates the internal year component to 2 digits | `{dmy:yy}` → `241026` | +| `:z` | `{tz}` | Narrow timezone offset | `{tz:z}` → `+10` | +| `:zz` | `{tz}` | Short timezone offset | `{tz:zz}` → `+10:00` | +| `:zzz` | `{tz}` | Techie timezone offset | `{tz:zzz}` → `+1000` | +| `:zzzz` | `{tz}` | Short localized timezone name (fallback: ID) | `{tz:zzzz}` → `AEST` | +| `:zzzzz` | `{tz}` | Long localized timezone name (fallback: ID) | `{tz:zzzzz}` → `Australian Eastern Standard Time` | ### 🔄 Automatic Meridiem If your format string contains `{h12}` (12-hour clock) but lacks a `{mer}` token, Tempo will automatically append a `{mer}` token with the same modifiers as the `{h12}` token after the last time component to ensure the time remains unambiguous. @@ -135,7 +140,7 @@ If your format string contains `{h12}` (12-hour clock) but lacks a `{mer}` token *(If you explicitly want a 12-hour digit without an auto-appended meridiem, use the `:raw` modifier: `{h12:raw}`)* > [!NOTE] -> **Why `{h12}` and `{h24}`?** In other date libraries, `{hh}` could mean 12-hour and `{HH}` means 24-hour time. This is confusing and error-prone. Tempo standardizes `{hh}` on the default 24-hour expectation, but provides explicit `{h12}` and `{h24}` tokens to completely eliminate ambiguity. This keeps all token definitions fully lowercase and semantic, without relying on uppercase variations like `{HH}`. +> **Why `{h12}` and `{h24}`?** In other date libraries, `{hh}` could mean 12-hour and `{HH}` could mean 24-hour time. This is confusing and error-prone. Tempo standardizes `{hh}` on the default 24-hour expectation, but provides explicit `{h12}` and `{h24}` tokens to completely eliminate ambiguity. This keeps all token definitions fully lowercase and semantic, without relying on uppercase variations like `{HH}`. ```typescript t.format('{h12}:{mi}'); // "03:30pm" (auto-append standard meridiem) diff --git a/packages/tempo/src/module/module.format.ts b/packages/tempo/src/module/module.format.ts index b5c5abf..fd6d9f8 100644 --- a/packages/tempo/src/module/module.format.ts +++ b/packages/tempo/src/module/module.format.ts @@ -312,10 +312,11 @@ export function format(obj?: any, fmt?: any, options?: any): any { } } else { const dtOptions = config?.intl?.dateTimeFormat ?? {}; - if (token === 'mon') res = zdt.toLocaleString(config?.locale, { ...dtOptions, month: 'long' }); - else if (token === 'mmm') res = zdt.toLocaleString(config?.locale, { ...dtOptions, month: 'short' }); - else if (token === 'wkd') res = zdt.toLocaleString(config?.locale, { ...dtOptions, weekday: 'long' }); - else if (token === 'www') res = zdt.toLocaleString(config?.locale, { ...dtOptions, weekday: 'short' }); + const tzOpts = { ...dtOptions, timeZone: zdt.timeZoneId, calendar: zdt.calendarId }; + if (token === 'mon') res = getDTF(config?.locale, { ...tzOpts, month: 'long' }).format(zdt.epochMilliseconds); + else if (token === 'mmm') res = getDTF(config?.locale, { ...tzOpts, month: 'short' }).format(zdt.epochMilliseconds); + else if (token === 'wkd') res = getDTF(config?.locale, { ...tzOpts, weekday: 'long' }).format(zdt.epochMilliseconds); + else if (token === 'www') res = getDTF(config?.locale, { ...tzOpts, weekday: 'short' }).format(zdt.epochMilliseconds); else if (token === 'mer') { const period = formatDayPeriod(zdt.epochMilliseconds, config?.locale, { ...dtOptions, hour: 'numeric', hour12: true, timeZone: tz }); if (period) res = period; @@ -326,6 +327,29 @@ export function format(obj?: any, fmt?: any, options?: any): any { } break; } + case 'z': + if (token === 'tz') res = zdt.offset.endsWith(':00') ? zdt.offset.slice(0, -3) : zdt.offset; + break; + case 'zz': + if (token === 'tz') res = zdt.offset; + break; + case 'zzz': + if (token === 'tz') res = zdt.offset.replace(':', ''); + break; + case 'zzzz': + if (token === 'tz') { + const dtOptions = config?.intl?.dateTimeFormat ?? {}; + const parts = getDTF(config?.locale, { ...dtOptions, timeZone: zdt.timeZoneId, timeZoneName: 'short' }).formatToParts(zdt.epochMilliseconds); + res = parts.find(p => p.type === 'timeZoneName')?.value ?? zdt.timeZoneId; + } + break; + case 'zzzzz': + if (token === 'tz') { + const dtOptions = config?.intl?.dateTimeFormat ?? {}; + const parts = getDTF(config?.locale, { ...dtOptions, timeZone: zdt.timeZoneId, timeZoneName: 'long' }).formatToParts(zdt.epochMilliseconds); + res = parts.find(p => p.type === 'timeZoneName')?.value ?? zdt.timeZoneId; + } + break; } } From d206ad030abc92e24acc4e7b5f1791e72405e8d1 Mon Sep 17 00:00:00 2001 From: Michael McRae Date: Fri, 3 Jul 2026 20:49:13 +1000 Subject: [PATCH 2/4] prepare Community build --- package-lock.json | 6 +- package.json | 2 +- packages/library/package.json | 2 +- .../src/common/international.library.ts | 8 +- packages/tempo/.vitepress/config.ts | 2 +- .../theme/components/CatalogList.vue | 201 ++++++++++++++++++ packages/tempo/.vitepress/theme/index.ts | 10 + packages/tempo/CHANGELOG.md | 6 + packages/tempo/README.md | 13 ++ packages/tempo/doc/ecosystem.md | 17 ++ packages/tempo/doc/installation.md | 22 +- packages/tempo/doc/releases/index.md | 4 +- packages/tempo/package.json | 2 +- packages/tempo/public/plugin-logo.svg | 15 ++ packages/tempo/rollup.config.js | 39 +++- packages/tempo/src/module/module.duration.ts | 3 +- packages/tempo/src/plugin-api.index.ts | 1 + packages/tempo/src/plugin/plugin.index.ts | 2 +- packages/tempo/src/tempo.entry.ts | 1 + packages/tempo/src/tempo.version.ts | 2 +- 20 files changed, 335 insertions(+), 23 deletions(-) create mode 100644 packages/tempo/.vitepress/theme/components/CatalogList.vue create mode 100644 packages/tempo/.vitepress/theme/index.ts create mode 100644 packages/tempo/doc/ecosystem.md create mode 100644 packages/tempo/public/plugin-logo.svg diff --git a/package-lock.json b/package-lock.json index 385ff7e..14aae88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "tempo-monorepo", - "version": "3.5.1", + "version": "3.5.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tempo-monorepo", - "version": "3.5.1", + "version": "3.5.2", "workspaces": [ "packages/*" ], @@ -10149,7 +10149,7 @@ }, "packages/library": { "name": "@magmacomputing/library", - "version": "3.5.1", + "version": "3.5.2", "license": "MIT", "dependencies": { "tslib": "^2.8.1" diff --git a/package.json b/package.json index e532b8a..e4100e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tempo-monorepo", - "version": "3.5.1", + "version": "3.5.2", "private": true, "engines": { "node": ">=20.0.0" diff --git a/packages/library/package.json b/packages/library/package.json index e18e5c4..fa449bc 100644 --- a/packages/library/package.json +++ b/packages/library/package.json @@ -1,6 +1,6 @@ { "name": "@magmacomputing/library", - "version": "3.5.1", + "version": "3.5.2", "description": "Shared utility library for Tempo", "author": "Magma Computing Solutions", "license": "MIT", diff --git a/packages/library/src/common/international.library.ts b/packages/library/src/common/international.library.ts index a712191..63471f5 100644 --- a/packages/library/src/common/international.library.ts +++ b/packages/library/src/common/international.library.ts @@ -3,8 +3,8 @@ import { memoizeFunction } from '#library/function.library.js'; import { isFunction, isDefined } from '#library/assertion.library.js'; /** memoized helper for Intl.RelativeTimeFormat instances */ -const getRTF = memoizeFunction((locale?: string, style: Intl.RelativeTimeFormatStyle = 'narrow') => { - return new Intl.RelativeTimeFormat(locale, { style }); +const getRTF = memoizeFunction((locale?: string, style: Intl.RelativeTimeFormatStyle = 'narrow', numeric: Intl.RelativeTimeFormatNumeric = 'always') => { + return new Intl.RelativeTimeFormat(locale, { style, numeric }); }); /** memoized helper for Intl.ListFormat instances */ @@ -63,9 +63,9 @@ export function canonicalLocale(locale: string): string | undefined { } /** return a localized relative time string (e.g., 'in 2 days') */ -export function getRelativeTime(value: number, unit: Intl.RelativeTimeFormatUnit, locale?: string, style: Intl.RelativeTimeFormatStyle = 'narrow') { +export function getRelativeTime(value: number, unit: Intl.RelativeTimeFormatUnit, locale?: string, style: Intl.RelativeTimeFormatStyle = 'narrow', numeric: Intl.RelativeTimeFormatNumeric = 'always') { try { - return getRTF(locale, style).format(value, unit); + return getRTF(locale, style, numeric).format(value, unit); } catch (e) { return `${value} ${unit}`; } diff --git a/packages/tempo/.vitepress/config.ts b/packages/tempo/.vitepress/config.ts index 97ebc73..166b918 100644 --- a/packages/tempo/.vitepress/config.ts +++ b/packages/tempo/.vitepress/config.ts @@ -58,7 +58,7 @@ export default defineConfig({ { text: 'Modularity', link: '/doc/tempo.modularity' }, { text: 'Terms Plugins', link: '/doc/tempo.plugin' }, { text: 'Extension Plugins', link: '/doc/tempo.extension' }, - { text: 'Premium Plugins ↗', link: 'https://magmacomputing.github.io/tempo-plugin-docs/' }, + { text: 'Plugin Ecosystem', link: '/doc/ecosystem' } ] }, { diff --git a/packages/tempo/.vitepress/theme/components/CatalogList.vue b/packages/tempo/.vitepress/theme/components/CatalogList.vue new file mode 100644 index 0000000..133e4d4 --- /dev/null +++ b/packages/tempo/.vitepress/theme/components/CatalogList.vue @@ -0,0 +1,201 @@ + + + + + diff --git a/packages/tempo/.vitepress/theme/index.ts b/packages/tempo/.vitepress/theme/index.ts new file mode 100644 index 0000000..5077e97 --- /dev/null +++ b/packages/tempo/.vitepress/theme/index.ts @@ -0,0 +1,10 @@ +import DefaultTheme from 'vitepress/theme' +import type { Theme } from 'vitepress' +import CatalogList from './components/CatalogList.vue' + +export default { + extends: DefaultTheme, + enhanceApp({ app }) { + app.component('CatalogList', CatalogList) + } +} satisfies Theme diff --git a/packages/tempo/CHANGELOG.md b/packages/tempo/CHANGELOG.md index be609ae..86acef4 100644 --- a/packages/tempo/CHANGELOG.md +++ b/packages/tempo/CHANGELOG.md @@ -6,6 +6,12 @@ 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.5.2] + +### Added +- **Minified Global Bundles**: The build pipeline now natively produces highly optimized, minified IIFE bundles (`*.min.js`) for both Tempo Core and all Community Plugins, significantly reducing payload size for developers using CDN ` - - + + + + + ``` diff --git a/packages/tempo/doc/releases/index.md b/packages/tempo/doc/releases/index.md index a2ed6dd..32d2c10 100644 --- a/packages/tempo/doc/releases/index.md +++ b/packages/tempo/doc/releases/index.md @@ -2,9 +2,9 @@ Explore the evolution of Tempo through its version history. -- [Version 3.x](./v3.x) - Removal of deprecated shorthands and major engine hardening. - [Version 4.x (Planned)](./v4.x) - Removal of deprecated legacy discovery root properties. -- [Version 2.x (Current)](./v2.x) - Modular architecture, Shorthand engine, and Ticker stability. +- [Version 3.x (Current)](./v3.x) - Removal of deprecated shorthands and major engine hardening. +- [Version 2.x](./v2.x) - Modular architecture, Shorthand engine, and Ticker stability. - [Version 1.x (Legacy)](./v1.x) - Initial public release and Temporal polyfill integration. - [Version 0.x (Legacy)](./v0.x) - Initial release. diff --git a/packages/tempo/package.json b/packages/tempo/package.json index 70b7e1c..f1f91bf 100644 --- a/packages/tempo/package.json +++ b/packages/tempo/package.json @@ -1,6 +1,6 @@ { "name": "@magmacomputing/tempo", - "version": "3.5.1", + "version": "3.5.2", "engines": { "node": ">=20.0.0" }, diff --git a/packages/tempo/public/plugin-logo.svg b/packages/tempo/public/plugin-logo.svg new file mode 100644 index 0000000..53b62c2 --- /dev/null +++ b/packages/tempo/public/plugin-logo.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/tempo/rollup.config.js b/packages/tempo/rollup.config.js index 9be6635..676ea12 100644 --- a/packages/tempo/rollup.config.js +++ b/packages/tempo/rollup.config.js @@ -121,7 +121,7 @@ export default [ { file: 'dist/tempo.bundle.js', format: 'iife', - name: '_TempoExport', + name: 'Magma', exports: 'named', sourcemap: false, indent: '\t', @@ -151,6 +151,43 @@ export default [ ], }, + // 2b. 🌐 MINIFIED GLOBAL IIFE BUNDLE + { + input: path.join(distPath, 'tempo.entry.js'), + output: [ + { + file: 'dist/tempo.bundle.min.js', + format: 'iife', + name: 'Magma', + exports: 'named', + sourcemap: true, + indent: '\t', + inlineDynamicImports: true, + globals: { + '@js-temporal/polyfill': 'Temporal' + } + }, + { + file: 'dist/tempo.bundle.esm.min.js', + format: 'es', + sourcemap: true, + indent: '\t', + inlineDynamicImports: true, + } + ], + external: ['@js-temporal/polyfill'], + plugins: [ + alias({ + entries: [ + // Pull in the already-obfuscated monolith! + { find: '#tempo/license', replacement: path.resolve(__dirname, 'dist/plugin/license/license.validator.js') } + ] + }), + resolve({ extensions: ['.js', '.ts'] }), + esbuild({ target: 'esnext', minify: true }) + ], + }, + // 3. 🧩 GRANULAR ESM { input: entryPoints, diff --git a/packages/tempo/src/module/module.duration.ts b/packages/tempo/src/module/module.duration.ts index 4577e7e..74c7b5e 100644 --- a/packages/tempo/src/module/module.duration.ts +++ b/packages/tempo/src/module/module.duration.ts @@ -211,7 +211,8 @@ function duration(this: Tempo, type: 'until' | 'since', arg?: any, until?: any) if (isFunction(rtf)) return rtf(val, su); if (rtf instanceof Intl.RelativeTimeFormat) return rtf.format(val, su); const style = rtOptions?.style || rtConfig?.style || opts['intl']?.relativeTimeFormat?.style || opts['rtfStyle'] || (this as any).config.intl?.relativeTimeFormat?.style || (this as any).config['rtfStyle'] || 'narrow'; - return getRelativeTime(val, su as Intl.RelativeTimeFormatUnit, locale, style); + const numeric = rtOptions?.numeric || rtConfig?.numeric || opts['intl']?.relativeTimeFormat?.numeric || opts['rtfNumeric'] || (this as any).config.intl?.relativeTimeFormat?.numeric || (this as any).config['rtfNumeric'] || 'always'; + return getRelativeTime(val, su as Intl.RelativeTimeFormatUnit, locale, style, numeric); } switch (res.unit) { diff --git a/packages/tempo/src/plugin-api.index.ts b/packages/tempo/src/plugin-api.index.ts index e7d5ff0..16470a2 100644 --- a/packages/tempo/src/plugin-api.index.ts +++ b/packages/tempo/src/plugin-api.index.ts @@ -15,5 +15,6 @@ export * from './library.index.js'; export * from './plugin/plugin.index.js'; export * from './support/support.enum.js'; export * from './plugin/term/term.index.js'; +export { defineTerm, defineRange, getTermRange } from './plugin/term/term.index.js'; export * from '#tempo/license'; diff --git a/packages/tempo/src/plugin/plugin.index.ts b/packages/tempo/src/plugin/plugin.index.ts index 81e4946..fe503ae 100644 --- a/packages/tempo/src/plugin/plugin.index.ts +++ b/packages/tempo/src/plugin/plugin.index.ts @@ -9,4 +9,4 @@ export * from './plugin.util.js'; export * from './plugin.type.js'; export * from './term/term.type.js'; -export { Validator, definePremiumPlugin, definePremiumTerm } from '#tempo/license'; +export { Validator } from '#tempo/license'; diff --git a/packages/tempo/src/tempo.entry.ts b/packages/tempo/src/tempo.entry.ts index a5ff700..610a697 100644 --- a/packages/tempo/src/tempo.entry.ts +++ b/packages/tempo/src/tempo.entry.ts @@ -2,6 +2,7 @@ // (This is already handled by tempo.index.js) import { Tempo } from './tempo.index.js'; export * from './tempo.index.js'; +export * as pluginApi from './plugin-api.index.js'; // NOTE: This file is referenced by Rollup during the build process to create the production-ready browser bundle. diff --git a/packages/tempo/src/tempo.version.ts b/packages/tempo/src/tempo.version.ts index 674c584..ba19ea3 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.5.1'; +export const TEMPO_VERSION = '3.5.2'; From 91e75af7ec01c54ac54c5e03ad2c770d510fcd94 Mon Sep 17 00:00:00 2001 From: Michael McRae Date: Sat, 4 Jul 2026 11:14:22 +1000 Subject: [PATCH 3/4] ready for review --- packages/tempo/CHANGELOG.md | 3 +++ packages/tempo/doc/releases/v3.x.md | 19 +++++++++++++++++++ packages/tempo/src/tempo.index.ts | 7 +++++++ 3 files changed, 29 insertions(+) diff --git a/packages/tempo/CHANGELOG.md b/packages/tempo/CHANGELOG.md index 86acef4..b522155 100644 --- a/packages/tempo/CHANGELOG.md +++ b/packages/tempo/CHANGELOG.md @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Minified Global Bundles**: The build pipeline now natively produces highly optimized, minified IIFE bundles (`*.min.js`) for both Tempo Core and all Community Plugins, significantly reducing payload size for developers using CDN `