Twix.js is a Moment.js plugin for working with time ranges. Use it to manipulate, interrogate, and intelligently format a block of time. You can find the source on Github and the docs here.
Grab the file as well as moment.js. Then simply reference twix after Moment:
<script src="moment.min.js"></script>
<script src="twix.min.js"></script>
If you want to pull from a CDN, this one is automatically kept up to date.
Twix supports AMD, so you can load it as a RequireJS module. Natrually, it depends on Moment:
define(["moment", "twix"], function(moment){
//you don't usually need a reference to twix itself
});
To install, run
npm install twix
And then in your application, just require
Moment and Twix.
var moment = require('moment');
require('twix');
Twix mixes the twix()
method into all Moment objects. You use that to create a time range from that Moment:
var range = moment(startTime).twix(endTime); //=> from start time until end time
You can also create a range "statically":
var range = moment.twix(startTime, endTime);
You can also create a range from a Moment duration object. See Creating a range from a duration.
TLDR, do one of these:
someMoment.twix(otherMoment);
someMoment.twix('2012-05-25');
someMoment.twix({year: 2012, month: 5, day: 25});
someMoment.twix("05/25/1982", "MM/DD/YYYY", {parseStrict: true});
More comprehensively, this is the signature:
moment.twix(anyMomentArg, [parseFormatString,] optionsObject);
That allows you to pass:
someMoment.twix(new Date(...))
someMoment.twix(otherMoment)
someMoment.twix('2012 August', 'YYYY MMM')
. The format is specified by Moment. You can also use Moment's strict parsing by specifying the parseStrict
option, like someMoment.twix('2012 August', 'YYYY MMM', {parseStrict: true})
.If you want more complicated parsing, just use Moment for that:
var startTime = moment('2012 juillet', 'YYYY MMM', 'fr');
var endTime = moment('2012 August', 'YYYY MMM', 'en');
var range = startTime.twix(endTime); //=> from July 1 to August 1
Regular ranges last from a specific millisecond another specific millisecond. All-day ranges, on the other hand, capture the concept of the entire day. It's an important distinction in several respects:
You create an all-day range by specifying the allDay
option:
moment('1982-05-25').twix('1982-05-26', {allDay: true});
You can also pass a boolean instead of the option hash, and Twix will use it as the all-day option:
moment('1982-05-25').twix('1982-05-26', true);
Returns false if the range's start time is after the end time, and true otherwise.
moment().twix(moment()).isValid(); //=> true
moment().twix(moment().add(1, "day")).isValid(); //=> true
moment().twix(moment().subtract(1, "day")).isValid(); //=> false
Does the range begin and end on the same minute/hour/day/month/year? Any time period understood by Moment will work.
moment("1982-05-25T05:00").twix("1982-05-26T06:00").isSame("day"); //=> false
moment("1982-05-25T05:00").twix("1982-05-25T06:00").isSame("day"); //=> true
moment("1982-05-25T05:00").twix("1982-05-25T06:00").isSame("year"); //=> true
Does the range end in the past?
moment("1982-05-25").twix("1982-05-26").isPast(); //=> true
Does the range start in the future?
moment("2054-05-25").twix("2054-05-26").isFuture(); //=> true
Does the range include the current time?
moment.subtract(1, "hour").twix(moment().add(1, "hour")).isCurrent(); //=> true
Determine whether a range contains a time. You can pass in a Moment object, a JS date, or a string parsable by the Date constructor. The range is considered inclusive of its endpoints.
moment("1982-05-25").twix("1982-05-28").contains("1982-05-26"); //=> true
Calculate the length of the range in terms of minutes/hours/days/months/etc. Any time period understood by Moment will work. Accepts an optional boolean argument to switch on floating point results.
moment("1982-05-25T05:30").twix("1982-05-25T06:30").length("hours") //=> 1
moment("1982-05-25T05:00").twix("1982-05-30T06:00").length("days") //=> 5.041666666666667
moment("1982-05-25T05:00").twix("1982-05-30T06:00").length("days", true) //=> 6
See also asDuration().
The number of minutes/hours/days/months/years the range includes, even in part. Any time period understood by Moment will work.
moment("1982-05-25T05:00").twix("1982-05-25T06:00").count("days") //=> 1
moment("1982-05-25T05:00").twix("1982-05-26T06:00").count("days") //=> 2
Note that this is counting sections of the calendar, not periods of time. So it asks "what dates are included by this range?" as opposed to "how many 24-hour periods are contained in this range?" For the latter, see length().
The number of minutes/hours/days/months/years that are completely contained, such that both the beginning and end of the period fall inside the range. Any time period understood by Moment will work.
moment("1982-05-25T05:00").twix("1982-05-25T06:00").countInner("days") //=> 0
moment("1982-05-24T05:00").twix("1982-05-26T06:00").countInner("days") //=> 1
See also count() and length().
Returns an iterator that will return each a Moment for each time period included in the range. Any time period understood by Moment will work.
var iter = moment("1982-05-25T05:00").twix("1982-05-26T06:00").iterate("days");
iter.hasNext(); //=> true
iter.next(); //=> moment("1982-05-25")
iter.next(); //=> moment("1982-05-26")
iter.hasNext(); //=> false
iter.next(); //=> null
You can also iterate with more complicated periods like "2 hours" or "4 days".
var iter = moment("16", "hh").twix(endTime).iterate(2, 'hours');
iter.next().format('LT'); //=> '4:00 PM'
iter.next().format('LT'); //=> '6:00 PM'
It also works with arbitrary durations objects:
var duration = moment.duration({hours: 2, minutes: 30, seconds: 20});
var iter = moment("16", "hh").twix(endTime).iterate(duration);
iter.next().format('LT'); //=> '4:00 PM'
iter.next().format('LT'); //=> '6:30 PM'
iter.next().format('LT'); //=> '9:00 PM'
Like iterate(), but only for days completely contained in the range.
var iter = moment("1982-05-24T05:00").twix("1982-05-27T06:00").iterateInner("days");
iter.hasNext(); //=> true
iter.next(); //=> moment("1982-05-25")
iter.next(); //=> moment("1982-05-26")
iter.hasNext(); //=> false
iter.next(); //=> null
iterateInner
takes all the same duration arguments as iterate
.
Returns an array of Moment objects from the range, spaced out by the specified period. Works the same as iterate() and takes all the same arguments, but collects the results for you.
var arr = moment('1982-05-24T05:00').twix('1982-05-27T06:00').toArray('days');
arr[0].format('LLL'); //=> 'May 24, 1982 12:00 AM'
arr[1].format('LLL'); //=> 'May 25, 1982 12:00 AM'
arr[2].format('LLL'); //=> 'May 26, 1982 12:00 AM'
Returns the start of the range as a Moment instance. Mutating the returned value does not affect the range. This accessor is often useful when using Twix functions that create ranges, such as split()
and intersection()
.
moment("1982-05-24T05:00").twix(someTime).start(); //=> moment("1982-05-25T05:00")
Returns the end of the range as a Moment instance. Mutating the returned value does not affect the range. This accessor is often useful when using Twix functions that create ranges, such as split()
and intersection()
.
moment(someTime).twix("1982-05-27T06:00").end(); //=> moment("1982-05-27T06:00")
Does this range overlap another range?
var range1 = moment("1982-05-25").twix("1982-05-30");
var range2 = moment("1982-05-27").twix("1982-06-13");
range1.overlaps(range2); //=> true
Does this range have a start time before and an end time after another range?
var range1 = moment("1982-05-25").twix("1982-08-30");
var range2 = moment("1982-05-27").twix("1982-06-13");
range1.engulfs(range2); //=> true
range2.engulfs(range1); //=> false
Are these two ranges the same? Equality also requires that either both or neither ranges are all-day.
var range1 = moment("1982-05-25").twix("1982-08-30");
var range2 = moment("1982-05-25").twix("1982-08-30");
range1.equals(range2); //=> true
range2.equals(range1); //=> true
Produce a range that has the minimum start time and the maximum end time of the two ranges.
var range1 = moment("1982-05-25").twix("1982-05-30");
var range2 = moment("1982-05-27").twix("1982-06-13");
range1.union(range2); //=> 5/25/82 - 6/13/1982
Produce a range that has the maximum start time and the minimum end time of the two ranges.
var range1 = moment("1982-05-25").twix("1982-05-30");
var range2 = moment("1982-05-27").twix("1982-06-13");
range1.intersection(range2); //=> 5/27/82 - 5/30/1982
Returns an array of ranges that are in one range or the other, but not both.
var range1 = moment("1982-05-24").twix("1982-05-28", true);
var range2 = moment("1982-05-22").twix("1982-05-26", true);
var xorred = range1.xor(range2);
xorred[0].format() //=> 'May 22 - 23, 1982'
xorred[1].format() //=> 'May 27 - 28, 1982'
The array returned may contain 0, 1, or 2 ranges.
Returns an array of ranges that are in this range but not the target range.
var range1 = moment("1982-05-23").twix("1982-05-28", true);
var range2 = moment("1982-05-25").twix("1982-05-26", true);
var diff = range1.difference(range2);
diff[0].format(); //=> 'May 23 - 24, 1982'
diff[1].format(); //=> 'May 27 - 28, 1982'
The array returned may contain 0, 1, or 2 ranges.
Splits a range into multiple ranges. Can take either a duration, the arguments for creating a duration, or any number of times.
var range = moment("1982-05-25T05:01").twix("1982-05-25T07:30");
var splits = range.split(1, "hour");
splits[0].format({hideDate: true}); //=> '5:01 - 6:01 AM'
splits[1].format({hideDate: true}); //=> '6:01 - 7:01 AM'
splits[2].format({hideDate: true}); //=> '7:01 - 7:30 AM'
//other signatures
range.split(moment.duration({"h": 1})).length; //=> 3
range.split(moment("1982-05-25T06:00")).length; //=> 2
range.split(moment("1982-05-25T06:00"), moment("1982-05-25T07:00")).length; //=> 3
Divides a range into n evenly-side ranges.
var divided = moment("1982-05-25T05:00").twix("1982-05-25T07:00").divide(2);
divided.length; //=> 2
divided[0].format({hideDate: true}) //=> '5 - 6 AM'
divided[1].format({hideDate: true}) //=> '6 - 7 AM'
Moment now has durations, which represent a block of time, but not a specific block of time, just a period of, say, hours or days. Twix provides some utilities for working with durations.
You can create a range from a duration by anchoring it to a time:
var d = moment.duration(2, "days");
var range = d.afterMoment("1982-05-25"); //=> 5/25/1982 - 5/27/1982
You can also make the range extend backward by the duration:
var d = moment.duration(2, "days");
d.beforeMoment("1982-05-25"); //=> 5/23/1982 - 5/25/1982
You can also create durations from ranges:
var range = moment("1982-05-25").twix("1982-05-28");
range.asDuration("days"); //=> duration object with {days: 3}
See also length().
While Twix's formatting options focus on smart formatting, it also has a few other formatting methods.
Get the length of a range in human-readable terms.
var range = moment("1982-05-25T8:00").twix("1982-05-25T10:00");
range.humanizeLength(); //=> "2 hours"
range = moment("1982-05-25").twix("2013-01-01");
range.humanizeLength(); //=> 31 years
Simple format produces a very simple string representation of the range. It's useful if you don't want all the cleverness of smart formatting. The signature is simpleFormat(momentFormat, options)
and both args are optional. Here's how it works.
var range = moment("1982-05-25T9:00").twix("1982-05-25T12:00");
range.simpleFormat(); //=> '1982-05-25T09:00:00-04:00 - 1982-05-25T12:00:00-04:00'
But you probably want to pass a Moment formatting string. It will format both ends of the range accordingly:
range.simpleFormat("ddd, hA"); //=> 'Tue, 9AM - Tue, 12PM'
All-day ranges will add some extra text:
var range = moment("1982-05-25").twix("1982-05-26", {allDay: true});
range.simpleFormat(); //=> '1982-05-25T00:00:00-04:00 - 1982-05-26T00:00:00-04:00 (all day)'
range.simpleFormat(YYYY-MM-DD); //=> '1982-05-25 - 1982-05-26 (all day)'
You can control that text through the options argument, and even get rid of it altogether:
range.simpleFormat(null, {allDay: "-- all day! --"}); //=> '1982-05-25T00:00:00-04:00 - 1982-05-26T00:00:00-04:00 -- all day! --'
range.simpleFormat(null, {allDay: null}); //=> '1982-05-25T00:00:00-04:00 - 1982-05-26T00:00:00-04:00'
You can also control the spacing and divider. You can set it on individual calls or globally:
range.simpleFormat("HH:mm", {template: function(left, right){return left + " | " + right;}}); //=> '16:21 | 17:21'
Or you can set it globally:
moment.twixClass.formatTemplate = function(left, right){return left + " | " + right;};
range.simpleFormat("HH:mm"); //=> '16:29 | 17:29'
The most important feature is formatting. By default, Twix tries to make brief, readable strings.
Twix's format
method returns a string showing the range. Called with no arguments it uses the default options for how to do that. The most important part of that is that it elides as much redundant information as it can. For example, if the range begins and ends today, it doesn't specify today's date twice. This makes for short, natural-looking time ranges.
moment("1982-01-25T09:00").twix("1982-01-25T11:00").format(); //=> 'Jan 25, 1982, 9 - 11 AM'
moment("1982-01-25T9:00").twix("1982-01-26T13:00").format(); //=> 'Jan 25, 9 AM - Jan 26, 1 PM, 1982'
All day ranges won't show times: they're just assumed to take up the full day local time.
moment("2012-01-25").twix("2012-01-25", {allDay: true}).format(); //=> Jan 25
moment("1982-01-25").twix("1982-01-25", {allDay: true}).format(); //=> Jan 25, 1982
moment("2012-01-25").twix("2012-01-26", {allDay: true}).format(); //=> Jan 25 - 26
moment("1982-01-25").twix("1982-02-25", {allDay: true}).format(); //=> Jan 25 - Feb 25, 1982
moment("1982-01-25").twix(new Date(), {allDay: true}).format(); //=> Jan 25, 1982 - Jan 9, 2012
Notice the various the different kinds of groupings and abbreviations:
Unless the allDay parameter is set to true, the time is considered relevant:
moment("1982-01-25T9:30").twix("1/25/1982 1:30 PM").format(); //=> Jan 25, 1982, 9:30 AM - 1:30 PM
moment("1982-01-25T9:30").twix(new Date()).format(); //=> Jan 25, 1982, 9:30 AM - Jan 9, 2012, 3:05 AM
moment("1982-01-25").twix("1982-01-27").format(); //=> Jan 25, 12 AM - Jan 27, 12 AM, 1982
Twix chops off the :00
on whole hours and, where possible, only display AM/PM once. This can be turned off:
var twix = moment("2012-05-25T9:00").twix("2012-05-25T10:00");
twix.format(); //=> May 25, 9 - 10 AM
twix.format({implicitMinutes: false, groupMeridiems: false}); //=> May 25, 9:00 AM - 10:00 AM
There's an implicitYear
option, which you can use to always show the year, even when it's this year:
var twix = moment().twix(moment().add('days', 1));
twix.format({implicitYear: false}); //=> Mar 28, 1:13 AM - Mar 29, 1:13 AM, 2013
Finally, implicitDate
is just like implicitYear
but for hiding the date if it's today. But it defaults to false
.
Right, not everyone is American. If you use a Moment locale for the range's start, like "en-gb", Twix will automatically use its hour format setting.
Otherwise, simply override the hourFormat
option.
moment("2012-05-25T16:00").twix("2012-05-25T17:00").format({hourFormat: "H"});
//=> May 25, 16:00 - 17:00
This uses Moment's formatting tokens ("H" = 1- or 2-digit 24-hour time, "HH" = always 2-digit, whereas "h" and "hh" are the American versions). Notice there minutes are there in 24-hour time even if they're zero.
The old twentyFourHour: true
setting is no longer supported.
I've made the format hackable, allowing you to specify the Moment formatting parameters externally -- these are what Twix uses to format the bits and pieces of text it glues together. You can use that to adjust how, say, months are displayed:
moment("2012-01-25T8:00").twix("2012-01-25T17:00").format({
monthFormat: "MMMM",
dayFormat: "Do"
}); //=> January 25th, 8 AM - 5 PM
See all the *Format
options below. You should look at Moment's format documentation for more info. YMMV -- because of the string munging, not everything will act quite like you expect.
You can get rid of the space before the meridiem:
moment("2012-05-25T8:00").twix("2012-05-25T17:00").format({spaceBeforeMeridiem: false});
//=> May 25, 8AM - 5PM
If you're showing the date somewhere else, it's sometimes useful to only show the times:
moment("2012-05-25T8:00").twix("2012-05-25T17:00").format({hideDate: true}); //=> 8 AM - 5 PM
If you combine an all-day range with hideDate: true
, you get this:
moment("2012-01-25").twix("2012-01-25", {allDay: true}).format({hideDate: true}); //=> All day
That text is customizable through the allDay
option.
If you would like to hide the times you can use hideTime: true
:
moment("2012-05-25T8:00").twix("2012-05-27T17:00").format({hideTime: true}); //=> May 25 - 27, 2012
If you would like to hide the year, specify hideYear: true
:
moment("2012-05-25").twix("2012-05-27").format({hideYear: true}); //=> May 25 - May 27
Here are all the options and their defaults
{
groupMeridiems: true,
spaceBeforeMeridiem: true,
showDayOfWeek: false,
hideTime: false,
hideDate: false,
hideYear: false,
implicitMinutes: true,
implicitDate: false,
implicitYear: true,
yearFormat: "YYYY",
monthFormat: "MMM",
weekdayFormat: "ddd",
dayFormat: "D",
meridiemFormat: "A",
hourFormat: "h",
minuteFormat: "mm",
allDay: "all day",
explicitAllDay: false,
lastNightEndsAt: 0
}
Twix is open source (MIT License) and hosted on Github here. Instructions for building/contributing are there.