Luxon Home Reference Source Repository

Install guide

Install guide

Luxon provides different builds for different JS environments. See below for a link to the right one and instructions on how to use it.

Basic browser setup

Just include Luxon in a script tag. You can access its various classes through the luxon global.

<script src="luxon.js"></script>

You may wish to alias the classes you use:

var DateTime = luxon.DateTime;

Node

Install via NPM:

npm install --save luxon
const { DateTime } = require('luxon');

AMD (System.js, RequireJS, etc)

requirejs(['luxon'], function(luxon) {
  //...
});

ES6

import { DateTime } from 'luxon';

Webpack

npm install --save luxon
import { DateTime } from 'luxon';

Meteor

[Help wanted.]

React Native

[This section is a bit of a placeholder because I know little about RN. So contributions welcome!]

Luxon works in React Native. On Android, the Intl API isn't provided out of the box. Luxon works without Intl support but a lot of its features work as you expect, especially regarding time zones and internationalization. You can use the international variant of jsc-android-buildscripts to Intl support.

A quick tour

A quick tour

Luxon is a library that makes it easier to work with dates and times in Javascript. If you want, add and subtract them, format and parse them, ask them hard questions, and so on, Luxon provides a much easier and comprehensive interface than the native types it wraps. We're going to talk about the most immediately useful subset of that interface.

This is going to be a bit brisk, but keep in mind that the API docs are comprehensive, so if you want to know more, feel free to dive into them.

Your first DateTime

The most important class in Luxon is DateTime. A DateTime represents a specific millisecond in time, along with a time zone and a locale. Here's one that represents May 15, 2017 at 8:30 in the morning:

var dt = DateTime.local(2017, 5, 15, 8, 30);

To get the current time, just do this:

var now = DateTime.local();

DateTime.local takes any number of arguments, all the way out to milliseconds. Underneath, this is just a Javascript Date object. But we've decorated it with lots of useful methods.

Creating a DateTime

There are lots of ways to create a DateTime by parsing strings or constructing them out of parts. You've already seen one, DateTime.local(), but let's talk about two more.

Create from an object

The most powerful way to create a DateTime instance is to provide an object containing all the information:

dt = DateTime.fromObject({day: 22, hour: 12, zone: 'America/Los_Angeles', numberingSystem: 'beng'})

Don't worry too much about the properties you don't understand yet; the point is that you can set every attribute of a DateTime when you create it. One thing to notice from the example is that we just set the day and hour; the year and month get defaulted to the current one and the minutes, seconds, and milliseconds get defaulted to 0. So DateTime.fromObject is sort of the power user interface.

Parse from ISO 8601

Luxon has lots of parsing capabilities, but the most important one is parsing ISO 8601 strings, because they're more-or-less the standard wire format for dates and times. Use DateTime.fromISO.

DateTime.fromISO("2017-05-15")          //=> May 15, 2017 at midnight
DateTime.fromISO("2017-05-15T08:30:00") //=> May 15, 2017 at 8:30

You can parse a bunch of other formats, including your own custom ones.

Getting to know your DateTime instance

Now that we've made some DateTimes, let's see what we can ask of it.

toString

The first thing we want to see is the DateTime as a string. Luxon returns ISO 8601 strings:

DateTime.local().toString() //=> '2017-09-14T03:20:34.091-04:00'

Getting at components

We can get at the components of the time individually through getters. For example:

dt = DateTime.local()
dt.year     //=> 2017
dt.month    //=> 9
dt.day      //=> 14
dt.second   //=> 47
dt.weekday  //=> 4

Other fun accessors

dt.zoneName     //=> 'America/New_York'
dt.offset       //=> -240
dt.daysInMonth  //=> 30

There are lots more!

Formatting your DateTime

You may want to output your DateTime to a string for a machine or a human to read. Luxon has lots of tools for this, but two of them are most important. If you want to format a human-readable string, use toLocaleString:

dt.toLocaleString()      //=> '9/14/2017'
dt.toLocaleString(DateTime.DATETIME_MED) //=> 'September 14, 3:21 AM'

This works well across different locales (languages) by letting the browser figure out what order the different parts go in and how to punctuate them.

If you want the string read by another program, you almost certainly want to use toISO:

dt.toISO() //=> '2017-09-14T03:21:47.070-04:00'

Custom formats are also supported. See formatting.

Transforming your DateTime

Immutability

Luxon objects are immutable. That means that you can't alter them in place, just create altered copies. Throughout the documentation, we use terms like "alter", "change", and "set" loosely, but rest assured we mean "create a new instance with different properties".

Math

This is easier to show than to tell. All of these calls return new DateTime instances:

var dt = DateTime.local();
dt.plus({hours: 3, minutes: 2});
dt.minus({days: 7});
dt.startOf('day');
dt.endOf('hour');

Set

You can create new instances by overriding specific properties:

var dt = DateTime.local();
dt.set({hour: 3}).hour   //=> 3

Intl

Luxon provides several different Intl capabilities, but the most important one is in formatting:

var dt = DateTime.local();
var f = {month: 'long', day: 'numeric'};
dt.setLocale('fr').toLocaleString(f)      //=> '14 septembre'
dt.setLocale('en-GB').toLocaleString(f)   //=> '14 September'
dt.setLocale('en-US').toLocaleString(f)  //=> 'September 14'

Luxon's Info class can also list months or weekdays for different locales:

Info.months('long', {locale: 'fr'}) //=> [ 'janvier', 'février', 'mars', 'avril', ... ]

Time zones

Luxon supports time zones. There's a whole big section about it. But briefly, you can create DateTimes in specific zones and change their zones:

DateTime.fromObject({zone: 'America/Los_Angeles'}) // now, but expressed in LA's local time
DateTime.local().setZone('America/Los_Angeles') // same

Luxon also supports UTC directly:

DateTime.utc(2017, 5, 15);
DateTime.utc();
DateTime.local().toUTC();
DateTime.utc().toLocal();

Durations

The Duration class represents a quantity of time such as "2 hours and 7 minutes". You create them like this:

var dur = Duration.fromObject({hours: 2, minutes: 7});

They can be add or subtracted from DateTimes like this:

dt.plus(dur);

They have getters just like DateTime:

dur.hours   //=> 2
dur.minutes //=> 7
dur.seconds //=> 0

And some other useful stuff:

dur.as('seconds') //=> 7620
dur.toObject()    //=> { hours: 2, minutes: 7 }
dur.toISO()       //=> 'PT2H7M'

You can also format, negate, and normalize them. See it all in the Duration API docs.

Intervals

Intervals are a specific period of time, such as "between now and midnight". They're really a wrapper for two DateTimes that form its endpoints. Here's what you can do with them:

now = DateTime.local();
later = DateTime.local(2020, 10, 12);
i = Interval.fromDateTimes(now, later);

i.length()                             //=> 97098768468
i.length('years', true)                //=> 3.0762420239726027
i.contains(DateTime.local(2019))       //=> true

i.toISO()       //=> '2017-09-14T04:07:11.532-04:00/2020-10-12T00:00:00.000-04:00'
i.toString()    //=> '[2017-09-14T04:07:11.532-04:00 – 2020-10-12T00:00:00.000-04:00)

Intervals can be split up into smaller intervals, perform set-like operations with other intervals, and few other handy features. See the Interval API docs.

Intl

Intl

Luxon uses the native Intl API to provide easy-to-use internationalization. A quick example:

DateTime.local().setLocale('el').toLocaleString(DateTime.DATE_FULL); //=>  '24 Σεπτεμβρίου 2017'

How locales work

Luxon DateTimes can be configured using BCP 47 locale strings specifying the language to use generating or interpreting strings. The native Intl API provides the actual internationalized strings; Luxon just wraps it with a nice layer of convenience and integrates the localization functionality into the rest of Luxon. The Mozilla MDN Intl docs have a good description of how the locale argument works. In Luxon, the methods are different but the semantics are the same, except in that Luxon allows you to specify a numbering system and output calendar independently of the locale string.

The rest of this document will concentrate on what Luxon does when provided with locale information.

Setting the locale

locale is a property of Luxon object. Thus, locale is a sort of setting on the DateTime object, as opposed to an argument you provide the different methods that need internationalized.

You can generally set it at construction time:

var dt = DateTime.fromISO('2017-09-24', { locale: 'fr' })
dt.locale //=> 'fr'

In this case, the specified locale didn't change the how the parsing worked (there's nothing localized about it), but it did set the locale property in the resulting instance. For other factory methods, such as fromString, the locale argument does affect how the string is parsed. See further down for more.

You can change the locale of a DateTime instance (meaning, create a clone DateTime with a different locale) using setLocale:

DateTime.local().setLocale('fr').locale //=> 'fr'

setLocale is just a convenience for reconfigure:

DateTime.local().reconfigure({ locale: 'fr' }).locale; //=> 'fr'

Default locale

Out-of-the-box behavior

By default the locale property of a new DateTime or Duration is null. This means different things in different contexts:

  1. DateTime#toLocaleString, DateTime#toLocaleParts, and other human-readable-string methods like Info.months will fall back on the system locale. On a browser, that means whatever the user has their browser or OS language set to. On Node, that usually means en-US.
  2. DateTime.fromString and DateTime#toFormat fall back on en-US. That's because these methods are often used to parse or format strings for consumption by APIs that don't care about the user's locale. So we need to pick a locale and stick with it, or the code will break depending on whose browser it runs in.
  3. There's an exception, though: DateTime#toFormat can take "macro" formats like "D" that produce localized strings as part of a larger string. These do default to the system locale because their entire purpose is to be localized.

Setting the default

You can set a default locale so that news instances will always be created with the specified locale:

Settings.defaultLocale = 'fr';
DateTime.local().locale; //=> 'fr'

Note that this also alters the behavior of DateTime#toFormat and DateTime#fromString.

Using the system locale in string parsing

