UsefulJS.Date

The Date module provides a Format class for date and time formatting and parsing that uses an identical API to Intl.DateTimeFormat:

var d = new Date(),
    opts = {weekday: "long", year: "numeric", month: "long", day: "numeric"},
    fmt = new UsefulJS.Date.Format("fr", opts),
    dateStr = fmt.format(d);                // "dimanche 3 novembre 2013"

The values provided by Intl.DateTimeFormat are strictly one way: there is no way to read them back. UsefulJS.Date.Format objects can read back strings that they produce and turn them back into Date objects:

fmt.parse("dimanche 3 novembre 2013"); // Sun Nov 03 2013 00:00:00 GMT+0000 (GMT)

UsefulJS.Date.Format

Constructor for the class.

Syntax
new UsefulJS.Date.Format([requestedLocales[, options]])
Parameters

Locales

The requestedLocales parameter is specified either as a single BCP-47 locale tag ("en" or "en-GB") or an array of such values indicating the preferred locale and one or more fallbacks. For example, ["quz-PE", "es-PE", "es"] would result in the "es-PE" locale being selected for date and time formatting since Quechua is not a supported language whereas Spanish is and there is a locale object for Spanish (Peru).

The requestedLocales parameter is optional. You can set UsefulJS.Locale.current to control the default locale.

Unicode extensions

Together with selecting the locale itself, you may specifiy Unicode extensions to control the numbering system used and the calendar used to measure dates. "-u-" is used to specify an extension and then "nu-<N>" for the numbering system and "ca-<C>" for the calendar. For example, to do date formatting in Hindi using Devangaric digits and the Indian national calendar, you would specify "hi-u-nu-deva-ca-indian". Latin ("latn") digits are the default for most locales together with the Gregorian ("gregory") calendar.

Numbering systems

You may specify the following values for the numbering system: "arab", "arabext", "bali", "beng", "deva", "fullwide", "gujr", "guru", "hanidec", "khmr", "knda", "laoo", "latn", "limb", "mlym", "mong", "mymr", "orya", "tamldec", "telu", "thai" and "tibt". Of these "arab" is the default numbering system used in dates for most Arabic locales apart from Tunisia, Algeria and Morocco, "arabext" is the default for "fa" (Iran) and "beng" is the default for "bn-BD" (Bangladesh). "deva" would be valid for "hi" (Hindi), "thai" for "th" (Thailand) and "hanidec"/"fullwide" for Chinese and Japanese locales.

Calendars

The internationalization API allows for fourteen calendars: "buddhist", "chinese", "coptic", "ethioaa", "ethiopic", "gregory", "hebrew", "indian", "islamic", "islamicc", "iso8601", "japanese", "persian" and "roc". Of these, the following calendars are supported by UsefulJS:

buddhist

The Buddhist calendar is measured from 543 BCE. Year 0 is formatted as "0" and years before the era are formatted as negative values. Otherwise it is the same as the Gregorian calendar (i.e. the year begins on January 1st and leap years are the same). It is the default calendar for the "th" (Thai) locale. The era name ("BE") is only localized for Thai.

gregory

The Gregorian calendar introduced in 1582 CE corrected the excess leap years of the Julian calendar which by that time was eleven days out of step with the equinoxes. Years are measured from 1 CE, Years prior to this are formatted as abs(n) + 1, so that 0 becomes "1 BCE". This is the default calendar for most locales and the months and era names, "CE" and "BCE", are localized for all locales.

indian

The Indian national calendar has had official status in India since 1957 CE (or 1879 Saka Era). The year starts on March 22nd relative to the Gregorian (21st in leap years). The first month has thirty days (thirty one in leap years) and is followed by five months of thirty one days and six of thirty. Years are measured from 78 CE which is formatted as "0 Saka Era". Years prior to this are negative values. Leap years are synchronized with Gregorian leap years. The month names and era name are only localized for Hindi.

iso8601

ISO 8601 is a standard for formatting dates and times to make them more amenable to machine processing: lexicographical order is the same as chronological order and fixed width fields and invariant field separators means that the date value can be read unambiguously from its textual representation. Selection of this calendar imposes a number of restrictions: the date separator is always '-', ignoring the value for the locale, and the time separator is always ':'. Field values are padded with zeroes to make them the correct width, overriding values specified in the options hash. Likewise the numbering system is always "latn" no matter the default for the locale or the system requested through the "-u-" extension. 1 BCE is formatted as "+0000" and years prior to this are negative values. Era names are not used. Time is always in 24-hour format and the first day of the week is Monday.

