A typescript library for interacting with and modifying both the Maya Long Count (LC) and Calendar Round (CR) dates.
This library supports Node.js versions 20, 22, and 24 (LTS). Development is done on Node 24.
npm install @drewsonne/maya-datesimport { LongCountFactory } from '@drewsonne/maya-dates';
// Long Count from Initial Series on east side of Stele E, Quirigua
const lc = new LongCountFactory().parse('9.17.0.0.0');
console.log(`${lc.buildFullDate()}`);This should print 13 Ajaw 18 Kumk'u 9.17. 0. 0. 0.
Most implementations will consist of creating a fullDate, and passing it to an operation.
Creating a fullDate can be done either by:
- using a factory class (
LongCountFactory,CalendarRoundFactory,FullDateFactory) and calling itsparse(raw_string)function, whereraw_stringis a LC, CR, or CR and LC combination encoded as a string. To specify missing values in a fullDate, using*. For example,
import {
Wildcard,
isWildcard,
LongCountFactory,
CalendarRoundFactory,
FullDateFactory
} from '@drewsonne/maya-dates';
const wildcard = new Wildcard();
const partial_lc = new LongCountFactory().parse('9.17.0.0.*');
const partial_cr = new CalendarRoundFactory().parse('13 Ajaw * Kumk\'u');
const partial_date = new FullDateFactory().parse('13 Ajaw * Kumk\'u 9.17.0.0.*');
console.log(`LC: ${isWildcard(partial_lc.kIn)}`);
console.log(`CR: ${isWildcard(partial_cr.haab.coeff)}`);
console.log(`Full Date: ${isWildcard(partial_date.lc.kIn)}`);- passing explicit values into a model factory or class (
getCalendarRound,LongCount,FullDate).
import {
getCalendarRound,
getHaab,
getTzolkin,
getHaabMonth,
getTzolkinDay,
coefficientParser as _,
FullDate,
LongCount
} from '@drewsonne/maya-dates';
const calendarRound = getCalendarRound(
getTzolkin(_(13), getTzolkinDay('Ajaw')),
getHaab(_(18), getHaabMonth('Kumk\'u'))
);
const lc = new LongCount(0, 0, 0, 17, 9);
const fullDate = new FullDate(calendarRound, lc);
console.log(`${lc}`);
console.log(`${calendarRound}`);
console.log(`${fullDate}`);Once a full date object has been created, you can either add an integer to a fullDate
using shift(number), or filling in missing values in wildcards. The
operations module provides operators to expand a fullDate with a wildcard into all possible
values for dates matching that wildcard pattern.
Parse different Maya date formats:
import {
LongCountFactory,
CalendarRoundFactory,
FullDateFactory
} from '@drewsonne/maya-dates';
// Parse a Long Count date
const lcFactory = new LongCountFactory();
const longCount = lcFactory.parse('9.17.0.0.0');
// Parse a Calendar Round date
const crFactory = new CalendarRoundFactory();
const calendarRound = crFactory.parse('13 Ajaw 18 Kumk\'u');
// Parse a Full Date (Calendar Round + Long Count)
const fdFactory = new FullDateFactory();
const fullDate = fdFactory.parse('13 Ajaw 18 Kumk\'u 9.17.0.0.0');
console.log(`Long Count: ${longCount}`);
console.log(`Calendar Round: ${calendarRound}`);
console.log(`Full Date: ${fullDate}`);Convert between Gregorian dates and Maya Long Count:
import { LongCount, LongCountFactory } from '@drewsonne/maya-dates';
// Convert from JavaScript Date object
const date = new Date('2012-12-21');
const lc1 = LongCount.fromGregorian(date);
console.log(`Date object = ${lc1}`);
// Output: "Date object = 13. 0. 0. 0. 0"
// Convert from ISO 8601 date string
const lc2 = LongCount.fromGregorian('2012-12-21');
console.log(`ISO 8601 = ${lc2}`);
// Output: "ISO 8601 = 13. 0. 0. 0. 0"
// Alternative: Convert from Julian Day Number directly
const lc3 = LongCount.fromJulianDay(2456283);
console.log(`JDN 2456283 = ${lc3}`);
// Output: "JDN 2456283 = 13. 0. 0. 0. 0"
// Convert Long Count to Gregorian (reverse direction)
const lcFactory = new LongCountFactory();
const longCountDate = lcFactory.parse('13.0.0.0.0');
const gregorian = longCountDate.gregorian;
console.log(`Long Count ${longCountDate} = ${gregorian.day}/${gregorian.month}/${gregorian.year} ${gregorian.era}`);
// Output: "Long Count 13. 0. 0. 0. 0 = 21/12/2012 CE"
// Roundtrip conversion example
const original = lcFactory.parse('9.17.0.0.0');
const asGregorian = original.gregorian;
const backToLC = LongCount.fromJulianDay(asGregorian.julianDay);
console.log(`Original: ${original}, Roundtrip: ${backToLC}`);
// Both will be identical: " 9.17. 0. 0. 0"Available conversion methods:
LongCount.fromGregorian(date | isoString, correlation?)- Convert from Date object or ISO 8601 date string (date only)LongCount.fromJulianDay(jdn, correlation?)- Convert from Julian Day NumberLongCount.fromMayanDayNumber(mdn, correlation?)- Convert from Maya Day NumberlongCount.gregorian- Convert Long Count to Gregorian (getter property)longCount.julianDay- Get Julian Day Number for Long Count (getter property)
Use wildcards to represent partial or unknown dates:
import {
LongCountFactory,
isWildcard,
LongCountWildcard
} from '@drewsonne/maya-dates';
// Create a partial date with a wildcard
const partialLc = new LongCountFactory().parse('9.17.0.0.*');
// Check if a component is a wildcard
if (isWildcard(partialLc.kIn)) {
console.log('The k\'in position is a wildcard');
}
// Expand wildcards to get all possible dates
const wildcard = new LongCountWildcard(partialLc);
const allDates = wildcard.run();
console.log(`Found ${allDates.length} possible dates`);Perform arithmetic operations on Maya dates:
import { LongCountFactory, LongCount, DistanceNumber } from '@drewsonne/maya-dates';
const lc = new LongCountFactory().parse('9.17.0.0.0');
// Add days using plus() with a DistanceNumber
const oneDay = new DistanceNumber(1);
const nextDate = lc.plus(LongCount.fromDistanceNumber(oneDay)).equals();
console.log(`Next day: ${nextDate}`);
// Add multiple days
const manyDays = new DistanceNumber(365);
const futureDate = lc.plus(LongCount.fromDistanceNumber(manyDays)).equals();
console.log(`365 days later: ${futureDate}`);
// Subtract days using minus()
const hundredDays = new DistanceNumber(100);
const pastDate = lc.minus(LongCount.fromDistanceNumber(hundredDays)).equals();
console.log(`100 days earlier: ${pastDate}`);
// Get the full Calendar Round for a Long Count
const fullDate = lc.buildFullDate();
console.log(`Full date: ${fullDate}`);
// Shift Calendar Round dates
const shifted = fullDate.cr.shift(20);
console.log(`Calendar Round shifted by 20 days: ${shifted}`);Support alternative spellings and transliterations of Maya calendar terms:
import {
CalendarRoundFactory,
getI18nManager,
LocaleDefinition
} from '@drewsonne/maya-dates';
// Register alternative spellings
const i18n = getI18nManager();
const altSpellings: LocaleDefinition = {
locale: 'alternative',
name: 'Alternative Spellings',
tzolkinDays: {
'Ajaw': { canonical: 'Ajaw', alternatives: ['Ahau', 'Ajau'] }
},
haabMonths: {
'Kumk\'u': { canonical: 'Kumk\'u', alternatives: ['Kumku', 'Cumku'] }
}
};
i18n.registerLocale(altSpellings);
// Parse dates using alternative spellings
const factory = new CalendarRoundFactory();
const cr1 = factory.parse('4 Ahau 8 Kumku'); // Alternative
const cr2 = factory.parse('4 Ajaw 8 Kumk\'u'); // Canonical
// Both produce the same result
console.log(cr1.toString()); // "4 Ajaw 8 Kumk'u"
console.log(cr2.toString()); // "4 Ajaw 8 Kumk'u"For more details, see I18n Documentation and the i18n example.
This version introduces the exports field for better encapsulation and tree-shaking support.
import { LongCountFactory, CalendarRoundFactory, FullDateFactory } from "@drewsonne/maya-dates";// ⚠️ DEPRECATED: Still works for backwards compatibility
import LongCountFactory from "@drewsonne/maya-dates/lib/factory/long-count";
import CalendarRoundFactory from "@drewsonne/maya-dates/lib/factory/calendar-round";Note: Deep imports via /lib/* are supported for backwards compatibility but may be removed in a future major version. Please migrate to named imports from the package root.
- ✅ Better tree-shaking (smaller bundle sizes)
- ✅ Cleaner, more maintainable code
- ✅ Future-proof for ESM support
- ✅ Consistent with modern npm package standards
If you're upgrading from version 1.x, you'll need to update your import statements.
Before (v1.x):
import LongCountFactory from "@drewsonne/maya-dates/lib/factory/long-count";
import {Wildcard, isWildcard} from "@drewsonne/maya-dates/lib/wildcard";
import {getCalendarRound} from "@drewsonne/maya-dates/lib/cr/calendar-round";After (v2.x):
import {
LongCountFactory,
Wildcard,
isWildcard,
getCalendarRound
} from '@drewsonne/maya-dates';Key Changes:
- All exports are now available from the package root (
@drewsonne/maya-dates) - No need to specify
/lib/paths or know internal directory structure - Factory classes are now named exports instead of default exports
- All class and function exports use named exports for better tree-shaking
Migration Steps:
- Replace all
/lib/...imports with root-level imports - Change default imports to named imports for factory classes
- Group related imports together in a single import statement
- Test your code to ensure all imports resolve correctly
See docs/development/workflow.md for the development workflow and contribution guidelines.
npm run build # Compile TypeScript to JavaScript
npm run build:check # Type check without emitting filesnpm test # Run all tests
npm run test:coverage # Run tests with coverage reportReleases are automated with
release-please. Commits on main
that follow Conventional Commits
(fix:, feat:, feat!:/BREAKING CHANGE:) accumulate into an open
chore(main): release x.y.z pull request that carries the version bump and the
CHANGELOG entry.
Merging that pull request is the release: it creates the tag and the GitHub
Release, then publishes to npm and GitHub Packages. Commits without a
conventional prefix, and chore:/docs:/test: commits, do not on their own
trigger a release, so dependency updates accumulate rather than each shipping a
version.
.release-please-manifest.json records the last published version, which is
what commits are diffed against; it is not necessarily the version currently in
package.json.
Publishing to npm authenticates with
trusted publishing (OIDC) rather
than a long-lived token, so there is no NPM_TOKEN to rotate. The trusted
publisher is configured on npmjs.com against this repository and the workflow
filename release.yml; if publishing ever moves to a different workflow file,
that setting has to move with it. The publish step deliberately sets no
NODE_AUTH_TOKEN - setup-node writes _authToken=${NODE_AUTH_TOKEN} into
.npmrc, and an empty value there makes npm skip the OIDC exchange and fail
with ENEEDAUTH.
If a tag and GitHub Release exist but the npm publish failed, re-publish it by
running the Release workflow manually with publish_tag set to that tag (for
example v1.3.12) rather than cutting a new version.
release.yml is the only workflow that publishes. There is no NPM_TOKEN
secret anywhere in the repository.
The documentation site under website/ has its own dependencies. Install them
once before running any of the docs: scripts:
npm run docs:install # Install website dependencies (website/)
npm run docs:start # Start Docusaurus dev server
npm run docs:build # Build static documentation site
npm run docs:serve # Serve built documentation locallyFull documentation is built with Docusaurus and includes API reference generated from TypeDoc. You can browse the documentation at https://drewsonne.github.io/maya-dates/.