In input forms on web pages you often have to validate dates and users are always entering something off the wall. Simon Incutio came up with a script to parse the dates. I added some stuff and turned it into a control of sorts. It is sort of US centric with date entering but it does accept iso style entries.
1
2 // Date Box Control
3 //
4 // based on:
5 // 'Magic' date parsing, by Simon Willison (6th October 2003)
6 // http://simon.incutio.com/archive/2003/10/06/betterDateInput
7 //
8 // Notes
9 // To create a date box control, call the SetupDateBoxControl function.
10 // It should be passed an input element with type of text.
11 // This will first create a div after the input box.
12 // Then it will associate the div with the input element.
13 // The div will use the css classes: DateBoxControlMsg, DateBoxControlErrorMsg.
14 // Then the div is associated with the input element.
15 // Then the contents are validated so the div will get populated initially.
16 // The onchange event of the input element will invoke the validation function.
17 // The validation function populates the div.
18 // If a successfully parsed date, the div gets a nicely formatted date.
19 // If an unsuccessfully parse date, the div gets an error message.
20 //
21 // History
22 // 02/03/2005 - WSR : modified for use as datebox control
23 // 09/09/2005 - WSR : datebox is not required anymore (blank input is valid)
24 // : added style class to datebox itself
25
26 // hooks functionality up to given textbox
27 function SetupDateBoxControl( ctlDateBox )
28 {
29
30 // if a valid object was given
31 if (ctlDateBox)
32 {
33
34 // add div after control for messages
35 var divMessage = document.createElement('div');
36 divMessage.className = 'DateBoxControlMsg';
37
38 // if there is a next sibling
39 if (ctlDateBox.nextSibling)
40 {
41
42 // insert before next sibling
43 ctlDateBox.parentNode.insertBefore( divMessage, ctlDateBox.nextSibling );
44
45 }
46 // if there is not a next sibling
47 else
48 {
49
50 // append child to parent
51 ctlDateBox.parentNode.appendChild( divMessage );
52
53 }
54
55 // link message div to textbox for easy script access
56 ctlDateBox.message = divMessage;
57
58 // validate current contents
59 DateBoxControl_Validate( ctlDateBox );
60
61 // hook up event handlers
62 ctlDateBox.onchange = function () { DateBoxControl_Validate(this); };
63
64 }
65
66 }
67
68 // add indexOf function to Array type
69 // finds the index of the first occurence of item in the array, or -1 if not found
70 Array.prototype.indexOf = function(item) {
71 for (var i = 0; i < this.length; i++) {
72 if (this[i] == item) {
73 return i;
74 }
75 }
76 return -1;
77 };
78
79 // add filter function to Array type
80 // returns an array of items judged true by the passed in test function
81 Array.prototype.filter = function(test) {
82 var matches = [];
83 for (var i = 0; i < this.length; i++) {
84 if (test(this[i])) {
85 matches[matches.length] = this[i];
86 }
87 }
88 return matches;
89 };
90
91 // add right function to String type
92 // returns the rightmost x characters
93 String.prototype.right = function( intLength ) {
94 if (intLength >= this.length)
95 return this;
96 else
97 return this.substr( this.length - intLength, intLength );
98 };
99
100 // add trim function to String type
101 // trims leading and trailing whitespace
102 String.prototype.trim = function() { return this.replace(/^\s+|\s+$/, ''); };
103
104 // arrays for month and weekday names
105 var monthNames = "January February March April May June July August September October November December".split(" ");
106 var weekdayNames = "Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" ");
107
108 /* Takes a string, returns the index of the month matching that string, throws
109 an error if 0 or more than 1 matches
110 */
111 function parseMonth(month) {
112 var matches = monthNames.filter(function(item) {
113 return new RegExp("^" + month, "i").test(item);
114 });
115 if (matches.length == 0) {
116 throw new Error("Invalid month string");
117 }
118 if (matches.length < 1) {
119 throw new Error("Ambiguous month");
120 }
121 return monthNames.indexOf(matches[0]);
122 }
123
124 /* Same as parseMonth but for days of the week */
125 function parseWeekday(weekday) {
126 var matches = weekdayNames.filter(function(item) {
127 return new RegExp("^" + weekday, "i").test(item);
128 });
129 if (matches.length == 0) {
130 throw new Error("Invalid day string");
131 }
132 if (matches.length < 1) {
133 throw new Error("Ambiguous weekday");
134 }
135 return weekdayNames.indexOf(matches[0]);
136 }
137
138 function DateInRange( yyyy, mm, dd )
139 {
140
141 // if month out of range
142 if ( mm < 0 || mm > 11 )
143 throw new Error('Invalid month value. Valid months values are 1 to 12');
144
145 // get last day in month
146 var d = (11 == mm) ? new Date(yyyy + 1, 0, 0) : new Date(yyyy, mm + 1, 0);
147
148 // if date out of range
149 if ( dd < 1 || dd > d.getDate() )
150 throw new Error('Invalid date value. Valid date values for ' + monthNames[mm] + ' are 1 to ' + d.getDate().toString());
151
152 return true;
153
154 }
155
156 /* Array of objects, each has 're', a regular expression and 'handler', a
157 function for creating a date from something that matches the regular
158 expression. Handlers may throw errors if string is unparseable.
159 */
160 var dateParsePatterns = [
161 // Today
162 { re: /^today/i,
163 handler: function() {
164 return new Date();
165 }
166 },
167 // Tomorrow
168 { re: /^tomorrow/i,
169 handler: function() {
170 var d = new Date();
171 d.setDate(d.getDate() + 1);
172 return d;
173 }
174 },
175 // Yesterday
176 { re: /^yesterday/i,
177 handler: function() {
178 var d = new Date();
179 d.setDate(d.getDate() - 1);
180 return d;
181 }
182 },
183 // mmddyyyy (American style)
184 { re: /(\d{2})(\d{2})(\d{4})/,
185 handler: function(bits) {
186
187 var yyyy = parseInt(bits[3], 10);
188 var dd = parseInt(bits[2], 10);
189 var mm = parseInt(bits[1], 10) - 1;
190
191 if ( DateInRange( yyyy, mm, dd ) )
192 return new Date(yyyy, mm, dd);
193
194 }
195 },
196 // mmddyy (American style) short year
197 { re: /(\d{2})(\d{2})(\d{2})/,
198 handler: function(bits) {
199
200 var d = new Date();
201 var yyyy = d.getFullYear() - (d.getFullYear() % 100) + parseInt(bits[3], 10);
202 var dd = parseInt(bits[2], 10);
203 var mm = parseInt(bits[1], 10) - 1;
204
205 if ( DateInRange(yyyy, mm, dd) )
206 return new Date(yyyy, mm, dd);
207
208 }
209 },
210 // 4th
211 { re: /^(\d{1,2})(st|nd|rd|th)?$/i,
212 handler: function(bits) {
213
214 var d = new Date();
215 var yyyy = d.getFullYear();
216 var dd = parseInt(bits[1], 10);
217 var mm = d.getMonth();
218
219 if ( DateInRange( yyyy, mm, dd ) )
220 return new Date(yyyy, mm, dd);
221
222 }
223 },
224 // 4th Jan
225 { re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+)$/i,
226 handler: function(bits) {
227
228 var d = new Date();
229 var yyyy = d.getFullYear();
230 var dd = parseInt(bits[1], 10);
231 var mm = parseMonth(bits[2]);
232
233 if ( DateInRange( yyyy, mm, dd ) )
234 return new Date(yyyy, mm, dd);
235
236 }
237 },
238 // 4th Jan 2003
239 { re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+),? (\d{4})$/i,
240 handler: function(bits) {
241
242 var yyyy = parseInt(bits[3], 10);
243 var dd = parseInt(bits[1], 10);
244 var mm = parseMonth(bits[2]);
245
246 if ( DateInRange( yyyy, mm, dd ) )
247 return new Date(yyyy, mm, dd);
248
249 }
250 },
251 // Jan 4th
252 { re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?$/i,
253 handler: function(bits) {
254
255 var d = new Date();
256 var yyyy = d.getFullYear();
257 var dd = parseInt(bits[2], 10);
258 var mm = parseMonth(bits[1]);
259
260 if ( DateInRange( yyyy, mm, dd ) )
261 return new Date(yyyy, mm, dd);
262
263 }
264 },
265 // Jan 4th 2003
266 { re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{4})$/i,
267 handler: function(bits) {
268
269 var yyyy = parseInt(bits[3], 10);
270 var dd = parseInt(bits[2], 10);
271 var mm = parseMonth(bits[1]);
272
273 if ( DateInRange( yyyy, mm, dd ) )
274 return new Date(yyyy, mm, dd);
275
276 }
277 },
278 // next Tuesday - this is suspect due to weird meaning of "next"
279 { re: /^next (\w+)$/i,
280 handler: function(bits) {
281
282 var d = new Date()