japanese

The Japanese calendar reckons years according to the reign of the Emperor with the era named after him. An era ends when an emperor dies and a new one begins. This means that a single year can span two eras: 7th Jan 1989 was in the year 64 Shōwa while the next day was 1 Heisei. Eras are as follows:

NameKanjiStartEnd
Meiji明治8 Sep 186830 Jul 1912
Taishō大正31 Jul 191225 Dec 1926
Shōwa昭和26 Dec 19267 Jan 1989
Heisei平成8 Jan 1989-

This calendar was only established in its modern form during the Meiji era, so this implementation does not use eras prior to that. Dates in 1868 prior 8 Sep are formatted as "-1 Meiji". Years prior to 1868 become "-2 Meiji" and so on. This is not correct usage so use the Gregorian calendar for dates before 1868. When formatting dates using this calendar, the era name is always included. Besides the year numbering this calendar is identical to the Gregorian.

persian

The Persian or Solar Hijri calendar is the official calendar of Iran. Years are measured from 622 CE which is formatted as "0 AP". It is an observational calendar with the year beginning on the day on which the vernal equinox occurs at or after solar noon at the longitude of Tehran. The year consists of six months of thirty one days, five of thirty and one of twenty nine (thirty in leap years). Since the calendar is observational, it is completely accurate: a leap year occurs when the period between equinoxes is 366 days. It is the default calendar in the "fa" (Iran) locale

The Persian new year falls approximately on 21st March relative to the Gregorian calendar. However, since leap years are not synchronized with Gregorian leap years this shifts from year to year. I've not been able to figure out the algorithm for this so the calendar implementation uses lookup tables for the years 1900 - 2100 inclusive. Dates outside this period are liable to be one or two days out.

roc

The Republic of China (R.O.C.) calendar measures years from the foundation of the republic, 1911 CE. The era name is always included in date strings. Otherwise, it behaves identically to the Gregorian calendar, including the fomatting of years <= 0. The era names are only localized for the "zh-Hant-TW" (Taiwan) locale. Use of this calendar in the mainland Chinese locale might be something of a faux pas!

Options

The options hash passed to the constructor may contain combinations of various named values that control the formatting of date and time values. The options and their permissible values are detailed below:

Option name Values Notes
weekday long, short, narrow "narrow" is treated the same as "short". Use the cal method if you need abbreviated weekday names for a calendar widget.
year numeric, 2-digit "numeric" results in an unpadded value unless you are using the "iso8601" calendar which pads year values to four digits.
month long, short, narrow, numeric, 2-digit "narrow" is treated the same as "short".
day numeric, 2-digit -
hour numeric, 2-digit -
minute numeric, 2-digit -
second numeric, 2-digit -
era long, short, narrow All values are treated as "short". This results in values of the form "AD". Era names in date strings are always output when using the "roc" and "japanese" calendars.
timeZoneName long, short Both values are treated as "long", which looks like "GMT+XXXX". When parsing a string, the resulting Date object will be in UTC.
timeZone "UTC", any The "UTC" timezone results in strings being formatted using UTC values rather than local time values. Parsed dates will be in UTC as well. Other values are ignored.
hour12 true, false Whether time values are output in 12-hour (AM/PM) format.

Any values outside the permissible range will result in RangeError exceptions as per-spec. The options parameter to the constructor is optional; if not supplied the effect will be the same as { year : "numeric", month : "numeric", day : "numeric" }.

The internationalization API allows for an additional two parameters: localeMatcher and formatMatcher. These are ignored - the algorithms used are "lookup" and "best fit" respectively.

Every locale has a defined value for the hour12 option. For example, 12-hour time is the default in the "en" (US) locale, but not in the "ru" (Russia) one. Before overriding the locale default, note that 12-hour time is not applicable in many locales and the values for "AM" and "PM" are not localized. Setting hour12 to false may be more appropriate as 24-hour time is comprehensible everywhere.

Any option combination will result in something but that something will only be sensible when the combination is sensible. The following date combinations are sensible:

As are the following time combinations:

