Skip to content

Core Concepts ​

Why Atemporal? ​

  • Immutable & Chainable: A fluid and predictable coding style, inspired by Day.js. Every manipulation returns a new instance.
  • Plugin-Powered Extensibility: Add only the functionality you need, keeping the core lightweight. 8 official plugins available, plus the ability to create your own.
  • Localization-Ready: Native integration with Intl for localized formats, weekday names, month names, and relative time.
  • Time Zone-Aware: First-class support for IANA time zones (e.g., America/New_York, Asia/Tokyo). All operations respect time zone context.
  • Type-Safe: Built in TypeScript for excellent autocompletion, rich type guards, and detailed type definitions.
  • Temporal-Powered: Uses the TC39 Stage 4 Temporal API (ES2026) as its foundation, with automatic polyfill fallback.

Immutability ​

All manipulation methods in Atemporal are immutable. This means that instead of modifying the existing instance, they return a new instance with the changes applied.

ts
const original = atemporal();
const future = original.add(1, "day");

console.log(original.isSame(future)); // false — original is unchanged

This prevents the class of bugs caused by accidentally mutating date objects shared across different parts of an application.

Plugin System ​

Atemporal has a lightweight plugin system that allows you to extend functionality only when needed. This keeps the core bundle small while enabling powerful features on demand.

How Plugins Work ​

Plugins are functions that receive the atemporal factory and instances, allowing them to add new methods:

ts
import atemporal from "atemporal";
import relativeTime from "atemporal/plugins/relativeTime";

// Register a plugin — it adds methods like .fromNow() and .toNow()
atemporal.extend(relativeTime);

// Now you can use plugin methods
atemporal().subtract(5, "minutes").fromNow(); // "5 minutes ago"

Lazy Loading ​

To reduce initial bundle size, official bundled plugins can be loaded on demand:

ts
// Load a plugin only when needed
await atemporal.lazyLoad("relativeTime");

// Load multiple plugins at once
await atemporal.lazyLoadMultiple(["businessDays", "timeSlots"]);

Plugin Utilities ​

ts
atemporal.isPluginLoaded("relativeTime");   // Check if a plugin is loaded
atemporal.getLoadedPlugins();                // List loaded official plugins only
atemporal.getAvailablePlugins();             // List official plugins available for lazy loading

extend() also accepts third-party plugins. They are applied normally, but they are intentionally not included in getLoadedPlugins() because that API tracks stable official plugin names only.

Creating Your Own Plugin ​

You can create custom plugins using the Plugin type signature:

ts
import type { Plugin } from "atemporal";
import { markAsPlugin } from "atemporal";

const myPlugin: Plugin = (Atemporal, atemporal, options) => {
  // Add instance methods to the prototype
  Atemporal.prototype.myMethod = function () {
    // 'this' refers to the atemporal instance
    return this.format("YYYY-MM-DD");
  };

  // Add static methods to the factory
  (atemporal as any).myStaticMethod = function () {
    return "hello from plugin";
  };
};

// Mark it as a valid atemporal plugin
markAsPlugin(myPlugin);

// Register it
atemporal.extend(myPlugin);

The Plugin type signature is:

ts
type Plugin = (
  Atemporal: typeof TemporalWrapper,
  atemporal: AtemporalFactory,
  options?: any
) => void;

Input Types (DateInput) ​

Atemporal accepts a wide variety of input types via the DateInput union type:

Input TypeExample
string (ISO 8601)"2025-07-09T15:30:00Z", "2025-07-09"
number (timestamp ms)1752096000000
Datenew Date()
Temporal.ZonedDateTimeTemporal.ZonedDateTime.from(...)
Temporal.PlainDateTimeTemporal.PlainDateTime.from(...)
Temporal.PlainDateTemporal.PlainDate.from(...)
Temporal.InstantTemporal.Instant.fromEpochMilliseconds(...)
FirebaseTimestamp{ seconds: 1672531200, nanoseconds: 0 }
Array[2025, 7, 9, 15, 30] (year, month, day, hour, min)
Plain Object{ year: 2025, month: 7, day: 9 }
TemporalWrapperAnother atemporal instance (cloned)

When no input is provided (atemporal()), the current date and time is used. Passing null or undefined behaves the same way.

Error Handling ​

Invalid inputs do not throw exceptions. Instead, an invalid instance is returned. You can check validity with .isValid():

ts
const maybeDate = atemporal("not a date");
console.log(maybeDate.isValid()); // false
console.log(maybeDate.format("YYYY-MM-DD")); // "Invalid Date"

You can also pre-validate inputs before creating instances:

ts
if (atemporal.isValid(someInput)) {
  const date = atemporal(someInput);
  // ...
}

Time Zone Management ​

Atemporal provides full IANA time zone support:

ts
// Set a global default time zone (default: UTC)
atemporal.setDefaultTimeZone("America/New_York");

// Create instances in specific zones
const ny = atemporal("2025-01-01T12:00:00", "America/New_York");
const tokyo = ny.timeZone("Asia/Tokyo");

// Check the current default
atemporal.getDefaultLocale();

Validating Time Zones ​

ts
atemporal.isValidTimeZone("America/New_York"); // true
atemporal.isValidTimeZone("Invalid/Zone");     // false

Locale & Internationalization ​

Atemporal uses the Intl API for locale-aware formatting:

ts
// Set a global default locale (default: "en")
atemporal.setDefaultLocale("es-CR");

// Format with a specific locale
atemporal().format({ dateStyle: "full" }, "ja-JP"); // Japanese full date
atemporal().format("dddd, MMMM Do YYYY", "es");     // Spanish with ordinals

Validating Locales ​

ts
atemporal.isValidLocale("en-US");  // true
atemporal.isValidLocale("en");     // true
atemporal.isValidLocale("zz");     // false

Temporal API ​

Atemporal is built directly on top of the Temporal API, which is the modern standard for date and time in JavaScript (TC39 Stage 4, ES2026). It solves many of the problems with the legacy Date object:

  • Immutability: Temporal objects are immutable by design
  • Time Zone Support: ZonedDateTime provides accurate wall-clock time with IANA zones
  • Date-Only Types: PlainDate and PlainDateTime for calendar operations without time zone concerns
  • Durations: First-class Duration type with correct arithmetic
  • Non-Gregorian Calendars: Built-in support for multiple calendar systems

Accessing the Underlying Temporal Object ​

You can access the raw Temporal.ZonedDateTime for advanced use cases:

ts
const date = atemporal();
const raw: Temporal.ZonedDateTime = date.datetime; // or date.raw

Checking Temporal Implementation ​

ts
const info = atemporal.getTemporalInfo();
// { isNative: boolean, environment: "browser" | "node" | "unknown", version: "native" | "polyfill" }

Internal Architecture ​

Under the hood, Atemporal is powered by several high-performance subsystems:

  • ParseCoordinator: Type-first parsing engine with 12 strategies (string, number, Date, Temporal variants, Firebase timestamps, arrays, objects). Uses intelligent caching and auto-optimization.
  • FormattingEngine: Token-based formatter with token compilation, object pooling, and multi-level caching for high-throughput scenarios.
  • ComparisonEngine: Optimized date comparison logic with its own cache layer.
  • DiffCache: LRU-based cache for diff() calculations between recurring date pairs.
  • IntlCache: Caches Intl.DateTimeFormat and Intl.RelativeTimeFormat instances to reduce GC pressure.
  • RegexCache: Centralized regex cache with precompiled static patterns and LRU-backed dynamic regex.
  • GlobalCacheCoordinator: Cross-plugin cache management with unified stats and bulk operations.

For advanced usage, see the Performance & Caching guide.