1/*
2 * Copyright (C) 2004 Baron Schwartz <baron at sequent dot org>
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by the
6 * Free Software Foundation, version 2.1.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10 * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
11 * details.
12 */
13
14Date.parseFunctions = {count:0};
15Date.parseRegexes = [];
16Date.formatFunctions = {count:0};
17
18Date.prototype.dateFormat = function(format) {
19    if (Date.formatFunctions[format] == null) {
20        Date.createNewFormat(format);
21    }
22    var func = Date.formatFunctions[format];
23    return this[func]();
24}
25
26Date.createNewFormat = function(format) {
27    var funcName = "format" + Date.formatFunctions.count++;
28    Date.formatFunctions[format] = funcName;
29    var code = "Date.prototype." + funcName + " = function(){return ";
30    var special = false;
31    var ch = '';
32    for (var i = 0; i < format.length; ++i) {
33        ch = format.charAt(i);
34        if (!special && ch == "\\") {
35            special = true;
36        }
37        else if (special) {
38            special = false;
39            code += "'" + String.escape(ch) + "' + ";
40        }
41        else {
42            code += Date.getFormatCode(ch);
43        }
44    }
45    eval(code.substring(0, code.length - 3) + ";}");
46}
47
48Date.getFormatCode = function(character) {
49    switch (character) {
50    case "d":
51        return "String.leftPad(this.getDate(), 2, '0') + ";
52    case "D":
53        return "Date.dayNames[this.getDay()].substring(0, 3) + ";
54    case "j":
55        return "this.getDate() + ";
56    case "l":
57        return "Date.dayNames[this.getDay()] + ";
58    case "S":
59        return "this.getSuffix() + ";
60    case "w":
61        return "this.getDay() + ";
62    case "z":
63        return "this.getDayOfYear() + ";
64    case "W":
65        return "this.getWeekOfYear() + ";
66    case "F":
67        return "Date.monthNames[this.getMonth()] + ";
68    case "m":
69        return "String.leftPad(this.getMonth() + 1, 2, '0') + ";
70    case "M":
71        return "Date.monthNames[this.getMonth()].substring(0, 3) + ";
72    case "n":
73        return "(this.getMonth() + 1) + ";
74    case "t":
75        return "this.getDaysInMonth() + ";
76    case "L":
77        return "(this.isLeapYear() ? 1 : 0) + ";
78    case "Y":
79        return "this.getFullYear() + ";
80    case "y":
81        return "('' + this.getFullYear()).substring(2, 4) + ";
82    case "a":
83        return "(this.getHours() < 12 ? 'am' : 'pm') + ";
84    case "A":
85        return "(this.getHours() < 12 ? 'AM' : 'PM') + ";
86    case "g":
87        return "((this.getHours() %12) ? this.getHours() % 12 : 12) + ";
88    case "G":
89        return "this.getHours() + ";
90    case "h":
91        return "String.leftPad((this.getHours() %12) ? this.getHours() % 12 : 12, 2, '0') + ";
92    case "H":
93        return "String.leftPad(this.getHours(), 2, '0') + ";
94    case "i":
95        return "String.leftPad(this.getMinutes(), 2, '0') + ";
96    case "s":
97        return "String.leftPad(this.getSeconds(), 2, '0') + ";
98    case "O":
99        return "this.getGMTOffset() + ";
100    case "T":
101        return "this.getTimezone() + ";
102    case "Z":
103        return "(this.getTimezoneOffset() * -60) + ";
104    default:
105        return "'" + String.escape(character) + "' + ";
106    }
107}
108
109Date.parseDate = function(input, format) {
110    if (Date.parseFunctions[format] == null) {
111        Date.createParser(format);
112    }
113    var func = Date.parseFunctions[format];
114    return Date[func](input);
115}
116
117Date.createParser = function(format) {
118    var funcName = "parse" + Date.parseFunctions.count++;
119    var regexNum = Date.parseRegexes.length;
120    var currentGroup = 1;
121    Date.parseFunctions[format] = funcName;
122
123    var code = "Date." + funcName + " = function(input){\n"
124        + "var y = -1, m = -1, d = -1, h = -1, i = -1, s = -1;\n"
125        + "var d = new Date();\n"
126        + "y = d.getFullYear();\n"
127        + "m = d.getMonth();\n"
128        + "d = d.getDate();\n"
129        + "var results = input.match(Date.parseRegexes[" + regexNum + "]);\n"
130        + "if (results && results.length > 0) {"
131    var regex = "";
132
133    var special = false;
134    var ch = '';
135    for (var i = 0; i < format.length; ++i) {
136        ch = format.charAt(i);
137        if (!special && ch == "\\") {
138            special = true;
139        }
140        else if (special) {
141            special = false;
142            regex += String.escape(ch);
143        }
144        else {
145            obj = Date.formatCodeToRegex(ch, currentGroup);
146            currentGroup += obj.g;
147            regex += obj.s;
148            if (obj.g && obj.c) {
149                code += obj.c;
150            }
151        }
152    }
153
154    code += "if (y > 0 && m >= 0 && d > 0 && h >= 0 && i >= 0 && s >= 0)\n"
155        + "{return new Date(y, m, d, h, i, s);}\n"
156        + "else if (y > 0 && m >= 0 && d > 0 && h >= 0 && i >= 0)\n"
157        + "{return new Date(y, m, d, h, i);}\n"
158        + "else if (y > 0 && m >= 0 && d > 0 && h >= 0)\n"
159        + "{return new Date(y, m, d, h);}\n"
160        + "else if (y > 0 && m >= 0 && d > 0)\n"
161        + "{return new Date(y, m, d);}\n"
162        + "else if (y > 0 && m >= 0)\n"
163        + "{return new Date(y, m);}\n"
164        + "else if (y > 0)\n"
165        + "{return new Date(y);}\n"
166        + "}return null;}";
167
168    Date.parseRegexes[regexNum] = new RegExp("^" + regex + "$");
169    eval(code);
170}
171
172Date.formatCodeToRegex = function(character, currentGroup) {
173    switch (character) {
174    case "D":
175        return {g:0,
176        c:null,
177        s:"(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)"};
178    case "j":
179    case "d":
180        return {g:1,
181            c:"d = parseInt(results[" + currentGroup + "], 10);\n",
182            s:"(\\d{1,2})"};
183    case "l":
184        return {g:0,
185            c:null,
186            s:"(?:" + Date.dayNames.join("|") + ")"};
187    case "S":
188        return {g:0,
189            c:null,
190            s:"(?:st|nd|rd|th)"};
191    case "w":
192        return {g:0,
193            c:null,
194            s:"\\d"};
195    case "z":
196        return {g:0,
197            c:null,
198            s:"(?:\\d{1,3})"};
199    case "W":
200        return {g:0,
201            c:null,
202            s:"(?:\\d{2})"};
203    case "F":
204        return {g:1,
205            c:"m = parseInt(Date.monthNumbers[results[" + currentGroup + "].substring(0, 3)], 10);\n",
206            s:"(" + Date.monthNames.join("|") + ")"};
207    case "M":
208        return {g:1,
209            c:"m = parseInt(Date.monthNumbers[results[" + currentGroup + "]], 10);\n",
210            s:"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)"};
211    case "n":
212    case "m":
213        return {g:1,
214            c:"m = parseInt(results[" + currentGroup + "], 10) - 1;\n",
215            s:"(\\d{1,2})"};
216    case "t":
217        return {g:0,
218            c:null,
219            s:"\\d{1,2}"};
220    case "L":
221        return {g:0,
222            c:null,
223            s:"(?:1|0)"};
224    case "Y":
225        return {g:1,
226            c:"y = parseInt(results[" + currentGroup + "], 10);\n",
227            s:"(\\d{4})"};
228    case "y":
229        return {g:1,
230            c:"var ty = parseInt(results[" + currentGroup + "], 10);\n"
231                + "y = ty > Date.y2kYear ? 1900 + ty : 2000 + ty;\n",
232            s:"(\\d{1,2})"};
233    case "a":
234        return {g:1,
235            c:"if (results[" + currentGroup + "] == 'am') {\n"
236                + "if (h == 12) { h = 0; }\n"
237                + "} else { if (h < 12) { h += 12; }}",
238            s:"(am|pm)"};
239    case "A":
240        return {g:1,
241            c:"if (results[" + currentGroup + "] == 'AM') {\n"
242                + "if (h == 12) { h = 0; }\n"
243                + "} else { if (h < 12) { h += 12; }}",
244            s:"(AM|PM)"};
245    case "g":
246    case "G":
247    case "h":
248    case "H":
249        return {g:1,
250            c:"h = parseInt(results[" + currentGroup + "], 10);\n",
251            s:"(\\d{1,2})"};
252    case "i":
253        return {g:1,
254            c:"i = parseInt(results[" + currentGroup + "], 10);\n",
255            s:"(\\d{2})"};
256    case "s":
257        return {g:1,
258            c:"s = parseInt(results[" + currentGroup + "], 10);\n",
259            s:"(\\d{2})"};
260    case "O":
261        return {g:0,
262            c:null,
263            s:"[+-]\\d{4}"};
264    case "T":
265        return {g:0,
266            c:null,
267            s:"[A-Z]{3}"};
268    case "Z":
269        return {g:0,
270            c:null,
271            s:"[+-]\\d{1,5}"};
272    default:
273        return {g:0,
274            c:null,
275            s:String.escape(character)};
276    }
277}
278
279Date.prototype.getTimezone = function() {
280    return this.toString().replace(
281        /^.*? ([A-Z]{3}) [0-9]{4}.*$/, "$1").replace(
282        /^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, "$1$2$3");
283}
284
285Date.prototype.getGMTOffset = function() {
286    return (this.getTimezoneOffset() > 0 ? "-" : "+")
287        + String.leftPad(Math.floor(this.getTimezoneOffset() / 60), 2, "0")
288        + String.leftPad(this.getTimezoneOffset() % 60, 2, "0");
289}
290
291Date.prototype.getDayOfYear = function() {
292    var num = 0;
293    Date.daysInMonth[1] = this.isLeapYear() ? 29 : 28;
294    for (var i = 0; i < this.getMonth(); ++i) {
295        num += Date.daysInMonth[i];
296    }
297    return num + this.getDate() - 1;
298}
299
300Date.prototype.getWeekOfYear = function() {
301    // Skip to Thursday of this week
302    var now = this.getDayOfYear() + (4 - this.getDay());
303    // Find the first Thursday of the year
304    var jan1 = new Date(this.getFullYear(), 0, 1);
305    var then = (7 - jan1.getDay() + 4);
306    document.write(then);
307    return String.leftPad(((now - then) / 7) + 1, 2, "0");
308}
309
310Date.prototype.isLeapYear = function() {
311    var year = this.getFullYear();
312    return ((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
313}
314
315Date.prototype.getFirstDayOfMonth = function() {
316    var day = (this.getDay() - (this.getDate() - 1)) % 7;
317    return (day < 0) ? (day + 7) : day;
318}
319
320Date.prototype.getLastDayOfMonth = function() {
321    var day = (this.getDay() + (Date.daysInMonth[this.getMonth()] - this.getDate())) % 7;
322    return (day < 0) ? (day + 7) : day;
323}
324
325Date.prototype.getDaysInMonth = function() {
326    Date.daysInMonth[1] = this.isLeapYear() ? 29 : 28;
327    return Date.daysInMonth[this.getMonth()];
328}
329
330Date.prototype.getSuffix = function() {
331    switch (this.getDate()) {
332        case 1:
333        case 21:
334        case 31:
335            return "st";
336        case 2:
337        case 22:
338            return "nd";
339        case 3:
340        case 23:
341            return "rd";
342        default:
343            return "th";
344    }
345}
346
347String.escape = function(string) {
348    return string.replace(/('|\\)/g, "\\$1");
349}
350
351String.leftPad = function (val, size, ch) {
352    var result = new String(val);
353    if (ch == null) {
354        ch = " ";
355    }
356    while (result.length < size) {
357        result = ch + result;
358    }
359    return result;
360}
361
362Date.daysInMonth = [31,28,31,30,31,30,31,31,30,31,30,31];
363Date.monthNames =
364   ["January",
365    "February",
366    "March",
367    "April",
368    "May",
369    "June",
370    "July",
371    "August",
372    "September",
373    "October",
374    "November",
375    "December"];
376Date.dayNames =
377   ["Sunday",
378    "Monday",
379    "Tuesday",
380    "Wednesday",
381    "Thursday",
382    "Friday",
383    "Saturday"];
384Date.y2kYear = 50;
385Date.monthNumbers = {
386    Jan:0,
387    Feb:1,
388    Mar:2,
389    Apr:3,
390    May:4,
391    Jun:5,
392    Jul:6,
393    Aug:7,
394    Sep:8,
395    Oct:9,
396    Nov:10,
397    Dec:11};
398Date.patterns = {
399    ISO8601LongPattern:"Y-m-d H:i:s",
400    ISO8601ShortPattern:"Y-m-d",
401    ShortDatePattern: "n/j/Y",
402    LongDatePattern: "l, F d, Y",
403    FullDateTimePattern: "l, F d, Y g:i:s A",
404    MonthDayPattern: "F d",
405    ShortTimePattern: "g:i A",
406    LongTimePattern: "g:i:s A",
407    SortableDateTimePattern: "Y-m-d\\TH:i:s",
408    UniversalSortableDateTimePattern: "Y-m-d H:i:sO",
409    YearMonthPattern: "F, Y"};
410
411var date = new Date("1/1/2007 1:11:11");
412
413for (i = 0; i < 4000; ++i) {
414    var shortFormat = date.dateFormat("Y-m-d");
415    var longFormat = date.dateFormat("l, F d, Y g:i:s A");
416    date.setTime(date.getTime() + 84266956);
417}
418