You generally don't want DateTime#fromString and DateTime#toFormat to use the system's locale, since your format won't be sensitive to the locale's string ordering. That's why Luxon doesn't behave that way by default. But if you really want that behavior, you can always do this:

 Settings.defaultLocale = DateTime.local().resolvedLocaleOpts().locale;

Checking what you got

The local environment may not support the exact locale you asked for. The native Intl API will try to find the best match. If you want to know what that match was, use resolvedLocaleOpts:

DateTime.fromObject({locale: 'fr-co'}).resolvedLocaleOpts(); //=> { locale: 'fr',
                                                             //     numberingSystem: 'latn',
                                                             //     outputCalendar: 'gregory' }

Methods affected by the locale

Formatting

The most important method affected by the locale setting is toLocaleString, which allows you to produce internationalized, human-readable strings.

dt.setLocale('fr').toLocaleString(DateTime.DATE_FULL) //=> '25 septembre 2017'

That's the normal way to do it: set the locale as property of the DateTime itself and let the toLocaleString inherit it. But you can specify the locale directly to toLocaleString too:

dt.toLocaleString( Object.assign({ locale: 'es' }, DateTime.DATE_FULL)) //=> '25 de septiembre de 2017'

Ad-hoc formatting also respects the locale:

dt.setLocale('fr').toFormat('MMMM dd, yyyy GG'); //=> 'septembre 25, 2017 après Jésus-Christ'

Parsing

You can parse localized strings:

DateTime.fromString('septembre 25, 2017 après Jésus-Christ', 'MMMM dd, yyyy GG', {locale: 'fr'})

Listing

Some of the methods in the Info class let you list strings like months, weekdays, and eras, and they can be localized:

Info.months('long', { locale: 'fr' })    //=> [ 'janvier', 'février', ...
Info.weekdays('long', { locale: 'fr' })  //=> [ 'lundi', 'mardi', ...
Info.eras('long', { locale: 'fr' })      //=> [ 'avant Jésus-Christ', 'après Jésus-Christ' ]

numberingSystem

DateTimes also have a numberingSystem setting that lets you control what system of numerals is used in formatting. In general, you shouldn't override the numbering system provided by the locale. For example, no extra work is needed to get Arabic numbers to show up in Arabic-speaking locales:

var dt = DateTime.local().setLocale('ar')


dt.resolvedLocaleOpts() //=> { locale: 'ar',
                        //     numberingSystem: 'arab',
                        //     outputCalendar: 'gregory' }

dt.toLocaleString() //=> '٢٤‏/٩‏/٢٠١٧'

For this reason, Luxon defaults its own numberingSystem property to null, by which it means "let the Intl API decide". However, you can override it if you want. This example is admittedly ridiculous:

var dt  = DateTime.local().reconfigure({ locale: 'it', numberingSystem: 'beng' })
dt.toLocaleString(DateTime.DATE_FULL) //=> '২৪ settembre ২০১৭'

Similar to locale, you can set the default numbering system for new instances:

Settings.defaultNumberingSystem = 'beng';

Time zones and offsets

Time zones and offsets

Luxon has support for time zones. This page explains how to use them.

Don't worry!