A sensible date combination may be combined with a sensible time combination to format dates and times together. The timeZoneName option is only used when outputting times and the era option is only used when outputting dates. They are orthogonal to the other options: the era code is added to the date format string after the format string has been determined using a locale-aware pattern. The format code for the timezone is added to the time format string in a similar manner.

Format codes

The options hash is resolved internally into a format string and you can pass format strings diectly to the format and parse methods to avoid the hassle of constructing new Format objects. These format strings use familiar strftime format codes with three additions. See strftime for more information.

CodePurposeExample
%a Abbreviated weekday name Thu
%A Full weekday name Thursday
%b Abbreviated month name Aug
%B Full month name August
%c "Preferred" date and time representation for the locale; tends to be quite Unix-y Thu Aug 23 14:55:02 2001
%C The century number: full year / 100 20
%d The day of the month, 0-padded 03 or 23
%D Fixed date; equivalent to "%m/%d/%y"; waste of a code 08/23/01
%e The day of the month, unpadded 3 or 23
%E Used to create an alternate representation for certain codes. This does nothing - specify a calendar extension in the locale value instead. -
%F ISO-8601 date format; equivalent to %Y-%m-%d 2001-08-23
%g Same as %y, except: if %V would evaluate to week 53 of the previous year, the previous year is output. So not at all confusing -
%G As %g except a four-digit year is emitted -
%h Exactly the same as %b; another waste of a code -
%H The hour using the 24-hour clock, 0-padded 00 or 23
%I The hour using the 12-hour clock, 0-padded 01 or 12
%j The day of the year (0 - 366) -
%k The hour using the 24-hour clock, unpadded 0 or 23
%l The hour using the 12-hour clock, unpadded 1 or 12
%m The month as a decimal number with a leading zero 08 or 12
%M Minutes as a two-digit number 55
%n Newline character, \n -
%N The month as an unpadded decimal number; UsefulJS extension 8 or 12
%O Used as a modifier on numeric fields, values are output in locale digits rather than 0-9. This modifier will be applied to numeric fields automatically if the numbering system is something other than "latn". %OY: ٢٠١٣
%p The meridiem code in uppercase; irrelevant in many locales while others have more than two possible values AM or PM
%P The meridiem code in lowercase; why %p and %P aren't the other way round is beyond me am or pm
%r Time in am/pm notation, formatted for the locale; identical to %X in locales that do not use the 12-hour clock 01:30:45 PM
%R Equivalent to %H:%M; not adjusted for the locale 14:55
%s The number of seconds since the epoch -
%S Seconds as a two-digit number 02
%t Tab character, \t -
%T Equivalent to %H:%M:%S; not adjusted for the locale 14:55:02
%u The day of the week with Monday as 1 and Sunday as 7 4
%U The week number of the year (00-53), where the first Sunday of January is the first day of week 1; days prior to this are in week 0 -
%V The ISO 8601 week number of the year (01-53), where week 1 is the first week that has four days in the year; dates before this are considered to lie in week 53 of the PREVIOUS year -
%w The day of the week with Sunday as 0 and Saturday as 6 4
%W The week number of the year (00-53), where the first Monday of January is the first day of week 1; days prior to this are in week 0 -
%x The "preferred" date representation for the locale; usually has leading zero on the date value which makes it suitable for fixed layouts 23/08/2001
%X The "preferred" time representation for the locale; usually 24-hour clock with leading zero on the hour value which makes it suitable for fixed layouts 14:55:02
%y Two-digit year 01
%Y Four-digit year 2001
%z The hour and minute offset from UTC +0100
%Z The timezone code; not available on every system and even then it needs to be extracted from the toString representation; not recommended BST
%% A literal '%' character -
%/ Fractional seconds (ms) from 000-999; UsefulJS extension -
%! The era name. UsefulJS extension. AD

Codes that produce unpadded values - %e, %k and %l - are space-padded according to the POSIX spec. However, space-padding is not terribly useful when outputting values for the web. The "numeric" month option requires the month value to be unpadded; the %N code has been added to make this possible.

The %Z code will not result in any output on some systems and this, depending on the format string, may cause the date string to be unparseable. Therefore, its use is not recommended. If you are formatting dates in UTC, the timezone code is always set to "UTC".

