Skip to content

Commit 3e397ff

Browse files
committed
Added fecha.strict.js for strict date parsing
1 parent 65054e2 commit 3e397ff

File tree

4 files changed

+542
-2
lines changed

4 files changed

+542
-2
lines changed

fecha.strict.js

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
(function (main) {
2+
'use strict';
3+
4+
/**
5+
* Parse or format dates
6+
* @class fecha
7+
*/
8+
var fecha = {};
9+
var token = /d{1,4}|M{1,4}|YY(?:YY)?|S{1,3}|Do|ZZ|([HhMsDm])\1?|[aA]|"[^"]*"|'[^']*'/g;
10+
var twoDigits = '\\d\\d?';
11+
var threeDigits = '\\d{3}';
12+
var fourDigits = '\\d{4}';
13+
var word = '[^\\s]+';
14+
var literal = /\[([^]*?)\]/gm;
15+
var noop = function () {
16+
};
17+
18+
function regexEscape(str) {
19+
return str.replace( /[|\\{()[^$+*?.-]/g, '\\$&');
20+
}
21+
22+
function shorten(arr, sLen) {
23+
var newArr = [];
24+
for (var i = 0, len = arr.length; i < len; i++) {
25+
newArr.push(arr[i].substr(0, sLen));
26+
}
27+
return newArr;
28+
}
29+
30+
function monthUpdate(arrName) {
31+
return function (d, v, i18n) {
32+
var index = i18n[arrName].indexOf(v.charAt(0).toUpperCase() + v.substr(1).toLowerCase());
33+
if (~index) {
34+
d.month = index;
35+
}
36+
};
37+
}
38+
39+
function pad(val, len) {
40+
val = String(val);
41+
len = len || 2;
42+
while (val.length < len) {
43+
val = '0' + val;
44+
}
45+
return val;
46+
}
47+
48+
var dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
49+
var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
50+
var monthNamesShort = shorten(monthNames, 3);
51+
var dayNamesShort = shorten(dayNames, 3);
52+
fecha.i18n = {
53+
dayNamesShort: dayNamesShort,
54+
dayNames: dayNames,
55+
monthNamesShort: monthNamesShort,
56+
monthNames: monthNames,
57+
amPm: ['am', 'pm'],
58+
DoFn: function DoFn(D) {
59+
return D + ['th', 'st', 'nd', 'rd'][D % 10 > 3 ? 0 : (D - D % 10 !== 10) * D % 10];
60+
}
61+
};
62+
63+
var formatFlags = {
64+
D: function(dateObj) {
65+
return dateObj.getDate();
66+
},
67+
DD: function(dateObj) {
68+
return pad(dateObj.getDate());
69+
},
70+
Do: function(dateObj, i18n) {
71+
return i18n.DoFn(dateObj.getDate());
72+
},
73+
d: function(dateObj) {
74+
return dateObj.getDay();
75+
},
76+
dd: function(dateObj) {
77+
return pad(dateObj.getDay());
78+
},
79+
ddd: function(dateObj, i18n) {
80+
return i18n.dayNamesShort[dateObj.getDay()];
81+
},
82+
dddd: function(dateObj, i18n) {
83+
return i18n.dayNames[dateObj.getDay()];
84+
},
85+
M: function(dateObj) {
86+
return dateObj.getMonth() + 1;
87+
},
88+
MM: function(dateObj) {
89+
return pad(dateObj.getMonth() + 1);
90+
},
91+
MMM: function(dateObj, i18n) {
92+
return i18n.monthNamesShort[dateObj.getMonth()];
93+
},
94+
MMMM: function(dateObj, i18n) {
95+
return i18n.monthNames[dateObj.getMonth()];
96+
},
97+
YY: function(dateObj) {
98+
return String(dateObj.getFullYear()).substr(2);
99+
},
100+
YYYY: function(dateObj) {
101+
return dateObj.getFullYear();
102+
},
103+
h: function(dateObj) {
104+
return dateObj.getHours() % 12 || 12;
105+
},
106+
hh: function(dateObj) {
107+
return pad(dateObj.getHours() % 12 || 12);
108+
},
109+
H: function(dateObj) {
110+
return dateObj.getHours();
111+
},
112+
HH: function(dateObj) {
113+
return pad(dateObj.getHours());
114+
},
115+
m: function(dateObj) {
116+
return dateObj.getMinutes();
117+
},
118+
mm: function(dateObj) {
119+
return pad(dateObj.getMinutes());
120+
},
121+
s: function(dateObj) {
122+
return dateObj.getSeconds();
123+
},
124+
ss: function(dateObj) {
125+
return pad(dateObj.getSeconds());
126+
},
127+
S: function(dateObj) {
128+
return Math.round(dateObj.getMilliseconds() / 100);
129+
},
130+
SS: function(dateObj) {
131+
return pad(Math.round(dateObj.getMilliseconds() / 10), 2);
132+
},
133+
SSS: function(dateObj) {
134+
return pad(dateObj.getMilliseconds(), 3);
135+
},
136+
a: function(dateObj, i18n) {
137+
return dateObj.getHours() < 12 ? i18n.amPm[0] : i18n.amPm[1];
138+
},
139+
A: function(dateObj, i18n) {
140+
return dateObj.getHours() < 12 ? i18n.amPm[0].toUpperCase() : i18n.amPm[1].toUpperCase();
141+
},
142+
ZZ: function(dateObj) {
143+
var o = dateObj.getTimezoneOffset();
144+
return (o > 0 ? '-' : '+') + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4);
145+
}
146+
};
147+
148+
var parseFlags = {
149+
D: [twoDigits, function (d, v) {
150+
d.day = v;
151+
}],
152+
Do: [twoDigits + word, function (d, v) {
153+
d.day = parseInt(v, 10);
154+
}],
155+
M: [twoDigits, function (d, v) {
156+
d.month = v - 1;
157+
}],
158+
YY: [twoDigits, function (d, v) {
159+
var da = new Date(), cent = +('' + da.getFullYear()).substr(0, 2);
160+
d.year = '' + (v > 68 ? cent - 1 : cent) + v;
161+
}],
162+
h: [twoDigits, function (d, v) {
163+
d.hour = v;
164+
}],
165+
m: [twoDigits, function (d, v) {
166+
d.minute = v;
167+
}],
168+
s: [twoDigits, function (d, v) {
169+
d.second = v;
170+
}],
171+
YYYY: [fourDigits, function (d, v) {
172+
d.year = v;
173+
}],
174+
S: ['\\d', function (d, v) {
175+
d.millisecond = v * 100;
176+
}],
177+
SS: ['\\d{2}', function (d, v) {
178+
d.millisecond = v * 10;
179+
}],
180+
SSS: [threeDigits, function (d, v) {
181+
d.millisecond = v;
182+
}],
183+
d: [twoDigits, noop],
184+
ddd: [word, noop],
185+
MMM: [word, monthUpdate('monthNamesShort')],
186+
MMMM: [word, monthUpdate('monthNames')],
187+
a: [word, function (d, v, i18n) {
188+
var val = v.toLowerCase();
189+
if (val === i18n.amPm[0]) {
190+
d.isPm = false;
191+
} else if (val === i18n.amPm[1]) {
192+
d.isPm = true;
193+
}
194+
}],
195+
ZZ: ['[^\\s]*?[\\+\\-]\\d\\d:?\\d\\d', function (d, v) {
196+
var parts = (v + '').match(/([\+\-]|\d\d)/gi), minutes;
197+
198+
if (parts) {
199+
minutes = +(parts[1] * 60) + parseInt(parts[2], 10);
200+
d.timezoneOffset = parts[0] === '+' ? minutes : -minutes;
201+
}
202+
}]
203+
};
204+
parseFlags.dd = parseFlags.d;
205+
parseFlags.dddd = parseFlags.ddd;
206+
parseFlags.DD = parseFlags.D;
207+
parseFlags.mm = parseFlags.m;
208+
parseFlags.hh = parseFlags.H = parseFlags.HH = parseFlags.h;
209+
parseFlags.MM = parseFlags.M;
210+
parseFlags.ss = parseFlags.s;
211+
parseFlags.A = parseFlags.a;
212+
213+
214+
// Some common format strings
215+
fecha.masks = {
216+
default: 'ddd MMM DD YYYY HH:mm:ss',
217+
shortDate: 'M/D/YY',
218+
mediumDate: 'MMM D, YYYY',
219+
longDate: 'MMMM D, YYYY',
220+
fullDate: 'dddd, MMMM D, YYYY',
221+
shortTime: 'HH:mm',
222+
mediumTime: 'HH:mm:ss',
223+
longTime: 'HH:mm:ss.SSS'
224+
};
225+
226+
/***
227+
* Format a date
228+
* @method format
229+
* @param {Date|number} dateObj
230+
* @param {string} mask Format of the date, i.e. 'mm-dd-yy' or 'shortDate'
231+
*/
232+
fecha.format = function (dateObj, mask, i18nSettings) {
233+
var i18n = i18nSettings || fecha.i18n;
234+
235+
if (typeof dateObj === 'number') {
236+
dateObj = new Date(dateObj);
237+
}
238+
239+
if (Object.prototype.toString.call(dateObj) !== '[object Date]' || isNaN(dateObj.getTime())) {
240+
throw new Error('Invalid Date in fecha.format');
241+
}
242+
243+
mask = fecha.masks[mask] || mask || fecha.masks['default'];
244+
245+
var literals = [];
246+
247+
// Make literals inactive by replacing them with ??
248+
mask = mask.replace(literal, function($0, $1) {
249+
literals.push($1);
250+
return '??';
251+
});
252+
// Apply formatting rules
253+
mask = mask.replace(token, function ($0) {
254+
return $0 in formatFlags ? formatFlags[$0](dateObj, i18n) : $0.slice(1, $0.length - 1);
255+
});
256+
// Inline literal values back into the formatted value
257+
return mask.replace(/\?\?/g, function() {
258+
return literals.shift();
259+
});
260+
};
261+
262+
/**
263+
* Parse a date string into an object, changes - into /
264+
* @method parse
265+
* @param {string} dateStr Date string
266+
* @param {string} format Date parse format
267+
* @returns {Date|boolean}
268+
*/
269+
fecha.parse = function (dateStr, format, i18nSettings) {
270+
var i18n = i18nSettings || fecha.i18n;
271+
272+
if (typeof format !== 'string') {
273+
throw new Error('Invalid format in fecha.parse');
274+
}
275+
276+
format = fecha.masks[format] || format;
277+
278+
// Avoid regular expression denial of service, fail early for really long strings
279+
// https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS
280+
if (dateStr.length > 1000) {
281+
return null;
282+
}
283+
284+
var dateInfo = {};
285+
var parseInfo = [];
286+
var newFormat = regexEscape(format).replace(token, function ($0) {
287+
if (parseFlags[$0]) {
288+
var info = parseFlags[$0];
289+
parseInfo.push(info[1]);
290+
return '(' + info[0] + ')';
291+
}
292+
293+
return $0;
294+
});
295+
var matches = dateStr.match(new RegExp(newFormat, 'i'));
296+
if (!matches) {
297+
return null;
298+
}
299+
300+
for (var i = 1; i < matches.length; i++) {
301+
parseInfo[i - 1](dateInfo, matches[i], i18n);
302+
}
303+
304+
var today = new Date();
305+
if (dateInfo.isPm === true && dateInfo.hour != null && +dateInfo.hour !== 12) {
306+
dateInfo.hour = +dateInfo.hour + 12;
307+
} else if (dateInfo.isPm === false && +dateInfo.hour === 12) {
308+
dateInfo.hour = 0;
309+
}
310+
311+
var date;
312+
if (dateInfo.timezoneOffset != null) {
313+
dateInfo.minute = +(dateInfo.minute || 0) - +dateInfo.timezoneOffset;
314+
date = new Date(Date.UTC(dateInfo.year || today.getFullYear(), dateInfo.month || 0, dateInfo.day || 1,
315+
dateInfo.hour || 0, dateInfo.minute || 0, dateInfo.second || 0, dateInfo.millisecond || 0));
316+
} else {
317+
date = new Date(dateInfo.year || today.getFullYear(), dateInfo.month || 0, dateInfo.day || 1,
318+
dateInfo.hour || 0, dateInfo.minute || 0, dateInfo.second || 0, dateInfo.millisecond || 0);
319+
}
320+
return date;
321+
};
322+
323+
/* istanbul ignore next */
324+
if (typeof module !== 'undefined' && module.exports) {
325+
module.exports = fecha;
326+
} else if (typeof define === 'function' && define.amd) {
327+
define(function () {
328+
return fecha;
329+
});
330+
} else {
331+
main.fecha = fecha;
332+
}
333+
})(this);

fecha.strict.min.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
"description": "Date formatting and parsing",
55
"main": "fecha.js",
66
"scripts": {
7-
"test": "eslint fecha.js && nyc --cache --reporter=text node test.js",
8-
"build": "uglifyjs fecha.js -m -o fecha.min.js"
7+
"test": "eslint fecha.js fecha.strict.js && nyc --cache --reporter=text painless test.*",
8+
"build": "uglifyjs fecha.js -m -o fecha.min.js && uglifyjs fecha.strict.js -m -o fecha.strict.min.js"
99
},
1010
"repository": {
1111
"type": "git",

0 commit comments

Comments
 (0)