/** * @fileOverview Date parsing and formatting operations without extending the Date built-in object. * @author Chris Leonello * @version #VERSION# * @date #DATE# */ (function($) { /** * @description *

Object with extended date parsing and formatting capabilities. * This library borrows many concepts and ideas from the Date Instance * Methods by Ken Snyder along with some parts of Ken's actual code.

* *

jsDate takes a different approach by not extending the built-in * Date Object, improving date parsing, allowing for multiple formatting * syntaxes and multiple and more easily expandable localization.

* * @author Chris Leonello * @date #date# * @version #VERSION# * @copyright (c) 2010 Chris Leonello * jsDate is currently available for use in all personal or commercial projects * under both the MIT and GPL version 2.0 licenses. This means that you can * choose the license that best suits your project and use it accordingly. * *

Ken's origianl Date Instance Methods and copyright notice:

*
     * Ken Snyder (ken d snyder at gmail dot com)
     * 2008-09-10
     * version 2.0.2 (http://kendsnyder.com/sandbox/date/)     
     * Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/)
     * 
* * @class * @name jsDate * @param {String | Number | Array | Date Object | Options Object} arguments Optional arguments, either a parsable date/time string, * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds], * a Date object, or an options object of form {syntax: "perl", date:some Date} where all options are optional. */ var jsDate = function () { this.syntax = jsDate.config.syntax; this._type = "jsDate"; this.utcOffset = new Date().getTimezoneOffset * 60000; this.proxy = new Date(); this.options = {}; this.locale = jsDate.regional.getLocale(); this.formatString = ''; this.defaultCentury = jsDate.config.defaultCentury; switch ( arguments.length ) { case 0: break; case 1: // other objects either won't have a _type property or, // if they do, it shouldn't be set to "jsDate", so // assume it is an options argument. if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") { var opts = this.options = arguments[0]; this.syntax = opts.syntax || this.syntax; this.defaultCentury = opts.defaultCentury || this.defaultCentury; this.proxy = jsDate.createDate(opts.date); } else { this.proxy = jsDate.createDate(arguments[0]); } break; default: var a = []; for ( var i=0; i 0 ? 'floor' : 'ceil'](unitDiff)); }; /** * Get the abbreviated name of the current week day * * @returns {String} */ jsDate.prototype.getAbbrDayName = function() { return jsDate.regional[this.locale]["dayNamesShort"][this.proxy.getDay()]; }; /** * Get the abbreviated name of the current month * * @returns {String} */ jsDate.prototype.getAbbrMonthName = function() { return jsDate.regional[this.locale]["monthNamesShort"][this.proxy.getMonth()]; }; /** * Get UPPER CASE AM or PM for the current time * * @returns {String} */ jsDate.prototype.getAMPM = function() { return this.proxy.getHours() >= 12 ? 'PM' : 'AM'; }; /** * Get lower case am or pm for the current time * * @returns {String} */ jsDate.prototype.getAmPm = function() { return this.proxy.getHours() >= 12 ? 'pm' : 'am'; }; /** * Get the century (19 for 20th Century) * * @returns {Integer} Century (19 for 20th century). */ jsDate.prototype.getCentury = function() { return parseInt(this.proxy.getFullYear()/100, 10); }; /** * Implements Date functionality */ jsDate.prototype.getDate = function() { return this.proxy.getDate(); }; /** * Implements Date functionality */ jsDate.prototype.getDay = function() { return this.proxy.getDay(); }; /** * Get the Day of week 1 (Monday) thru 7 (Sunday) * * @returns {Integer} Day of week 1 (Monday) thru 7 (Sunday) */ jsDate.prototype.getDayOfWeek = function() { var dow = this.proxy.getDay(); return dow===0?7:dow; }; /** * Get the day of the year * * @returns {Integer} 1 - 366, day of the year */ jsDate.prototype.getDayOfYear = function() { var d = this.proxy; var ms = d - new Date('' + d.getFullYear() + '/1/1 GMT'); ms += d.getTimezoneOffset()*60000; d = null; return parseInt(ms/60000/60/24, 10)+1; }; /** * Get the name of the current week day * * @returns {String} */ jsDate.prototype.getDayName = function() { return jsDate.regional[this.locale]["dayNames"][this.proxy.getDay()]; }; /** * Get the week number of the given year, starting with the first Sunday as the first week * @returns {Integer} Week number (13 for the 13th full week of the year). */ jsDate.prototype.getFullWeekOfYear = function() { var d = this.proxy; var doy = this.getDayOfYear(); var rdow = 6-d.getDay(); var woy = parseInt((doy+rdow)/7, 10); return woy; }; /** * Implements Date functionality */ jsDate.prototype.getFullYear = function() { return this.proxy.getFullYear(); }; /** * Get the GMT offset in hours and minutes (e.g. +06:30) * * @returns {String} */ jsDate.prototype.getGmtOffset = function() { // divide the minutes offset by 60 var hours = this.proxy.getTimezoneOffset() / 60; // decide if we are ahead of or behind GMT var prefix = hours < 0 ? '+' : '-'; // remove the negative sign if any hours = Math.abs(hours); // add the +/- to the padded number of hours to : to the padded minutes return prefix + addZeros(Math.floor(hours), 2) + ':' + addZeros((hours % 1) * 60, 2); }; /** * Implements Date functionality */ jsDate.prototype.getHours = function() { return this.proxy.getHours(); }; /** * Get the current hour on a 12-hour scheme * * @returns {Integer} */ jsDate.prototype.getHours12 = function() { var hours = this.proxy.getHours(); return hours > 12 ? hours - 12 : (hours == 0 ? 12 : hours); }; jsDate.prototype.getIsoWeek = function() { var d = this.proxy; var woy = d.getWeekOfYear(); var dow1_1 = (new Date('' + d.getFullYear() + '/1/1')).getDay(); // First week is 01 and not 00 as in the case of %U and %W, // so we add 1 to the final result except if day 1 of the year // is a Monday (then %W returns 01). // We also need to subtract 1 if the day 1 of the year is // Friday-Sunday, so the resulting equation becomes: var idow = woy + (dow1_1 > 4 || dow1_1 <= 1 ? 0 : 1); if(idow == 53 && (new Date('' + d.getFullYear() + '/12/31')).getDay() < 4) { idow = 1; } else if(idow === 0) { d = new jsDate(new Date('' + (d.getFullYear()-1) + '/12/31')); idow = d.getIsoWeek(); } d = null; return idow; }; /** * Implements Date functionality */ jsDate.prototype.getMilliseconds = function() { return this.proxy.getMilliseconds(); }; /** * Implements Date functionality */ jsDate.prototype.getMinutes = function() { return this.proxy.getMinutes(); }; /** * Implements Date functionality */ jsDate.prototype.getMonth = function() { return this.proxy.getMonth(); }; /** * Get the name of the current month * * @returns {String} */ jsDate.prototype.getMonthName = function() { return jsDate.regional[this.locale]["monthNames"][this.proxy.getMonth()]; }; /** * Get the number of the current month, 1-12 * * @returns {Integer} */ jsDate.prototype.getMonthNumber = function() { return this.proxy.getMonth() + 1; }; /** * Implements Date functionality */ jsDate.prototype.getSeconds = function() { return this.proxy.getSeconds(); }; /** * Return a proper two-digit year integer * * @returns {Integer} */ jsDate.prototype.getShortYear = function() { return this.proxy.getYear() % 100; }; /** * Implements Date functionality */ jsDate.prototype.getTime = function() { return this.proxy.getTime(); }; /** * Get the timezone abbreviation * * @returns {String} Abbreviation for the timezone */ jsDate.prototype.getTimezoneAbbr = function() { return this.proxy.toString().replace(/^.*\(([^)]+)\)$/, '$1'); }; /** * Get the browser-reported name for the current timezone (e.g. MDT, Mountain Daylight Time) * * @returns {String} */ jsDate.prototype.getTimezoneName = function() { var match = /(?:\((.+)\)$| ([A-Z]{3}) )/.exec(this.toString()); return match[1] || match[2] || 'GMT' + this.getGmtOffset(); }; /** * Implements Date functionality */ jsDate.prototype.getTimezoneOffset = function() { return this.proxy.getTimezoneOffset(); }; /** * Get the week number of the given year, starting with the first Monday as the first week * @returns {Integer} Week number (13 for the 13th week of the year). */ jsDate.prototype.getWeekOfYear = function() { var doy = this.getDayOfYear(); var rdow = 7 - this.getDayOfWeek(); var woy = parseInt((doy+rdow)/7, 10); return woy; }; /** * Get the current date as a Unix timestamp * * @returns {Integer} */ jsDate.prototype.getUnix = function() { return Math.round(this.proxy.getTime() / 1000, 0); }; /** * Implements Date functionality */ jsDate.prototype.getYear = function() { return this.proxy.getYear(); }; /** * Return a date one day ahead (or any other unit) * * @param {String} unit Optional, year | month | day | week | hour | minute | second | millisecond * @returns {jsDate} */ jsDate.prototype.next = function(unit) { unit = unit || 'day'; return this.clone().add(1, unit); }; /** * Set the jsDate instance to a new date. * * @param {String | Number | Array | Date Object | jsDate Object | Options Object} arguments Optional arguments, * either a parsable date/time string, * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds], * a Date object, jsDate Object or an options object of form {syntax: "perl", date:some Date} where all options are optional. */ jsDate.prototype.set = function() { switch ( arguments.length ) { case 0: this.proxy = new Date(); break; case 1: // other objects either won't have a _type property or, // if they do, it shouldn't be set to "jsDate", so // assume it is an options argument. if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") { var opts = this.options = arguments[0]; this.syntax = opts.syntax || this.syntax; this.defaultCentury = opts.defaultCentury || this.defaultCentury; this.proxy = jsDate.createDate(opts.date); } else { this.proxy = jsDate.createDate(arguments[0]); } break; default: var a = []; for ( var i=0; ijsDate attempts to detect locale when loaded and defaults to 'en'. * If a localization is detected which is not available, jsDate defaults to 'en'. * Additional localizations can be added after jsDate loads. After adding a localization, * call the jsDate.regional.getLocale() method. Currently, en, fr and de are defined.

* *

Localizations must be an object and have the following properties defined: monthNames, monthNamesShort, dayNames, dayNamesShort and Localizations are added like:

*
     * jsDate.regional['en'] = {
     * monthNames      : 'January February March April May June July August September October November December'.split(' '),
     * monthNamesShort : 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' '),
     * dayNames        : 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday'.split(' '),
     * dayNamesShort   : 'Sun Mon Tue Wed Thu Fri Sat'.split(' ')
     * };
     * 
*

After adding localizations, call jsDate.regional.getLocale(); to update the locale setting with the * new localizations.

*/ jsDate.regional = { 'en': { monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'], monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun','Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], formatString: '%Y-%m-%d %H:%M:%S' }, 'fr': { monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'], monthNamesShort: ['Jan','Fév','Mar','Avr','Mai','Jun','Jul','Aoû','Sep','Oct','Nov','Déc'], dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'], dayNamesShort: ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'], formatString: '%Y-%m-%d %H:%M:%S' }, 'de': { monthNames: ['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'], monthNamesShort: ['Jan','Feb','Mär','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'], dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'], dayNamesShort: ['So','Mo','Di','Mi','Do','Fr','Sa'], formatString: '%Y-%m-%d %H:%M:%S' }, 'es': { monthNames: ['Enero','Febrero','Marzo','Abril','Mayo','Junio', 'Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'], monthNamesShort: ['Ene','Feb','Mar','Abr','May','Jun', 'Jul','Ago','Sep','Oct','Nov','Dic'], dayNames: ['Domingo','Lunes','Martes','Miércoles','Jueves','Viernes','Sábado'], dayNamesShort: ['Dom','Lun','Mar','Mié','Juv','Vie','Sáb'], formatString: '%Y-%m-%d %H:%M:%S' }, 'ru': { monthNames: ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'], monthNamesShort: ['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек'], dayNames: ['воскресенье','понедельник','вторник','среда','четверг','пятница','суббота'], dayNamesShort: ['вск','пнд','втр','срд','чтв','птн','сбт'], formatString: '%Y-%m-%d %H:%M:%S' }, 'ar': { monthNames: ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'آذار', 'حزيران','تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], monthNamesShort: ['1','2','3','4','5','6','7','8','9','10','11','12'], dayNames: ['السبت', 'الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة'], dayNamesShort: ['سبت', 'أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة'], formatString: '%Y-%m-%d %H:%M:%S' }, 'pt': { monthNames: ['Janeiro','Fevereiro','Março','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'], monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'], dayNames: ['Domingo','Segunda-feira','Terça-feira','Quarta-feira','Quinta-feira','Sexta-feira','Sábado'], dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'], formatString: '%Y-%m-%d %H:%M:%S' }, 'pt-BR': { monthNames: ['Janeiro','Fevereiro','Março','Abril','Maio','Junho', 'Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'], monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'], dayNames: ['Domingo','Segunda-feira','Terça-feira','Quarta-feira','Quinta-feira','Sexta-feira','Sábado'], dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'], formatString: '%Y-%m-%d %H:%M:%S' } }; // Set english variants to 'en' jsDate.regional['en-US'] = jsDate.regional['en-GB'] = jsDate.regional['en']; /** * Try to determine the users locale based on the lang attribute of the html page. Defaults to 'en' * if it cannot figure out a locale of if the locale does not have a localization defined. * @returns {String} locale */ jsDate.regional.getLocale = function () { var l = jsDate.config.defaultLocale; if ( document && document.getElementsByTagName('html') && document.getElementsByTagName('html')[0].lang ) { l = document.getElementsByTagName('html')[0].lang; if (!jsDate.regional.hasOwnProperty(l)) { l = jsDate.config.defaultLocale; } } return l; }; // ms in day var day = 24 * 60 * 60 * 1000; // padd a number with zeros var addZeros = function(num, digits) { num = String(num); var i = digits - num.length; var s = String(Math.pow(10, i)).slice(1); return s.concat(num); }; // representations used for calculating differences between dates. // This borrows heavily from Ken Snyder's work. var multipliers = { millisecond: 1, second: 1000, minute: 60 * 1000, hour: 60 * 60 * 1000, day: day, week: 7 * day, month: { // add a number of months add: function(d, number) { // add any years needed (increments of 12) multipliers.year.add(d, Math[number > 0 ? 'floor' : 'ceil'](number / 12)); // ensure that we properly wrap betwen December and January var prevMonth = d.getMonth() + (number % 12); if (prevMonth == 12) { prevMonth = 0; d.setYear(d.getFullYear() + 1); } else if (prevMonth == -1) { prevMonth = 11; d.setYear(d.getFullYear() - 1); } d.setMonth(prevMonth); }, // get the number of months between two Date objects (decimal to the nearest day) diff: function(d1, d2) { // get the number of years var diffYears = d1.getFullYear() - d2.getFullYear(); // get the number of remaining months var diffMonths = d1.getMonth() - d2.getMonth() + (diffYears * 12); // get the number of remaining days var diffDays = d1.getDate() - d2.getDate(); // return the month difference with the days difference as a decimal return diffMonths + (diffDays / 30); } }, year: { // add a number of years add: function(d, number) { d.setYear(d.getFullYear() + Math[number > 0 ? 'floor' : 'ceil'](number)); }, // get the number of years between two Date objects (decimal to the nearest day) diff: function(d1, d2) { return multipliers.month.diff(d1, d2) / 12; } } }; // // Alias each multiplier with an 's' to allow 'year' and 'years' for example. // This comes from Ken Snyders work. // for (var unit in multipliers) { if (unit.substring(unit.length - 1) != 's') { // IE will iterate newly added properties :| multipliers[unit + 's'] = multipliers[unit]; } } // // take a jsDate instance and a format code and return the formatted value. // This is a somewhat modified version of Ken Snyder's method. // var format = function(d, code, syntax) { // if shorcut codes are used, recursively expand those. if (jsDate.formats[syntax]["shortcuts"][code]) { return jsDate.strftime(d, jsDate.formats[syntax]["shortcuts"][code], syntax); } else { // get the format code function and addZeros() argument var getter = (jsDate.formats[syntax]["codes"][code] || '').split('.'); var nbr = d['get' + getter[0]] ? d['get' + getter[0]]() : ''; if (getter[1]) { nbr = addZeros(nbr, getter[1]); } return nbr; } }; /** * @static * Static function for convert a date to a string according to a given format. Also acts as namespace for strftime format codes. *

strftime formatting can be accomplished without creating a jsDate object by calling jsDate.strftime():

*
     * var formattedDate = jsDate.strftime('Feb 8, 2006 8:48:32', '%Y-%m-%d %H:%M:%S');
     * 
* @param {String | Number | Array | jsDate Object | Date Object} date A parsable date string, JavaScript time stamp, Array of form [year, month, day, hours, minutes, seconds, milliseconds], jsDate Object or Date object. * @param {String} formatString String with embedded date formatting codes. * See: {@link jsDate.formats}. * @param {String} syntax Optional syntax to use [default perl]. * @param {String} locale Optional locale to use. * @returns {String} Formatted representation of the date. */ // // Logic as implemented here is very similar to Ken Snyder's Date Instance Methods. // jsDate.strftime = function(d, formatString, syntax, locale) { var syn = 'perl'; var loc = jsDate.regional.getLocale(); // check if syntax and locale are available or reversed if (syntax && jsDate.formats.hasOwnProperty(syntax)) { syn = syntax; } else if (syntax && jsDate.regional.hasOwnProperty(syntax)) { loc = syntax; } if (locale && jsDate.formats.hasOwnProperty(locale)) { syn = locale; } else if (locale && jsDate.regional.hasOwnProperty(locale)) { loc = locale; } if (get_type(d) != "[object Object]" || d._type != "jsDate") { d = new jsDate(d); d.locale = loc; } if (!formatString) { formatString = d.formatString || jsDate.regional[loc]['formatString']; } // default the format string to year-month-day var source = formatString || '%Y-%m-%d', result = '', match; // replace each format code while (source.length > 0) { if (match = source.match(jsDate.formats[syn].codes.matcher)) { result += source.slice(0, match.index); result += (match[1] || '') + format(d, match[2], syn); source = source.slice(match.index + match[0].length); } else { result += source; source = ''; } } return result; }; /** * @namespace * Namespace to hold format codes and format shortcuts. "perl" and "php" format codes * and shortcuts are defined by default. Additional codes and shortcuts can be * added like: * *
     * jsDate.formats["perl"] = {
     *     "codes": {
     *         matcher: /someregex/,
     *         Y: "fullYear",  // name of "get" method without the "get",
     *         ...,            // more codes
     *     },
     *     "shortcuts": {
     *         F: '%Y-%m-%d',
     *         ...,            // more shortcuts
     *     }
     * };
     * 
* *

Additionally, ISO and SQL shortcuts are defined and can be accesses via: * jsDate.formats.ISO and jsDate.formats.SQL */ jsDate.formats = { ISO:'%Y-%m-%dT%H:%M:%S.%N%G', SQL:'%Y-%m-%d %H:%M:%S' }; /** * Perl format codes and shortcuts for strftime. * * A hash (object) of codes where each code must be an array where the first member is * the name of a Date.prototype or jsDate.prototype function to call * and optionally a second member indicating the number to pass to addZeros() * *

The following format codes are defined:

* *
     * Code    Result                    Description
     * == Years ==           
     * %Y      2008                      Four-digit year
     * %y      08                        Two-digit year
     * 
     * == Months ==          
     * %m      09                        Two-digit month
     * %#m     9                         One or two-digit month
     * %B      September                 Full month name
     * %b      Sep                       Abbreviated month name
     * 
     * == Days ==            
     * %d      05                        Two-digit day of month
     * %#d     5                         One or two-digit day of month
     * %e      5                         One or two-digit day of month
     * %A      Sunday                    Full name of the day of the week
     * %a      Sun                       Abbreviated name of the day of the week
     * %w      0                         Number of the day of the week (0 = Sunday, 6 = Saturday)
     * 
     * == Hours ==           
     * %H      23                        Hours in 24-hour format (two digits)
     * %#H     3                         Hours in 24-hour integer format (one or two digits)
     * %I      11                        Hours in 12-hour format (two digits)
     * %#I     3                         Hours in 12-hour integer format (one or two digits)
     * %p      PM                        AM or PM
     * 
     * == Minutes ==         
     * %M      09                        Minutes (two digits)
     * %#M     9                         Minutes (one or two digits)
     * 
     * == Seconds ==         
     * %S      02                        Seconds (two digits)
     * %#S     2                         Seconds (one or two digits)
     * %s      1206567625723             Unix timestamp (Seconds past 1970-01-01 00:00:00)
     * 
     * == Milliseconds ==    
     * %N      008                       Milliseconds (three digits)
     * %#N     8                         Milliseconds (one to three digits)
     * 
     * == Timezone ==        
     * %O      360                       difference in minutes between local time and GMT
     * %Z      Mountain Standard Time    Name of timezone as reported by browser
     * %G      06:00                     Hours and minutes between GMT
     * 
     * == Shortcuts ==       
     * %F      2008-03-26                %Y-%m-%d
     * %T      05:06:30                  %H:%M:%S
     * %X      05:06:30                  %H:%M:%S
     * %x      03/26/08                  %m/%d/%y
     * %D      03/26/08                  %m/%d/%y
     * %#c     Wed Mar 26 15:31:00 2008  %a %b %e %H:%M:%S %Y
     * %v      3-Sep-2008                %e-%b-%Y
     * %R      15:31                     %H:%M
     * %r      03:31:00 PM               %I:%M:%S %p
     * 
     * == Characters ==      
     * %n      \n                        Newline
     * %t      \t                        Tab
     * %%      %                         Percent Symbol
     * 
* *

Formatting shortcuts that will be translated into their longer version. * Be sure that format shortcuts do not refer to themselves: this will cause an infinite loop.

* *

Format codes and format shortcuts can be redefined after the jsDate * module is imported.

* *

Note that if you redefine the whole hash (object), you must supply a "matcher" * regex for the parser. The default matcher is:

* * /()%(#?(%|[a-z]))/i * *

which corresponds to the Perl syntax used by default.

* *

By customizing the matcher and format codes, nearly any strftime functionality is possible.

*/ jsDate.formats.perl = { codes: { // // 2-part regex matcher for format codes // // first match must be the character before the code (to account for escaping) // second match must be the format code character(s) // matcher: /()%(#?(%|[a-z]))/i, // year Y: 'FullYear', y: 'ShortYear.2', // month m: 'MonthNumber.2', '#m': 'MonthNumber', B: 'MonthName', b: 'AbbrMonthName', // day d: 'Date.2', '#d': 'Date', e: 'Date', A: 'DayName', a: 'AbbrDayName', w: 'Day', // hours H: 'Hours.2', '#H': 'Hours', I: 'Hours12.2', '#I': 'Hours12', p: 'AMPM', // minutes M: 'Minutes.2', '#M': 'Minutes', // seconds S: 'Seconds.2', '#S': 'Seconds', s: 'Unix', // milliseconds N: 'Milliseconds.3', '#N': 'Milliseconds', // timezone O: 'TimezoneOffset', Z: 'TimezoneName', G: 'GmtOffset' }, shortcuts: { // date F: '%Y-%m-%d', // time T: '%H:%M:%S', X: '%H:%M:%S', // local format date x: '%m/%d/%y', D: '%m/%d/%y', // local format extended '#c': '%a %b %e %H:%M:%S %Y', // local format short v: '%e-%b-%Y', R: '%H:%M', r: '%I:%M:%S %p', // tab and newline t: '\t', n: '\n', '%': '%' } }; /** * PHP format codes and shortcuts for strftime. * * A hash (object) of codes where each code must be an array where the first member is * the name of a Date.prototype or jsDate.prototype function to call * and optionally a second member indicating the number to pass to addZeros() * *

The following format codes are defined:

* *
     * Code    Result                    Description
     * === Days ===        
     * %a      Sun through Sat           An abbreviated textual representation of the day
     * %A      Sunday - Saturday         A full textual representation of the day
     * %d      01 to 31                  Two-digit day of the month (with leading zeros)
     * %e      1 to 31                   Day of the month, with a space preceding single digits.
     * %j      001 to 366                Day of the year, 3 digits with leading zeros
     * %u      1 - 7 (Mon - Sun)         ISO-8601 numeric representation of the day of the week
     * %w      0 - 6 (Sun - Sat)         Numeric representation of the day of the week
     *                                  
     * === Week ===                     
     * %U      13                        Full Week number, starting with the first Sunday as the first week
     * %V      01 through 53             ISO-8601:1988 week number, starting with the first week of the year 
     *                                   with at least 4 weekdays, with Monday being the start of the week
     * %W      46                        A numeric representation of the week of the year, 
     *                                   starting with the first Monday as the first week
     * === Month ===                    
     * %b      Jan through Dec           Abbreviated month name, based on the locale
     * %B      January - December        Full month name, based on the locale
     * %h      Jan through Dec           Abbreviated month name, based on the locale (an alias of %b)
     * %m      01 - 12 (Jan - Dec)       Two digit representation of the month
     * 
     * === Year ===                     
     * %C      19                        Two digit century (year/100, truncated to an integer)
     * %y      09 for 2009               Two digit year
     * %Y      2038                      Four digit year
     * 
     * === Time ===                     
     * %H      00 through 23             Two digit representation of the hour in 24-hour format
     * %I      01 through 12             Two digit representation of the hour in 12-hour format
     * %l      1 through 12              Hour in 12-hour format, with a space preceeding single digits
     * %M      00 through 59             Two digit representation of the minute
     * %p      AM/PM                     UPPER-CASE 'AM' or 'PM' based on the given time
     * %P      am/pm                     lower-case 'am' or 'pm' based on the given time
     * %r      09:34:17 PM               Same as %I:%M:%S %p
     * %R      00:35                     Same as %H:%M
     * %S      00 through 59             Two digit representation of the second
     * %T      21:34:17                  Same as %H:%M:%S
     * %X      03:59:16                  Preferred time representation based on locale, without the date
     * %z      -0500 or EST              Either the time zone offset from UTC or the abbreviation
     * %Z      -0500 or EST              The time zone offset/abbreviation option NOT given by %z
     * 
     * === Time and Date ===            
     * %D      02/05/09                  Same as %m/%d/%y
     * %F      2009-02-05                Same as %Y-%m-%d (commonly used in database datestamps)
     * %s      305815200                 Unix Epoch Time timestamp (same as the time() function)
     * %x      02/05/09                  Preferred date representation, without the time
     * 
     * === Miscellaneous ===            
     * %n        ---                     A newline character (\n)
     * %t        ---                     A Tab character (\t)
     * %%        ---                     A literal percentage character (%)
     * 
*/ jsDate.formats.php = { codes: { // // 2-part regex matcher for format codes // // first match must be the character before the code (to account for escaping) // second match must be the format code character(s) // matcher: /()%((%|[a-z]))/i, // day a: 'AbbrDayName', A: 'DayName', d: 'Date.2', e: 'Date', j: 'DayOfYear.3', u: 'DayOfWeek', w: 'Day', // week U: 'FullWeekOfYear.2', V: 'IsoWeek.2', W: 'WeekOfYear.2', // month b: 'AbbrMonthName', B: 'MonthName', m: 'MonthNumber.2', h: 'AbbrMonthName', // year C: 'Century.2', y: 'ShortYear.2', Y: 'FullYear', // time H: 'Hours.2', I: 'Hours12.2', l: 'Hours12', p: 'AMPM', P: 'AmPm', M: 'Minutes.2', S: 'Seconds.2', s: 'Unix', O: 'TimezoneOffset', z: 'GmtOffset', Z: 'TimezoneAbbr' }, shortcuts: { D: '%m/%d/%y', F: '%Y-%m-%d', T: '%H:%M:%S', X: '%H:%M:%S', x: '%m/%d/%y', R: '%H:%M', r: '%I:%M:%S %p', t: '\t', n: '\n', '%': '%' } }; // // Conceptually, the logic implemented here is similar to Ken Snyder's Date Instance Methods. // I use his idea of a set of parsers which can be regular expressions or functions, // iterating through those, and then seeing if Date.parse() will create a date. // The parser expressions and functions are a little different and some bugs have been // worked out. Also, a lot of "pre-parsing" is done to fix implementation // variations of Date.parse() between browsers. // jsDate.createDate = function(date) { // if passing in multiple arguments, try Date constructor if (date == null) { return new Date(); } // If the passed value is already a date object, return it if (date instanceof Date) { return date; } // if (typeof date == 'number') return new Date(date * 1000); // If the passed value is an integer, interpret it as a javascript timestamp if (typeof date == 'number') { return new Date(date); } // Before passing strings into Date.parse(), have to normalize them for certain conditions. // If strings are not formatted staccording to the EcmaScript spec, results from Date parse will be implementation dependent. // // For example: // * FF and Opera assume 2 digit dates are pre y2k, Chome assumes <50 is pre y2k, 50+ is 21st century. // * Chrome will correctly parse '1984-1-25' into localtime, FF and Opera will not parse. // * Both FF, Chrome and Opera will parse '1984/1/25' into localtime. // remove leading and trailing spaces var parsable = String(date).replace(/^\s*(.+)\s*$/g, '$1'); // replace dahses (-) with slashes (/) in dates like n[nnn]/n[n]/n[nnn] parsable = parsable.replace(/^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,4})/, "$1/$2/$3"); ///////// // Need to check for '15-Dec-09' also. // FF will not parse, but Chrome will. // Chrome will set date to 2009 as well. ///////// // first check for 'dd-mmm-yyyy' or 'dd/mmm/yyyy' like '15-Dec-2010' parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{4})/i, "$1 $2 $3"); // Now check for 'dd-mmm-yy' or 'dd/mmm/yy' and normalize years to default century. var match = parsable.match(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i); if (match && match.length > 3) { var m3 = parseFloat(match[3]); var ny = jsDate.config.defaultCentury + m3; ny = String(ny); // now replace 2 digit year with 4 digit year parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i, match[1] +' '+ match[2] +' '+ ny); } // Check for '1/19/70 8:14PM' // where starts with mm/dd/yy or yy/mm/dd and have something after // Check if 1st postiion is greater than 31, assume it is year. // Assme all 2 digit years are 1900's. // Finally, change them into US style mm/dd/yyyy representations. match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})[^0-9]/); function h1(parsable, match) { var m1 = parseFloat(match[1]); var m2 = parseFloat(match[2]); var m3 = parseFloat(match[3]); var cent = jsDate.config.defaultCentury; var ny, nd, nm, str; if (m1 > 31) { // first number is a year nd = m3; nm = m2; ny = cent + m1; } else { // last number is the year nd = m2; nm = m1; ny = cent + m3; } str = nm+'/'+nd+'/'+ny; // now replace 2 digit year with 4 digit year return parsable.replace(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})/, str); } if (match && match.length > 3) { parsable = h1(parsable, match); } // Now check for '1/19/70' with nothing after and do as above var match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})$/); if (match && match.length > 3) { parsable = h1(parsable, match); } var i = 0; var length = jsDate.matchers.length; var pattern, ms, current = parsable; while (i < length) { ms = Date.parse(current); if (!isNaN(ms)) { return new Date(ms); } pattern = jsDate.matchers[i]; if (typeof pattern == 'function') { var obj = pattern.call(jsDate, current); if (obj instanceof Date) { return obj; } } else { current = parsable.replace(pattern[0], pattern[1]); } i++; } return NaN; }; /** * @static * Handy static utility function to return the number of days in a given month. * @param {Integer} year Year * @param {Integer} month Month (1-12) * @returns {Integer} Number of days in the month. */ // // handy utility method Borrowed right from Ken Snyder's Date Instance Mehtods. // jsDate.daysInMonth = function(year, month) { if (month == 2) { return new Date(year, 1, 29).getDate() == 29 ? 29 : 28; } return [undefined,31,undefined,31,30,31,30,31,31,30,31,30,31][month]; }; // // An Array of regular expressions or functions that will attempt to match the date string. // Functions are called with scope of a jsDate instance. // jsDate.matchers = [ // convert dd.mmm.yyyy to mm/dd/yyyy (world date to US date). [/(3[01]|[0-2]\d)\s*\.\s*(1[0-2]|0\d)\s*\.\s*([1-9]\d{3})/, '$2/$1/$3'], // convert yyyy-mm-dd to mm/dd/yyyy (ISO date to US date). [/([1-9]\d{3})\s*-\s*(1[0-2]|0\d)\s*-\s*(3[01]|[0-2]\d)/, '$2/$3/$1'], // Handle 12 hour or 24 hour time with milliseconds am/pm and optional date part. function(str) { var match = str.match(/^(?:(.+)\s+)?([012]?\d)(?:\s*\:\s*(\d\d))?(?:\s*\:\s*(\d\d(\.\d*)?))?\s*(am|pm)?\s*$/i); // opt. date hour opt. minute opt. second opt. msec opt. am or pm if (match) { if (match[1]) { var d = this.createDate(match[1]); if (isNaN(d)) { return; } } else { var d = new Date(); d.setMilliseconds(0); } var hour = parseFloat(match[2]); if (match[6]) { hour = match[6].toLowerCase() == 'am' ? (hour == 12 ? 0 : hour) : (hour == 12 ? 12 : hour + 12); } d.setHours(hour, parseInt(match[3] || 0, 10), parseInt(match[4] || 0, 10), ((parseFloat(match[5] || 0)) || 0)*1000); return d; } else { return str; } }, // Handle ISO timestamp with time zone. function(str) { var match = str.match(/^(?:(.+))[T|\s+]([012]\d)(?:\:(\d\d))(?:\:(\d\d))(?:\.\d+)([\+\-]\d\d\:\d\d)$/i); if (match) { if (match[1]) { var d = this.createDate(match[1]); if (isNaN(d)) { return; } } else { var d = new Date(); d.setMilliseconds(0); } var hour = parseFloat(match[2]); d.setHours(hour, parseInt(match[3], 10), parseInt(match[4], 10), parseFloat(match[5])*1000); return d; } else { return str; } }, // Try to match ambiguous strings like 12/8/22. // Use FF date assumption that 2 digit years are 20th century (i.e. 1900's). // This may be redundant with pre processing of date already performed. function(str) { var match = str.match(/^([0-3]?\d)\s*[-\/.\s]{1}\s*([a-zA-Z]{3,9})\s*[-\/.\s]{1}\s*([0-3]?\d)$/); if (match) { var d = new Date(); var cent = jsDate.config.defaultCentury; var m1 = parseFloat(match[1]); var m3 = parseFloat(match[3]); var ny, nd, nm; if (m1 > 31) { // first number is a year nd = m3; ny = cent + m1; } else { // last number is the year nd = m1; ny = cent + m3; } var nm = inArray(match[2], jsDate.regional[this.locale]["monthNamesShort"]); if (nm == -1) { nm = inArray(match[2], jsDate.regional[this.locale]["monthNames"]); } d.setFullYear(ny, nm, nd); d.setHours(0,0,0,0); return d; } else { return str; } } ]; // // I think John Reisig published this method on his blog, ejohn. // function inArray( elem, array ) { if ( array.indexOf ) { return array.indexOf( elem ); } for ( var i = 0, length = array.length; i < length; i++ ) { if ( array[ i ] === elem ) { return i; } } return -1; } // // Thanks to Kangax, Christian Sciberras and Stack Overflow for this method. // function get_type(thing){ if(thing===null) return "[object Null]"; // special case return Object.prototype.toString.call(thing); } $.jsDate = jsDate; })(jQuery);