The %U, %W and %V codes produce week number values relative to Jan 1st which is incorrect for the indian and persian calendars. The %g, %G or %V codes are intended for ISO-8601 date strings.

UsefulJS.Date.Format instance methods

format

Formats a Date object using either the internal format string or a specified format string.

Syntax
format(date[, fmtStr])
Parameters

Returns: String. The formatted date string

Throws: TypeError: input is not a Date object; Error: unrecognized format code.

Description

Many of the available format codes are never present in the internally generated format string, but you can use them by supplying your own format string. Any character data in the format string that isn't a format code appears in the output string; this allows you to insert punctuation and sensible whitespace (and anything else you want for that matter).

Usage
// Create a format object for the default locale that uses the default format string
var fmt1 = new UsefulJS.Date.Format();
// An object for outputting ISO-8601 strings
var fmt_iso = new UsefulJS.Date.Format("en-u-ca-iso8601");
// An object for outputting dates in Japanese imperial format
var fnt_jp = new UsefulJS.Date.Format("ja-u-nu-fullwide-ca-japanese",
    { year : "numeric", month : "short", day : "numeric" });
// Output some strings
var d = new Date();
var dateStr_us = fmt1.format(d);                // 11/17/2013, assuming "en" is the current locale
var isoDateStr = fmt_iso.format(d);             // 2013-11-17
// Our application needs ISO week values
var isoWeekStr = fmt_iso.format(d, "%G-%V-%u"); // 2013-46-7
// ...And, for some reason, Japanese values
var impDateStr = fmt_jp.format(d);              // 平成25年11月17日

parse

Parses a string produced by format back into a Date object.

Syntax
parse(dateStr[, fmtStr])
Parameters

Returns: Date. The parsed Date object.

Throws: TypeError if given something that isn't a non-empty string to parse; Error if an unrecognized format code is encountered; Error if the date string contains values that cannot be matched against the format string (such as non-numeric data for a numeric field); RangeError if any values are outside the valid range (such as "%j" having a value greater than 366); Error if the format string is not entirely processed (on the assumption that the input is lacking required information); it is not an error if the input string is not entirely consumed.

Description

The parse method follows strptime rules: case is ignored, whitespace is insignificant (but not other character data), and field widths, if applicable, are ignored. Two digit years are disambiguated by the following rule: if, after calendar correction, the value is < 30, the year is 20xx, otherwise it is 19xx.

Usage
// Assuming the same objects as above, all will produce 17 Nov, 2013
var d1 = fmt1.parse("11/17/2013"),
    d2 = fmt_iso.parse("2013-11-17"),
    d3 = fmt_iso.parse("2013-46-7", "%G-%V-%u"),
    d4 = fmt_jp.parse("平成25年11月17日");
Entirely numeric date strings will be unparseable when using most calendars. For example, the format string "%Y%N%e" will result in a date string that cannot be interpreted correctly. ISO-8601 defines fixed widths and allows for all-numeric strings. Using the iso8601 calendar, "2013321" can be parsed unambiguously with the format strings "%G%V%u" and "%Y%j".
As per-spec, the format and parse methods are bound to their instances. What this means is that you can use a method pointer where a function pointer is expected (e.g., setTimeout or Array iteration methods) and everything just works.

cal

Creates a calendar for the given month and year

Syntax
cal([dLocal[, firstDay]])
Parameters

Returns: Array. Calendar data for the month.

Description

Returns a two dimensional array suitable for use in a calendar widget or similar. Format of the data is similar to the Unix `cal` command: the first row contains seven abbreviated day names followed by four to six rows of month days. Empty entries are represented by non-breaking spaces, \u00a0.

Usage
// Generate calendar data for a year for the current locale
var fmt = new UsefulJS.Date.Format(null, { year : "numeric", month : "long" }),
    cal = fmt.getCalendar(),
    // This year in calendar time
    y = cal.today().yy;
cal.months().forEach(function(n, idx) {
    // Calendar time to internal time
    var dLocal = { yy : y, mm : idx, dd : 1 },
        dInternal = UsefulJS.clone(dLocal);
    cal.fromCalendarDate(dInternal);
    // Generate a calendar header
    var d = new Date(dInternal.yy, dInternal.mm, dInternal.dd),
        headerStr = fmt.format(d),
        // Month data
        monthData = fmt.cal(dLocal);
    // Populate the calendar widget
    ...
});

