mirror of
https://github.com/samsonjs/strftime.git
synced 2026-03-25 09:05:48 +00:00
571 lines
20 KiB
JavaScript
571 lines
20 KiB
JavaScript
//
|
|
// strftime
|
|
// github.com/samsonjs/strftime
|
|
// @_sjs
|
|
//
|
|
// Copyright 2010 - 2015 Sami Samhuri <sami@samhuri.net>
|
|
//
|
|
// MIT License
|
|
// http://sjs.mit-license.org
|
|
//
|
|
|
|
;(function() {
|
|
|
|
//// Where to export the API
|
|
var namespace,
|
|
DefaultLocale = {
|
|
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
|
shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
|
shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
|
AM: 'AM',
|
|
PM: 'PM',
|
|
am: 'am',
|
|
pm: 'pm',
|
|
formats: {
|
|
D: '%m/%d/%y',
|
|
F: '%Y-%m-%d',
|
|
R: '%H:%M',
|
|
T: '%H:%M:%S',
|
|
X: '%T',
|
|
c: '%a %b %d %X %Y',
|
|
r: '%I:%M:%S %p',
|
|
v: '%e-%b-%Y',
|
|
x: '%D'
|
|
}
|
|
},
|
|
|
|
defaultStrftime = new Strftime(DefaultLocale, 0, false);
|
|
|
|
// CommonJS / Node module
|
|
if (typeof module !== 'undefined') {
|
|
namespace = module.exports = defaultStrftime;
|
|
}
|
|
|
|
// Browsers and other environments
|
|
else {
|
|
// Get the global object. Works in ES3, ES5, and ES5 strict mode.
|
|
namespace = (function() { return this || (1,eval)('this'); }());
|
|
}
|
|
|
|
// Deprecated API, to be removed in v1.0
|
|
var _deprecationWarnings = {};
|
|
function deprecationWarning(name, instead) {
|
|
if (!_deprecationWarnings[name]) {
|
|
console.warn("[WARNING] `require('strftime')." + name + "` is deprecated and will be removed in version 1.0. Instead, use `" + instead + "`.");
|
|
_deprecationWarnings[name] = true;
|
|
}
|
|
}
|
|
|
|
namespace.strftime = function(fmt, d, locale) {
|
|
deprecationWarning("strftime", "require('strftime')(format, date)` or `require('strftime').localize(locale)(format, date)");
|
|
var strftime = locale ? defaultStrftime.localize(locale) : defaultStrftime;
|
|
return strftime(fmt, d);
|
|
};
|
|
|
|
namespace.strftimeTZ = function(fmt, d, locale, timezone) {
|
|
deprecationWarning("strftimeTZ", "require('strftime').timezone(tz)(format, date)` or `require('strftime').timezone(tz).localize(locale)(format, date)");
|
|
if ((typeof locale == 'number' || typeof locale == 'string') && timezone == null) {
|
|
timezone = locale;
|
|
locale = undefined;
|
|
}
|
|
var strftime = (locale ? defaultStrftime.localize(locale) : defaultStrftime).timezone(timezone);
|
|
return strftime(fmt, d);
|
|
};
|
|
|
|
namespace.strftimeUTC = function(fmt, d, locale) {
|
|
deprecationWarning("strftimeUTC", "require('strftime').utc()(format, date)` or `require('strftime').localize(locale).utc()(format, date)");
|
|
var strftime = (locale ? defaultStrftime.localize(locale) : defaultStrftime).utc();
|
|
return strftime(fmt, d);
|
|
};
|
|
|
|
namespace.localizedStrftime = function(locale) {
|
|
deprecationWarning("localizedStrftime", "require('strftime').localize(locale)");
|
|
return defaultStrftime.localize(locale);
|
|
};
|
|
// End of deprecated API
|
|
|
|
// Polyfill Date.now for old browsers.
|
|
if (typeof Date.now !== 'function') {
|
|
Date.now = function() {
|
|
return +new Date();
|
|
};
|
|
}
|
|
|
|
function Strftime(locale, customTimezoneOffset, useUtcTimezone) {
|
|
var _locale = locale || DefaultLocale,
|
|
_customTimezoneOffset = customTimezoneOffset || 0,
|
|
_useUtcBasedDate = useUtcTimezone || false,
|
|
|
|
// we store unix timestamp value here to not create new Date() each iteration (each millisecond)
|
|
// Date.now() is 2 times faster than new Date()
|
|
// while millisecond precise is enough here
|
|
// this could be very helpful when strftime triggered a lot of times one by one
|
|
_cachedDateTimestamp = 0,
|
|
_cachedDate;
|
|
|
|
function _strftime(format, date) {
|
|
var timestamp;
|
|
|
|
if (!date) {
|
|
var currentTimestamp = Date.now();
|
|
if (currentTimestamp > _cachedDateTimestamp) {
|
|
_cachedDateTimestamp = currentTimestamp;
|
|
_cachedDate = new Date(_cachedDateTimestamp);
|
|
|
|
timestamp = _cachedDateTimestamp;
|
|
|
|
if (_useUtcBasedDate) {
|
|
// how to avoid duplication of date instantiation for utc here?
|
|
// we tied to getTimezoneOffset of the current date
|
|
_cachedDate = new Date(_cachedDateTimestamp + getTimestampToUtcOffsetFor(_cachedDate) + _customTimezoneOffset);
|
|
}
|
|
}
|
|
date = _cachedDate;
|
|
}
|
|
else {
|
|
timestamp = date.getTime();
|
|
|
|
if (_useUtcBasedDate) {
|
|
date = new Date(date.getTime() + getTimestampToUtcOffsetFor(date) + _customTimezoneOffset);
|
|
}
|
|
}
|
|
|
|
return _processFormat(format, date, _locale, timestamp);
|
|
}
|
|
|
|
function _processFormat(format, date, locale, timestamp) {
|
|
var resultString = '',
|
|
padding = null,
|
|
isInScope = false,
|
|
length = format.length,
|
|
extendedTZ = false;
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
|
|
var currentCharCode = format.charCodeAt(i);
|
|
|
|
if (isInScope === true) {
|
|
// '-'
|
|
if (currentCharCode === 45) {
|
|
padding = '';
|
|
continue;
|
|
}
|
|
// '_'
|
|
else if (currentCharCode === 95) {
|
|
padding = ' ';
|
|
continue;
|
|
}
|
|
// '0'
|
|
else if (currentCharCode === 48) {
|
|
padding = '0';
|
|
continue;
|
|
}
|
|
// ':'
|
|
else if (currentCharCode === 58) {
|
|
extendedTZ = true;
|
|
continue;
|
|
}
|
|
|
|
switch (currentCharCode) {
|
|
|
|
// Examples for new Date(0) in GMT
|
|
|
|
// 'Thursday'
|
|
// case 'A':
|
|
case 65:
|
|
resultString += locale.days[date.getDay()];
|
|
break;
|
|
|
|
// 'January'
|
|
// case 'B':
|
|
case 66:
|
|
resultString += locale.months[date.getMonth()];
|
|
break;
|
|
|
|
// '19'
|
|
// case 'C':
|
|
case 67:
|
|
resultString += padTill2(Math.floor(date.getFullYear() / 100), padding);
|
|
break;
|
|
|
|
// '01/01/70'
|
|
// case 'D':
|
|
case 68:
|
|
resultString += _processFormat(locale.formats.D, date, locale, timestamp);
|
|
break;
|
|
|
|
// '1970-01-01'
|
|
// case 'F':
|
|
case 70:
|
|
resultString += _processFormat(locale.formats.F, date, locale, timestamp);
|
|
break;
|
|
|
|
// '00'
|
|
// case 'H':
|
|
case 72:
|
|
resultString += padTill2(date.getHours(), padding);
|
|
break;
|
|
|
|
// '12'
|
|
// case 'I':
|
|
case 73:
|
|
resultString += padTill2(hours12(date.getHours()), padding);
|
|
break;
|
|
|
|
// '000'
|
|
// case 'L':
|
|
case 76:
|
|
resultString += padTill3(Math.floor(timestamp % 1000));
|
|
break;
|
|
|
|
// '00'
|
|
// case 'M':
|
|
case 77:
|
|
resultString += padTill2(date.getMinutes(), padding);
|
|
break;
|
|
|
|
// 'am'
|
|
// case 'P':
|
|
case 80:
|
|
resultString += date.getHours() < 12 ? locale.am : locale.pm;
|
|
break;
|
|
|
|
// '00:00'
|
|
// case 'R':
|
|
case 82:
|
|
resultString += _processFormat(locale.formats.R, date, locale, timestamp);
|
|
break;
|
|
|
|
// '00'
|
|
// case 'S':
|
|
case 83:
|
|
resultString += padTill2(date.getSeconds(), padding);
|
|
break;
|
|
|
|
// '00:00:00'
|
|
// case 'T':
|
|
case 84:
|
|
resultString += _processFormat(locale.formats.T, date, locale, timestamp);
|
|
break;
|
|
|
|
// '00'
|
|
// case 'U':
|
|
case 85:
|
|
resultString += padTill2(weekNumber(date, 'sunday'), padding);
|
|
break;
|
|
|
|
// '00'
|
|
// case 'W':
|
|
case 87:
|
|
resultString += padTill2(weekNumber(date, 'monday'), padding);
|
|
break;
|
|
|
|
// '16:00:00'
|
|
// case 'X':
|
|
case 88:
|
|
resultString += _processFormat(locale.formats.X, date, locale, timestamp);
|
|
break;
|
|
|
|
// '1970'
|
|
// case 'Y':
|
|
case 89:
|
|
resultString += date.getFullYear();
|
|
break;
|
|
|
|
// 'GMT'
|
|
// case 'Z':
|
|
case 90:
|
|
if (_useUtcBasedDate && _customTimezoneOffset === 0) {
|
|
resultString += "GMT";
|
|
}
|
|
else {
|
|
// fixme optimize
|
|
var tzString = date.toString().match(/\((\w+)\)/);
|
|
resultString += tzString && tzString[1] || '';
|
|
}
|
|
break;
|
|
|
|
// 'Thu'
|
|
// case 'a':
|
|
case 97:
|
|
resultString += locale.shortDays[date.getDay()];
|
|
break;
|
|
|
|
// 'Jan'
|
|
// case 'b':
|
|
case 98:
|
|
resultString += locale.shortMonths[date.getMonth()];
|
|
break;
|
|
|
|
// ''
|
|
// case 'c':
|
|
case 99:
|
|
resultString += _processFormat(locale.formats.c, date, locale, timestamp);
|
|
break;
|
|
|
|
// '01'
|
|
// case 'd':
|
|
case 100:
|
|
resultString += padTill2(date.getDate(), padding);
|
|
break;
|
|
|
|
// ' 1'
|
|
// case 'e':
|
|
case 101:
|
|
resultString += padTill2(date.getDate(), padding == null ? ' ' : padding);
|
|
break;
|
|
|
|
// 'Jan'
|
|
// case 'h':
|
|
case 104:
|
|
resultString += locale.shortMonths[date.getMonth()];
|
|
break;
|
|
|
|
// '000'
|
|
// case 'j':
|
|
case 106:
|
|
var y = new Date(date.getFullYear(), 0, 1);
|
|
var day = Math.ceil((date.getTime() - y.getTime()) / (1000 * 60 * 60 * 24));
|
|
resultString += padTill3(day);
|
|
break;
|
|
|
|
// ' 0'
|
|
// case 'k':
|
|
case 107:
|
|
resultString += padTill2(date.getHours(), padding == null ? ' ' : padding);
|
|
break;
|
|
|
|
// '12'
|
|
// case 'l':
|
|
case 108:
|
|
resultString += padTill2(hours12(date.getHours()), padding == null ? ' ' : padding);
|
|
break;
|
|
|
|
// '01'
|
|
// case 'm':
|
|
case 109:
|
|
resultString += padTill2(date.getMonth() + 1, padding);
|
|
break;
|
|
|
|
// '\n'
|
|
// case 'n':
|
|
case 110:
|
|
resultString += '\n';
|
|
break;
|
|
|
|
// '1st'
|
|
// case 'o':
|
|
case 111:
|
|
resultString += String(date.getDate()) + ordinal(date.getDate());
|
|
break;
|
|
|
|
// 'AM'
|
|
// case 'p':
|
|
case 112:
|
|
resultString += date.getHours() < 12 ? locale.AM : locale.PM;
|
|
break;
|
|
|
|
// '12:00:00 AM'
|
|
// case 'r':
|
|
case 114:
|
|
resultString += _processFormat(locale.formats.r, date, locale, timestamp);
|
|
break;
|
|
|
|
// '0'
|
|
// case 's':
|
|
case 115:
|
|
resultString += Math.floor(timestamp / 1000);
|
|
break;
|
|
|
|
// '\t'
|
|
// case 't':
|
|
case 116:
|
|
resultString += '\t';
|
|
break;
|
|
|
|
// '4'
|
|
// case 'u':
|
|
case 117:
|
|
var day = date.getDay();
|
|
resultString += day === 0 ? 7 : day;
|
|
break; // 1 - 7, Monday is first day of the week
|
|
|
|
// ' 1-Jan-1970'
|
|
// case 'v':
|
|
case 118:
|
|
resultString += _processFormat(locale.formats.v, date, locale, timestamp);
|
|
break;
|
|
|
|
// '4'
|
|
// case 'w':
|
|
case 119:
|
|
resultString += date.getDay();
|
|
break; // 0 - 6, Sunday is first day of the week
|
|
|
|
// '12/31/69'
|
|
// case 'x':
|
|
case 120:
|
|
resultString += _processFormat(locale.formats.x, date, locale, timestamp);
|
|
break;
|
|
|
|
// '70'
|
|
// case 'y':
|
|
case 121:
|
|
resultString += ('' + date.getFullYear()).slice(2);
|
|
break;
|
|
|
|
// '+0000'
|
|
// case 'z':
|
|
case 122:
|
|
if (_useUtcBasedDate && _customTimezoneOffset === 0) {
|
|
resultString += extendedTZ ? "+00:00" : "+0000";
|
|
}
|
|
else {
|
|
var off;
|
|
if (_customTimezoneOffset !== 0) {
|
|
off = _customTimezoneOffset / (60 * 1000);
|
|
}
|
|
else {
|
|
off = -date.getTimezoneOffset();
|
|
}
|
|
var sign = off < 0 ? '-' : '+';
|
|
var sep = extendedTZ ? ':' : '';
|
|
var hours = Math.floor(Math.abs(off / 60));
|
|
var mins = Math.abs(off % 60);
|
|
resultString += sign + padTill2(hours) + sep + padTill2(mins);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
resultString += format[i];
|
|
break;
|
|
}
|
|
|
|
padding = null;
|
|
isInScope = false;
|
|
continue;
|
|
}
|
|
|
|
// '%'
|
|
if (currentCharCode === 37) {
|
|
isInScope = true;
|
|
continue;
|
|
}
|
|
|
|
resultString += format[i];
|
|
}
|
|
|
|
return resultString;
|
|
}
|
|
|
|
var strftime = _strftime;
|
|
|
|
strftime.localize = function(locale) {
|
|
return new Strftime(locale || _locale, _customTimezoneOffset, _useUtcBasedDate);
|
|
};
|
|
|
|
strftime.timezone = function(timezone) {
|
|
var customTimezoneOffset = _customTimezoneOffset;
|
|
var useUtcBasedDate = _useUtcBasedDate;
|
|
|
|
var timezoneType = typeof timezone;
|
|
if (timezoneType === 'number' || timezoneType === 'string') {
|
|
useUtcBasedDate = true;
|
|
|
|
// ISO 8601 format timezone string, [-+]HHMM
|
|
if (timezoneType === 'string') {
|
|
var sign = timezone[0] === '-' ? -1 : 1,
|
|
hours = parseInt(timezone.slice(1, 3), 10),
|
|
minutes = parseInt(timezone.slice(3, 5), 10);
|
|
|
|
customTimezoneOffset = sign * ((60 * hours) + minutes) * 60 * 1000;
|
|
// in minutes: 420
|
|
}
|
|
else if (timezoneType === 'number') {
|
|
customTimezoneOffset = timezone * 60 * 1000;
|
|
}
|
|
}
|
|
|
|
return new Strftime(_locale, customTimezoneOffset, useUtcBasedDate);
|
|
};
|
|
|
|
strftime.utc = function() {
|
|
return new Strftime(_locale, _customTimezoneOffset, true);
|
|
};
|
|
|
|
return strftime;
|
|
}
|
|
|
|
function padTill2(numberToPad, paddingChar) {
|
|
if (paddingChar === '' || numberToPad > 9) {
|
|
return numberToPad;
|
|
}
|
|
if (paddingChar == null) {
|
|
paddingChar = '0';
|
|
}
|
|
return paddingChar + numberToPad;
|
|
}
|
|
|
|
function padTill3(numberToPad) {
|
|
if (numberToPad > 99) {
|
|
return numberToPad;
|
|
}
|
|
if (numberToPad > 9) {
|
|
return '0' + numberToPad;
|
|
}
|
|
return '00' + numberToPad;
|
|
}
|
|
|
|
function hours12(hour) {
|
|
if (hour === 0) {
|
|
return 12;
|
|
}
|
|
else if (hour > 12) {
|
|
return hour - 12;
|
|
}
|
|
return hour;
|
|
}
|
|
|
|
// firstWeekday: 'sunday' or 'monday', default is 'sunday'
|
|
//
|
|
// Pilfered & ported from Ruby's strftime implementation.
|
|
function weekNumber(date, firstWeekday) {
|
|
firstWeekday = firstWeekday || 'sunday';
|
|
|
|
// This works by shifting the weekday back by one day if we
|
|
// are treating Monday as the first day of the week.
|
|
var weekday = date.getDay();
|
|
if (firstWeekday === 'monday') {
|
|
if (weekday === 0) // Sunday
|
|
weekday = 6;
|
|
else
|
|
weekday--;
|
|
}
|
|
var firstDayOfYear = new Date(date.getFullYear(), 0, 1),
|
|
yday = (date - firstDayOfYear) / 86400000,
|
|
weekNum = (yday + 7 - weekday) / 7;
|
|
|
|
return Math.floor(weekNum);
|
|
}
|
|
|
|
// Get the ordinal suffix for a number: st, nd, rd, or th
|
|
function ordinal(number) {
|
|
var i = number % 10;
|
|
var ii = number % 100;
|
|
|
|
if ((ii >= 11 && ii <= 13) || i === 0 || i >= 4) {
|
|
return 'th';
|
|
}
|
|
switch (i) {
|
|
case 1: return 'st';
|
|
case 2: return 'nd';
|
|
case 3: return 'rd';
|
|
}
|
|
}
|
|
|
|
function getTimestampToUtcOffsetFor(date) {
|
|
return (date.getTimezoneOffset() || 0) * 60000;
|
|
}
|
|
|
|
}());
|