Skip to content

Commit 00fd55b

Browse files
committed
Merge branch 'strtotime' of github.com:mfaber/phpjs into mfaber-strtotime
Conflicts: functions/datetime/strtotime.js
2 parents c5a4b9d + 529bbf1 commit 00fd55b

File tree

1 file changed

+205
-115
lines changed

1 file changed

+205
-115
lines changed

functions/datetime/strtotime.js

Lines changed: 205 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,225 @@
1+
function strtotime (text, now) {
2+
// Convert string representation of date and time to a timestamp
3+
//
4+
// version: 1109.2016
5+
// discuss at: http://phpjs.org/functions/strtotime
6+
// + original by: Caio Ariede (http://caioariede.com)
7+
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
8+
// + input by: David
9+
// + improved by: Caio Ariede (http://caioariede.com)
10+
// + bugfixed by: Wagner B. Soares
11+
// + bugfixed by: Artur Tchernychev
12+
// + improved by: A. Matías Quezada (http://amatiasq.com)
13+
// + improved by: preuter
14+
// + improved by: Brett Zamir (http://brett-zamir.me)
15+
// + improved by: Mirko Faber
16+
// % note 1: Examples all have a fixed timestamp to prevent tests to fail because of variable time(zones)
17+
// * example 1: strtotime('+1 day', 1129633200);
18+
// * returns 1: 1129719600
19+
// * example 2: strtotime('+1 week 2 days 4 hours 2 seconds', 1129633200);
20+
// * returns 2: 1130425202
21+
// * example 3: strtotime('last month', 1129633200);
22+
// * returns 3: 1127041200
23+
// * example 4: strtotime('2009-05-04 08:30:00 GMT');
24+
// * returns 4: 1241425800
25+
var parsed, match, year, date, days, ranges, len, times, regex, i;
26+
27+
if (!text) {
28+
return null;
29+
}
130

2-
function strtotime(text, now) {
3-
// Convert string representation of date and time to a timestamp
4-
//
5-
// version: 1109.2015
6-
// discuss at: http://phpjs.org/functions/strtotime
7-
// + original by: Caio Ariede (http://caioariede.com)
8-
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
9-
// + input by: David
10-
// + improved by: Caio Ariede (http://caioariede.com)
11-
// + bugfixed by: Wagner B. Soares
12-
// + bugfixed by: Artur Tchernychev
13-
// + improved by: A. Matías Quezada (http://amatiasq.com)
14-
// + improved by: preuter
15-
// + improved by: Brett Zamir (http://brett-zamir.me)
16-
// % note 1: Examples all have a fixed timestamp to prevent tests to fail because of variable time(zones)
17-
// * example 1: strtotime('+1 day', 1129633200);
18-
// * returns 1: 1129719600
19-
// * example 2: strtotime('+1 week 2 days 4 hours 2 seconds', 1129633200);
20-
// * returns 2: 1130425202
21-
// * example 3: strtotime('last month', 1129633200);
22-
// * returns 3: 1127041200
23-
// * example 4: strtotime('2009-05-04 08:30:00');
24-
// * returns 4: 1241418600
25-
var parsed, match, year, date, days, ranges, len, times, regex, i;
26-
27-
if (!text) {
28-
return null;
29-
}
30-
31-
// Unecessary spaces
32-
text = text.replace(/^\s+|\s+$/g, '')
31+
// Unecessary spaces
32+
text = text.replace(/^\s+|\s+$/g, '')
3333
.replace(/\s{2,}/g, ' ')
3434
.replace(/[\t\r\n]/g, '')
3535
.toLowerCase();
3636

37-
if (text === 'now') {
38-
return now === null || isNaN(now) ? new Date().getTime() / 1000 | 0 : now | 0;
39-
}
40-
41-
match = text.match(/^(\d{2,4})-(\d{2})-(\d{2})(?:\s(\d{1,2}):(\d{2})(?::\d{2})?)?(?:\.(\d+)?)?$/);
42-
if (match) {
43-
year = match[1] >= 0 && match[1] <= 69 ? + match[1] + 2000 : match[1];
44-
return new Date(year, parseInt(match[2], 10) - 1, match[3],
45-
match[4] || 0, match[5] || 0, match[6] || 0, match[7] || 0) / 1000 | 0;
46-
}
47-
48-
date = now ? new Date(now * 1000) : new Date();
49-
days = {
50-
'sun': 0,
51-
'mon': 1,
52-
'tue': 2,
53-
'wed': 3,
54-
'thu': 4,
55-
'fri': 5,
56-
'sat': 6
57-
};
58-
ranges = {
59-
'yea': 'FullYear',
60-
'mon': 'Month',
61-
'day': 'Date',
62-
'hou': 'Hours',
63-
'min': 'Minutes',
64-
'sec': 'Seconds'
65-
};
66-
67-
function lastNext(type, range, modifier) {
68-
var diff, day = days[range];
69-
70-
if (typeof day !== 'undefined') {
71-
diff = day - date.getDay();
72-
73-
if (diff === 0) {
74-
diff = 7 * modifier;
75-
}
76-
else if (diff > 0 && type === 'last') {
77-
diff -= 7;
78-
}
79-
else if (diff < 0 && type === 'next') {
80-
diff += 7;
81-
}
82-
83-
date.setDate(date.getDate() + diff);
37+
// in contrast to php, js Date.parse function interprets:
38+
// dates given as yyyy-mm-dd as in timezone: UTC,
39+
// dates with "." or "-" as MDY instead of DMY
40+
// dates with two-digit years differently
41+
// etc...etc...
42+
// ...therefore we manually parse lots of common date formats
43+
var match = text.match(/^(\d{1,4})([\-\.\/\:])(\d{1,2})([\-\.\/\:])(\d{1,4})(?:\s(\d{1,2}):(\d{2})?:?(\d{2})?)?(?:\s([A-Z]+)?)?$/);
44+
if (match && match[2]==match[4]) {
45+
if (match[1]>1901) {
46+
switch (match[2]) {
47+
case '-': { // YYYY-M-D
48+
if (match[3]>12 | match[5]>31) return(0);
49+
return new Date(match[1], parseInt(match[3], 10) - 1, match[5],
50+
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
51+
break;
52+
}
53+
case '.': { // YYYY.M.D is not parsed by strtotime()
54+
return(0);
55+
break;
56+
}
57+
case '/': { // YYYY/M/D
58+
if (match[3]>12 | match[5]>31) return(0);
59+
return new Date(match[1], parseInt(match[3], 10) - 1, match[5],
60+
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
61+
break;
62+
}
63+
}
64+
}
65+
else if (match[5]>1901) {
66+
switch (match[2]) {
67+
case '-': { // D-M-YYYY
68+
if (match[3]>12 | match[1]>31) return(0);
69+
return new Date(match[5], parseInt(match[3], 10) - 1, match[1],
70+
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
71+
break;
72+
}
73+
case '.': { // D.M.YYYY
74+
if (match[3]>12 | match[1]>31) return(0);
75+
return new Date(match[5], parseInt(match[3], 10) - 1, match[1],
76+
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
77+
break;
78+
}
79+
case '/': { // M/D/YYYY
80+
if (match[1]>12 | match[3]>31) return(0);
81+
return new Date(match[5], parseInt(match[1], 10) - 1, match[3],
82+
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
83+
break;
84+
}
85+
}
86+
}
87+
else {
88+
switch (match[2]) {
89+
case '-': { // YY-M-D
90+
if (match[3]>12 | match[5]>31 | (match[1] < 70 & match[1]>38)) return(0);
91+
var year = match[1] >= 0 && match[1] <= 38 ? +match[1] + 2000 : match[1];
92+
return new Date(year, parseInt(match[3], 10) - 1, match[5],
93+
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
94+
break;
95+
}
96+
case '.': { // D.M.YY or H.MM.SS
97+
if (match[5]>=70) { // D.M.YY
98+
if (match[3]>12 | match[1]>31) return(0);
99+
return new Date(match[5], parseInt(match[3], 10) - 1, match[1],
100+
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
101+
}
102+
else if (match[5]<60 & !(match[6])) { // H.MM.SS
103+
if (match[1]>23 | match[3]>59) return(0);
104+
var today = new Date();
105+
return new Date(today.getFullYear(), today.getMonth(), today.getDate(),
106+
match[1] || 0, match[3] || 0, match[5] || 0, match[9] || 0) / 1000;
107+
}
108+
else return(0); // invalid format, cannot be parsed
109+
break;
110+
}
111+
case '/': { // M/D/YY
112+
if (match[1]>12 | match[3]>31 | (match[5] < 70 & match[5]>38)) return(0);
113+
var year = match[5] >= 0 && match[5] <= 38 ? +match[5] + 2000 : match[5];
114+
return new Date(year, parseInt(match[1], 10) - 1, match[3],
115+
match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
116+
break;
117+
}
118+
case ':': { // HH:MM:SS
119+
if (match[1]>23 | match[3]>59 | match[5]>59) return(0);
120+
var today = new Date();
121+
return new Date(today.getFullYear(), today.getMonth(), today.getDate(),
122+
match[1] || 0, match[3] || 0, match[5] || 0) / 1000;
123+
break;
124+
}
125+
}
126+
}
127+
}
128+
129+
130+
// other formats and "now" should be parsed by Date.parse()
131+
if (text === 'now') {
132+
return now === null || isNaN(now) ? new Date().getTime() / 1000 | 0 : now | 0;
133+
}
134+
if (!isNaN(parsed = Date.parse(text))) {
135+
return parsed / 1000 | 0;
136+
}
137+
138+
date = now ? new Date(now * 1000) : new Date();
139+
days = {
140+
'sun': 0,
141+
'mon': 1,
142+
'tue': 2,
143+
'wed': 3,
144+
'thu': 4,
145+
'fri': 5,
146+
'sat': 6
147+
};
148+
ranges = {
149+
'yea': 'FullYear',
150+
'mon': 'Month',
151+
'day': 'Date',
152+
'hou': 'Hours',
153+
'min': 'Minutes',
154+
'sec': 'Seconds'
155+
};
156+
157+
function lastNext(type, range, modifier) {
158+
var diff, day = days[range];
159+
160+
if (typeof day !== 'undefined') {
161+
diff = day - date.getDay();
162+
163+
if (diff === 0) {
164+
diff = 7 * modifier;
165+
}
166+
else if (diff > 0 && type === 'last') {
167+
diff -= 7;
168+
}
169+
else if (diff < 0 && type === 'next') {
170+
diff += 7;
171+
}
172+
173+
date.setDate(date.getDate() + diff);
174+
}
84175
}
85-
}
86-
function process(val) {
87-
var splt = val.split(' '), // Todo: Reconcile this with regex using \s, taking into account browser issues with split and regexes
176+
function process(val) {
177+
var splt = val.split(' '), // Todo: Reconcile this with regex using \s, taking into account browser issues with split and regexes
88178
type = splt[0],
89179
range = splt[1].substring(0, 3),
90180
typeIsNumber = /\d+/.test(type),
91181
ago = splt[2] === 'ago',
92182
num = (type === 'last' ? -1 : 1) * (ago ? -1 : 1);
93183

94-
if (typeIsNumber) {
95-
num *= parseInt(type, 10);
184+
if (typeIsNumber) {
185+
num *= parseInt(type, 10);
186+
}
187+
188+
if (ranges.hasOwnProperty(range) && !splt[1].match(/^mon(day|\.)?$/i)) {
189+
return date['set' + ranges[range]](date['get' + ranges[range]]() + num);
190+
}
191+
if (range === 'wee') {
192+
return date.setDate(date.getDate() + (num * 7));
193+
}
194+
195+
if (type === 'next' || type === 'last') {
196+
lastNext(type, range, num);
197+
}
198+
else if (!typeIsNumber) {
199+
return false;
200+
}
201+
return true;
96202
}
97203

98-
if (ranges.hasOwnProperty(range) && !splt[1].match(/^mon(day|\.)?$/i)) {
99-
return date['set' + ranges[range]](date['get' + ranges[range]]() + num);
100-
}
101-
if (range === 'wee') {
102-
return date.setDate(date.getDate() + (num * 7));
103-
}
204+
times = '(years?|months?|weeks?|days?|hours?|minutes?|min|seconds?|sec' +
205+
'|sunday|sun\\.?|monday|mon\\.?|tuesday|tue\\.?|wednesday|wed\\.?' +
206+
'|thursday|thu\\.?|friday|fri\\.?|saturday|sat\\.?)';
207+
regex = '([+-]?\\d+\\s' + times + '|' + '(last|next)\\s' + times + ')(\\sago)?';
104208

105-
if (type === 'next' || type === 'last') {
106-
lastNext(type, range, num);
209+
match = text.match(new RegExp(regex, 'gi'));
210+
if (!match) {
211+
return false;
107212
}
108-
else if (!typeIsNumber) {
109-
return false;
110-
}
111-
return true;
112-
}
113-
114-
times = '(years?|months?|weeks?|days?|hours?|minutes?|min|seconds?|sec' +
115-
'|sunday|sun\\.?|monday|mon\\.?|tuesday|tue\\.?|wednesday|wed\\.?' +
116-
'|thursday|thu\\.?|friday|fri\\.?|saturday|sat\\.?)';
117-
regex = '([+-]?\\d+\\s' + times + '|' + '(last|next)\\s' + times + ')(\\sago)?';
118-
119-
match = text.match(new RegExp(regex, 'gi'));
120-
if (!match) {
121-
return false;
122-
}
123-
124-
for (i = 0, len = match.length; i < len; i++) {
125-
if (!process(match[i])) {
126-
return false;
213+
214+
for (i = 0, len = match.length; i < len; i++) {
215+
if (!process(match[i])) {
216+
return false;
217+
}
127218
}
128-
}
129219

130-
// ECMAScript 5 only
131-
//if (!match.every(process))
132-
// return false;
220+
// ECMAScript 5 only
221+
// if (!match.every(process))
222+
// return false;
133223

134-
return (date.getTime() / 1000) | 0;
224+
return (date.getTime() / 1000);
135225
}

0 commit comments

Comments
 (0)