Immutable date and time handling for JavaScript with locale-aware formatting, parsing, calendar math, and IANA or fixed-offset time zones. Frost DateTime works in Node and bundlers, and also ships a browser-friendly UMD bundle that exposes globalThis.DateTime.
- Default ESM
DateTimeexport for Node and bundlers - Prebuilt ESM and UMD bundles in
dist/ - No runtime dependencies
- Immutable operations across getters, setters, and date math
- Locale-aware formatting, parsing, relative time, and week rules through
Intl - IANA time zones such as
Australia/Brisbaneand fixed offsets such as+10:00 - JSDoc-powered IntelliSense
npm i @fr0st/datetimeFrost DateTime's package entry point is ESM-only. Use import syntax in Node and bundlers.
import DateTime from '@fr0st/datetime';Import the minified ESM bundle directly from a CDN:
<script type="module">
import DateTime from 'https://cdn.jsdelivr.net/npm/@fr0st/datetime@latest/dist/frost-datetime.esm.min.js';
const date = DateTime.now({ timeZone: 'UTC' });
console.log(date.toIsoString());
</script>Load the bundle from your own copy or a CDN:
<script src="/path/to/dist/frost-datetime.min.js"></script>
<!-- or -->
<script src="https://cdn.jsdelivr.net/npm/@fr0st/datetime@latest/dist/frost-datetime.min.js"></script>
<script>
const date = globalThis.DateTime.now({ timeZone: 'UTC' });
console.log(date.toIsoString());
</script>The package root resolves to the prebuilt ESM bundle. Published files under dist/ and src/ are also available through matching package subpaths.
import DateTime from '@fr0st/datetime';
const meeting = DateTime.fromFormat(
'yyyy-MM-dd HH:mm:ss',
'2026-03-23 09:30:00',
{ locale: 'en', timeZone: 'Australia/Brisbane' },
);
const nextWeek = meeting.addWeeks(1);
nextWeek.toString();
// Mon Mar 30 2026 09:30:00 +1000 (Australia/Brisbane)
nextWeek.toIsoString();
// 2026-03-29T23:30:00.000+00:00
nextWeek.monthName();
// MarchTypeScript note: Frost DateTime is written in JavaScript and uses JSDoc types, which most editors surface as IntelliSense.
Frost DateTime revolves around an immutable DateTime class and a small set of predictable parsing and formatting rules.
- Instance setters and manipulation methods return new instances
- Constructor numbers are milliseconds since the UNIX epoch
fromTimestamp()andwithTimestamp()use seconds since the UNIX epoch- Strings without a zone designator are interpreted in the requested or default time zone
- Strings with an explicit zone or offset define an instant;
options.timeZoneonly changes its representation - Calendar fields and localized date names use the Gregorian calendar
- Week calculations such as
getWeek(),getWeekYear(),withWeekYear(), andweeksInYear()use the active locale's week rules, including Unicodergregion overrides
const a = DateTime.fromArray([2026, 3, 23], { timeZone: 'UTC' });
const b = a.addDays(1);
a.toIsoString(); // 2026-03-23T00:00:00.000+00:00
b.toIsoString(); // 2026-03-24T00:00:00.000+00:00
new DateTime('January 1, 2019 00:00:00', { timeZone: 'Australia/Brisbane' })
.toIsoString();
// 2018-12-31T14:00:00.000+00:00
new DateTime('2024-01-01T12:00:00.1Z', { timeZone: 'Australia/Brisbane' })
.toIsoString();
// 2024-01-01T12:00:00.100+00:00Frost DateTime exports a default DateTime class from @fr0st/datetime.
All creation methods accept an optional options object:
{
timeZone?: string;
locale?: string;
}new DateTime(date?, options?): create from now, milliseconds, or a string accepted byDate.parse()DateTime.fromArray(dateArray, options?): create from[year, month, date, hours, minutes, seconds, milliseconds]DateTime.fromDate(date, options?): wrap a nativeDateDateTime.fromFormat(formatString, dateString, options?): parse a string with a known token patternDateTime.fromISOString(dateString, options?): parse an ISO date with milliseconds and a numeric offset, using a four-digit or signed six-digit astronomical yearDateTime.fromTimestamp(timestamp, options?): create from seconds since the UNIX epochDateTime.now(options?): create the current time
For constructor strings, an explicit Z or numeric offset defines the instant. Passing a different options.timeZone changes only how that instant is represented. Supported unzoned ISO forms are interpreted as wall time in the requested or default time zone:
yyyyyyyy-MMyyyy-MM-ddyyyy-MM-dd HH:mm[:ss[.fraction]]yyyy-MM-ddTHH:mm[:ss[.fraction]]
Omitted month and day fields default to 1, and omitted time fields default to local midnight. Other accepted string shapes are parsed through Date.parse(); unzoned results are likewise interpreted as local wall time in the requested or default time zone.
const now = new DateTime();
const fromMillis = new DateTime(1711152000000);
const fromArray = DateTime.fromArray([2026, 3, 23, 9, 30], {
timeZone: 'Europe/London',
});
const fromFormat = DateTime.fromFormat(
'dd/MM/yyyy HH:mm:ss',
'23/03/2026 09:30:00',
{ timeZone: 'Australia/Brisbane' },
);fromFormat() and fromISOString() can return an invalid DateTime when the text parses structurally but the calendar values are impossible. This validity is preserved when applying locale or time-zone options and when copying the instance.
const invalid = DateTime.fromFormat('yyyy-MM-dd', '2019-02-31');
invalid.isValid; // falsefromISOString() expects the shape emitted by toIsoString(), including milliseconds and a numeric offset such as +00:00 for UTC. Use the constructor for ISO strings ending in Z.
Exactly two input digits parsed with y, yy, Y, or YY use ICU's moving 100-year window. The window starts 80 calendar years before the UTC reference time captured at the beginning of each fromFormat() call. The complete parsed date, including its time and offset, determines the century at the boundary. Week dates retain their locale-specific week number and weekday when the century changes.
Single-digit and longer input years are interpreted literally. Patterns with three or more year letters (yyy, yyyy, YYY, YYYY, etc.) also interpret the year literally.
Calendar-year y tokens use the year within the era: 0001 BC is 1 BC, and neither era has a year zero. getYear(), array construction, and ISO parsing/output use astronomical years, where 0 is 1 BC and -1 is 2 BC. For example, year 0 formats as 0001 BC with yyyy G, while its ISO year is 0000.
A time-only fromFormat() pattern starts from January 1, 1970 in the requested local time zone. Directly adjacent numeric tokens consume their pattern widths exactly, so compact fixed-width patterns can be parsed; standalone numeric tokens are not capped at the pattern width:
DateTime.fromFormat('yyyyMMddHHmmss', '20190102123456');In format patterns, single quotes delimit literal text and doubled apostrophes represent a literal apostrophe. Unicode literals, including emoji, work in both formatting and parsing. Parsing requires literals to match exactly:
DateTime.fromFormat("yyyy'😀'MM-dd", '2024😀01-01', { timeZone: 'UTC' })
.toIsoString();
// 2024-01-01T00:00:00.000+00:00fromFormat() rejects output-only or intentionally unsupported token widths.
Format tokens are documented in Formats.md.
format(formatString): format with Frost DateTime's token settoString():eee MMM dd yyyy HH:mm:ss xx (VV)toDateString():eee MMM dd yyyytoTimeString():HH:mm:ss xx (VV)toIsoString(): UTC ISO string with milliseconds and+00:00; astronomical years outside0000–9999use a sign and six digitstoJSON(): same UTC ISO string for valid dates,nullfor invalid datestoUTCString():toString()shape in English and UTC
const date = DateTime.fromArray([2026, 3, 23, 9, 30, 15], {
locale: 'en',
timeZone: 'Australia/Brisbane',
});
date.format('eee MMM dd yyyy HH:mm:ss xxx (VV)');
// Mon Mar 23 2026 09:30:15 +10:00 (Australia/Brisbane)Supported format tokens are listed in Formats.md.
Relevant instance methods:
getLocale()withLocale(locale)getTimeZone()getTimeZoneOffset()withTimeZone(timeZone)withTimeZoneOffset(offsetMinutes)dayName(type?)dayPeriod(type?)monthName(type?)era(type?)timeZoneName(type?)
Accepted time-zone formats:
- IANA names such as
Europe/LondonandAmerica/New_York - Zero-offset names
UTC,GMT, andZ - Numeric offsets in
±HH,±HHMM,±HH:MM,±HHMMSS, or±HH:MM:SSform - The same numeric forms prefixed with
GMT, such asGMT+10:00
The absolute fixed offset must be less than 24 hours and have whole-second precision. getTimeZoneOffset() and withTimeZoneOffset() use the native Date#getTimezoneOffset() sign convention: a UTC-10:00 zone reports 600, while UTC+10:00 reports -600. Fractional minutes can represent whole seconds, such as 31 / 60 for 31 seconds.
The O and OOOO tokens can parse their GMT-prefixed output, such as GMT+05:30. The VV token can format and parse both IANA names and fixed offsets such as +05:30.
const brisbane = DateTime.fromArray([2026, 3, 23, 9, 30], {
locale: 'en',
timeZone: 'Australia/Brisbane',
});
brisbane.withTimeZone('UTC').toString();
// Sun Mar 22 2026 23:30:00 +0000 (UTC)
DateTime.fromArray([2026, 3, 23], { locale: 'ar-eg' }).toDateString();| Value | Getter | With |
|---|---|---|
| day of month | getDate() |
withDate(date) |
day of week (0-6, Sunday-based) |
getDay() |
withDay(day) |
| day of year | getDayOfYear() |
withDayOfYear(dayOfYear) |
month (1-12) |
getMonth() |
withMonth(month, date?) |
quarter (1-4) |
getQuarter() |
withQuarter(quarter) |
| year | getYear() |
withYear(year, month?, date?) |
| Value | Getter | With |
|---|---|---|
| locale-aware week of year | getWeek() |
withWeek(week, day?) |
locale-aware day of week (1-7) |
getWeekDay() |
withWeekDay(day) |
weekday occurrence in month (1-5) |
getWeekDayInMonth() |
withWeekDayInMonth(week) |
locale-aware week of month (0-6) |
getWeekOfMonth() |
withWeekOfMonth(week) |
| locale-aware week year | getWeekYear() |
withWeekYear(year, week?, day?) |
getWeekDayInMonth() counts occurrences of the current weekday within the month (1-5). getWeekOfMonth() follows the locale's first weekday and minimum days in the first week; an opening partial week can be week 0, as with January 1, 2021 in en-GB.
When parsing, F with a weekday selects that occurrence within the specified month, while W selects the locale-aware week of the month. An explicit day of month must agree with any supplied F or W value:
DateTime.fromFormat('yyyy-MM F e', '2024-09 1 1', {
locale: 'en-GB',
timeZone: 'UTC',
}).toIsoString();
// 2024-09-02T00:00:00.000+00:00 (first Monday in September)| Value | Getter | With |
|---|---|---|
| hour | getHours() |
withHours(hours, minutes?, seconds?, milliseconds?) |
| minute | getMinutes() |
withMinutes(minutes, seconds?, milliseconds?) |
| second | getSeconds() |
withSeconds(seconds, milliseconds?) |
| millisecond | getMilliseconds() |
withMilliseconds(milliseconds) |
| seconds since UNIX epoch | getTimestamp() |
withTimestamp(timestamp) |
| milliseconds since UNIX epoch | getTime() |
withTime(time) |
| Add | Subtract |
|---|---|
addDay() / addDays(amount) |
subDay() / subDays(amount) |
addWeek() / addWeeks(amount) |
subWeek() / subWeeks(amount) |
addMonth() / addMonths(amount) |
subMonth() / subMonths(amount) |
addYear() / addYears(amount) |
subYear() / subYears(amount) |
addHour() / addHours(amount) |
subHour() / subHours(amount) |
addMinute() / addMinutes(amount) |
subMinute() / subMinutes(amount) |
addSecond() / addSeconds(amount) |
subSecond() / subSeconds(amount) |
| Start | End |
|---|---|
startOfDay() |
endOfDay() |
startOfWeek() |
endOfWeek() |
startOfMonth() |
endOfMonth() |
startOfQuarter() |
endOfQuarter() |
startOfYear() |
endOfYear() |
startOfHour() |
endOfHour() |
startOfMinute() |
endOfMinute() |
startOfSecond() |
endOfSecond() |
diff(other): millisecondsdiffInDays(other, options?)diffInWeeks(other, options?)diffInMonths(other, options?)diffInYears(other, options?)diffInHours(other, options?)diffInMinutes(other, options?)diffInSeconds(other, options?)
options.relative defaults to true for unit-based differences and compares calendar boundaries. For days and weeks, this uses local calendar dates and locale-aware week starts.
With relative: false, weeks, days, hours, minutes, and seconds count completed elapsed units, truncating toward zero; a day is 24 hours and a week is 168 hours. Months and years count completed calendar units using the current date-clamping setting.
const a = DateTime.fromArray([2026, 3, 23]);
const b = DateTime.fromArray([2026, 3, 30]);
a.diffInDays(b); // -7humanDiff(other)humanDiffInDays(other)humanDiffInWeeks(other)humanDiffInMonths(other)humanDiffInYears(other)humanDiffInHours(other)humanDiffInMinutes(other)humanDiffInSeconds(other)
const earlier = DateTime.fromArray([2026, 3, 23], {
locale: 'en',
timeZone: 'UTC',
});
earlier.addWeeks(1).humanDiff(earlier);
// "next week"Base comparisons:
isAfter(other)isBefore(other)isBetween(start, end)isSame(other)isSameOrAfter(other)isSameOrBefore(other)
isBetween() and its scoped variants exclude both endpoints.
Scoped comparisons exist for these units:
DayWeekMonthYearHourMinuteSecond
Examples:
isAfterDay(other)isBetweenMonth(start, end)isSameWeek(other)isSameOrBeforeYear(other)
daysInMonth()daysInYear()weeksInYear()isLeapYear()isDst()
DateTime.dayOfYear(year, month, date)DateTime.daysInMonth(year, month)DateTime.daysInYear(year)DateTime.isLeapYear(year)
The default locale and time zone initially come from the host's Intl settings. They apply to new instances when you omit the corresponding option:
DateTime.getDefaultLocale()DateTime.setDefaultLocale(locale)DateTime.getDefaultTimeZone()DateTime.setDefaultTimeZone(timeZone)
DateTime.setDateClamping(enabled) controls month and year changes for all instances, including existing ones. It defaults to true: preserving a day that does not exist in the target month clamps to that month's last day. Setting it to false allows overflow into the next month.
DateTime.clearDataCache() clears cached formatter and locale data.
DateTime.setDateClamping(true);
DateTime.clearDataCache();- Constructor-based parsing throws on unparseable strings or unsupported time zones.
fromFormat()throws on unmatched tokens, mismatched literals, or trailing characters, and marks impossible parsed dates asisValid === false.- Copies and arithmetic preserve
isValid; invalid dates remain invalid. fromISOString()parses the RFC 3339 / ISO-style shape used bytoIsoString().toIsoString()always returns a UTC string regardless of the instance time zone.toJSON()returns the same value astoIsoString()for valid dates andnullfor invalid dates.withTimeZone()keeps the same instant and changes representation.withTimeZoneOffset()returns a fixed-offset view of the same instant.- Construction and calendar setters shift nonexistent local wall times forward by the gap duration; repeated wall times use the later occurrence.
fromFormat()still marks the result invalid if its final fields differ from the parsed values. - Adding and subtracting calendar days or weeks resolve a nonexistent target time in the operation direction, including across a fully deleted day.
- Hour, minute, and second arithmetic uses elapsed time.
- Date clamping controls whether month and year changes clamp invalid dates.
DateTime.clearDataCache()clears cached formatter and locale data, which is mainly useful in tests and long-lived processes.
npm test
npm run lint
npm run buildFrost DateTime is released under the MIT License.