datetimejs

Library for manipulating, formatting, and parsing dates and times

npm install datetimejs
16 downloads in the last week
32 downloads in the last month

DatetimeJS

This is a shameless (or perhaps shameful) imitation of Python's datetime library for JavaScript. It provides methods for creating, comparing, formatting and parsing dates and times.

Motivation

This library existed a bit longer than it may seem from commit logs and other information. It has been sitting on my hard drive much longer than some of the other libraries out there that provide things like strftime formatting. Unfortunately, I have not released it until now because I was rather busy with other things. It just so happened that I had time on my hands, so I decided to polish and release this.

Another reason is that I have started to use MomentJS, and I hated it with passion. There are many reasons why strftime formatting is great, and habit is not even the first reason that comes to mind. Without going further into philosophical discussion, I consider strftime formatting to be superior and I want to use it in JavaScript. Period.

Installation

The datetime.js module is in UMD format, and it is usable in both the browsers and NodeJS. It has no external dependencies.

On Node, you can install using NPM:

npm install datetimejs

For browsers, you can simply add it using a <script> tag or require it as you normally do if you use RequireJS and similar AMD loaders.

It is also installable with volo:

volo add foxbunny/datetimejs

Basic usage

Since the juicy bit is strftime formatting, let's see an example of that first.

var dt = require('datetime');  // if you need to
var date = new Date(2014, 4, 12, 14, 0, 0);
dt.strftime(date, 'The year is %Y, around %i %p on %b %d');
// returns 'The year is 2014, around 2 p.m. on May 12'

Ideally, we would be able parse using the same formatting string and the result we've got from #strftime(), but that's just not possible right now. So we'll go easy with a slightly more reasonable example.

date = '9/2/2013 11:45 a.m.'
dt.strptime(date, '%n/%D/%Y %i:%M %p');
// Returns Date object for Mon Sep 02 2013 11:45:00

But formatting and parsing isn't all. How about finding out the difference in time between two Date objects?

// Wrapping it in immmediate-execution for people who are 
// trying this in a shell.
(function() {
    date = new Date();
    setTimeout(function() {
        var date1 = new Date();
        var delta = dt.datetime.delta(date, date1)
        console.log('Took me ' + delta.seconds + ' seconds');    
    }, 5400);
}());
// Will eventually print 'Took me 6 seconds'

Or maybe you don't need an exact delta. You only need to know if something is before or after?

var date = new Date(2013, 6, 12);
var date1 = new Date(2013, 6, 15);
dt.datetime.isAfter(date, date1); // false
dt.datetime.isBefore(date, date1); // true

Sorting dates is also quite easy:

dt.datetime.reorder(
    new Date(2013, 6, 12),
    new Date(2013, 4, 10),
    new Date(2013, 5, 15)
);
// returns [Date(2013, 4, 10), Date(2013, 5, 15), Date(2013, 6, 12)]

API reference

The datetime.js module is organized into several submodules. These are:

  • datetime.datetime - Date object creation and manipulaton
  • datetime.dtdelta - Date- and time-difference-related methods
  • datetime.format - Date and time formatting
  • datetime.parse - Date and time parsing
  • datetime.utils - Utility functions

datetime.datetime

This submodule contains methods for creating and manipulating Date objects.

#clone(d)

Creates a new Date object that has identical date and time to d.

#addDays(d, v)

Adds v number of days to d Date object and returns a new instance. v can be either positive or negative. To remove days, simply pass a negative value. Note that this crosses month and year boundaries, so adding 365 days to a Date object will forward that object by approximately one year. This does not take into account leap years, though, so you should use #addYears() instead.

#addMonths(d, v)

Adds v number of months to d Date object and returns a new instance. v can be either positive or negative. This method will also change the year if needed. For eample, adding 12 month will forward the date by one year.

#addYears(d, v)

Adds v years to d Date object. v can either be positive or negative.

resetTime(d)

Resets the hours, minutes, seconds, and milliseconds to 0 and returns a new instance.

#today()

Returns a Date object that represents today's date with time part reset to 0.

#thisMonth()

Returns a Date object that represents the first day of current month.

#thisWeek()

Returns a Date object that represents the first day of current week. This method will use the WEEK_START variable to determine what day the week starts on.

#toUTC(d)

Since JavaScript's date and time API does not really provide means for manipulating time zones, this method provides an ugly hack to get the UTC time for a given Date object. It simply shifts the date by the offset of the date object and returns an instance that is in local time, but has date and time shifted so it represents the same values as the UTC time zone.

datetime.dtdelta

This module contains methods for working with date and time differences.

