Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
506 changes: 501 additions & 5 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tempo-monorepo",
"version": "2.3.0",
"version": "2.4.0",
"private": true,
"description": "Magma Computing Monorepo",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/library/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@magmacomputing/library",
"version": "2.3.0",
"version": "2.4.0",
"description": "Shared utility library for Tempo",
"author": "Magma Computing Solutions",
"license": "MIT",
Expand Down
6 changes: 3 additions & 3 deletions packages/library/test/common/pledge.class.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('Pledge', () => {
expect(p1.isResolved).toBe(true);
expect(await p1.promise).toBe('ok');

const p2 = new Pledge<string>({ catch: true });
const p2 = new Pledge<string>({ catch: true, silent: true });
p2.reject(new Error('fail'));
expect(p2.isRejected).toBe(true);
await expect(p2.promise).rejects.toThrow('fail');
Expand All @@ -22,7 +22,7 @@ describe('Pledge', () => {
});

test('disposal', async () => {
const p = new Pledge({ catch: true });
const p = new Pledge({ catch: true, silent: true });
p[Symbol.dispose]();
expect(p.isRejected).toBe(true);
await expect(p.promise).rejects.toThrow('Pledge disposed');
Expand All @@ -37,7 +37,7 @@ describe('Pledge', () => {
await p1.promise;
expect(onResolve).toHaveBeenCalledWith('data');

const p2 = new Pledge({ onReject, catch: true });
const p2 = new Pledge({ onReject, catch: true, silent: true });
await expect(p2.reject(new Error('err'))).rejects.toThrow('err');
expect(onReject).toHaveBeenCalled();
});
Expand Down
16 changes: 16 additions & 0 deletions packages/tempo/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export default defineConfig({
base: '/magma/',
title: "Tempo",
description: "The Professional Date-Time Library for Temporal",
markdown: {
math: true
},
themeConfig: {
logo: '/logo.svg',
search: {
Expand All @@ -36,6 +39,7 @@ export default defineConfig({
items: [
{ text: 'Configuration', link: '/doc/tempo.config' },
{ text: 'Smart Parsing', link: '/doc/tempo.parse' },
{ text: 'Smart Formatting', link: '/doc/tempo.format' },
{ text: 'Modularity', link: '/doc/tempo.modularity' },
{ text: 'Layout Patterns', link: '/doc/tempo.layout' },
{ text: 'Terms System', link: '/doc/tempo.term' },
Expand Down Expand Up @@ -114,6 +118,18 @@ export default defineConfig({
find: /^@magmacomputing\/tempo\/ticker$/,
replacement: fileURLToPath(new URL('../dist/plugin/extend/extend.ticker.js', import.meta.url))
},
{
find: /^@magmacomputing\/tempo\/parse$/,
replacement: fileURLToPath(new URL('../dist/discrete/discrete.parse.js', import.meta.url))
},
{
find: /^@magmacomputing\/tempo\/format$/,
replacement: fileURLToPath(new URL('../dist/discrete/discrete.format.js', import.meta.url))
},
{
find: /^@magmacomputing\/tempo\/discrete$/,
replacement: fileURLToPath(new URL('../dist/discrete/discrete.index.js', import.meta.url))
},
{
find: /^@magmacomputing\/tempo$/,
replacement: fileURLToPath(new URL('../dist/tempo.index.js', import.meta.url))
Expand Down
12 changes: 6 additions & 6 deletions packages/tempo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,18 @@
```javascript
import { Tempo } from '@magmacomputing/tempo';

// 🎯 Natural Language Parsing
const event = new Tempo('next Friday 3pm');
// 🎯 Natural Language Parsing (Deterministic anchor)
const event = new Tempo('next Friday 3pm', { anchor: '2026-10-15' });

// 🔄 Fluent Mutations (Immutable)
const reminder = event.add({ hours: 2 }).set({ minute: 0 });

// ⏳ Comparative Durations
const diff = event.until('next month');
console.log(diff.toString()); // e.g. P3W2D
const diff = event.until('2026-12-25');
console.log(diff.iso); // P2M2D

// 📝 Beautiful Formatting
console.log(event.format('{mon} {day}, {year}')); // e.g. Oct 24, 2026
console.log(event.format('{mon} {day}, {yyyy}')); // October 23, 2026
```

---
Expand Down Expand Up @@ -89,7 +89,7 @@ For granular "Lite" builds, see the [Full Installation Guide](https://magmacompu

## ✨ Why Tempo?
* **🏗️ Future Standard**: Built natively on the TC39 `Temporal` proposal. Inherit the reliability of the future standard.
* **🗣️ Natural Language**: Resolve complex terms like `#friday.last` or "two days ago" with zero configuration.
* **🗣️ Natural Language**: Resolve complex terms like `#quarter.last` or "two days ago" with zero configuration.
* **🔄 Cycle Persistence**: Shift by semantic terms (Quarters, Seasons) while preserving your relative day-of-period offset.
* **⚡ 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.
Expand Down
80 changes: 61 additions & 19 deletions packages/tempo/bin/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,72 @@
"rootDir": "..",
"noEmit": true,
"composite": true,
"types": ["node"],
"types": [
"node"
],
"paths": {
"#library": ["../../library/src/common.index.ts"],
"#library/*": ["../../library/src/common/*"],
"#browser/*": ["../../library/src/browser/*"],
"#server/*": ["../../library/src/server/*"],
"#tempo": ["../src/tempo.index.ts"],
"#tempo/core": ["../src/core.index.ts"],
"#tempo/duration": ["../src/plugin/module/module.duration.ts"],
"#tempo/format": ["../src/plugin/module/module.format.ts"],
"#tempo/ticker": ["../src/plugin/extend/extend.ticker.ts"],
"#tempo/term/*": ["../src/plugin/term/term.*.ts"],
"#tempo/plugin/plugin.*.js": ["../src/plugin/plugin.*.ts"],
"#tempo/plugin/extend.*.js": ["../src/plugin/extend/extend.*.ts"],
"#tempo/plugin/module.*.js": ["../src/plugin/module/module.*.ts"],
"#tempo/plugin/term.*.js": ["../src/plugin/term/term.*.ts"],
"#tempo/*.js": ["../src/*.ts"],
"#tempo/*": ["../src/*"]
"#library": [
"../../library/src/common.index.ts"
],
"#library/*": [
"../../library/src/common/*"
],
"#browser/*": [
"../../library/src/browser/*"
],
"#server/*": [
"../../library/src/server/*"
],
"#tempo": [
"../src/tempo.index.ts"
],
"#tempo/core": [
"../src/core.index.ts"
],
"#tempo/format": [
"../src/discrete/discrete.format.ts"
],
"#tempo/parse": [
"../src/discrete/discrete.parse.ts"
],
"#tempo/discrete": [
"../src/discrete/discrete.index.ts"
],
"#tempo/ticker": [
"../src/plugin/extend/extend.ticker.ts"
],
"#tempo/term/*": [
"../src/plugin/term/term.*.ts"
],
"#tempo/duration": [
"../src/plugin/module/module.duration.ts"
],
"#tempo/plugin/plugin.*.js": [
"../src/plugin/plugin.*.ts"
],
"#tempo/plugin/extend.*.js": [
"../src/plugin/extend/extend.*.ts"
],
"#tempo/plugin/module.*.js": [
"../src/plugin/module/module.*.ts"
],
"#tempo/plugin/term.*.js": [
"../src/plugin/term/term.*.ts"
],
"#tempo/*.js": [
"../src/*.ts"
],
"#tempo/*": [
"../src/*"
]
}
},
"include": [
"**/*.ts"
],
"references": [
{ "path": "../src/tsconfig.json" }
{
"path": "../src/tsconfig.json"
}
]
}
}
2 changes: 0 additions & 2 deletions packages/tempo/doc/lazy-evaluation-pattern.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ To get around freezing, you might try taking all property descriptors, wiping th
Tempo achieves lazy evaluation in $O(1)$ time using a **Delegator Proxy** that memoizes results back onto the target object.

```javascript
// The O(1) approach - Extremely fast, zero overhead

#setLazy(target, name, defineFunction) {
const get = () => {
const value = defineFunction.call(this); // Evaluate the value
Expand Down
24 changes: 24 additions & 0 deletions packages/tempo/doc/migration-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,29 @@ t.set({ start: '#quarter' });
t.add({ '#quarter': 2 });
```

## 🚀 Tempo v2.4.0: Standalone Utilities & Path Deprecation

Tempo v2.4.0 introduces a new `discrete/` folder for standalone utilities (`parse` and `format`).

### 🛠️ Standalone Imports
You can now import lightweight, tree-shakable versions of our parsing and formatting engines without the `Tempo` class:
```javascript
import { parse } from '@magmacomputing/tempo/parse';
import { format } from '@magmacomputing/tempo/format';
```

### ⚠️ Removed Paths
We have reorganized the internal file structure to optimize for standalone usage. The following internal paths have been **removed** from the public export map in v2.4.0:

* ❌ `@magmacomputing/tempo/module/parse`
* ❌ `@magmacomputing/tempo/module/format`

**Action Required**:
1. **Do not use `dist/` paths** in your imports. These are unstable and may change.
2. **Use package subpath maps**: Update your imports to use the official entry points:
* ✅ `@magmacomputing/tempo/parse`
* ✅ `@magmacomputing/tempo/format`
3. **Check your Import Maps**: If you use browser-side import maps, ensure they point to the new package-mapped locations rather than internal `plugin/module/` paths.

## 🧪 Testing and Stability
v2.x has been hardened with a 100% pass rate on our regression suite. If you were relying on undocumented "quirks" or bugs in v1.x parsing, you may find that v2.x is more strict and deterministic.
2 changes: 1 addition & 1 deletion packages/tempo/doc/tempo.benchmarks.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The static `#guard` regex acts as a rapid "Sync Point."
The benchmark script used `performance.now()` within a Vitest environment to ensure accurate module resolution and internal alias support (`#library`).

1. **Lazy Creation**: Creates a `new Tempo('2024-05-20')` without accessing any properties.
2. **Eager Simulation**: Creates a `new Tempo()` and manually triggers discovery on 5 core properties to simulate O(N) initialization.
2. **Eager Simulation**: Creates a `new Tempo()` and manually triggers discovery on 5 core properties to simulate $O(N)$ initialization.
3. **Invalid Parse**: Passes a string that fails the Master Guard (e.g., includes emojis or exotic symbols) to measure rejection speed.

> [!NOTE]
Expand Down
3 changes: 2 additions & 1 deletion packages/tempo/doc/tempo.config.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,12 @@ When `mode: 'defer'` is set, the registry-discovery logic is deferred until the
```javascript
// Optimized for mass-creation
const t = new Tempo('now', { mode: 'defer' });
// No registries are built yet. The constructor returns in O(1) time.

console.log(t.format('{yyyy}')); // Discovery triggers NOW, only once.
```

When initialized this way, no registries are built upfront. The constructor returns in $O(1)$ time.

> [!TIP]
> **Zero-Cost Constructor**: Combining the **Master Guard** (automatic) and the **`defer`** mode allows Tempo to satisfy the "Zero-Cost Constructor" requirement for mass-processing applications.

Expand Down
123 changes: 123 additions & 0 deletions packages/tempo/doc/tempo.format.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Smart Formatting Guide

Tempo provides a powerful token-based formatting engine that goes beyond the standard ISO strings of native `Temporal`.

## 🚀 Standalone Formatting (Zero-Overhead)

If you have a native `Temporal.ZonedDateTime` and want to format it using Tempo's readable tokens, you can use the standalone `format` function. This allows you to use Tempo's formatting logic without importing the full `Tempo` class.

```typescript
import { format } from '@magmacomputing/tempo/format';

const zdt = Temporal.Now.zonedDateTimeISO();
const str = format(zdt, '{mon} {day}, {yyyy}');

console.log(str); // e.g., "October 24, 2026"
```

> [!IMPORTANT]
> **Terms and Standalone Formatting**: When using `format()` with native `Temporal` objects, **Terms** (tokens starting with `#`) are not resolved. To use Terms resolution in your format strings, you must either pass a `Tempo` instance to the `format()` utility or use the class-based `.format()` method.

### Supported Input Types
The engine can interpret:
* **Temporal Objects**: `ZonedDateTime`, `Instant` (auto-projected to ZDT), `PlainDate`, `PlainDateTime`.
* **Tempo Instances**: Any instance of the `Tempo` class.
* **ISO Strings**: Valid Temporal ISO-8601 strings.
* **Defaults**: If no object is provided, it defaults to **Now** in the configured timezone.

---

## 🏗️ Class-Based Formatting

When using the `Tempo` class, the `.format()` method is available on every instance.

```typescript
import { Tempo } from '@magmacomputing/tempo';

const t = new Tempo('2026-10-24T15:30:00');
console.log(t.format('display')); // Sat, 24 Oct 2026 (using a named format alias)
```

### Named Formats
Tempo comes with several pre-configured format aliases. You can also define your own globally during initialization.

```typescript
Tempo.init({
formats: {
'fancy': '{mon} the {dd}th day of {yyyy}'
}
});

const t = new Tempo('2026-10-24');
console.log(t.format('fancy')); // October the 24th day of 2026
```

---

## 🧩 Modularity: Core vs. Full

Like the parsing engine, the formatting engine is modular:

| Version | Formatting Status |
| :--- | :--- |
| **Tempo Full** | **Built-in**. Works out of the box. |
| **Tempo Core** | **Opt-in**. You must call `Tempo.extend(FormatModule)` to enable `.format()`. |

### Enabling Formatting in Core
If you are using `@magmacomputing/tempo/core`, you must explicitly register the formatting engine:

```typescript
import { Tempo } from '@magmacomputing/tempo/core';
import { FormatModule } from '@magmacomputing/tempo/format';

Tempo.extend(FormatModule);
```

---

## 🔠 Supported Tokens

| Token | Description | Example |
| :--- | :--- | :--- |
| `{yyyy}` | 4-digit Year | `2026` |
| `{yy}` | 2-digit Year | `26` |
| `{yw}` | Year of Week (ISO) | `2026` |
| `{yyww}` | Year & Week (ISO) | `202617` |
| `{mon}` | Full Month Name | `October` |
| `{mmm}` | Short Month Name | `Oct` |
| `{mm}` | 2-digit Month | `10` |
| `{dd}` | 2-digit Day | `24` |
| `{day}` | Unpadded Day | `24` (or `9`) |
| `{wkd}` | Full Weekday Name | `Saturday` |
| `{www}` | Short Weekday Name | `Sat` |
| `{dow}` | Day of Week (1-7) | `6` |
| `{hh}` | 2-digit Hour (24h) | `15` |
| `{HH}` | 2-digit Hour (12h) | `03` |
| `{mer}` | am/pm marker | `pm` |
| `{MER}` | AM/PM marker | `PM` |
| `{mi}` | Minutes | `30` |
| `{ss}` | Seconds | `45` |
| `{hhmiss}` | Compact Time (24h) | `153045` |
| `{ms}` | 3-digit Milliseconds | `123` |
| `{us}` | 3-digit Microseconds | `456` |
| `{ns}` | 3-digit Nanoseconds | `789` |
| `{ff}` | Fractional Seconds | `123456789` |
| `{ts}` | Unix Timestamp | `1792843200000` |
| `{nano}` | Nanosecond Timestamp | `1792843200000000000` |
| `{tz}` | Time Zone ID | `Australia/Sydney` |

### 🔄 Automatic Meridiem
If your format string contains `{HH}` (12-hour clock) but lacks a `{mer}` or `{MER}` token, Tempo will automatically append `{mer}` to the end of the last time component to ensure the time remains unambiguous.

```typescript
t.format('{HH}:{mi}'); // "03:30pm" (auto-appended pm)
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

### 🔢 Numeric Resolution
If your format string consists *only* of numeric tokens (e.g., `{yyyy}{mm}{dd}`), the `format()` function will return a **Number** instead of a string. This is useful for generating sortable keys or IDs.

```typescript
const key = t.format('{yyyy}{mm}{dd}');
console.log(typeof key); // "number"
console.log(key); // 20261024
Comment thread
coderabbitai[bot] marked this conversation as resolved.
```
Loading