├── .gitignore ├── .editorconfig ├── benchmark ├── slow.js └── fast.js ├── package.json ├── src ├── locales │ └── en_US.js ├── runtime.js ├── strftime.js └── parse.js ├── LICENSE ├── README.md └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | fast-strftime.sublime-workspace 3 | fast-strftime.sublime-project 4 | npm-debug.log 5 | nodex64.exe 6 | throwaway.js 7 | reader.js 8 | bump.js 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [**.js] 13 | indent_style = space 14 | indent_size = 4 15 | -------------------------------------------------------------------------------- /benchmark/slow.js: -------------------------------------------------------------------------------- 1 | var strftime = require("strftime"); 2 | var date = new Date(); 3 | console.log(strftime('%F %r %z', date)); 4 | var ops = 1e5; 5 | var l = ops; 6 | while(l--)strftime('%F %r %z', date) 7 | 8 | var now = Date.now(); 9 | var ops = 1e6; 10 | var l = ops; 11 | while(l--)strftime('%F %r %z', date) 12 | console.log( 13 | 14 | (((ops / (Date.now() - now)) * 1000)|0) + " op/s" 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /benchmark/fast.js: -------------------------------------------------------------------------------- 1 | var strftime = require("../src/strftime.js"); 2 | var date = new Date(); 3 | console.log(strftime('%F %r %z', date)); 4 | var ops = 1e5; 5 | var l = ops; 6 | while(l--)strftime('%F %r %z', date) 7 | 8 | var now = Date.now(); 9 | var ops = 1e6; 10 | var l = ops; 11 | while(l--)strftime('%F %r %z', date) 12 | console.log( 13 | 14 | (((ops / (Date.now() - now)) * 1000)|0) + " op/s" 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fast-strftime", 3 | "description": "Extremely fast implementation of node strftime library", 4 | "version": "1.1.0", 5 | "keywords": [ 6 | "fast", 7 | "parse", 8 | "parser", 9 | "time", 10 | "format", 11 | "performance" 12 | ], 13 | "scripts": { 14 | "test": "node ./test/test.js" 15 | }, 16 | "homepage": "https://github.com/petkaantonov/fast-strftime", 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/petkaantonov/fast-strftime.git" 20 | }, 21 | "bugs": { 22 | "url": "http://github.com/petkaantonov/fast-strftime/issues" 23 | }, 24 | "license": "MIT", 25 | "author": { 26 | "name": "Petka Antonov", 27 | "email": "petka_antonov@hotmail.com", 28 | "url": "http://github.com/petkaantonov/" 29 | }, 30 | "devDependencies": { 31 | "strftime": "latest" 32 | }, 33 | "readmeFilename": "README.md", 34 | "main": "./src/strftime.js", 35 | "files": [ 36 | "src", 37 | "LICENSE" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /src/locales/en_US.js: -------------------------------------------------------------------------------- 1 | var locale = { 2 | days: [ 3 | "Sunday", 4 | "Monday", 5 | "Tuesday", 6 | "Wednesday", 7 | "Thursday", 8 | "Friday", 9 | "Saturday" 10 | ], 11 | shortDays: [ 12 | "Sun", 13 | "Mon", 14 | "Tue", 15 | "Wed", 16 | "Thu", 17 | "Fri", 18 | "Sat" 19 | ], 20 | months: [ 21 | "January", 22 | "February", 23 | "March", 24 | "April", 25 | "May", 26 | "June", 27 | "July", 28 | "August", 29 | "September", 30 | "October", 31 | "November", 32 | "December" 33 | ], 34 | shortMonths: [ 35 | "Jan", 36 | "Feb", 37 | "Mar", 38 | "Apr", 39 | "May", 40 | "Jun", 41 | "Jul", 42 | "Aug", 43 | "Sep", 44 | "Oct", 45 | "Nov", 46 | "Dec" 47 | ], 48 | "AM": "AM", 49 | "PM": "PM", 50 | "am": "am", 51 | "pm": "pm" 52 | }; 53 | 54 | var formats = { 55 | R: void 0, r: void 0, v: void 0, 56 | T: void 0, F: void 0, D: void 0 57 | }; 58 | 59 | locale.formats = formats; 60 | module.exports = locale; 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Petka Antonov 2 | 3 | With parts by Sami Samhuri 4 | Copyright 2010 - 2014 Sami Samhuri under the terms of the MIT license found 5 | at http://sjs.mit-license.org/ 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Introduction 2 | 3 | Extremely fast implementation of the [node strftime module](https://github.com/samsonjs/strftime). 4 | 5 | npm install fast-strftime 6 | 7 | #Benchmarks 8 | 9 | Results using node 0.11.13 10 | 11 | $ node ./benchmark/fast.js 12 | 2014-05-20 08:15:18 PM +0300 13 | 2898550 op/s 14 | 15 | $ node ./benchmark/slow.js 16 | 2014-05-20 08:15:22 PM +0300 17 | 99522 op/s 18 | 19 | -> Up to 29x faster than original 20 | 21 | #License 22 | 23 | MIT License: 24 | 25 | Copyright (c) 2014 Petka Antonov 26 | 27 | With parts by Sami Samhuri 28 | Copyright 2010 - 2014 Sami Samhuri under the terms of the MIT license found 29 | at http://sjs.mit-license.org/ 30 | 31 | Permission is hereby granted, free of charge, to any person obtaining a copy 32 | of this software and associated documentation files (the "Software"), to deal 33 | in the Software without restriction, including without limitation the rights 34 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 35 | copies of the Software, and to permit persons to whom the Software is 36 | furnished to do so, subject to the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be included in 39 | all copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 42 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 43 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 44 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 45 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 47 | THE SOFTWARE. 48 | -------------------------------------------------------------------------------- /src/runtime.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* 3 | Copyright (c) 2014 Petka Antonov 4 | 5 | With parts by Sami Samhuri 6 | Copyright 2010 - 2014 Sami Samhuri under the terms of the MIT license found 7 | at http://sjs.mit-license.org/ 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | */ 27 | var ord = new Array(31); 28 | var timeZones = new Array(1800); 29 | var zeroPads2 = new Array(10); 30 | var zeroPads3 = new Array(100); 31 | var spacePads2 = new Array(10); 32 | var spacePads3 = new Array(100); 33 | 34 | function Runtime() {} 35 | 36 | Runtime.prototype.weekNumber = function(d, firstWeekday) { 37 | firstWeekday = firstWeekday || 'sunday'; 38 | 39 | var wday = d.getDay(); 40 | if (firstWeekday == 'monday') { 41 | if (wday == 0) // Sunday 42 | wday = 6; 43 | else 44 | wday--; 45 | } 46 | var firstDayOfYear = new Date(d.getFullYear(), 0, 1); 47 | var yday = (d - firstDayOfYear) / 86400000; 48 | var weekNum = (yday + 7 - wday) / 7; 49 | return weekNum | 0; 50 | }; 51 | 52 | Runtime.prototype.to12 = function to12(hour) { 53 | if (hour === 0) return 12; 54 | else if (hour > 12) return hour - 12; 55 | else return hour; 56 | }; 57 | 58 | Runtime.prototype.pad = function pad(num, ch, length) { 59 | if (ch === -1) return '' + num; 60 | var arrLength = length === 3 ? 100 : 10; 61 | if (num >= arrLength) { 62 | return '' + num; 63 | } 64 | var arr = ch === 1 65 | ? (length === 3 ? this.spacePads3 : this.spacePads2) 66 | : (length === 3 ? this.zeroPads3 : this.zeroPads2); 67 | 68 | return arr[num]; 69 | }; 70 | 71 | Runtime.prototype.formatTz = function(offset) { 72 | var index = (offset + 900) | 0; 73 | var str = this.timeZones[index]; 74 | 75 | if (str === "") { 76 | str = (offset < 0 ? '-' : '+') + 77 | this.pad(Math.abs(offset / 60 | 0) | 0, 0, 2) + 78 | this.pad(offset % 60 | 0, 0, 2); 79 | this.timeZones[index] = internalizeString(str); 80 | } 81 | return str; 82 | }; 83 | 84 | Runtime.prototype.ord = ord; 85 | Runtime.prototype.timeZones = timeZones; 86 | Runtime.prototype.zeroPads2 = zeroPads2; 87 | Runtime.prototype.zeroPads3 = zeroPads3; 88 | Runtime.prototype.spacePads2 = spacePads2; 89 | Runtime.prototype.spacePads3 = spacePads3; 90 | 91 | var internalizeString = (function() { 92 | var o = {"- ": 0}; 93 | delete o["- "]; 94 | return function(str) { 95 | o[str] = true; 96 | var ret = Object.keys(o)[0]; 97 | delete o[str]; 98 | return ret; 99 | try {} finally {} 100 | }; 101 | })(); 102 | 103 | function _pad(num, ch, length) { 104 | return ((new Array(length + 1).join(ch)) + num).slice(-length); 105 | } 106 | 107 | for (var i = 0; i < 10; ++i) { 108 | zeroPads2[i] = internalizeString(_pad(i, "0", 2)); 109 | spacePads2[i] = internalizeString(_pad(i, " ", 2)); 110 | } 111 | 112 | for (var i = 0; i < 100; ++i) { 113 | zeroPads3[i] = internalizeString(_pad(i, "0", 3)); 114 | spacePads3[i] = internalizeString(_pad(i, " ", 3)); 115 | } 116 | 117 | for (var i = 0; i < timeZones.length; ++i) { 118 | timeZones[i] = ""; 119 | } 120 | 121 | for (var i = 0; i < ord.length; ++i) { 122 | ord[i] = internalizeString((i + 1) + "th"); 123 | } 124 | ord[0] = "1st"; 125 | ord[1] = "2nd"; 126 | ord[2] = "3rd"; 127 | ord[20] = "21st"; 128 | ord[21] = "22nd"; 129 | ord[22] = "23rd"; 130 | ord[30] = "31st"; 131 | 132 | module.exports = Runtime; 133 | -------------------------------------------------------------------------------- /src/strftime.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* 3 | Copyright (c) 2014 Petka Antonov 4 | 5 | With parts by Sami Samhuri 6 | Copyright 2010 - 2014 Sami Samhuri under the terms of the MIT license found 7 | at http://sjs.mit-license.org/ 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | */ 27 | var defaultLocale = require("./locales/en_US.js"); 28 | var cpDefaultLocale = JSON.parse(JSON.stringify(defaultLocale)); 29 | var Runtime = require("./runtime.js"); 30 | var parse = require("./parse.js"); 31 | var compiledFormatters = {}; 32 | var defaultOptions = { 33 | utc: false, 34 | timezone: void 0 35 | }; 36 | var defaultFormats = defaultLocale.formats; 37 | Runtime.prototype.strftime = strftime; 38 | var runtime = new Runtime(); 39 | 40 | function dateToUtc(d) { 41 | var msDelta = (d.getTimezoneOffset() || 0) * 60000; 42 | return new Date(d.getTime() + msDelta); 43 | } 44 | 45 | function parse2Digit(ch1, ch2) { 46 | return (ch1 - 48) * 10 + (ch2 - 48); 47 | } 48 | 49 | function toFastProperties(o) { 50 | function f() {} 51 | f.prototype = o; 52 | return o; 53 | } 54 | 55 | function read(obj, fmt) { 56 | return obj[fmt]; 57 | try {} finally {} 58 | } 59 | 60 | function write(obj, fmt, value) { 61 | obj[fmt] = value; 62 | return; 63 | try {} finally {} 64 | } 65 | 66 | function getCompiledFormatter(fmt) { 67 | if (typeof fmt !== "string") fmt = '' + fmt; 68 | var compiled = compiledFormatters; 69 | var ret = read(compiled, fmt); 70 | if (ret !== void 0) return ret; 71 | ret = parse(fmt); 72 | write(compiled, fmt, ret); 73 | toFastProperties(compiled); 74 | return ret; 75 | } 76 | 77 | function strftime(fmt, d, locale, options) { 78 | // Optimize common case 79 | if ((locale === void 0 || locale === defaultLocale) && 80 | (d === void 0 || d instanceof Date)) { 81 | var fn = getCompiledFormatter(fmt); 82 | if (d === void 0) d = new Date(); 83 | return fn.call(runtime, d, false, -1800, defaultLocale, defaultFormats, d.getTime()); 84 | 85 | } 86 | options = options === void 0 ? defaultOptions : options; 87 | 88 | if (d !== void 0 && !(d instanceof Date)) { 89 | locale = d; 90 | d = void 0; 91 | } 92 | 93 | d = d === void 0 ? new Date() : d; 94 | locale = locale === void 0 ? defaultLocale : locale; 95 | var formats = locale.formats === void 0 ? defaultFormats : locale.formats; 96 | 97 | var tz = options.timezone; 98 | var timestamp = d.getTime(); 99 | if (options.utc || typeof tz === "number" || typeof tz === "string") { 100 | d = dateToUtc(d); 101 | } 102 | 103 | if (tz !== void 0) { 104 | if (typeof tz === "string") { 105 | var sign = tz.charCodeAt(0) === 45 /*-*/ ? -1 : 1; 106 | var hours = parse2Digit(tz.charCodeAt(1), tz.charCodeAt(2)); 107 | var mins = parse2Digit(tz.charCodeAt(3), tz.charCodeAt(4)); 108 | tz = sign * (60 * hours) + mins; 109 | } 110 | d = new Date(d.getTime() + (tz * 60000)); 111 | } 112 | else { 113 | tz = -1800; 114 | } 115 | 116 | var fn = getCompiledFormatter(fmt); 117 | return fn.call(runtime, d, options.utc, tz, locale, formats, timestamp); 118 | } 119 | 120 | strftime.strftimeTZ = function(fmt, d, locale, timezone) { 121 | if ((typeof locale == 'number' || typeof locale == 'string') && timezone == null) { 122 | timezone = locale; 123 | locale = void 0; 124 | } 125 | if (locale === void 0) locale = cpDefaultLocale; 126 | return strftime(fmt, d, locale, { timezone: timezone }); 127 | }; 128 | 129 | strftime.strftimeUTC = function (fmt, d, locale) { 130 | if (locale === void 0) locale = cpDefaultLocale; 131 | return strftime(fmt, d, locale, { utc: true }); 132 | }; 133 | 134 | strftime.localizedStrftime = function(locale) { 135 | return function(fmt, d, options) { 136 | return strftime(fmt, d, locale, options); 137 | }; 138 | }; 139 | 140 | strftime.strftime = strftime; 141 | 142 | module.exports = strftime; 143 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Based on CoffeeScript by andrewschaaf on github 4 | // 5 | // TODO: 6 | // - past and future dates, especially < 1900 and > 2100 7 | // - locales 8 | // - look for edge cases 9 | 10 | var assert = require('assert') 11 | , libFilename = process.argv[2] || '../src/strftime.js' 12 | , lib = require(libFilename) 13 | 14 | // Tue, 07 Jun 2011 18:51:45 GMT 15 | , Time = new Date(1307472705067) 16 | 17 | assert.fn = function(value, msg) { 18 | assert.equal('function', typeof value, msg) 19 | } 20 | 21 | assert.format = function(format, expected, expectedUTC, time) { 22 | time = time || Time 23 | function _assertFmt(expected, name) { 24 | name = name || 'strftime' 25 | var actual = lib[name](format, time) 26 | assert.equal(expected, actual, 27 | name + '("' + format + '", ' + time + ') is ' + JSON.stringify(actual) 28 | + ', expected ' + JSON.stringify(expected)) 29 | } 30 | if (expected) _assertFmt(expected, 'strftime') 31 | _assertFmt(expectedUTC || expected, 'strftimeUTC') 32 | } 33 | 34 | /// check exports 35 | assert.fn(lib.strftime) 36 | assert.fn(lib.strftimeUTC) 37 | assert.fn(lib.localizedStrftime) 38 | ok('Exports') 39 | 40 | /// time zones 41 | if (process.env.TZ == 'America/Vancouver') { 42 | testTimezone('P[DS]T') 43 | assert.format('%C', '01', '01', new Date(100, 0, 1)) 44 | assert.format('%j', '097', '098', new Date(1365390736236)) 45 | ok('Time zones (' + process.env.TZ + ')') 46 | } 47 | else if (process.env.TZ == 'CET') { 48 | testTimezone('CES?T') 49 | assert.format('%C', '01', '00', new Date(100, 0, 1)) 50 | assert.format('%j', '098', '098', new Date(1365390736236)) 51 | ok('Time zones (' + process.env.TZ + ')') 52 | } 53 | else { 54 | console.log('(Current timezone has no tests: ' + (process.env.TZ || 'none') + ')') 55 | } 56 | 57 | /// check all formats in GMT, most coverage 58 | assert.format('%A', 'Tuesday') 59 | assert.format('%a', 'Tue') 60 | assert.format('%B', 'June') 61 | assert.format('%b', 'Jun') 62 | assert.format('%C', '20') 63 | assert.format('%D', '06/07/11') 64 | assert.format('%d', '07') 65 | assert.format('%-d', '7') 66 | assert.format('%_d', ' 7') 67 | assert.format('%0d', '07') 68 | assert.format('%e', '7') 69 | assert.format('%F', '2011-06-07') 70 | assert.format('%H', null, '18') 71 | assert.format('%h', 'Jun') 72 | assert.format('%I', null, '06') 73 | assert.format('%-I', null, '6') 74 | assert.format('%_I', null, ' 6') 75 | assert.format('%0I', null, '06') 76 | assert.format('%j', null, '158') 77 | assert.format('%k', null, '18') 78 | assert.format('%L', '067') 79 | assert.format('%l', null, ' 6') 80 | assert.format('%-l', null, '6') 81 | assert.format('%_l', null, ' 6') 82 | assert.format('%0l', null, '06') 83 | assert.format('%M', null, '51') 84 | assert.format('%m', '06') 85 | assert.format('%n', '\n') 86 | assert.format('%o', '7th') 87 | assert.format('%P', null, 'pm') 88 | assert.format('%p', null, 'PM') 89 | assert.format('%R', null, '18:51') 90 | assert.format('%r', null, '06:51:45 PM') 91 | assert.format('%S', '45') 92 | assert.format('%s', '1307472705') 93 | assert.format('%T', null, '18:51:45') 94 | assert.format('%t', '\t') 95 | assert.format('%U', '23') 96 | assert.format('%U', '24', null, new Date(+Time + 5 * 86400000)) 97 | assert.format('%u', '2') 98 | assert.format('%v', '7-Jun-2011') 99 | assert.format('%W', '23') 100 | assert.format('%W', '23', null, new Date(+Time + 5 * 86400000)) 101 | assert.format('%w', '2') 102 | assert.format('%Y', '2011') 103 | assert.format('%y', '11') 104 | assert.format('%Z', null, 'GMT') 105 | assert.format('%z', null, '+0000') 106 | assert.format('%%', '%') // any other char 107 | ok('GMT') 108 | 109 | 110 | /// locales 111 | 112 | var it_IT = 113 | { days: words('domenica lunedi martedi mercoledi giovedi venerdi sabato') 114 | , shortDays: words('dom lun mar mer gio ven sab') 115 | , months: words('gennaio febbraio marzo aprile maggio giugno luglio agosto settembre ottobre novembre dicembre') 116 | , shortMonths: words('gen feb mar apr mag giu lug ago set ott nov dic') 117 | , AM: 'it$AM' 118 | , PM: 'it$PM' 119 | , am: 'it$am' 120 | , pm: 'it$pm' 121 | , formats: { 122 | D: 'it$%m/%d/%y' 123 | , F: 'it$%Y-%m-%d' 124 | , R: 'it$%H:%M' 125 | , r: 'it$%I:%M:%S %p' 126 | , T: 'it$%H:%M:%S' 127 | , v: 'it$%e-%b-%Y' 128 | } 129 | } 130 | 131 | assert.format_it = function(format, expected, expectedUTC) { 132 | function _assertFmt(expected, name) { 133 | name = name || 'strftime' 134 | var actual = lib[name](format, Time, it_IT) 135 | assert.equal(expected, actual, 136 | name + '("' + format + '", Time) is ' + JSON.stringify(actual) 137 | + ', expected ' + JSON.stringify(expected)) 138 | } 139 | 140 | if (expected) _assertFmt(expected, 'strftime') 141 | _assertFmt(expectedUTC || expected, 'strftimeUTC') 142 | } 143 | 144 | assert.format_it('%A', 'martedi') 145 | assert.format_it('%a', 'mar') 146 | assert.format_it('%B', 'giugno') 147 | assert.format_it('%b', 'giu') 148 | assert.format_it('%D', 'it$06/07/11') 149 | assert.format_it('%F', 'it$2011-06-07') 150 | assert.format_it('%p', null, 'it$PM') 151 | assert.format_it('%P', null, 'it$pm') 152 | assert.format_it('%R', null, 'it$18:51') 153 | assert.format_it('%r', null, 'it$06:51:45 it$PM') 154 | assert.format_it('%T', null, 'it$18:51:45') 155 | assert.format_it('%v', 'it$7-giu-2011') 156 | ok('Localization') 157 | 158 | 159 | /// timezones 160 | 161 | assert.formatTZ = function(format, expected, tz, time) { 162 | time = time || Time; 163 | var actual = lib.strftimeTZ(format, time, tz) 164 | assert.equal( 165 | expected, actual, 166 | ('strftime("' + format + '", ' + time + ') is ' + JSON.stringify(actual) + ', expected ' + JSON.stringify(expected)) 167 | ) 168 | } 169 | 170 | assert.formatTZ('%F %r %z', '2011-06-07 06:51:45 PM +0000', 0) 171 | assert.formatTZ('%F %r %z', '2011-06-07 06:51:45 PM +0000', '+0000') 172 | assert.formatTZ('%F %r %z', '2011-06-07 08:51:45 PM +0200', 120) 173 | assert.formatTZ('%F %r %z', '2011-06-07 08:51:45 PM +0200', '+0200') 174 | assert.formatTZ('%F %r %z', '2011-06-07 11:51:45 AM -0700', -420) 175 | assert.formatTZ('%F %r %z', '2011-06-07 11:51:45 AM -0700', '-0700') 176 | ok('Time zone offset') 177 | 178 | 179 | /// helpers 180 | 181 | function words(s) { return (s || '').split(' '); } 182 | 183 | function ok(s) { console.log('[ \033[32mOK\033[0m ] ' + s) } 184 | 185 | // Pass a regex or string that matches the timezone abbrev, e.g. %Z above. 186 | // Don't pass GMT! Every date includes it and it will fail. 187 | // Be careful if you pass a regex, it has to quack like the default one. 188 | function testTimezone(regex) { 189 | regex = typeof regex === 'string' ? RegExp('\\((' + regex + ')\\)$') : regex 190 | var match = Time.toString().match(regex) 191 | if (match) { 192 | var off = Time.getTimezoneOffset() 193 | , hourOff = off / 60 194 | , hourDiff = Math.floor(hourOff) 195 | , hours = 18 - hourDiff 196 | , padSpace24 = hours < 10 ? ' ' : '' 197 | , padZero24 = hours < 10 ? '0' : '' 198 | , hour24 = String(hours) 199 | , padSpace12 = (hours % 12) < 10 ? ' ' : '' 200 | , padZero12 = (hours % 12) < 10 ? '0' : '' 201 | , hour12 = String(hours % 12) 202 | , sign = hourDiff < 0 ? '+' : '-' 203 | , minDiff = Time.getTimezoneOffset() - (hourDiff * 60) 204 | , mins = String(51 - minDiff) 205 | , tz = match[1] 206 | , ampm = hour12 == hour24 ? 'AM' : 'PM' 207 | , R = hour24 + ':' + mins 208 | , r = padZero12 + hour12 + ':' + mins + ':45 ' + ampm 209 | , T = R + ':45' 210 | assert.format('%H', padZero24 + hour24, '18') 211 | assert.format('%I', padZero12 + hour12, '06') 212 | assert.format('%k', padSpace24 + hour24, '18') 213 | assert.format('%l', padSpace12 + hour12, ' 6') 214 | assert.format('%M', mins) 215 | assert.format('%P', ampm.toLowerCase(), 'pm') 216 | assert.format('%p', ampm, 'PM') 217 | assert.format('%R', R, '18:51') 218 | assert.format('%r', r, '06:51:45 PM') 219 | assert.format('%T', T, '18:51:45') 220 | assert.format('%Z', tz, 'GMT') 221 | assert.format('%z', sign + '0' + Math.abs(hourDiff) + '00', '+0000') 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/parse.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* 3 | Copyright (c) 2014 Petka Antonov 4 | 5 | With parts by Sami Samhuri 6 | Copyright 2010 - 2014 Sami Samhuri under the terms of the MIT license found 7 | at http://sjs.mit-license.org/ 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | */ 27 | function compileComponents(components) { 28 | var componentStr = ""; 29 | for (var i = 0; i < components.length - 1; ++i) { 30 | componentStr += "(" + components[i] + ") +\n"; 31 | } 32 | if (components.length > 0) { 33 | componentStr += "(" + components[i] + ");" 34 | } 35 | var body = "var $tmp;\n\ 36 | return '' + " + componentStr; 37 | 38 | return new Function("d", "utc", "tz", "locale", "formats", "timestamp", body); 39 | } 40 | 41 | function parse(fmtString) { 42 | var len = fmtString.length; 43 | var components = []; 44 | var curStr = ""; 45 | for(var i = 0; i < len; ++i) { 46 | var ch = fmtString.charCodeAt(i); 47 | var padding = 0; 48 | if (ch === 37 /*%*/) { 49 | if (++i >= len) { 50 | curStr += "%"; 51 | break; 52 | } 53 | ch = fmtString.charCodeAt(i); 54 | var formatter = null; 55 | if (ch === 45 /*-*/) { 56 | if (++i >= len) { 57 | curStr += "%-"; 58 | break; 59 | } 60 | padding = -1; 61 | formatter = getFormatter((ch = fmtString.charCodeAt(i))); 62 | if (formatter === null) { 63 | curStr += ("%-" + String.fromCharCode(ch)); 64 | continue; 65 | } 66 | } else if (ch === 95 /*_*/) { 67 | if (++i >= len) { 68 | curStr += "%_"; 69 | break; 70 | } 71 | padding = 1; 72 | formatter = getFormatter((ch = fmtString.charCodeAt(i))); 73 | if (formatter === null) { 74 | curStr += ("%_" + String.fromCharCode(ch)); 75 | continue; 76 | } 77 | } else if (ch !== 48 /*0*/) { 78 | formatter = getFormatter(ch); 79 | if (formatter === null) { 80 | curStr += ("%" + ch === 37 ? '' : String.fromCharCode(ch)); 81 | continue; 82 | } 83 | } else { 84 | if (++i >= len) { 85 | curStr += "%0"; 86 | break; 87 | } 88 | padding = 2; 89 | formatter = getFormatter((ch = fmtString.charCodeAt(i))); 90 | if (formatter === null) { 91 | curStr += ("%0" + String.fromCharCode(ch)); 92 | continue; 93 | } 94 | } 95 | 96 | if (curStr.length > 0) { 97 | components.push(new StringFormatter(curStr)); 98 | } 99 | components.push(new formatter(ch, padding)); 100 | curStr = ""; 101 | 102 | 103 | } else if (ch === 39/*'*/ || ch === 92/*\*/) { 104 | curStr += "\\" + String.fromCharCode(ch) 105 | } else { 106 | curStr += String.fromCharCode(ch); 107 | } 108 | } 109 | if (curStr.length > 0) { 110 | components.push(new StringFormatter(curStr)); 111 | } 112 | return compileComponents(components); 113 | } 114 | 115 | function getFormatter(ch) { 116 | if (ch > 127) return null; 117 | return formatters[ch]; 118 | } 119 | 120 | var formatters = new Array(128); 121 | for (var i = 0; i < formatters.length; ++i) { 122 | formatters[i] = null; 123 | } 124 | 125 | formatters['A'.charCodeAt(0)] = 126 | formatters['a'.charCodeAt(0)] = 127 | formatters['B'.charCodeAt(0)] = 128 | formatters['b'.charCodeAt(0)] = 129 | formatters['C'.charCodeAt(0)] = 130 | formatters['D'.charCodeAt(0)] = 131 | formatters['d'.charCodeAt(0)] = 132 | formatters['e'.charCodeAt(0)] = 133 | formatters['F'.charCodeAt(0)] = 134 | formatters['H'.charCodeAt(0)] = 135 | formatters['h'.charCodeAt(0)] = 136 | formatters['I'.charCodeAt(0)] = 137 | formatters['j'.charCodeAt(0)] = 138 | formatters['k'.charCodeAt(0)] = 139 | formatters['L'.charCodeAt(0)] = 140 | formatters['l'.charCodeAt(0)] = 141 | formatters['M'.charCodeAt(0)] = 142 | formatters['m'.charCodeAt(0)] = 143 | formatters['n'.charCodeAt(0)] = 144 | formatters['o'.charCodeAt(0)] = 145 | formatters['P'.charCodeAt(0)] = 146 | formatters['p'.charCodeAt(0)] = 147 | formatters['R'.charCodeAt(0)] = 148 | formatters['r'.charCodeAt(0)] = 149 | formatters['S'.charCodeAt(0)] = 150 | formatters['s'.charCodeAt(0)] = 151 | formatters['T'.charCodeAt(0)] = 152 | formatters['t'.charCodeAt(0)] = 153 | formatters['U'.charCodeAt(0)] = 154 | formatters['u'.charCodeAt(0)] = 155 | formatters['v'.charCodeAt(0)] = 156 | formatters['W'.charCodeAt(0)] = 157 | formatters['w'.charCodeAt(0)] = 158 | formatters['Y'.charCodeAt(0)] = 159 | formatters['y'.charCodeAt(0)] = 160 | formatters['Z'.charCodeAt(0)] = 161 | formatters['z'.charCodeAt(0)] = DateFormatter; 162 | 163 | function StringFormatter(string) { 164 | this.string = string; 165 | } 166 | 167 | StringFormatter.prototype.toString = function() { 168 | return "'" + this.string + "'"; 169 | }; 170 | 171 | function DateFormatter(flag, paddingCh) { 172 | this.flag = flag; 173 | this.paddingCh = paddingCh; 174 | } 175 | 176 | DateFormatter.prototype.toString = function() { 177 | var p = this.paddingCh; 178 | var month = "this.pad(d.getMonth() + 1, "+p+", 2)"; 179 | var day = "this.pad(d.getDate(), "+p+", 2)"; 180 | var dayUnpadded = "d.getDate()"; 181 | var year = "('' + (d.getFullYear() % 100|0))"; 182 | var fullYear = "d.getFullYear()"; 183 | var timestamp = "d.getTime()"; 184 | 185 | var hour24 = "this.pad(d.getHours(), "+p+", 2)"; 186 | var hour12 = "this.pad(this.to12(d.getHours()), "+p+", 2)"; 187 | var minutes = "this.pad(d.getMinutes(), "+p+", 2)"; 188 | var seconds = "this.pad(d.getSeconds(), "+p+", 2)"; 189 | 190 | var amPm = "(d.getHours() < 12 ? locale.AM : locale.PM)"; 191 | var shortMonth = "locale.shortMonths[d.getMonth()]"; 192 | 193 | switch(this.flag) { 194 | case 65: 195 | return "locale.days[d.getDay()]"; 196 | break; 197 | case 97: 198 | return "locale.shortDays[d.getDay()]"; 199 | break; 200 | case 66: 201 | return "locale.months[d.getMonth()]"; 202 | break; 203 | case 98: 204 | return shortMonth; 205 | break; 206 | case 67: 207 | return "this.pad(d.getFullYear() / 100 | 0, " + p + ", 2)"; 208 | break; 209 | case 68: 210 | return "(($tmp = formats.D) !== void 0\n\ 211 | ? this.strftime($tmp, d, locale)\n\ 212 | : "+month+"+'/'+"+day+"+'/'+"+ year + ")"; 213 | break; 214 | case 100: 215 | return day; 216 | break; 217 | case 101: 218 | return dayUnpadded; 219 | break; 220 | case 70: 221 | return "(($tmp = formats.F) !== void 0\n\ 222 | ? this.strftime($tmp, d, locale)\n\ 223 | : "+fullYear+"+'-'+"+month+"+'-'+" + day+ ")"; 224 | break; 225 | case 72: 226 | return hour24; 227 | break; 228 | case 104: 229 | return "locale.shortMonths[d.getMonth()]"; 230 | break; 231 | case 73: 232 | return hour12; 233 | break; 234 | case 106: 235 | return "(($tmp = new Date(d.getFullYear(), 0, 1)), \n\ 236 | ($tmp = Math.ceil((d.getTime() - $tmp.getTime()) / 86400000)), \n\ 237 | (this.pad($tmp, 0, 3)))"; 238 | break; 239 | case 107: 240 | return p === 0 241 | ? "this.pad(d.getHours(), 1, 2)" 242 | : "this.pad(d.getHours(), "+p+", 2)"; 243 | break; 244 | case 76: 245 | return "this.pad(timestamp % 1000|0, 0, 3)"; 246 | break; 247 | case 108: 248 | return p === 0 249 | ? "this.pad(this.to12(d.getHours()), 1, 2)" 250 | : "this.pad(this.to12(d.getHours()), "+p+", 2)"; 251 | break; 252 | case 77: 253 | return minutes; 254 | break; 255 | case 109: 256 | return month; 257 | break; 258 | case 110: 259 | return "'\\n'"; 260 | break; 261 | case 111: 262 | return "this.ord[d.getDate() - 1]"; 263 | break; 264 | case 80: 265 | return "(d.getHours() < 12 ? locale.am : locale.pm)"; 266 | break; 267 | case 112: 268 | return amPm; 269 | break; 270 | case 82: 271 | return "(($tmp = formats.R) !== void 0\n\ 272 | ? this.strftime($tmp, d, locale)\n\ 273 | : "+hour24+"+':'+"+minutes +")"; 274 | break; 275 | case 114: 276 | return "(($tmp = formats.r) !== void 0\n\ 277 | ? this.strftime($tmp, d, locale)\n\ 278 | : "+hour12+"+':'+"+minutes+"+':'+"+seconds+"+' '+"+amPm+")"; 279 | break; 280 | case 83: 281 | return seconds; 282 | break; 283 | case 115: 284 | return "timestamp/1000|0"; 285 | break; 286 | case 84: 287 | return "(($tmp = formats.T) !== void 0\n\ 288 | ? this.strftime($tmp, d, locale)\n\ 289 | : "+hour24+"+':'+"+minutes+"+':'+"+seconds+")"; 290 | break; 291 | case 116: 292 | return "'\\t'"; 293 | break; 294 | case 85: 295 | return "this.pad(this.weekNumber(d, 'sunday'), "+p+", 2)"; 296 | break; 297 | case 117: 298 | return "($tmp = d.getDay(), $tmp == 0 ? '7' : '' + $tmp)"; 299 | break; 300 | case 118: 301 | return "(($tmp = formats.v) !== void 0\n\ 302 | ? this.strftime($tmp, d, locale)\n\ 303 | : "+dayUnpadded+"+'-'+"+shortMonth+"+'-'+" + fullYear+ ")"; 304 | break; 305 | case 87: 306 | return "this.pad(this.weekNumber(d, 'monday'), "+p+", 2)"; 307 | break; 308 | case 119: 309 | return "d.getDay()"; 310 | break; 311 | case 89: 312 | return fullYear; 313 | break; 314 | case 121: 315 | return year; 316 | break; 317 | case 90: 318 | // TODO use faster method 319 | return "(utc ? 'GMT' : \n\ 320 | ($tmp = d.toString().match(/\((\w+)\)/),\n\ 321 | $tmp && $tmp[1] || ''))"; 322 | break; 323 | case 122: 324 | return "(utc\n\ 325 | ? '+0000'\n\ 326 | : this.formatTz(tz === -1800 ? -d.getTimezoneOffset() : tz))"; 327 | break; 328 | } 329 | }; 330 | module.exports = parse; 331 | --------------------------------------------------------------------------------