Please note that in comparison methods like #isBefore() or #isAfter(), the first argument is always the value we want to test, and the other arguments are reference values.

datetime.dtdelta.isBefore(a, b);
// 'Is a before b?'

datetime.dtdelta.isBeteween(a, b, c);
// 'Is a between b and c?'

#delta(d1, d2)

Calculates the difference between two Date objects and returns a delta object. The delta object has the following structure:

d.delta // relative difference
d.milliseconds // total absolute difference in milliseconds
d.seconds // total absolute difference in seconds (rounded up)
d.minutes // total absolute difference in minutes (rounded up)
d.hours // total absolute difference in hours (rounded up)
d.days // total absolute difference in days (rounded up)
d.composite // composite absolute difference

The delta key is the same as performing d2 - d1 (and that is how it's obtained).

'Relative difference' means the difference between d1 and d2 relative to d1. This can be a negative or positive number in milliseconds. All other values (including the milliseconds key) are absolute, which means they are always positive.

The composite difference is an array containing the total difference broken down into days, hours, minutes, seconds, and milliseconds.

#reorder(d, [d1, d2...])

Reorders the Date objects passed as arguments from the oldest to newest. This method takes any number of arguments, and does not take arrays.

If you want to pass an array, you can simply apply #reorder():

var a = [d1, d2, d3];
datetime.dtdelta.reorder.apply(null, a);

There is no native support for reverse sorting. You can always reverse the result, though:

var res = datetime.dtdelta.reorder(d1, d2, d3);
res.reverse()

New in 0.3.0: This method no longer uses #delta() internally, so overloading the #delta() method will not change its behavior.

#isBefore(d, d1)

Returns true is d is before d1. Note that if the objects represent the same time, the method will return false.

#isAfter(d, d1)

Returns true if d is after d1. Note that if the objects rerpresent the same time, the method will return false.

#isBetween(d, d1, d2)

Returns true if d is between d1 and d2 (and not equal to either d1 or d2). Note that the order of d1 and d2 does not matter.

#isDateBefore(d, d1)

Same as #isBefore() but ignores the time differences.

#isDateAfter(d, d1)

Same as #isAfter() but ignores the time differences.

#isDateBetween(d, d1, d2)

Sale as #isBetween() but ignores the time differences.

#isLeapYear(d)

Returns true if d object is in a leap year.

datetime.format

This submodule contains methods related to formatting dates.

#strftime(d, sformat)

Formats date and time. The d argument is a JavaScript Date object, and sformat is an arbitrary string containing the formatting tokens (see Formatting tokens).

This method is aliased as datetime.strftime for convenience.

Example:

var d = new Date(2013, 8, 21, 12, 10, 32)
datetime.strftime(d, 'The date is %D %B %Y');
// returns 'The date is 21 September 2013'

#isoformat(d)

Shortcut for formatting using ISO format. You can change the ISO format you are going to be using by changing the ISO_FORMAT variable.

The date is internally converted to UTC before formatting, so the resulting timestamp is always in UTC regardless of your platform's local time zone.

This method is aliased as datetime.isoformat for convenience.

#reformat(s, [input,] output)

Reformats the string s from input format to output format.

Input format is optional and this method will defer to datetime.parse.isoparse() if input is not specified.

Example:

var s = '12 May 2013'
datetime.reformat(s, '%D %B %Y', '%Y-%m-%d')
// returns '2013-05-12'

datetime.parse

This submodule contains methods for parsing dates and times.

#strptime(s, sformat)

Parses date and time. This method will parse a string s containing a date and/or time formatted as sformat. The format string is similar to the one used in #strftime() except that it uses a subset of formatting tokens (see Parsing tokens).

This method is aliased as datetime.strptime for convenience.

Example:

var s = 'The date is 21 September 2013'
datetime.strptime(s, 'The date is %D %B %Y')
// returns Date(2013, 8, 21, 0, 0, 0, 0)

#isoparse(s)

Shortcut for parsing a string using ISO format. You can change the ISO format you are going to be using by changing the ISO_FORMAT variable.

The input string is considered to represent a date and time in UTC and is compensated for time zone difference between UTC and platform's local time zone. The output date time object will always be in local time.

This method is alias as datetime.strptime for convenience.

datetime.utils

TODO

Variables

Some of DatetimeJS's behavior and output can be customized by changing the internal variables.

The internal variables are all exposed through thedatetime module. This is done intentionally for customization purposes.

To get or set a variable simple use it as datetime property:

datetime.DY = ['su', 'mo', 'tu', 'we', 'th', 'fr', 'sa'];

DATE_MS

Default is 86400000. Number of milliseconds in a day. Changing this will generally lead to weird behavior, but you're free to do it if you know what you are doing.

REGEXP_CHARS

An array of regexp characters that the parser will escape before parsing. By default, these are the following:

^ $ [ ] ( ) . { } + * ? |

MONTHS

Array containing full month names. Default is English month names.

MNTH

Array containing three-letter abbreviation of month names. Default is abbreviated English month names.

DAYS

Array containing full English week day names.

DY

Array containing abbreviated three-letter English week day names.

AM

The abbreviated ante-meridiem. By default it's 'a.m.'.

PM

The abbreviated post-meridiem. By default it's 'p.m.'.

WEEK_START

Numeric week day on which week starts, where 0 is Sunday. By default, it's 0 (Sunday).

FORMAT_TOKENS

This variable is an object, that maps formatting tokens to formatting functions.

Each formatting function is applied to the date object being formatted and returns a string representing the value of its token.

To add new tokens, simply add a new key to this object that represents the token (it will be used directly in a regexp, so make sure any special characters are escaped), and assign a function that will do the formatting. The this inside the function represents the date object.

For example, let's add a token '%o' that will return a date in ordinal format:

datetime.FORMAT_TOKENS['%o'] = function() {
    var date = this.getDate().toString();
    if (['11', '12', '13'].indexOf(date) >= 0) {
        return date + 'th';
    }
    switch (date.slice(-1)) {
        case '1': return date + 'st';
        case '2': return date + 'nd';
        case '3': return date + 'rd';
        default: return date + 'th';
    }
};

var d1 = new Date(2013, 8, 1);
var d2 = new Date(2013, 8, 2);
var d3 = new Date(2013, 8, 3);
var d4 = new Date(2013, 8, 15);

console.log(datetime.strftime(d1, 'On %o'));
console.log(datetime.strftime(d2, 'On %o'));
console.log(datetime.strftime(d3, 'On %o'));
console.log(datetime.strftime(d4, 'On %o'));

// On 1st
// On 2nd
// On 3rd
// On 4th

As you can see, you are not really limited to standard tokens for formatting. Developers can use this feature to add tokens that are specific to the application as well, not just date formatting in general (e.g., output an entire block of HTML depending on the date's value).

Note that tokens are not limited to 2 characters nor do they have to start with a percent character. They are case sensitive, though.

(If you are wondering why this seemingly very useful token isn't included by default, it's because it's English only, and DatetimeJS is supposed to b easy to localize.)

PARSE_RECIPES

This variable is a FORMAT_TOKENS counterpart used for parsing. Again, each token is a key on this object, and it maps to a function that performs the parsing.

The parsing function must return an object with two keys. The re key contains the regexp fragment that matches values for the given token, and the fn key contains a function that transforms a meta object later used by the parser to construct a date object.

The meta object has following properties:

meta.year // full integer year
meta.month // 0-indexed month
meta.date // integer date (as in day of month)
meta.hour // integer hour in 12- or 24-hour format
meta.minute // integer minute
meta.second // integer second
meta.millisecond // integer millisecond (0 to 999)
meta.timeAdjust // for 12-hour hour format adjust for PM by adding 12 hours
meta.teimzone // the time zone offset in minutes (-720 to +720)

Each parsing function will modify the meta object with its own data. You can also read the data off the meta object, but you should keep in mind the order in which parsing functions are executed since you will only be able to read the data added by the previous parse functions. The order in which functions are run is determined by the order in which tokens appear in the format string.

The regular expression fragment must have all its backslashes escaped. So, instead of typing '\d', you must type '\\d'.

New in 0.3.1: After minor fixes to the way variables are used internally, it is finally possible to add new parse recipes.

Let's demonstrate writing a parse function by adding a parse function of the new token we've added in the previous section.

datetime.PARSE_RECIPES['%o'] = function() {
    return {
        re: '31st|30th|20th|1\\dth|2?(?:1st|2nd|3rd|[4-9]th)',
        fn: function(s, meta) {
            meta.date = parseInt(s.slice(0, -2), 10);
        }
    };
};

var s1 = 'December 1st, 2012'
var s2 = 'April 22nd, 2003'
var s3 = 'January 11th, 2014'
var format = '%B %o, %Y'

console.log(datetime.strptime(s1, format));
console.log(datetime.strptime(s2, format));
console.log(datetime.strptime(s3, format));

And yes, I know the regexp isn't particularly clever, but it's generally a good idea to have a regexp that will match only what it needs to.

(If you are wondering why this seemingly very useful token isn't included by default, it's because it's English only, and DatetimeJS is supposed to b easy to localize.)

ISO_FORMAT

Default is '%Y-%m-%dT%H:%M:%f'. You can change it to whatever you like. Some of the other common forms are:

  • '%Y-%m-%dT%H:%M:%S'
  • '%Y-%m-%dT%H:%M:%f%z'
  • '%Y-%m-%dT%H:%M:%S%z'
  • '%Y-%m-%dT%H:%M:%fZ'
  • '%Y-%m-%dT%H:%M%z'

This variable is used by #isoformat() and #isoparse() methods.

Formatting tokens

The following tokens are recognized by datetime.strftime:

  • %a - Short week day name (e.g. 'Sun', 'Mon'...)
  • %A - Long week day name (e.g., 'Sunday', 'Monday'...)
  • %b - Short month name (e.g., 'Jan', 'Feb'...)
  • %B - Full month name (e.g., 'January', 'February'...)
  • %c - Locale-formatted date and time (platform-dependent)
  • %d - Zero-padded date (e.g, 02, 31...)
  • %D - Non-zero-padded date (e.g., 2, 31...)
  • %f - Zero-padded decimal seconds (e.g., 04.23, 23.50)
  • %H - Zero-padded hour in 24-hour format (e.g., 8, 13, 0...)
  • %i - Non-zero-padded hour in 12-hour format (e.g., 8, 1, 12...)
  • %I - Zero-padded hour in 12-hour format (e.g., 08, 01, 12...)
  • %j - Zero-padded day of year (e.g., 002, 145, 364...)
  • %m - Zero-padded month (e.g., 01, 02...)
  • %M - Zero-padded minutes (e.g., 01, 12, 59...)
  • %n - Non-zero-padded month (e.g., 1, 2...)
  • %N - Non-zero-padded minutes (e.g., 1, 12, 59)
  • %p - AM/PM (a.m. and p.m.)
  • %s - Non-zero-padded seconds (e.g., 1, 2, 50...)
  • %S - Zero-padded seconds (e.g., 01, 02, 50...)
  • %r - Milliseconds (e.g., 1, 24, 500...)
  • %w - Numeric week day where 0 is Sunday (e.g., 0, 1...)
  • %y - Zero-padded year without the century part (e.g., 01, 13, 99...)
  • %Y - Full year (e.g., 2001, 2013, 2099...)
  • %z - Timezone in +HHMM or -HHMM format (e.g., +0200, -0530)
  • %x - Locale-formatted date (platform dependent)
  • %X - Locale-formatted time (platform dependent)
  • %% - Literal percent character %

If you are coming from Python, you might be used to using %f to mean microseconds. The same token in DatetimeJS has a different meaning (float seconds).

Parsing tokens

The following tokens are recognized when parsing with datetime.strptime:

  • %b - Short month name (e.g., 'Jan', 'Feb'...)
  • %B - Full month name (e.g., 'January', 'February'...)
  • %d - Zero-padded date (e.g, 02, 31...)
  • %D - Non-zero-padded date (e.g., 2, 31...)
  • %H - Zero-padded hour in 24-hour format (e.g., 8, 13, 0...)
  • %i - Non-zero-padded hour in 12-hour format (e.g., 8, 1, 12...)
  • %I - Zero-padded hour in 12-hour format (e.g., 08, 01, 12...)
  • %m - Zero-padded month (e.g., 01, 02...)
  • %M - Zero-padded minutes (e.g., 01, 12, 59...)
  • %n - Non-zero-padded month (e.g., 1, 2...)
  • %N - Non-zero-padded minutes (e.g., 1, 12, 59)
  • %p - AM/PM (a.m. and p.m.)
  • %s - Non-zero-padded seconds (e.g., 1, 2, 50...)
  • %S - Zero-padded seconds (e.g., 01, 02, 50...)
  • %r - Milliseconds (e.g., 1, 24, 500...)
  • %y - Zero-padded year without the century part (e.g., 01, 13, 99...)
  • %Y - Full year (e.g., 2001, 2013, 2099...)
  • %z - Time zone in +HHMM or -HHMM format or 'Z' (e.g., +1000, -0200)

The %z token behaves slightly differently when parsing date and time strings. In addition to formats that strftime outputs, it also supports 'Z', which allows parsing of ISO timestamps.

Running unit tests

For in-browser testing, simply open the index.html located in the tests directory.

For NodeJS, run:

mocha tests/*.js

Known issues

It is not possible to override some of the internal variables that are exposed thorugh the module. This will be fixed in 0.4.0. There are currently no known workarounds.

Reporting bugs

Before reporting a bug, please make sure all tests are passing. If tests are not passing, please include information about failing tests with your bug report, even if it doesn't seem relevant.

Report all your issues (related to this library, of course) to the GitHub issue tracker.

npm loves you