getCalendar

Gets the calendar object used for formatting and parsing dates.

Syntax
getCalendar()
Description

See above for possible usage. See below for methods of calendar objects.

resolvedOptions

Gets an Object showing what the requested options resolved to. The locale property is useful in that it gives the actual locale being used (unlike supportedLocalesOf). See the Intl.DateTimeFormat documentation for more information.

Syntax
resolvedOptions()

supportedLocalesOf

Part of the internationalization API and not terribly useful. Takes an array of locale codes and returns the locales that are "supported" in the sense that they do not resolve to the default locale. For example, "ru-UK" (Ukrainian Russian) would return as "supported", even though the locale that is actually supported is "ru", i.e. Russian Russian. See the Intl documentation for more information.

Syntax
supportedLocalesOf(locales[, options])

Methods of the calendar object

The getCalendar method of the Format object returns an object that exposes the following methods:

isLeapYear

Returns true when a given year in a given era is a leap year.

Syntax
isLeapYear(year, eraNo)
Description

The gregory calendar has two eras, numbered 0 and 1, signifying CE and BCE respectively. The roc calendar is similar. The japanese calendar has (so far) four eras. The other calendars have just the one.

isLeapDay

Returns true when a given date in a given month is the extra day in a leap year.

Syntax
isLeapDay(month, date)
Description

For the gregory calendar and others similar to it, the combination 1,29 will return true. For the indian calendar, this will be 0,31 while the persian calendar uses 11,30.

months

Returns an array of the day counts for the months in the year.

Syntax
months(year, eraNo)
Description

See above for the significance of the era number. If the year is a leap year, the return value will have the correct number of days in the relevant month.

dateToCalendarDate

Turns a Date object into broken-down time representing the date in the calendar (which may well be quite unlike the internal date).

Syntax
dateToCalendarDate(date, utc)
Description

Returns an Object containing the calendar representation of the date in broken down time. Properties are: yy (year), mm (month), dd (date), wd (week day), en (era number), utc (whether the date is UTC) and dateObj (the input Date object). This method is used to pre-process dates for formatting. The utc parameter determines whether UTC methods (such as getUTCFullYear) are used in preference to non-UTC methods (such as getFullYear).

fromCalendarDate

Turns the broken-down calendar date into values in internal date representation.

Syntax
fromCalendarDate(dLocal)
Description

The input object has the same properties as above. The method does not return a value but instead modifies its argument in-place. It is used to post-process parsed date strings.

Interoperability with Intl.DateTimeFormat

The UsefulJS.Date.Format API is 100% compatible with Intl.DateTimeFormat (and one of the module fixes can add Intl.DateTimeFormat to the global namespace), but it does not follow that the date strings will be identical which means that there is no guarantee that you can take a date string created using a native Intl implementation and parse it using UsefulJS.Date.Format. The reason for this is twofold. The generation of the format string is algorithmic and I am not privy to the algorithm used by, say, Chrome and that algorithm will not be the same as that used by Internet Explorer 11. The other is that even given the same format string (assuming other implementations use strftime internally), we still may not produce the same date string. What constitutes the "correct" format for a given locale is a question without a definitive answer. I honestly have no idea how to correctly format date and time values in, say, Vietnamese so rely on another source for this information, assuming that they have 1) employed a local expert and 2) done their homework (in the case of the Microsoft style guides I used both are reasonable assumptions). A different implementation may have used a different source giving different guidance, resulting in different month abbreviations or commas where I have none or no comma where I add a comma, etc. The parse method is downright persnickety as to what it will accept - the input has to match the format string exactly. So here is my suggestion: if your application needs to parse date and time values, use the UsefulJS namespace. Otherwise use the Intl one (the module fix will ensure that this works in all browsers).

What about Date.prototype.toLocaleFormat?

The Date implementation in Firefox has a method, toLocaleFormat, not present in other implementations. I'm guessing that this is a remnant of a proposed standard that never quite got off the ground. It accepts a strftime format string and gives you back the date and time formatted accordingly. While it gives you some control over the output, unlike toLocaleString, it has the same problem as other toLocale* methods: the locale is baked in at compile time so that you cannot use it to generate localized date strings, unless the baked-in locale coincides with that of your application. In addition, there is no facility to obtain sensible format strings for different locales. Therefore its use, even when available, is not recommended.

