1 /**
  2  * Constructs a new date format with the specified string pattern.
  3  *
  4  * @class The format string is in the same format expected by the
  5  * <tt>strftime</tt> function in C. The following conversion specifications are
  6  * supported:<ul>
  7  *
  8  * <li>%a - abbreviated weekday name.</li>
  9  * <li>%A - full weekday name.</li>
 10  * <li>%b - abbreviated month names.</li>
 11  * <li>%B - full month names.</li>
 12  * <li>%c - locale's appropriate date and time.</li>
 13  * <li>%C - century number.</li>
 14  * <li>%d - day of month [01,31] (zero padded).</li>
 15  * <li>%D - same as %m/%d/%y.</li>
 16  * <li>%e - day of month [ 1,31] (space padded).</li>
 17  * <li>%h - same as %b.</li>
 18  * <li>%H - hour (24-hour clock) [00,23] (zero padded).</li>
 19  * <li>%I - hour (12-hour clock) [01,12] (zero padded).</li>
 20  * <li>%m - month number [01,12] (zero padded).</li>
 21  * <li>%M - minute [0,59] (zero padded).</li>
 22  * <li>%n - newline character.</li>
 23  * <li>%p - locale's equivalent of a.m. or p.m.</li>
 24  * <li>%r - same as %I:%M:%S %p.</li>
 25  * <li>%R - same as %H:%M.</li>
 26  * <li>%S - second [00,61] (zero padded).</li>
 27  * <li>%t - tab character.</li>
 28  * <li>%T - same as %H:%M:%S.</li>
 29  * <li>%x - same as %m/%d/%y.</li>
 30  * <li>%X - same as %I:%M:%S %p.</li>
 31  * <li>%y - year with century [00,99] (zero padded).</li>
 32  * <li>%Y - year including century.</li>
 33  * <li>%% - %.</li>
 34  *
 35  * </ul>The following conversion specifications are currently <i>unsupported</i>
 36  * for formatting:<ul>
 37  *
 38  * <li>%j - day number [1,366].</li>
 39  * <li>%u - weekday number [1,7].</li>
 40  * <li>%U - week number [00,53].</li>
 41  * <li>%V - week number [01,53].</li>
 42  * <li>%w - weekday number [0,6].</li>
 43  * <li>%W - week number [00,53].</li>
 44  * <li>%Z - timezone name or abbreviation.</li>
 45  *
 46  * </ul>In addition, the following conversion specifications are currently
 47  * <i>unsupported</i> for parsing:<ul>
 48  *
 49  * <li>%a - day of week, either abbreviated or full name.</li>
 50  * <li>%A - same as %a.</li>
 51  * <li>%c - locale's appropriate date and time.</li>
 52  * <li>%C - century number.</li>
 53  * <li>%D - same as %m/%d/%y.</li>
 54  * <li>%I - hour (12-hour clock) [1,12].</li>
 55  * <li>%n - any white space.</li>
 56  * <li>%p - locale's equivalent of a.m. or p.m.</li>
 57  * <li>%r - same as %I:%M:%S %p.</li>
 58  * <li>%R - same as %H:%M.</li>
 59  * <li>%t - same as %n.</li>
 60  * <li>%T - same as %H:%M:%S.</li>
 61  * <li>%x - locale's equivalent to %m/%d/%y.</li>
 62  * <li>%X - locale's equivalent to %I:%M:%S %p.</li>
 63  *
 64  * </ul>
 65  *
 66  * @see <a
 67  * href="http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html">strftime</a>
 68  * documentation.
 69  * @see <a
 70  * href="http://www.opengroup.org/onlinepubs/007908799/xsh/strptime.html">strptime</a>
 71  * documentation.
 72  * @extends pv.Format
 73  * @param {string} pattern the format pattern.
 74  */
 75 pv.Format.date = function(pattern) {
 76   var pad = pv.Format.pad;
 77 
 78   /** @private */
 79   function format(d) {
 80     return pattern.replace(/%[a-zA-Z0-9]/g, function(s) {
 81         switch (s) {
 82           case '%a': return [
 83               "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
 84             ][d.getDay()];
 85           case '%A': return [
 86               "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
 87               "Saturday"
 88             ][d.getDay()];
 89           case '%h':
 90           case '%b': return [
 91               "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
 92               "Oct", "Nov", "Dec"
 93             ][d.getMonth()];
 94           case '%B': return [
 95               "January", "February", "March", "April", "May", "June", "July",
 96               "August", "September", "October", "November", "December"
 97             ][d.getMonth()];
 98           case '%c': return d.toLocaleString();
 99           case '%C': return pad("0", 2, Math.floor(d.getFullYear() / 100) % 100);
100           case '%d': return pad("0", 2, d.getDate());
101           case '%x':
102           case '%D': return pad("0", 2, d.getMonth() + 1)
103                     + "/" + pad("0", 2, d.getDate())
104                     + "/" + pad("0", 2, d.getFullYear() % 100);
105           case '%e': return pad(" ", 2, d.getDate());
106           case '%H': return pad("0", 2, d.getHours());
107           case '%I': {
108             var h = d.getHours() % 12;
109             return h ? pad("0", 2, h) : 12;
110           }
111           // TODO %j: day of year as a decimal number [001,366]
112           case '%m': return pad("0", 2, d.getMonth() + 1);
113           case '%M': return pad("0", 2, d.getMinutes());
114           case '%n': return "\n";
115           case '%p': return d.getHours() < 12 ? "AM" : "PM";
116           case '%T':
117           case '%X':
118           case '%r': {
119             var h = d.getHours() % 12;
120             return (h ? pad("0", 2, h) : 12)
121                     + ":" + pad("0", 2, d.getMinutes())
122                     + ":" + pad("0", 2, d.getSeconds())
123                     + " " + (d.getHours() < 12 ? "AM" : "PM");
124           }
125           case '%R': return pad("0", 2, d.getHours()) + ":" + pad("0", 2, d.getMinutes());
126           case '%S': return pad("0", 2, d.getSeconds());
127           case '%Q': return pad("0", 3, d.getMilliseconds());
128           case '%t': return "\t";
129           case '%u': {
130             var w = d.getDay();
131             return w ? w : 1;
132           }
133           // TODO %U: week number (sunday first day) [00,53]
134           // TODO %V: week number (monday first day) [01,53] ... with weirdness
135           case '%w': return d.getDay();
136           // TODO %W: week number (monday first day) [00,53] ... with weirdness
137           case '%y': return pad("0", 2, d.getFullYear() % 100);
138           case '%Y': return d.getFullYear();
139           // TODO %Z: timezone name or abbreviation
140           case '%%': return "%";
141         }
142         return s;
143       });
144   }
145 
146   /**
147    * Converts a date to a string using the associated formatting pattern.
148    *
149    * @function
150    * @name pv.Format.date.prototype.format
151    * @param {Date} date a date to format.
152    * @returns {string} the formatted date as a string.
153    */
154   format.format = format;
155 
156   /**
157    * Parses a date from a string using the associated formatting pattern.
158    *
159    * @function
160    * @name pv.Format.date.prototype.parse
161    * @param {string} s the string to parse as a date.
162    * @returns {Date} the parsed date.
163    */
164   format.parse = function(s) {
165     var year = 1970, month = 0, date = 1, hour = 0, minute = 0, second = 0;
166     var fields = [function() {}];
167 
168     /* Register callbacks for each field in the format pattern. */
169     var re = pv.Format.re(pattern).replace(/%[a-zA-Z0-9]/g, function(s) {
170         switch (s) {
171           // TODO %a: day of week, either abbreviated or full name
172           // TODO %A: same as %a
173           case '%b': {
174             fields.push(function(x) { month = {
175                   Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5, Jul: 6, Aug: 7,
176                   Sep: 8, Oct: 9, Nov: 10, Dec: 11
177                 }[x]; });
178             return "([A-Za-z]+)";
179           }
180           case '%h':
181           case '%B': {
182             fields.push(function(x) { month = {
183                   January: 0, February: 1, March: 2, April: 3, May: 4, June: 5,
184                   July: 6, August: 7, September: 8, October: 9, November: 10,
185                   December: 11
186                 }[x]; });
187             return "([A-Za-z]+)";
188           }
189           // TODO %c: locale's appropriate date and time
190           // TODO %C: century number[0,99]
191           case '%e':
192           case '%d': {
193             fields.push(function(x) { date = x; });
194             return "([0-9]+)";
195           }
196           // TODO %D: same as %m/%d/%y
197           case '%I':
198           case '%H': {
199             fields.push(function(x) { hour = x; });
200             return "([0-9]+)";
201           }
202           // TODO %j: day number [1,366]
203           case '%m': {
204             fields.push(function(x) { month = x - 1; });
205             return "([0-9]+)";
206           }
207           case '%M': {
208             fields.push(function(x) { minute = x; });
209             return "([0-9]+)";
210           }
211           // TODO %n: any white space
212           // TODO %p: locale's equivalent of a.m. or p.m.
213           case '%p': { // TODO this is a hack
214             fields.push(function(x) {
215               if (hour == 12) {
216                 if (x == "am") hour = 0;
217               } else if (x == "pm") {
218                 hour = Number(hour) + 12;
219               }
220             });
221             return "(am|pm)";
222           }
223           // TODO %r: %I:%M:%S %p
224           // TODO %R: %H:%M
225           case '%S': {
226             fields.push(function(x) { second = x; });
227             return "([0-9]+)";
228           }
229           // TODO %t: any white space
230           // TODO %T: %H:%M:%S
231           // TODO %U: week number [00,53]
232           // TODO %w: weekday [0,6]
233           // TODO %W: week number [00, 53]
234           // TODO %x: locale date (%m/%d/%y)
235           // TODO %X: locale time (%I:%M:%S %p)
236           case '%y': {
237             fields.push(function(x) {
238                 x = Number(x);
239                 year = x + (((0 <= x) && (x < 69)) ? 2000
240                     : (((x >= 69) && (x < 100) ? 1900 : 0)));
241               });
242             return "([0-9]+)";
243           }
244           case '%Y': {
245             fields.push(function(x) { year = x; });
246             return "([0-9]+)";
247           }
248           case '%%': {
249             fields.push(function() {});
250             return "%";
251           }
252         }
253         return s;
254       });
255 
256     var match = s.match(re);
257     if (match) match.forEach(function(m, i) { fields[i](m); });
258     return new Date(year, month, date, hour, minute, second);
259   };
260 
261   return format;
262 };
263