Skip to main content

Migration Guide

Migrating to Atemporal from other date libraries is straightforward. This guide provides side-by-side comparisons and highlights key differences to help you transition smoothly.

Why Migrate to Atemporal?

Modern Foundation

Built on the Temporal API - the future standard for JavaScript date/time

Type-Safe

Full TypeScript support with excellent developer experience

Immutable by Design

All operations return new instances - no accidental mutations

Time Zone First

First-class IANA timezone support built into the core

From Day.js

Atemporal’s API is heavily inspired by Day.js, making migration especially easy. Most method names are identical.

Basic Usage

import dayjs from 'dayjs';

const now = dayjs();
const date = dayjs('2024-03-15');
const unix = dayjs.unix(1710504000);

Formatting

const formatted = dayjs().format('YYYY-MM-DD HH:mm:ss');

Manipulation

const future = dayjs()
  .add(7, 'day')
  .subtract(2, 'hour')
  .startOf('day');

Plugins

import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import customParseFormat from 'dayjs/plugin/customParseFormat';

dayjs.extend(relativeTime);
dayjs.extend(customParseFormat);

const ago = dayjs().subtract(2, 'hour').fromNow();
const parsed = dayjs('25/12/2024', 'DD/MM/YYYY');

Key Differences from Day.js

Day.js requires the timezone plugin for timezone support. Atemporal has first-class timezone support built into the core:
// Atemporal - timezone support built-in
const tokyo = atemporal('2024-03-15T10:00:00', 'Asia/Tokyo');
const newYork = tokyo.timeZone('America/New_York');

// Change default timezone globally
atemporal.setDefaultTimeZone('Europe/London');
Day.js uses the same constructor with a format parameter. Atemporal provides a dedicated static method:
// Day.js
dayjs('25/12/2024', 'DD/MM/YYYY');

// Atemporal
atemporal.fromFormat('25/12/2024', 'DD/MM/YYYY');
Atemporal uses native Temporal.Duration objects:
// Create a duration
const duration = atemporal.duration({ years: 1, months: 6, days: 15 });

// Humanize it (with durationHumanizer plugin)
atemporal.extend(durationHumanizer);
const readable = atemporal.humanize(duration); // "1 year, 6 months, 15 days"

From Luxon

Luxon users will find Atemporal’s immutability and timezone support familiar, but with a more chainable API.

Basic Usage

import { DateTime } from 'luxon';

const now = DateTime.now();
const date = DateTime.fromISO('2024-03-15');
const unix = DateTime.fromSeconds(1710504000);

Formatting

const formatted = DateTime.now().toFormat('yyyy-MM-dd HH:mm:ss');
Luxon uses lowercase tokens (yyyy, dd) while Atemporal uses uppercase tokens (YYYY, DD) following Day.js conventions.

Manipulation

const future = DateTime.now()
  .plus({ days: 7 })
  .minus({ hours: 2 })
  .startOf('day');

Timezone Operations

const tokyo = DateTime.now().setZone('Asia/Tokyo');
const newYork = tokyo.setZone('America/New_York');

Key Differences from Luxon

OperationLuxonAtemporal
Add time.plus({ days: 5 }).add(5, 'day')
Subtract time.minus({ hours: 2 }).subtract(2, 'hour')
Change timezone.setZone('Asia/Tokyo').timeZone('Asia/Tokyo')
Format.toFormat('yyyy-MM-dd').format('YYYY-MM-DD')
ISO String.toISO().toISOString()
Luxon uses object-based parameters, Atemporal uses positional parameters:
// Luxon
dt.plus({ years: 1, months: 2, days: 3 });

// Atemporal - chain multiple operations
atemporal()
  .add(1, 'year')
  .add(2, 'month')
  .add(3, 'day');
Both libraries handle invalid dates, but with different patterns:
// Luxon
const invalid = DateTime.fromISO('invalid');
if (!invalid.isValid) {
  console.log(invalid.invalidReason);
}