Julian day conversion

The Julian day value is a time measurement used by astronomers and is the number of days (including fractional part) since the beginning of the Julian Period (fixed at noon, 1st January 4713 BCE according to the Julian calendar). Note that the day begins at noon, not midnight. The Julian day value for the Unix epoch is 2,440,587.5.

toJulianDay

Converts a Date object to Julian day value

Syntax
toJulianDay(d)
Parameters

Returns Number: the Julian day value.

Throws TypeError: if d is not a Date object.

Usage
var gEpoch = Date.parse("1582-10-15T00:00:00Z"),      // Start of the Gregorian calendar
    jd = UsefulJS.Date.toJulianDay(new Date(gEpoch)); // 2299160.5

fromJulianDay

Converts a Julian day value to a Date object.

Syntax
fromJulianDay(jd)
Parameters

Returns Date: a native Date object.

Throws TypeError: if jd is not a finite number.

Usage
var jd = 2299160.5,
    gEpoch = UsefulJS.Date.fromJulianDay(jd); // "Fri Oct 15 1582 01:00:00 GMT+0100 (BST)"

Additional UsefulJS.Date properties and methods

Besides the Format class, UsefulJS.Date has a number of additional properties and methods.

UsefulJS.featureSupport.date

Adds a date entry to UsefulJS.featureSupport:

formats

Format strings for standardized date and time formats: iso8601 (ISO date-time string), iso8601_ext (ISO date-time string plus milliseconds), isoweek (year number, week number and day-of-the-week number), isoordinal (year and day-of-the-year) and rfc2822 (as used in the Date header in emails).

getFormatString

Turns format options into a format string

Syntax
getFormatString(opts, resolved)
Description

This is actually the internal function used to generate a default format string when constructing a Format object. It is exposed as a public method on the assumption that you might want to play with the format strings rather than having the process remain completely opaque. As such, the API is not the most intuitive - it is designed to be useful to me rather than to you! See the usage for details on how to call this.

Usage
// Get an internal format string
var fmtOpts = { weekday : "long", year : "numeric", month : "long", day : "numeric", era : "short", 
    hour : "numeric", minute : "2-digit", second : "2-digit", timeZoneName : "long", hour12 : true };
// What will this produce in the Finnish locale?
var dateOpts = UsefulJS.Locale.dateOptions("fi", fmtOpts);
// Get the format string, ignore resolved options
vat fmtStr = UsefulJS.Date.getFormatString(dateOpts, {});    // "%Ana %e. %Bta %Y %! %l.%M.%S %p UTC%z"

UTC

Given broken-down time, returns milliseconds since the epoch, UTC.

Syntax
Date.UTC(year,month[,date[,hrs[,min[,sec[,ms]]]]])
Description

This static method should be identical to Date.UTC. However, at least one implementation (Konqueror) has a bug where the local time difference is not taken into account so that the value returned is milliseconds since the epoch, local time, not UTC. The Date module detects this bug and creates an alternate UTC method that subtracts the local time difference from the value returned by Date.UTC. This is necessary to parse date strings correctly in UTC. However, if this bug is present other Date UTC methods (such as getUTCHours) must be considered untrustworthy:

if (UsefulJS.Date.UTC !== Date.UTC) {
    // UTC is broken
}

Fixes

The fixes for the Date module are in the _date namespace of the fix options.

now

Adds a now static method to the Date class when Date.now is not implemented natively. This fix is applied by default.

toISOString

Adds a toISOString method to Date.prototype when this is not implemented natively. The locale option is "en-u-ca-iso8601" and the format string used is formats.iso8601_ext. This fix is applied by default.

toRFC822String

Adds a toRFC822String method to Date.prototype. The locale option is "en" and the format string used is formats.rfc2822. More accurately, the resulting date-and-time string is RFC2822 compliant since RFC822 mandated a 2-digit year. As a library extension, this fix must be explicitly enabled.

The string values produced by toISOString and toRFC822String should be parseable by Date.parse.

intl_DateTimeFormat

Makes the UsefulJS.Date.Format class available through the Intl.DateTimeFormat namespace. This allows for completely portable, locale-aware date and time formatting that runs in pretty much any browser, using the native object when available. This fix is applied by default.