You usually don't need to worry about time zones. Your code runs on a computer with a particular time zone and everything will work consistently in that zone without you doing anything. It's when you want to do complicated stuff across zones that you have to think about it. Even then, here are some pointers to help you avoid situations where you have to think carefully about time zones:

  1. Don't make servers think about local times. Configure them to use UTC and write your server's code to work in UTC. Times can often be thought of as a simple count of epoch milliseconds; what you would call that time (e.g. 9:30) in what zone doesn't (again, often) matter.
  2. Communicate times between systems in ISO 8601, like "2017-05-15T13:30:34Z" where possible (it doesn't matter if you use Z or some local offset; the point is that it precisely identifies the millisecond on the global timeline).
  3. Where possible, only think of time zones as a formatting concern; your application ideally never knows that the time it's working with is called "9:00" until it's being rendered to the user.
  4. Barring 3, do as much manipulation of the time (say, adding an hour to the current time) in the client code that's already running in the time zone where the results will matter.

All those things will make it less likely you ever need to work explicitly with time zones and may also save you plenty of other headaches. But those aren't possible for some applications; you might need to work with times in zones other than the one the program is running in, for any number of reasons. And that's where Luxon's time zone support comes in.

Terminology

Bear with me here. Time zones are pain in the ass. Luxon has lots of tools to deal with them, but there's no getting around the fact that they're complicated. The terminology for time zones and offsets isn't well-established. But let's try to impose some order:

  1. An offset is a difference between the local time and the UTC time, such as +5 (hours) or -12:30. They may be expressed directly in minutes, or in hours, or in a combination of minutes and hours. Here we'll use hours.
  2. A time zone is a set of rules, associated with a geographical location, that determines the local offset from UTC at any given time. The best way to identify a zone is by its IANA string, such as "America/New_York". That zone says something to the effect of "The offset is -4, except between March and November, when it's -5".
  3. A fixed-offset time zone is any time zone that never changes offsets, such as UTC. Luxon supports fixed-offset zones directly; they're specified like UTC+7, which you can interpret as "always with an offset of +7".
  4. A named offset is a time zone-specific name for an offset, such as Eastern Daylight Time. It expresses both the zone (America's EST roughly implies America/New_York) and the current offset (EST means +4). They are also confusing in that they overspecify the offset (e.g. for any given time it is unnecessary to specify EST vs EDT; it's always whichever one is right). They are also ambiguous (BST is both British Summer Time and Bangladesh Standard Time), unstandardized, and internationalized (what would a Frenchman call the US's EST?). For all these reasons, you should avoid them when specifying times programmatically. Luxon only supports their use in formatting.

Some subtleties:

  1. Multiple zones can have the same offset (think about the US's zones and their Canadian equivalents), though they might not have the same offset all the time, depending on when their DSTs are. Thus zones and offsets have a many-to-many relationship.
  2. Just because a time zone doesn't have a DST now doesn't mean it's fixed. Perhaps it had one in the past. Regardless, Luxon does not have first-class access to the list of rules, so it assumes any IANA-specified zone is not fixed and checks for its current offset programmatically.

If all this seems too terse, check out these articles. The terminology in them is subtly different but the concepts are the same:

Luxon works with time zones

Luxon's DateTime class supports zones directly. By default, a date created in Luxon is "in" the local time zone of the machine it's running on. By "in" we mean that the DateTime has, as one of its properties, an associated zone.

It's important to remember that a DateTime represents a specific instant in time and that instant has an unambiguous meaning independent of what time zone you're in; the zone is really piece of social metadata that affects how humans interact with the time, rather than a fact about the passing of time itself. Of course, Luxon is a library for humans, so that social metadata affects Luxon's behavior too. It just doesn't change what time it is.

Specifically, a DateTime's zone affects its behavior in these ways:

  1. Times will be formatted as they would be in that zone.
  2. Transformations to the DateTime (such as plus or startOf) will obey any DSTs in that zone that affect the calculation (see "Math across DSTs" below)

Generally speaking, Luxon does not support changing a DateTime's offset, just its zone. That allows it to enforce the behaviors in the list above. The offset for that DateTime is just whatever the zone says it is. If you are unconcerned with the effects above, then you can always give your DateTime a fixed-offset zone.

Specifying a zone

Luxon's API methods that take a zone as an argument all let you specify the zone in a few ways.

Type Example Description
IANA 'America/New_York' that zone
local 'local' the system's local zone
UTC 'utc' Universal Coordinated Time
fixed offset 'UTC+7' a fixed offset zone
Zone new YourZone() A custom implementation of Luxon's Zone interface (advanced only)

IANA support

IANA-specified zones are string identifiers like "America/New_York" or "Asia/Tokyo". Luxon gains direct support for them by abusing built-in Intl APIs. However, your environment may not support them, in which case, you can't fiddle with the zones directly. You can always use the local zone your system is in, UTC, and any fixed-offset zone like UTC+7. You can check if your runtime environment supports IANA zones with our handy utility:

Info.features().zones; //=> true

If you're unsure if all your target environments (browser versions and Node versions) support this, check out the Support Matrix. You can generally count on modern browsers to have this feature, except IE (it is supported in Edge). You may also polyfill your environment.

If you specify a zone and your environment doesn't support that zone, you'll get an invalid DateTime. That could be because the environment doesn't support zones at all, because for whatever reason doesn't support that particular zone, or because the zone is just bogus. Like this:

bogus = DateTime.local().setZone('America/Bogus')

bogus.isValid;         //=> false
bogus.invalidReason;   //=> 'unsupported zone'

Creating DateTimes

Local by default

By default, DateTime instances are created in the system's local zone and parsed strings are interpreted as specifying times in the system's local zone. For example, my computer is configured to use America/New_York, which has an offset of -4 in May:

var local = DateTime.local(2017, 05, 15, 09, 10, 23);

local.zoneName;                //=> 'America/New_York'
local.toString();              //=> '2017-05-15T09:10:23.000-04:00'

var iso = DateTime.fromISO("2017-05-15T09:10:23");

iso.zoneName;                  //=> 'America/New_York'
iso.toString();                //=> '2017-05-15T09:10:23.000-04:00'

Creating DateTimes in a zone

Many of Luxon's factory methods allow you to tell it specifically what zone to create the DateTime in:

var overrideZone = DateTime.fromISO("2017-05-15T09:10:23", { zone: 'Europe/Paris' });

overrideZone.zoneName;         //=> 'Europe/Paris'
overrideZone.toString();       //=> '2017-05-15T09:10:23.000+02:00'

Note two things:

  1. The date and time specified in the string was interpreted as specifying a Parisian local time (i.e. it's the time that corresponds to what would be called 9:10 there).
  2. The resulting DateTime object is in Europe/Paris.

Those are conceptually independent (i.e. Luxon could have converted the time to the local zone), but it practice it's more convenient for the same option to govern both.

In addition, one static method, utc(), specifically interprets the input as being specified in UTC. It also creates a DateTime in UTC:

var utc = DateTime.utc(2017, 05, 15, 09, 10, 23);

utc.zoneName;                  //=> 'UTC'
utc.toString();                //=> '2017-05-15T09:10:23.000Z'

Strings that specify an offset

Some input strings may specify an offset as part of the string itself. In these case, Luxon interprets the time as being specified with that offset, but converts the resulting DateTime into the system's local zone:

var specifyOffset = DateTime.fromISO("2017-05-15T09:10:23-09:00");

specifyOffset.zoneName;         //=> 'America/New_York'
specifyOffset.toString();       //=> '2017-05-15T14:10:23.000-04:00'

var specifyZone = DateTime.fromString("2017-05-15T09:10:23 Europe/Paris", "yyyy-MM-dd'T'HH:mm:ss z");

specifyZone.zoneName           //=> 'America/New_York'
specifyZone.toString()         //=> '2017-05-15T03:10:23.000-04:00'

...unless a zone is specified as an option (see previous section), in which case the DateTime gets converted to that zone:

var specifyOffsetAndOverrideZone = DateTime.fromISO("2017-05-15T09:10:23-09:00", { zone: 'Europe/Paris' });

specifyOffsetAndOverrideZone.zoneName;                 //=> 'Europe/Paris'
specifyOffsetAndOverrideZone.toString();               //=> '2017-05-15T20:10:23.000+02:00'

setZone

Finally, some parsing functions allow you to "keep" the zone in the string as the DateTime's zone. Note that if only an offset is provided by the string, the zone will be a fixed-offset one, since Luxon doesn't know which zone is meant, even if you do.

var keepOffset = DateTime.fromISO("2017-05-15T09:10:23-09:00", { setZone: true });

keepOffset.zoneName;           //=> 'UTC-9'
keepOffset.toString();         //=> '2017-05-15T09:10:23.000-09:00'

var keepZone = DateTime.fromString("2017-05-15T09:10:23 Europe/Paris", "yyyy-MM-dd'T'HH:mm:ss z", { setZone: true });

keepZone.zoneName;             //=> 'Europe/Paris'
keepZone.toString()            //=> '2017-05-15T09:10:23.000+02:00'

Changing zones

setZone

Luxon objects are immutable, so when we say "changing zones" we really mean "creating a new instance with a different zone". Changing zone generally means "change the zone in which this DateTime is expressed (and according to which rules it is manipulated), but don't change the underlying timestamp." For example:

var local = DateTime.local();
var rezoned = local.setZone('America/Los_Angeles');

// different local times with different offsets
local.toString()     //=> '2017-09-13T18:30:51.141-04:00'
rezoned.toString()   //=> '2017-09-13T15:30:51.141-07:00'


// but actually the same time
local.valueOf() === rezoned.valueOf(); //=> true

keepCalendarTime

Generally, it's best to think of the zone as a sort of metadata that you slide around independent of the underlying count of milliseconds. However, sometimes that's not what you want. Sometimes you want to change zones while keeping the local time fixed and instead altering the timestamp. Luxon supports this:

var local = DateTime.local();
var rezoned = local.setZone('America/Los_Angeles', { keepCalendarTime: true });

local.toString();      //=> '2017-09-13T18:36:23.187-04:00'
rezoned.toString();    //=> '2017-09-13T18:36:23.187-07:00'

local.valueOf() === rezoned.valueOf()  //=> false

If you find that confusing, I recommend just not using it.

Accessors

Luxon DateTimes have a few different accessors that let you find out about the zone and offset:

var dt = DateTime.local();

dt.zoneName          //=> 'America/New_York'
dt.offset            //=> -240
dt.offsetNameShort   //=> 'EDT'
dt.offsetNameLong    //=> 'Eastern Daylight Time'
dt.isOffsetFixed     //=> false
dt.isInDST           //=> true

Those are all documented in the DateTime API docs.

DateTime also has a zone property that holds an Luxon Zone object. You don't normally need to interact with it, but don't get it confused with the zoneName.

dt.zone   //=> LocalZone {}

DST weirdness

Because our ancestors were morons, they opted for a system wherein many governments shift around the local time twice a year for no good reason. And it's not like they do it in a neat, coordinated fashion. No, they do it whimsically, varying the shifts' timing from country to country (or region to region!) and from year to year. And of course, they do it the opposite way south of the Equator. This all a tremendous waste of everyone's energy and, er, time, but it is how the world works and a date and a time library has to deal with it.

Most of the time, DST shifts will happen without you having to do anything about it and everything will just work. Luxon goes to some pains to make DSTs as unweird as possible. But there are exceptions. This section covers them.

Invalid times

Some local times simply don't exist. The Spring Forward DST shift involves shifting the local time forward by (usually) one hour. In my zone, America/New_York, on March 12, 2017 the millisecond after 1:59:59.999 became 3:00:00.000. Thus the times between 2:00:00.000 and 2:59:59.000, inclusive, don't exist in that zone. But of course, nothing stops a user from constructing a DateTime out of that local time.

If you create such a DateTime from scratch, the missing time will be advanced by an hour:

DateTime.local(2017, 3, 12, 2, 30).toString(); //=> '2017-03-12T03:30:00.000-04:00'

You can also do date math that lands you in the middle of the shift. These also push forward:

DateTime.local(2017, 3, 11, 2, 30).plus({days: 1}).toString()         //=> '2017-03-12T03:30:00.000-04:00'
DateTime.local(2017, 3, 13, 2, 30).minus({days: 1}).toString()        //=> '2017-03-12T03:30:00.000-04:00'

Ambiguous times

Harder to handle are ambiguous times. During Fall Back, some local times happen twice. In my zone, America/New_York, on November 5, 2017 the millisecond after 1:59:59.000 became 1:00:00.000. But of course there was already a 1:00 that day an hour before. So if you create a DateTime with a local time of 1:30, which time do you mean? It's an important question, because those correspond to different moments in time.

However, Luxon's behavior here is undefined. It makes no promises about which of the two possible timestamps the instance will represent. Currently, its specific behavior is like this:

DateTime.local(2017, 11, 5, 1, 30).offset / 60                   //=> -4
DateTime.local(2017, 11, 4, 1, 30).plus({days: 1}).offset / 60   //=> -4
DateTime.local(2017, 11, 6, 1, 30).minus({days: 1}).offset / 60  //=> -5

In other words, sometimes it picks one and sometimes the other. Luxon doesn't guarantee the specific behavior above. That's just what it happens to do.

If you're curious, this lack of definition is because Luxon doesn't actually know that any particular DateTime is an ambiguous time. It doesn't know the time zones rules at all. It just knows the local time does not contradict the offset and leaves it at that. To find out the time is ambiguous and define exact rules for how to resolve it, Luxon would have to test nearby times to see if it can find duplicate local time, and it would have to do that on every creation of a DateTime, regardless of whether it was anywhere near a real DST shift. Because that's onerous, Luxon doesn't bother.

Math across DSTs

There's a whole section about date and time math, but it's worth highlighting one thing here: when Luxon does math across DSTs, it adjusts for them when working with higher-order, variable-length units like days, weeks, months, and years. When working with lower-order, exact units like hours, minutes, and seconds, it does not. For example, DSTs mean that days are not always the same length: one day a year is (usually) 23 hours long and another is 25 hours long. Luxon makes sure that adding days takes that into account. On the other hand, an hour is always 3,600,000 milliseconds.

An easy way to think of it is that if you add a day to a DateTime, you should always get the same time the next day, regardless of any intervening DSTs. On the other hand, adding 24 hours will result in DateTime that is 24 hours later, which may or may not be the same time the next day. In this example, my zone is America/New_York, which had a Spring Forward DST in the early hours of March 12.

var start = DateTime.local(2017, 3, 11, 10);
start.hour                          //=> 10, just for comparison
start.plus({days: 1}).hour          //=> 10, stayed the same
start.plus({hours: 24}).hour        //=> 11, DST pushed forward an hour

Changing the default zone

By default, Luxon creates DateTimes in the system's local zone. However, you can override this behavior globally:

Settings.defaultZoneName = 'Asia/Tokyo'
DateTime.local().zoneName                 //=> 'Asia/Tokyo'

Settings.defaultZoneName = 'utc'
DateTime.local().zoneName                 //=> 'UTC'

// you can reset by setting to 'local'

Settings.defaultZoneName = 'local'
DateTime.local().zoneName                 //=> 'America/New_York'

Calendars

Calendars

This covers Luxon's support for various calendar systems. If you don't need to use non-standard calendars, you don't need to read any of this.

Fully supported calendars

Luxon has full support for Gregorian and ISO Week calendars. What I mean by that is that Luxon can parse dates specified in those calendars, format dates into strings using those calendars, and transform dates using the units of those calendars. For example, here is Luxon working directly with an ISO calendar:

DateTime.fromISO('2017-W23-3').plus({ weeks: 1, days: 2 }).toISOWeekDate(); //=>  '2017-W24-5'

The main reason I bring all this is up is to contrast it with the capabilities for other calendars described below.

Output calendars

Luxon has limited support for other calendaring systems. Which calendars are supported at all is a platform-dependent, but can generally be expected to be these: Buddhist, Chinese, Coptic, Ethioaa, Ethiopic, Hebrew, Indian, Islamic, Islamicc, Japanese, Persian, and ROC. Support is limited to formatting strings with them, hence the qualified name "output calendar".

In practice this is pretty useful; you can show users the date in their preferred calendaring system while the software works with dates using Gregorian units or Epoch milliseconds. But the limitations are real enough; Luxon doesn't know how to do things like "add one Islamic month".

The output calendar is a property of the DateTime itself. For example:

var dtHebrew = DateTime.local().reconfigure({ outputCalendar: 'hebrew' })
dtHebrew.outputCalendar; //=> 'hebrew'
dtHebrew.toLocaleString() //=> '4 Tishri 5778'

You can modulate the structure of that string with arguments to toLocaleString (see the docs on that), but the point here is just that you got the alternative calendar.

Generally supported calendars

Here's a table of the different calendars with examples generated formatting the same date generated like this:

DateTime.fromObject({ outputCalendar: c }).toLocaleString(DateTime.DATE_FULL);
Calendar Example
buddhist September 24, 2560 BE
chinese Eighth Month 5, 2017
coptic Tout 14, 1734 ERA1
ethioaa Meskerem 14, 7510 ERA0
ethiopic Meskerem 14, 2010 ERA1
hebrew 4 Tishri 5778
indian Asvina 2, 1939 Saka
islamic Muharram 4, 1439 AH
islamicc Muharram 3, 1439 AH
japanese September 24, 29 Heisei
persian Mehr 2, 1396 AP
roc September 24, 106 Minguo

Default output calendar

You can set the default output calendar for new DateTime instances like this:

Settings.defaultOuputCalendar = 'persian';

Formatting

Formatting

This section covers creating strings to represent a DateTime. There are three types of formatting capabilities:

  1. Technical formats like ISO 8601 and RFC 2822
  2. Internationalizable human-readable formats
  3. Token-based formatting

Technical formats (strings for computers)

ISO 8601

ISO 8601 is the most widely used set of string formats for dates and times. Luxon can parse a wide range of them, but provides direct support for formatting only a few of them:

dt.toISO();         //=> '2017-04-20T11:32:00.000-04:00'
dt.toISODate();     //=> '2017-04-20'
dt.toISOWeekDate(); //=> '2017-W17-7'
dt.toISOTime();     //=> '11:32:00.000-04:00'

Generally, you'll want the first one. Use it by default when building or interacting with APIs, communicating times over a wire, etc.

HTTP and RFC 2822

There are a number of legacy standard date and time formats out there, and Luxon supports some of them. You shouldn't use them unless you have a specific reason to.

dt.toRFC2822(); //=> 'Thu, 20 Apr 2017 11:32:00 -0400'
dt.toHTTP();    //=> 'Thu, 20 Apr 2017 03:32:00 GMT'

toLocaleString (strings for humans)

The basics

Modern browsers (and other JS environments) provide brought support for human-readable, internationalized strings. Luxon provides convenient support for them, and you should use them anytime you want to display a time to a user. Use toLocaleString to do it:

dt.toLocaleString();                                       //=> '4/20/2017'
dt.toLocaleString(DateTime.DATETIME_FULL);                 //=> 'April 20, 2017, 11:32 AM EDT'
dt.setLocale('fr').toLocaleString(DateTime.DATETIME_FULL); //=> '20 avril 2017 à 11:32 UTC−4'

Intl.DateTimeFormat

In the example above, DateTime.DATETIME_FULL is one of several convenience formats provided by Luxon. But the arguments are really any object of options that can be provided to Intl.DateTimeFormat. For example:

dt.toLocaleString({ month: 'long', day: 'numeric' }) //=> 'April 20'

And that's all the preset is:

DateTime.DATETIME_FULL;  //=> {
                         //     year: 'numeric',
                         //     month: 'long',
                         //     day: 'numeric',
                         //     hour: 'numeric',
                         //     minute: '2-digit',
                         //     timeZoneName: 'short'
                         //   }

This also means you can modify the presets as you choose:

dt.toLocaleString(DateTime.DATE_SHORT); //=>  '4/20/2017'
var newFormat = Object.assign({ weekday: 'long' }, DateTime.DATE_SHORT);
dt.toLocaleString(newFormat); //=>  'Thursday, 4/20/2017'

Presets

Here's the full set of provided presets using the October 14, 1983 at 13:30:23 as an example.

Name Description Example in EN_US Example in FR
DATE_SHORT short date 10/14/1983 14/10/1983
DATE_MED abbreviated date Oct 14, 1983 14 oct. 1983
DATE_FULL full date October 14, 1983 14 octobre 1983
DATE_HUGE full date with weekday Tuesday, October 14, 1983 vendredi 14 octobre 1983
TIME_SIMPLE time 1:30 PM 13:30
TIME_WITH_SECONDS time with seconds 1:30:23 PM 13:30:23
TIME_WITH_SHORT_OFFSET time with seconds and abbreviated named offset 1:30:23 PM EDT 13:30:23 UTC−4
TIME_WITH_LONG_OFFSET time with seconds and full named offset 1:30:23 PM Eastern Daylight Time 13:30:23 heure d’été de l’Est
TIME_24_SIMPLE 24-hour time 13:30 13:30
TIME_24_WITH_SECONDS 24-hour time with seconds 13:30:23 13:30:23
TIME_24_WITH_SHORT_OFFSET 24-hour time with seconds and abbreviated named offset 13:30:23 EDT 13:30:23 UTC−4
TIME_24_WITH_LONG_OFFSET 24-hour time with seconds and full named offset 13:30:23 Eastern Daylight Time 13:30:23 heure d’été de l’Est
DATETIME_SHORT short date & time 10/14/1983, 1:30 PM 14/10/1983 à 13:30
DATETIME_MED abbreviated date & time Oct 14, 1983, 1:30 PM 14 oct. 1983 à 13:30
DATETIME_FULL full date and time with abbreviated named offset 14 octobre 1983 à 13:30 UTC−4 14 octobre 1983 à 13:30 UTC−4
DATETIME_HUGE full date and time with weekday and full named offset Friday, October 14, 1983, 1:30 PM Eastern Daylight Time vendredi 14 octobre 1983 à 13:30 heure d’été de l’Est
DATETIME_SHORT_WITH_SECONDS short date & time with seconds 10/14/1983, 1:30:23 PM 14/10/1983 à 13:30:23
DATETIME_MED_WITH_SECONDS abbreviated date & time with seconds Oct 14, 1983, 1:30:23 PM 14 oct. 1983 à 13:30:23
DATETIME_FULL_WITH_SECONDS full date and time with abbreviated named offset with seconds October 14, 1983, 1:30:23 PM EDT 14 octobre 1983 à 13:30:23 UTC−4
DATETIME_HUGE_WITH_SECONDS full date and time with weekday and full named offset with seconds Friday, October 14, 1983, 1:30:23 PM Eastern Daylight Time vendredi 14 octobre 1983 à 13:30:23 heure d’été de l’Est

Intl

toLocaleString's behavior is affected by the DateTime's locale, numberingSystem, and outputCalendar properties. See the Intl section for more.

Formatting with tokens (strings for Cthulhu)

This section covers generating strings from DateTimes with programmer-specified formats.

Consider alternatives

You shouldn't create ad-hoc string formats if you can avoid it. If you intend for a computer to read the string, prefer ISO 8601. If a human will read it, prefer toLocaleString. Both are covered above. However, if you have some esoteric need where you need some specific format (e.g. because some other software expects it), then toFormat is how you do it.

toFormat

See DateTime#toFormat for the API signature. As a brief motivating example:

DateTime.fromISO('2014-08-06T13:07:04.054').toFormat('yyyy LLL dd') //=> '2014 Aug 06'

The supported tokens are described in the table below.

Intl

All of the strings (e.g. month names and weekday names) are internationalized by introspecting strings generated by the Intl API. Thus they exact strings you get are implementation-specific.

DateTime.fromISO('2014-08-06T13:07:04.054').setLocale('fr').toFormat('yyyy LLL dd') //=> '2014 août 06'

Escaping

You may escape strings using single quotes:

DateTime.local().toFormat("HH 'hours and' mm 'minutes'") //=> '20 hours and 55 minutes'

Standalone vs format tokens

Some tokens have a "standalone" and "format" version. Some languages require different forms of a word based on whether it is part of a longer phrase or just by itself (e.g. "Monday the 22nd" vs "Monday"). Use them accordingly.

var d = DateTime.fromISO('2014-08-06T13:07:04.054').setLocale('ru');
d.toFormat("LLLL") //=> 'август' (format)
d.toFormat("MMMM"); //=> 'августа' (standalone)

Macro tokens

Some of the formats are "macros", meaning they correspond to multiple components. These use the native Intl API and will order their constituent parts in a locale-friendly way.

DateTime.fromISO('2014-08-06T13:07:04.054').toFormat('ff') //=> 'Aug 6, 2014, 1:07 PM'

The macro options available correspond one-to-one with the preset formats defined for toLocaleString.

Table of tokens

(Examples below given for 2014-08-06T13:07:04.054 considered as a local time in America/New_York).

Standlone token Format token Description Example
S millisecond, no padding 54
SSS millisecond, padded to 3 054
u fractional seconds, functionally identical to SSS 054
s second, no padding 4
ss second, padded to 2 padding 04
m minute, no padding 7
mm minute, padded to 2 07
h hour in 12-hour time, no padding 1
hh hour in 12-hour time, padded to 2 01
H hour in 24-hour time, padded to 2 9
HH hour in 24-hour time, padded to 2 13
Z narrow offset +5
ZZ short offset +05:00
ZZZ techie offset +0500
ZZZZ abbreviated named offset EST
ZZZZZ unabbreviated named offset Eastern Standard Time
z IANA zone America/New_York
a meridiem AM
d day of the month, no padding 6
dd day of the month, padded to 2 06
c E day of the week, as number from 1-7 (Monday is 1, Sunday is 7) 3
ccc EEE day of the week, as an abbreviate localized string Wed
cccc EEEE day of the week, as an unabbreviated localized string Wednesday
ccccc EEEEE day of the week, as a single localized letter W
L M month as an unpadded number 8
LL MM month as an padded number 08
LLL MMM month as an abbreviated localized string Aug
LLLL MMMM month as an unabbreviated localized string August
LLLLL MMMMM month as a single localized letter A
y year, unpadded 2014
yy two-digit year 14
yyyy four- to six- digit year, pads to 4 2014
G abbreviated localized era AD
GG unabbreviated localized era Anno Domini
GGGGG one-letter localized era A
kk ISO week year, unpadded 17
kkkk ISO week year, padded to 4 2014
W ISO week number, unpadded 32
WW ISO week number, padded to 2 32
o ordinal (day of year), unpadded 218
ooo ordinal (day of year), padded to 3 218
D localized numeric date 9/4/2017
DD localized date with abbreviated month Aug 6, 2014
DDD localized date with full month August 6, 2014
DDDD localized date with full month and weekday Wednesday, August 6, 2014
t localized time 9:07 AM
tt localized time with seconds 1:07:04 PM
ttt localized time with seconds and abbreviated offset 1:07:04 PM EDT
tttt localized time with seconds and full offset 1:07:04 PM Eastern Daylight Time
T localized 24-hour time 13:07
TT localized 24-hour time with seconds 13:07:04
TTT localized 24-hour time with seconds and abbreviated offset 13:07:04 EDT
TTTT localized 24-hour time with seconds and full offset 13:07:04 Eastern Daylight Time
f short localized date and time 8/6/2014, 1:07 PM
ff less short localized date and time Aug 6, 2014, 1:07 PM
fff verbose localized date and time August 6, 2014, 1:07 PM EDT
ffff extra verbose localized date and time Wednesday, August 6, 2014, 1:07 PM Eastern Daylight Time
F short localized date and time with seconds 8/6/2014, 1:07:04 PM
FF less short localized date and time with seconds Aug 6, 2014, 1:07:04 PM
FFF verbose localized date and time with seconds August 6, 2014, 1:07:04 PM EDT
FFFF extra verbose localized date and time with seconds Wednesday, August 6, 2014, 1:07:04 PM Eastern Daylight Time

Parsing

Parsing

Luxon is not an NLP tool and isn't suitable for all date parsing jobs. But it can do some parsing:

  1. Direct support for several well-known formats, including most valid ISO 8601 formats
  2. An ad-hoc parser for parsing specific formats

Parsing technical formats

ISO 8601

Luxon supports a wide range of valid ISO 8601 formats through the fromISO method.

DateTime.fromISO('2016-05-25');

All of these are parsable by fromISO:

2016
2016-05
2016-05-25
20160525
2016-05-25T09
2016-05-25T09:24
2016-05-25T09:24:15
2016-05-25T09:24:15.123
2016-05-25T0924
2016-05-25T092415
2016-05-25T092415.123
2016-05-25T09:24:15,123
2016-W21-3
2016W213
2016-W21-3T09:24:15.123
2016W213T09:24:15.123
2016-200
2016200
2016-200T09:24:15.123

Missing lower-order values are always set to the minimum possible value. Midnight if the hours aren't specified, the first of the month if the day isn't, January if the month isn't, etc.

HTTP and RFC2822

Luxon also provides parsing for strings formatted according to RFC 2822 and the HTTP header specs (RFC 850 and 1123):

DateTime.fromRFC2822('Tue, 01 Nov 2016 13:23:12 +0630');
DateTime.fromHTTP('Sunday, 06-Nov-94 08:49:37 GMT');
DateTime.fromHTTP('Sun, 06 Nov 1994 08:49:37 GMT');

Ad-hoc parsing

Consider alternatives

You generally shouldn't use Luxon to parse arbitrarily formatted date strings:

  1. If the string was generated by a computer for programmatic access, use a standard format like ISO 8601. Then you can parse it using DateTime.fromISO.
  2. If the string is typed out by a human, it may not conform to the format you specify when asking Luxon to parse it. Luxon is quite strict about the format matching the string exactly.

Sometimes, though, you get a string from some legacy system in some terrible ad-hoc format and you need to parse it.

fromString

See DateTime.fromString for the method signature. A brief example:

DateTime.fromString('May 25 1982', 'LLLL dd yyyy');

Intl

Luxon supports parsing internationalized strings:

DateTime.fromString('mai 25 1982', 'LLLL dd yyyy', { locale: 'fr' });

Note, however, that Luxon derives the list of strings that can match, say, "LLLL" (and their meaning) by introspecting the environment's Intl implementation. Thus the exact strings may in some cases be environment-specific. You also need the Intl API available on the target platform (see the support matrix).

Limitations

Not every token supported by DateTime#toFormat is supported in the parser. For example, there's no ZZZZ or ZZZZZ tokens. This is for a few reasons:

  • Luxon relies on natively-available functionality that only provides the mapping in one way. We can ask what the named offset is and get "Eastern Standard Time" but not ask what "Eastern Standard Time" is most likely to mean.
  • Some things are ambiguous. There are several Eastern Standard Times in different countries and Luxon has no way to know which one you mean without additional information (such as that the zone is America/New_York) that would make EST superfluous anyway. Similarly, the single-letter month and weekday formats (EEEEE) that are useful in displaying calendars graphically can't be parsed because of their ambiguity.
  • Luxon doesn't yet support parsing the macro tokens it provides for formatting. This may eventually be addressed.

Debugging

There are two kinds of things that can go wrong when parsing a string: a) you make a mistake with the tokens or b) the information parsed from the string does not correspond to a valid date. To help you sort that out, Luxon provides a method called fromStringExplain. It takes the same arguments as fromString but returns a map of information about the parse that can be useful in debugging.

For example, here the code is using "MMMM" where "MMM" was needed. You can see the regex Luxon uses and see that it didn't match anything:

> DateTime.fromStringExplain("Aug 6 1982", "MMMM d yyyy")

{ input: 'Aug 6 1982',
  tokens:
   [ { literal: false, val: 'MMMM' },
     { literal: false, val: ' ' },
     { literal: false, val: 'd' },
     { literal: false, val: ' ' },
     { literal: false, val: 'yyyy' } ],
  regex: '(January|February|March|April|May|June|July|August|September|October|November|December)( )(\\d\\d?)( )(\\d{4})',
  matches: {},
  result: {},
  zone: null }

If you parse something and get an invalid date, the debugging steps are slightly different. Here, we're attempting to parse August 32nd, which doesn't exist:

var d = DateTime.fromString("August 32 1982", "MMMM d yyyy")
d.isValid //=> false
d.invalidReason //=> 'day out of range'

For more on validity and how to debug it, see validity. You may find more comprehensive tips there. But as it applies specifically to fromString, again try fromStringExplain:

> DateTime.fromStringExplain("August 32 1982", "MMMM d yyyy")

{ input: 'August 32 1982',
  tokens:
   [ { literal: false, val: 'MMMM' },
     { literal: false, val: ' ' },
     { literal: false, val: 'd' },
     { literal: false, val: ' ' },
     { literal: false, val: 'yyyy' } ],
  regex: '(January|February|March|April|May|June|July|August|September|October|November|December)( )(\\d\\d?)( )(\\d{4})',
  matches: { M: 8, d: 32, y: 1982 },
  result: { month: 8, day: 32, year: 1982 },
  zone: null }

Because Luxon was able to parse the string without difficulty, the output is a lot richer. And you can see that the "day" field is set to 32. Combined with the "out of range" explanation above, that should clear up the situation.

Table of tokens

(Examples below given for 2014-08-06T13:07:04.054 considered as a local time in America/New_York).

Standlone token Format token Description Example
S millisecond, no padding 54
SSS millisecond, padded to 3 054
u fractional seconds, (5 is a half second, 54 is slightly more) 54
s second, no padding 4
ss second, padded to 2 padding 04
m minute, no padding 7
mm minute, padded to 2 07
h hour in 12-hour time, no padding 1
hh hour in 12-hour time, padded to 2 01
H hour in 24-hour time, padded to 2 9
HH hour in 24-hour time, padded to 2 13
Z narrow offset +5
ZZ short offset +05:00
ZZZ techie offset +0500
z IANA zone America/New_York
a meridiem AM
d day of the month, no padding 6
dd day of the month, padded to 2 06
E c day of the week, as number from 1-7 (Monday is 1, Sunday is 7) 3
EEE ccc day of the week, as an abbreviate localized string Wed
EEEE cccc day of the week, as an unabbreviated localized string Wednesday
M L month as an unpadded number 8
MM LL month as an padded number 08
MMM LLL month as an abbreviated localized string Aug
MMMM LLLL month as an unabbreviated localized string August
y year, 1-6 digits, very literally 2014
yy two-digit year, interpreted as > 1960 (also accepts 4) 14
yyyy four-digit year 2014
yyyyy four- to six-digit years 10340
yyyyyy six-digit years 010340
G abbreviated localized era AD
GG unabbreviated localized era Anno Domini
GGGGG one-letter localized era A
kk ISO week year, unpadded 17
kkkk ISO week year, padded to 4 2014
W ISO week number, unpadded 32
WW ISO week number, padded to 2 32
o ordinal (day of year), unpadded 218
ooo ordinal (day of year), padded to 3 218
D localized numeric date 9/4/2017
DD localized date with abbreviated month Aug 6, 2014
DDD localized date with full month August 6, 2014
DDDD localized date with full month and weekday Wednesday, August 6, 2014

Math

Math

This page covers some oddball topics with date and time math, which has some quirky corner cases.

Calendar math vs time math

The basics

Math with dates and times can be unintuitive to programmers. If it's Feb 13, 2017 and I say "in exactly one month", you know I mean March 13. Exactly one month after that is April 13. But because February is a shorter month than March, that means we added a different amount of time in each case. On the other hand, if I said "30 days from February 13", you'd try to figure out what day that landed on in March. Here it is in Luxon:

DateTime.local(2017, 2, 13).plus({months: 1}).toISODate() //=> '2017-03-13'

DateTime.local(2017, 2, 13).plus({days: 30}).toISODate() //=> '2017-03-15'

More generally we can differentiate two modes of math:

  • Calendar math works with higher-order, variable-length units like years and months
  • Time math works with lower-order, constant-length units such as hours, minutes, and seconds.

Which units use which math?

These units use calendar math:

  • Years vary because of leap years.
  • Months vary because they're just different lengths.
  • Days vary because DST transitions mean some days are 23 or 25 hours long.
  • Weeks are always the same number of days, but days vary so weeks do too.

These units use time math:

  • Hours are always 60 minutes
  • Minutes are always 60 seconds
  • Seconds are always 1000 milliseconds

Don't worry about leap seconds. Javascript and most other programming environments don't account for them; they just happen as abrupt, invisible changes to the underlying system's time.

How to think about calendar math

It's best not to think of calendar math as requiring arcane checks on the lengths of intervening periods. Instead, think of them as adjusting that unit directly and keeping lower order date components constant. Let's go back to the Feb 13 + 1 month example. If you didn't have Luxon, you would do something like this to accomplish that:

var d = new Date('2017-02-13')
d.setMonth(d.getMonth() + 1)
d.toLocaleString() //=> '3/13/2017, 12:00:00 AM'

And under the covers, that's more or less what Luxon does too. It doesn't boil the operation down to a milliseconds delta because that's not what's being asked. Instead, it fiddles with what it thinks the date should be and then uses the built-in Gregorian calendar to compute the new timestamp.

DSTs

There's a whole section about this in the time zones documentation. But here's a quick example (Spring Forward is early on March 12 in my time zone):

var start = DateTime.local(2017, 3, 11, 10);
start.hour                          //=> 10, just for comparison
start.plus({days: 1}).hour          //=> 10, stayed the same
start.plus({hours: 24}).hour        //=> 11, DST pushed forward an hour

So in adding a day, we kept the hour at 10, even though that's only 23 hours later.

Time math

Time math is different. In time math, we're just adjusting the clock, adding or subtracting from the epoch timestamp. Adding 63 hours is really the same as adding 63 hours' worth of milliseconds. Under the covers, Luxon does this exactly the opposite of how it does calendar math; it boils the operation down to milliseconds, computes the new timestamp, and then computes the date out of that.

Math with multiple units

It's possible to do math with multiple units:

DateTime.fromISO('2017-05-15').plus({months: 2, days: 6}).toISODate(); //=> '2017-07-21'

This isn't as simple as it looks. For example, but should you expect this to do?

DateTime.fromISO('2017-04-30').plus({months: 1, days: 1}).toISODate() //=> '2017-05-31'

If the day is added first, we'll get an intermediate value of May 1. Adding a month to that gives us June 1. But if the month is added first, we'll an intermediate value of May 30 and day after that is May 31. (See "Calendar math vs time math above if this is confusing.) So the order matters.

Luxon has a simple rule for this: math is done from highest order to lowest order. So the result of the example above is May 31. This rule isn't logically necessary, but it does seem reflect what people mean. Of course, Luxon can't enforce this rule if you do the math in separate operations:

DateTime.fromISO('2017-04-30').plus({days: 1}).plus({months: 1}).toISODate() //=> '2017-06-01'

It's not a coincidence that Luxon's interface makes it awkward to do this wrong.

Duration math

Basics

Durations are quantities of time, like "3 days and 6 hours". Luxon has no idea which 3 days and 6 hours they represent; it's just how Luxon represents those quantities in abstract, unmoored from the timeline. This is both tremendously useful and occasionally confusing. I'm not going to give a detailed tour of their capabilities here (see the API docs for that), but I do want to clear up some of those confusions.

Here's some very basic stuff to get us going:

var dur = Duration.fromObject({ days: 3, hours: 6})

// examine it
dur.toObject()          //=> { days: 3, hours: 6 }

// express in minutes
dur.as('minutes')       //=> 4680

// convert to minutes
dur.shiftTo('minutes').toObject() //=> { minutes: 4680 }

// add to a DateTime
DateTime.fromISO("2017-05-15").plus(dur).toISO() //=> '2017-05-18T06:00:00.000-04:00'

Diffs

You can subtract one time from another to find out how much time there is between them. Luxon's diff method does this and it returns a Duration. For example:

var end = DateTime.fromISO('2017-03-13');
var start = DateTime.fromISO('2017-02-13');

var diffInMonths = end.diff(start, 'months');
diffInMonths.toObject(); //=> { months: 1 }

Notice we had to pick the unit to keep track of the diff in. The default is milliseconds:

var diff = end.diff(start);
diff.toObject() //=> { milliseconds: 2415600000 }

Finally, you can diff using multiple units:

var end = DateTime.fromISO('2017-03-13');
var start = DateTime.fromISO('2017-02-15');
end.diff(start, ['months', 'days']) //=> { months: 1, days: 2 }

Casual vs longterm conversion accuracy

Durations represent bundles of time with specific units, but Luxon allows you to convert between them:

  • shiftTo returns a new Duration denominated in the specified units.
  • as converts the duration to just that unit and returns its value
var dur = Duration.fromObject({ months: 4, weeks: 2, days: 6 })

dur.as('days')                            //=> 140
dur.shiftTo('days').toObject()            //=> { days: 140 }
dur.shiftTo('weeks', 'hours').toObject()  //=> { weeks: 18, hours: 144 }

But how do those conversions actually work? First, uncontroversially:

  • 1 week = 7 days
  • 1 day = 24 hours
  • 1 hour = 60 minutes
  • 1 minute = 60 seconds
  • 1 second = 1000 milliseconds

These are always true and you can roll them up and down with consistency (e.g. 1 hour = 60 * 60 * 1000 milliseconds). However, this isn't really true for the higher order units, which vary in length, even putting DSTs aside. A year is sometimes 365 days long and sometimes 366. Months are 28, 29, 30, or 31 days. By default Luxon converts between these units using what you might call "casual" conversions:

Month Week Day
Year 12 52 365
Month 4 30

These should match your intuition and for most purposes they work well. But they're not just wrong; they're not even self-consistent:

dur.shiftTo('months').shiftTo('days').as('years') //=> 0.9863013698630136

This is because 12 * 30 != 365. These errors can be annoying, but they can also cause significant issues if the errors accumulate:

var dur = Duration.fromObject({ years: 50000 });
DateTime.local().plus(dur.shiftTo('milliseconds')).year //=> 51984
DateTime.local().plus(dur).year                         //=> 52017

Those are 33 years apart! So Luxon offers an alternative conversion scheme, based on the 400-year calendar cycle:

Month Week Day
Year 12 52.1775 365.2425
Month 4.348125 30.436875

You can see why these are irritating to work with, which is why they're not the default.

Luxon methods that create Durations de novo accept an option called conversionAccuracy You can set it to 'casual' or 'longterm'. It's a property of the Duration itself, so any conversions you do use the rule you've picked, and any new Durations you derive from it will retain that property.

Duration.fromObject({ years: 23, conversionAccuracy: 'longterm' });
Duration.fromISO('PY23', { conversionAccuracy: 'longterm' });

end.diff(start, { conversionAccuracy: 'longterm' })

You can also create an accurate Duration out of an existing one:

var pedanticDuration = casualDuration.reconfigure({conversionAccuracy: 'longterm' });

These Durations will do their conversions differently.

Losing information

Be careful of converting between units. It's easy to lose information. Let's say we converted a diff into days:

var end = DateTime.fromISO('2017-03-13');
var start = DateTime.fromISO('2017-02-13');
diffInMonths.as('days'); //=> 30

That's our conversion between months and days (you could also do a longterm-accurate conversion; it wouldn't fix the issue ahead). But this isn't the number of days between February 15 and March 15!

var diffInDays = end.diff(start, 'days');
diffInDays.toObject(); //=> { days: 28 }

It's important to remember that diffs are Duration objects, and a Duration is just a dumb pile of time units our computation spat out. Unlike an Interval, a Duration doesn't "remember" what the inputs to the diff were. So we lost some information converting between units. This mistake is really common when rolling up:

var diff = end.diff(start) // default unit is milliseconds

// wtf, that's not a month!
diff.as('months'); //=> 0.9319444 

// it's not even the right number of days! (hint: my time zone has a DST)
diff.shiftTo('hours').as('days'); //=> 27.958333333333332

Normally you won't run into this problem if you think clearly about what you want to do with a diff. But sometimes you really do want an object that represents the subtraction itself, not the result. Intervals can help. Intervals are mostly used to keep track of ranges of time, but they make for "anchored" diffs too. For example:

var end = DateTime.fromISO('2017-03-13');
var start = DateTime.fromISO('2017-02-13');
var i = Interval.fromDateTimes(start, end);

i.length('days');       //=> 28
i.length('months')      //=> 1

Because the Interval stores its endpoints and computes length on the fly, it retakes the diff each time you query it. Of course, precisely because an Interval isn't an abstract bundle of time, it can't be used in places where Durations can. For example, you can't add them to DateTime via plus() because Luxon wouldn't know what units to do the math in (see "Calendar vs time math" above). But you can convert the interval into a Duration by picking the units:

i.toDuration('months').toObject(); //=> { months: 1 }
i.toDuration('days').toObject(); //=> { days: 28 }

You can even pick multiple units:

end = DateTime.fromISO('2018-05-25');
i = start.until(end);
i.toDuration(['years', 'months', 'days']).toObject(); //=> { years: 1, months: 3, days: 12 }

Validity

Validity

Invalid DateTimes

One of the most irritating aspects of programming with time is that it's possible to end up with invalid dates. This is a bit subtle: barring integer overflows, there's no count of milliseconds that don't correspond to a valid DateTime, but when working with calendar units, it's pretty easy to say something like "June 400th". Luxon considers that invalid and will mark it accordingly.

Unless if you've asked Luxon to throw an exception when it creates an invalid DateTime (see more on that below), it will fail silently, creating an instance that doesn't know how to do anything. You can check validity with isValid:

> var dt = DateTime.fromObject({ month: 6, day: 400 });
dt.isValid //=> false

All of the methods or getters that return primitives return degenerate ones:

dt.year; //=>  NaN
dt.toString(); //=> 'Invalid DateTime'
dt.toObject(); //=> {}

Methods that return other Luxon objects will return invalid ones:

dt.plus({ days: 4 }).isValid; //=> false

Reasons a DateTimes can be invalid

The most common way to do that is to over or underflow some unit:

  • February 40th
  • 28:00
  • -4 pm
  • etc

But there are other ways to do it:

// specify a time zone that doesn't exist
DateTime.local().setZone('America/Blorp').isValid //=> false

// provide contradictory information (here, this date is not a Wedensday)
DateTime.fromObject({ year: 2017, month: 5, day: 25, weekday: 3}).isValid //=> false

Note that some other kinds of mistakes throw, based on our judgment that they are more likely programmer errors than data issues:

DateTime.local().set({ blorp: 7 }); //=> kerplosion

Debugging invalid DateTimes

Because DateTimes fail silently, they can be a pain to debug. There are two features that can help.

invalidReason

Invalid DateTime objects are happy to tell you why they're invalid. Like this:

DateTime.local().setZone('America/Blorp').invalidReason; //=>  'unsupported zone'

throwOnInvalid

You can make Luxon throw whenever it creates an invalid DateTime.

Settings.throwOnInvalid = true
DateTime.local().setZone('America/Blorp'); // Error: Invalid DateTime: unsupported zone

You can of course leave this on in production too, but be sure to try/catch it appropriately.

Invalid Durations

Durations can be invalid too. The easiest way to get one is to diff an invalid DateTime.

DateTime.local(2017, 28).diffNow().isValid //=> false

Invalid Intervals

Intervals can be invalid. This can happen a few different ways:

  • The end time is before the start time
  • It was created from invalid DateTime or Duration

API reference

References

Class Summary

Static Public Class Summary
public

A DateTime is an immutable data structure representing a specific date and time and accompanying methods.

public

A Duration object represents a period of time, like "2 months" or "1 day, 1 hour".

public

The Info class contains static methods for retrieving general time and date related data.

public

An Interval object represents a half-open interval of time, where each endpoint is a DateTime.

public

Settings contains static getters and setters that control Luxon's overall behavior. Luxon is a simple library with few options, but the ones it does have live here.

Interface Summary

Static Public Interface Summary
public

Support matrix

Support matrix

Luxon uses a slew of new browser Intl capabilities to tackle some of the tricker parts of dates and times. This means that not everything works in every environment, especially things concerning explicit use of time zones and internationalization.

What works everywhere

Fortunately, most of Luxon works in anything remotely recent. A non-exhaustive list:

  • Create DateTime instances in the local or UTC time zones
  • Parse and output known formats
  • Parse and output using ad-hoc English formats
  • All the transformations like plus and minus
  • All of Duration and Interval

New capabilities and how they're used

Here are the areas that need help from newish browser capabilities:

  1. Basic internationalization. Luxon doesn't have internationalized strings in its code; instead it relies on the hosts implementation of the Intl API. This includes the very handy toLocaleString. Most browsers and recent versions of Node support this.
  2. Internationalized tokens. Listing the months or weekdays of a locale and outputting or parsing ad-hoc formats in non-English locales requires that Luxon be able to programmatically introspect the results of an Intl call. It does this using Intl's formatToParts method, which is a relatively recent addition in most browsers. So you could have the Intl API without having that.
  3. Zones. Luxon's support of IANA zones works by abusing the Intl API. That means you have to have that API and that the API must support a reasonable list of time zones. Zones are a recent addition to some platforms.

You can check whether your environment supports these capabilities using Luxon's Info class:

Info.features() //=> { intl: true, intlTokens: true, zones: true }

The matrix

Here's the level of support for these features in different environments:

Area Chrome FF IE Edge Safari Node
Intl 24 29 11 12 10 0.11 w/ICU†
Intl tokens 55 51 None 15 11 8 w/ICU
Zones 24† 52 None 15‡ 10 6

† This is an educated guess. I haven't tested this or found a definitive reference.

‡ Earlier versions may also support this, but I haven't been able to test them.

Notes:

  • "w/ICU" refers to providing Node with ICU data. See here for more info
  • Safari 11 is still in tech preview as of Sept 2017
  • IE is terrible and it's weird that anyone uses it

Here is the matrix pivoted, with some basic assumptions like "no one runs really old versions of Chrome":

Browser Versions Intl Intl tokens Zones
Chrome >= 56
< 56
FF >= 52
51
< 51
Edge >= 15
< 15
IE >= 11
< 11
Safari >= 11
10
Node w/ICU 8
>= 6
< 6
Node w/o ICU >= 6
< 6

What happens if a feature isn't supported?

You shouldn't use features of Luxon on projects that might be run on environments that don't support those features. Luxon tries to degrade gracefully if you don't, though. Specifically, Luxon hardcodes the set of English strings it needs, and falls back to them if it needs them. Here's a (possibly incomplete) list of Luxon features affected by platform features without universal support:

Feature Full support No Intl at all Intl but no formatToParts No IANA zone support
Most things OK OK OK OK
Using explicit time zones OK Invalid DateTime OK Invalid DateTime
DateTime#toLocaleString OK Uses English with caveats† OK OK
DateTime#toLocaleParts OK Empty array Empty array OK
DateTime#toFormat in en-US OK OK OK OK
DateTime#toFormat in other locales OK Uses English Uses English if format contains localized strings‡ OK
DateTime#fromString in en-US OK OK OK OK
DateTime#offsetNameShort, etc OK Returns null OK in most locales§
fromString in other locales OK Invalid DateTime if uses localized strings‡ Uses English if format contains localized strings‡ OK
Info.months, etc in en-US OK OK OK OK
Info.months, etc in other locales OK Uses English Uses English OK

† Specifically, the caveat here is that this English fallback only works as you might expect for Luxon-provided preset arguments, like DateTime.DATETIME_MED. It won't work if you provide your own, modify for the presets, or even clone them, it will use DateTime.DATETIME_HUGE. If you don't provide any arguments at all, it defaults to DateTime.DATE_SHORT.

‡ This means that Luxon can't parse anything with a word in it like localized versions of "January" or "Tuesday". It's fine with numbers, as long as their in Latin (i.e. Western) numbers.

§ This fallback uses a hack that is not guaranteed to work in every locale in every browser. It's worked where I tested it, though. It will fall back to returning null if it fails.

Polyfills

There are a couple of different polyfills available.

Intl

To backfill the Intl and Intl tokens, there's the Intl polyfill. Use it if your environment doesn't have Intl support or if it has Intl but not formatToParts. Note that this fill comes with its own strings; there's no way to, say, just add the formatToParts piece. Also note that the data isn't as complete as some of the browsers' and some more obscure parsing/formatting features in Luxon don't work very well with it. Finally, note that it does not add zone capabilities.

Zones

If you have an Intl API (either natively or through the Intl polyfill above) but no zone support, you can add it via the very nice DateTime format pollyfill.

For Moment users

For Moment users

Luxon borrows lots of ideas from Moment.js, but there are a lot of differences too. This document clarifies what they are.

Immutability

Luxon's objects are immutable, whereas Moment's are mutable. For example, in Moment:

var m1 = moment();
var m2 = m1.add(1, 'hours');
m1.valueOf() === m2.valueOf(); //=> true

This happens because m1 and m2 are really the same object; add() mutated the object to be an hour later. Compare that to Luxon:

var d1 = DateTime.local();
var d2 = d1.plus({ hours: 1 });
d1.valueOf() === d2.valueOf(); //=> false

This happens because the plus method returns a new instance, leaving d1 unmodified. It also means that Luxon doesn't require copy constructors or clone methods.

Major functional differences

  1. Months in Luxon are 1-indexed instead of 0-indexed like in Moment and the native Date type.
  2. Localizations and time zones are implemented by the native Intl API (or a polyfill of it), instead of by the library itself.
  3. Luxon has both a Duration type and an Interval type. The Interval type is like Twix.
  4. Luxon lacks the relative time features of Moment and won't support it until the required facilities are provided by the browser.

Other API style differences

  1. Luxon methods often take option objects as their last parameter
  2. Luxon has different static methods for object creation (e.g. fromISO), as opposed to Moment's one function that dispatches based on the input
  3. Luxon parsers are very strict, whereas Moment's are more lenient.
  4. Luxon uses getters instead of accessor methods, so dateTime.year instead of dateTime.year()
  5. Luxon centralizes its "setters", like dateTime.set({year: 2016, month: 4}) instead of dateTime.year(2016).month(4) like in Moment.
  6. Luxon's Durations are a separate top-level class.
  7. Arguments to Luxon's methods are not automatically coerced into Luxon instances. E.g. m.diff('2017-04-01') would be dt.diff(DateTime.fromISO('2017-04-01')).

DateTime method equivalence

Here's a rough mapping of DateTime methods in Moment to ones in Luxon. I haven't comprehensively documented stuff that's in Luxon but not in Moment, just a few odds and ends that seemed obvious for inclusion; there are more. I've probably missed a few things too.

Creation

Operation Moment Luxon Notes
Now moment() DateTime.local()
From ISO moment(String) DateTime.fromISO(String)
From RFC 2822 moment(String) DateTime.fromRFC2822(String)
From custom format moment(String, String) DateTime.fromString(String, String)
From object moment(Object) DateTime.fromObject(Object)
From timestamp moment(Number) DateTime.fromMillis(Number)
From JS Date moment(Date) DateTime.fromJSDate(Date)
From civil time moment(Array) DateTime.local(Number...) Like DateTime.local(2016, 12, 25, 10, 30)
From UTC civil time moment.utc(Array) DateTime.utc(Number...) Luxon also uses moment.utc() to take other arguments. In Luxon, use the appropriate method and pass in the { zone: 'utc'} option
Clone moment(Moment) N/A Immutability makes this pointless; just reuse the object
Use the string's offset parseZone See note Methods taking strings that can specify offset or zone take a keepZone argument

Getters and setters

Basic information getters

Property Moment Luxon Notes
Validity isValid() isValid See also invalidReason
Locale locale() locale
Zone tz() zone Moment requires a plugin for this, but not Luxon

Unit getters

Property Moment Luxon Notes
Year year() year
Month month() month
Day of month date() day
Day of week day(), weekday(), isoWeekday() weekday 1-7, Monday is 1, Sunday is 7, per ISO
Day of year dayOfYear() ordinal
Hour of day hour() hour
Minute of hour minute() minute
Second of minute second() second
Millisecond of seconds millisecond() millisecond
Week of ISO week year weekYear, isoWeekYear weekYear
Quarter quarter None Just divide the months by 4

Programmatic get and set

For programmatic getting and setting, Luxon and Moment are very similar here:

Operation Moment Luxon Notes
get value get(String) get(String)
set value set(String, Number) None
set values set(Object) set(Object) Like dt.set({ year: 2016, month: 3 })

Transformation

Operation Moment Luxon Notes
Addition add(Number, String) plus(Object) Like dt.plus({ months: 3, days: 2 })
Subtraction subtract(Number, String) minus(Object) Like dt.minus({ months: 3, days: 2 })
Start of unit startOf(String) startOf(String)
End of unit endOf(String) endOf(String)
Change unit values set(Object) set(Object) Like dt.set({ year: 2016, month: 3 })
Change time zone tz(String) zone(string) Luxon doesn't require a plugin
Change zone to utc utc() toUTC()
Change local zone local() toLocal()
Change offset utcOffset(Number) None Set the zone instead
Change locale locale(String) setLocale(String)

Query

Question Moment Luxon Notes
Is this time before that time? m1.isBefore(m2) dt1 < dt2 The Moment versions of these take a unit. To do that in Luxon, use startOf on both instances.
Is this time after that time? m1.isAfter(m2) dt1 > dt2
Is this time the same or before that time? m1.isSameOrBefore(m2) dt1 <= dt2
Is this time the same or after that time? m1.isSameOrAfter(m2) dt1 >= dt2
Do these two times have the same [unit]? m1.isSame(m2, unit) dt1.hasSame(dt2, unit)
Is this time between these two times? m1.isBetween(m2, m3) Interval.fromDateTimes(dt2, dt3).contains(dt1)
Is this time inside a DST isDST() isInDST
Is this time's year a leap year? isInLeapYear() isInLeapYear
How many days are in this time's month? daysInMonth() daysInMonth
How many days are in this time's year? None daysInYear

Output

Basics

See the formatting guide for more about the string-outputting methods.

Output Moment Luxon Notes
simple string toString() toString() Luxon just uses ISO 8601 for this. See Luxon's toLocaleString()
full ISO 8601 iso() toISO()
ISO date only None toISODate()
ISO time only None toISOTime()
custom format format(...) toFormat(...)
RFC 2822 toRFC2822()
HTTP date string toHTTP()
JS Date toDate() toJSDate()
Epoch time valueOf() valueOf()
Object toObject() toObject()
Duration diff(Moment) diff(DateTime) Moment's diff returns a count of milliseconds, but Luxon's returns a Duration. To replicate the Moment behavior, use dt1.diff(d2).milliseconds.

Humanization

Luxon doesn't support these, and won't until the Relative Time Format proposal lands in browsers.

Operation Moment Luxon
Time from now fromNow() None
Time from other time from(Moment) None
Time to now toNow() None
Time to other time to(Moment) None
"Calendar time" calendar() None

Durations

Moment Durations and Luxon Durations are broadly similar in purpose and capabilities. The main differences are:

  1. Luxon durations have more sophisticated conversion capabilities. They can convert from one set of units to another using shiftTo. They can also be configured to use different unit conversions. See Duration Math for more.
  2. Luxon does not (yet) have an equivalent of Moment's humanize method
  3. Like DateTimes, Luxon Durations have separate methods for creating objects from different sources.

See the Duration API docs for more.

Intervals

Moment doesn't have direct support intervals, which must be provided by plugins like Twix or moment-range. Luxon's Intervals have similar capabilities to theirs, with the exception of the humanization features. See the Interval API docs for more.

Why does Luxon exist?

Why does Luxon exist?

What's the deal with this whole Luxon thing anyway? Why did I write it? How is it related to the Moment project? What's different about it? This page tries to hash all that out.

A disclaimer

I should clarify here that I'm just one of Moment's maintainers; I'm not in charge and I'm not Moment's creator. The opinions here are solely mine. Finally, none of this is meant to bash Moment, a project I've spent a lot of time on and whose other developers I respect.

Origin

Luxon started because I had a bunch of ideas on how to improve Moment but kept finding Moment wasn't a good codebase to explore them with. Namely:

  • I wanted to try out some ideas that I thought would provide a better, more explicit API but didn't want to break everything in Moment.
  • I had an idea on how to provide out-of-the-box, no-data-files-required support for time zones, but Moment's design made that difficult.
  • I wanted to completely rethink how internationalization worked by using the Intl API that comes packaged in browsers.
  • I wanted to use a modern JS toolchain, which would require a major retrofit to Moment.

So I decided to write something from scratch, a sort of modernized Moment. It's a combination of all the things I learned maintaining Moment and Twix, plus a bunch of fresh ideas. I worked on it in little slivers of spare time for about two years. But now it's ready to actually use, and the Moment team likes it enough that we pulled it under the organization's umbrella.

Ideas in Luxon

Luxon is built around a few core ideas:

  1. Keep the basic chainable date wrapper idea from Moment.
  2. Make all the types immutable.
  3. Make the API explicit; different methods do different things and have well-defined options.
  4. Use the Intl API to provide internationalization, including token parsing. Fall back to English if the browser doesn't support those APIs.
  5. Abuse the Intl API horribly to provide time zone support. Only possible for modern browsers.
  6. Provide more comprehensive duration support.
  7. Directly provide interval support.
  8. Write inline docs for everything.

These ideas have some big advantages:

  1. It's much easier to understand and debug code that uses Luxon.
  2. Using native browser capabilities for internationalization leads to a much better behavior and is dramatically easier to maintain.
  3. Luxon has the best time zone support of any JS date library.
  4. Luxon's durations are both flexible and easy to use.
  5. The documentation is very good.

They also have some disadvantages:

  1. Using modern browser capabilities means that the fallback behavior introduces complexity for the programmer.
  2. Never keeping internationalized strings in the code base means that some capabilities have to wait until the browsers provide it.
  3. Some aspects of the Intl API are browser-dependent, which means Luxon's behavior is too.

Place in the Moment project

Luxon lives in the Moment project because, basically, we all really like it, and it represents a huge improvement.

But Luxon doesn't quite fulfill Moment's mandate. First, it doesn't provide some of Moment's most commonly-used features, like relative date formatting. These features will soon be added to modern browsers and quickly folded into Luxon, but they aren't there yet. And even when they are, Luxon will only be able to provide that functionality to those newer environments. In fact, none of Luxon's Intl features work as expected on sufficiently outdated browsers, whereas Moment's all work everywhere. That represents a good tradeoff, IMO, but it's clearly a different one than Moment makes. Luxon makes a major break in API conventions. Part of Moment's charm is that you just call moment() on basically anything and you get date, whereas Luxon forces you to decide that you want to call fromISO or whatever. The upshot of all that is that Luxon feels like a different library; that's why it's not Moment 3.0.

So what is it then? We're not really sure. We're calling it a Moment labs project. Will its ideas get backported into Moment 3? Will it gradually siphon users away from Moment and become the focus of the Moment project? Will the march of modern browsers retire the arguments above and cause us to revisit branding Luxon as Moment? We don't know.

There, now you know as much as I do.

Future plans

Luxon is fully usable and I plan to support it indefinitely. It's also largely complete. It will certainly add relative time formatting (and an English-only fallback) when that becomes possible. Luxon will also eventually strip out its fallbacks for missing platform features. But overall I expect the core functionality to stay basically as it is, adding mostly minor tweaks and bugfixes.

Changelog

Changelog

0.1.0

  • Add .fromSQL, #toSQL, #toSQLTime, #toSQLDate
  • Fix AM/PM parsing
  • Major perf improvements
  • Default to system locale when using macro formats in #toFormat
  • .fromISO accepts standalone times
  • See https://github.com/moment/luxon/issues/93 for important news concerning field accessibility

0.0.22

  • Add 'u' formatting and parsing
  • Add 'y', 'yyyyy', and 'yyyyyy' parsing tokens
  • Add 'yyyyyy' formatting token
  • Better error messages for missing arguments to DateTime.fromString

0.0.21

  • Fix zones for Edge

0.0.20

  • Fix fromISO to accept various levels of subsecond precision

0.0.19

  • Fixed parsing for ordinals
  • Made parsing stricter

0.0.18

  • Fixed formatting for non-hour aligned fixed-offset zones
  • Fixed longterm conversion accuracy option in diffs
  • Fixed invalid handling in Interval#set

0.0.17

  • Fixing formatting for fixed-offset zones

0.0.16

  • Fixes for IE 9 & 10

0.0.15

  • Fixing busted release 0.0.14

0.0.13

  • toLocaleString() and others default to the system's locale
  • support for ISO week durations in Duration.fromISO

0.0.12

  • Improve non-Intl fallbacks for toLocaleString
  • Fix offsetNameShort and offsetNameLong for non-Intl environments
  • Added weekdayShort, weekdayLong, monthShort, monthLong DateTime getters

0.0.10

  • Only include build dir in NPM module

0.0.9

  • Move to Moment Github org

0.0.8

  • The local zone can now report its IANA name
  • Fixed parsing bug for yy and kk
  • Improved test coverage

0.0.7

  • Added toLocaleParts
  • Slighly more friendly month/weekday parsing
  • Default locale setting

0.0.6

  • Stricter toJSDate
  • fromISO now supports year and year-month formats
  • More graceful degradation in the absence of platform features

0.0.5

Experimental, but now broadly useful.