// Atemporal
const invalid = atemporal('invalid');
if (!invalid.isValid()) {
  // All operations on invalid instances return the same invalid instance
  console.log(invalid.format('YYYY-MM-DD')); // 'Invalid Date'
}

From Moment.js

Moment.js is in maintenance mode. The Moment team recommends migrating to modern alternatives like Atemporal.

Basic Usage

import moment from 'moment';

const now = moment();
const date = moment('2024-03-15');
const unix = moment.unix(1710504000);

Critical: Mutability vs Immutability

Breaking Change: Moment.js is mutable by default. Atemporal is always immutable.
const date = moment('2024-03-15');
date.add(7, 'days'); // Mutates the original!
console.log(date.format('YYYY-MM-DD')); // '2024-03-22'

Manipulation

const date = moment()
  .add(7, 'days')
  .subtract(2, 'hours')
  .startOf('day');
Both use singular and plural forms, but Atemporal also supports short aliases (d, h, m, s).

Comparison

const a = moment('2024-03-15');
const b = moment('2024-03-20');

if (a.isBefore(b)) { /* ... */ }
if (a.isSame(b, 'day')) { /* ... */ }
if (a.isAfter(b)) { /* ... */ }

Differences

In Moment, you often need .clone() to avoid mutations. In Atemporal, every operation returns a new instance:
// Moment - need explicit clone
const original = moment();
const copy = original.clone().add(1, 'day');

// Atemporal - implicit immutability
const original = atemporal();
const copy = original.add(1, 'day'); // original unchanged
// Moment - global locale
moment.locale('es');

// Atemporal - global default locale
atemporal.setDefaultLocale('es-ES');
// Moment
const earliest = moment.min([date1, date2, date3]);
const latest = moment.max([date1, date2, date3]);

// Atemporal - supports both array and spread syntax
const earliest = atemporal.min([date1, date2, date3]);
const latest = atemporal.max(date1, date2, date3);

Migration Checklist

1

Install Atemporal

npm install atemporal
2

Update Imports

Replace your date library imports:
// Before
import dayjs from 'dayjs';
import { DateTime } from 'luxon';
import moment from 'moment';

// After
import atemporal from 'atemporal';
3

Update Method Calls

Use the comparison tables above to update method names and parameters.
4

Check for Mutations (from Moment)

If migrating from Moment, search for places where you relied on mutation and ensure you capture the returned value:
// Before (Moment)
date.add(1, 'day');

// After (Atemporal)
date = date.add(1, 'day');
5

Load Required Plugins

Identify which plugins you need and load them:
import atemporal from 'atemporal';
import relativeTime from 'atemporal/plugins/relativeTime';

atemporal.extend(relativeTime);

// Or use lazy loading
await atemporal.lazyLoad('relativeTime');
6

Test Thoroughly

Pay special attention to:
  • Timezone operations
  • Format strings (especially from Luxon)
  • Mutation assumptions (from Moment)
  • Edge cases with invalid dates

Common Pitfalls

Watch out for these common migration issues:

Format Token Differences

If migrating from Luxon, remember to convert format tokens:
LuxonAtemporal
yyyyYYYY
yyYY
ddDD
dD
HHHH
hh

Assuming Mutability

// ❌ Wrong - ignoring return value
const date = atemporal('2024-03-15');
date.add(7, 'day');
console.log(date.format('YYYY-MM-DD')); // Still '2024-03-15'

// ✅ Correct - using return value
const date = atemporal('2024-03-15');
const newDate = date.add(7, 'day');
console.log(newDate.format('YYYY-MM-DD')); // '2024-03-22'

Plugin Methods Before Loading

// ❌ Wrong - using plugin method before loading
const ago = atemporal().subtract(2, 'hour').fromNow();
// TypeError: fromNow is not a function

// ✅ Correct - load plugin first
import relativeTime from 'atemporal/plugins/relativeTime';
atemporal.extend(relativeTime);

const ago = atemporal().subtract(2, 'hour').fromNow();

Getting Help

If you encounter issues during migration:

Build docs developers (and LLMs) love