/**
* Date and time formatting
*
* @module datetimejs.format
*/
import {zeroPad, cycle} from './utils';
import {DEFAULT_CONFIG} from './config';
// Number of milliseconds in a day
export const DAY_MS = 86400000;
/**
* Functions for formatting Date objects
*
* This object maps format tokens to formatting functions. This object is used
* by `strftime()` to perform the formatting. It can also be manipulated to
* modify and/or enhance the way `stftime()` formats date and time.
*
* To add a new token, we first decide on the actual token we want to use, or
* whther we want to override an existing one. Then we assign a format function
* to that key.
*
* The format functions take two arguments, the `Date` object to format, and
* the configuration object.
*
* @example
*
* format.FORMAT_TOKENS['%R'] = function romanYear(dt, conf) {
* // Do something with `dt` and/or `conf` and return a string...
* };
*/
export let FORMAT_TOKENS = {
// Shorthand day-of-week name
'%a': function (dt, conf) {
return conf.DY[dt.getDay()];
},
// Full day-of-week name
'%A': function (dt, conf) {
return conf.DAYS[dt.getDay()];
},
// Shorthand month name
'%b': function (dt, conf) {
return conf.MNTH[dt.getMonth()];
},
// Full month name
'%B': function (dt, conf) {
return conf.MONTHS[dt.getMonth()];
},
// Locale-formatted date and time (dependent on browser/platform/device),
// here added for compatibility reasons.
'%c': function (dt, conf) {
return dt.toLocaleString();
},
// Zero-padded date (day of month)
'%d': function (dt) {
return zeroPad(dt.getDate(), 2);
},
// Non-zero-padded date (day of month)
'%D': function (dt) {
return '' + dt.getDate();
},
// Zero-padded seconds with decimal part
'%f': function (dt) {
let s = dt.getSeconds();
let m = dt.getMilliseconds();
let fs = Math.round((s + m / 1000) * 100) / 100;
return zeroPad(fs, 5, 2);
},
// Zero-padded hour in 24-hour format
'%H': function (dt) {
return zeroPad(dt.getHours(), 2);
},
// * Non-zero-padded hour in 12-hour format
'%i': function (dt) {
return cycle(dt.getHours(), 12);
},
// Zero-padded hour in 12-hour format
'%I': function (dt) {
return zeroPad(cycle(dt.getHours(), 12), 2);
},
// Zero-padded day of year
'%j': function (dt) {
let firstOfYear = new Date(dt.getFullYear(), 0, 1);
return zeroPad(Math.ceil((dt - firstOfYear) / DAY_MS), 3);
},
// Zero-padded numeric month
'%m': function (dt) {
return zeroPad(dt.getMonth() + 1, 2);
},
// Zero-padded minutes
'%M': function (dt) {
return zeroPad(dt.getMinutes(), 2);
},
// Non-zero-padded numeric month
'%n': function (dt) {
return `${dt.getMonth() + 1}`;
},
// Non-zero-padded minutes
'%N': function (dt) {
return '' + dt.getMinutes();
},
// am/pm
'%p': function (dt, conf) {
let h = dt.getHours();
return (0 <= h && h < 12) ? conf.AM : conf.PM;
},
// Non-zero-padded seconds
'%s': function (dt) {
return '' + dt.getSeconds();
},
// Zero-padded seconds
'%S': function (dt) {
return zeroPad(dt.getSeconds(), 2);
},
// Milliseconds
'%r': function (dt) {
return '' + dt.getMilliseconds();
},
// Numeric day of week (0 == Sunday)
'%w': function (dt) {
return '' + dt.getDay();
},
// Last two digits of the year
'%y': function (dt) {
return ('' + dt.getFullYear()).slice(2);
},
// Full year
'%Y': function (dt) {
return '' + dt.getFullYear();
},
// Locale-formatted date (without time)
'%x': function (dt) {
return dt.toLocaleDateString();
},
// Locale-formatted time
'%X': function (dt) {
return dt.toLocaleTimeString();
},
// Timezone in +HHMM or -HHMM format
'%z': function (dt) {
let prefix = dt.getTimezoneOffset() <= 0 ? '+' : '-';
let tz = Math.abs(dt.getTimezoneOffset());
return `${prefix}${zeroPad(Math.floor(tz / 60), 2)}${zeroPad(tz % 60, 2)}`;
},
// Literal percent character
'%%': function (dt) {
return '%';
},
// The following functions are unsupported
'%U': function (dt) {
return '';
},
'%Z': function (dt) {
return '';
},
};
/**
* Format a `Date` object according to a format string
*
* The formatting uses strftime-compatible syntax with follwing tokens:
*
* - `%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 %.
*
* Any characters that are not part of the %-something sequence are used as is
* in the final output.
*
* The third and optional argument is the format configuration object. This
* object is used to customize the human-readable strings used in the output,
* such as month names and similar. Please refer to
* `config.updateDefaultConfig()` function's documentation for information on
* what this object may contain.
*
* @example
*
* let d = new Date(2009, 4, 1, 12, 33);
*
* // US date format
* strftime(d, '%n/%D/%Y'); // '5/1/2019'
*
* // Using non-formatting characters
* strftime(d, 'On %b %D at %i:%M %p'); // 'On May 1 at 12:33 p.m.'
*
* // With 24-hour time
* strftime(d, '%Y-%m-%d %H:%M'); // '2019-05-01 12:33'
*
* // With localized short month names (this configuration is only used for
* // this particular call, and the global defaults are not modified)
* const srMnth = ['јан', 'феб', 'мар', 'апр', 'мај', 'јун', 'јул', 'авг',
* 'сеп', 'окт', 'нов', 'дец'];
* strftime(d, '%D. %b %Y.', {MNTH: srMnth}); // '1. мај 2019.'
*
* @param {Date} dt The `Date` object to format
* @param {string} formatString The format of the output
* @param {object} [config={}] Format configuration
*/
export function strftime(d, formatString, config = {}) {
config = {...DEFAULT_CONFIG, ...config};
// Create a regexp that matches any of the formatting tokens
const formatTokens = Object.keys(FORMAT_TOKENS).join('|') + '|%%';
const formatTokenRe = new RegExp(`(${formatTokens})`, 'g');
// Replace any encountered tokens with a result of calling a matching format
// function
return formatString.replace(formatTokenRe, function (_, token) {
if (token == '%%') {
return '%';
}
if (token in FORMAT_TOKENS) {
return FORMAT_TOKENS[token](d, config);
}
return token;
});
};