1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * 
 26  * @private
 27  */
 28 AjxDateUtil = function() {
 29 };
 30 
 31 AjxDateUtil.YEAR		= 1;
 32 AjxDateUtil.MONTH		= 2;
 33 AjxDateUtil.WEEK		= 3;
 34 AjxDateUtil.DAY			= 4;
 35 AjxDateUtil.TWO_WEEKS	= 5;
 36 
 37 AjxDateUtil.MSEC_PER_MINUTE = 60000;
 38 AjxDateUtil.MSEC_PER_FIFTEEN_MINUTES = 900000;
 39 AjxDateUtil.MSEC_PER_HALF_HOUR = 1800000;
 40 AjxDateUtil.MSEC_PER_HOUR = 3600000;
 41 AjxDateUtil.MSEC_PER_DAY = 24 * AjxDateUtil.MSEC_PER_HOUR;
 42 
 43 AjxDateUtil.MINUTES_PER_DAY = 60 * 24;
 44 AjxDateUtil.SECONDS_PER_DAY = 60 * 60 * 24;
 45 
 46 AjxDateUtil.DAYS_PER_WEEK = 7;
 47 
 48 AjxDateUtil.WEEKDAY_SHORT = AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.SHORT];
 49 AjxDateUtil.WEEKDAY_MEDIUM = AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.MEDIUM];
 50 AjxDateUtil.WEEKDAY_LONG = AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.LONG];
 51 
 52 AjxDateUtil.MONTH_SHORT = AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.SHORT];
 53 AjxDateUtil.MONTH_MEDIUM = AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.MEDIUM];
 54 AjxDateUtil.MONTH_LONG = AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.LONG];
 55 
 56 AjxDateUtil._daysPerMonth = {
 57 	0:31,
 58 	1:29,
 59 	2:31,
 60 	3:30,
 61 	4:31,
 62 	5:30,
 63 	6:31,
 64 	7:31,
 65 	8:30,
 66 	9:31,
 67 	10:30,
 68 	11:31
 69 };
 70 
 71 AjxDateUtil.MAX_DAYS_PER_MONTH = 31;
 72 
 73 AjxDateUtil.WEEK_ONE_JAN_DATE = 1;
 74 
 75 AjxDateUtil._init =
 76 function() {
 77 	AjxDateUtil._dateFormat = AjxDateFormat.getDateInstance(AjxDateFormat.SHORT).clone();
 78 	var segments = AjxDateUtil._dateFormat.getSegments();
 79 	for (var i = 0; i < segments.length; i++) {
 80 		if (segments[i] instanceof AjxDateFormat.YearSegment) {
 81 			segments[i] = new AjxDateFormat.YearSegment(AjxDateUtil._dateFormat, "yyyy");
 82 		}
 83 	}
 84 	AjxDateUtil._dateTimeFormat = 
 85 		new AjxDateFormat(AjxDateUtil._dateFormat.toPattern() + " " + AjxDateFormat.getTimeInstance(AjxDateFormat.SHORT));
 86 
 87 	AjxDateUtil._dateFormatNoYear = new AjxDateFormat(AjxMsg.formatDateMediumNoYear);
 88 };
 89 
 90 AjxDateUtil._init();                    
 91 
 92 /* return true if the specified date (yyyy|yy, m (0-11), d (1-31)) 
 93  * is valid or not.
 94  */
 95 AjxDateUtil.validDate =
 96 function(y, m, d) {
 97 	var date = new Date(y, m, d);
 98 	var year = y > 999 ? date.getFullYear() : date.getYear();
 99 	return date.getMonth() == m && date.getDate() == d && year == y;
100 };
101 
102 /* return number of days (1-31) in specified month (yyyy, mm (0-11))
103  */
104 AjxDateUtil.daysInMonth =
105 function(y, m) {
106 	var date = new Date(y, m, 1, 12);
107 	date.setMonth(date.getMonth()+1);
108 	date.setDate(date.getDate()-1);
109 	return date.getDate();
110 };
111 
112 /* return true if year is a leap year
113  */
114 AjxDateUtil.isLeapYear =
115 function(y) {
116 	return (new Date(y, 1, 29)).getMonth() == 1;
117 };
118 
119 /* returns true if user's locale uses 24-hour time
120  */
121 AjxDateUtil.isLocale24Hour =
122 function() {
123 	// XXX: is there better/easier way to determine this?!
124 	var timeFormatter = AjxDateFormat.getTimeInstance(AjxDateFormat.SHORT);
125 	var len = timeFormatter._segments.length;
126 	for (var j = 0; j < len; j++) {
127 		if (timeFormatter._segments[j]._s == "a")
128 			return false;
129 	}
130 	return true;
131 };
132 
133 /**
134  * rolls the month/year. If the day of month in the date passed in is greater
135  * then the max day in the new month, set it to the max. The date passed in is
136  * modified and also returned.
137  */
138 AjxDateUtil.roll = 
139 function(date, field, offset) {
140 	var d = date.getDate();
141 	 // move back to first day before rolling in case previous
142 	 // month/year has less days
143 
144 	if (field == AjxDateUtil.MONTH) {
145 		date.setDate(1);	
146 		date.setMonth(date.getMonth() + offset);
147 		var max = AjxDateUtil.daysInMonth(date.getFullYear(), date.getMonth());
148 		date.setDate(Math.min(d, max));		
149 	} else if (field == AjxDateUtil.YEAR) {
150 		date.setDate(1);		
151 		date.setFullYear(date.getFullYear() + offset);
152 		var max = AjxDateUtil.daysInMonth(date.getFullYear(), date.getMonth());
153 		date.setDate(Math.min(d, max));		
154 	} else if (field == AjxDateUtil.WEEK) {
155 		date.setDate(date.getDate() + 7*offset);
156 	} else if (field == AjxDateUtil.DAY) {
157 		date.setDate(date.getDate() + offset);
158 	} else if (field == AjxDateUtil.TWO_WEEKS) {
159 		date.setDate(date.getDate() + 14*offset);
160 	} else {
161 		return date;
162 	}
163 	return date;
164 };
165 
166 /**
167  * checks whether given date is derived from DST shift
168  */
169 AjxDateUtil.isDayShifted =
170 function(date) {
171     var refDate = new Date(date.getTime());
172 
173     //advance it by 1 day and reset to beginning of the day
174     refDate.setDate(refDate.getDate() +1)
175     refDate.setHours(0,0,0,0);
176 
177     //if DST has no effect the advanced time should differ from given time
178     return refDate.getTime() == date.getTime();
179 };
180 
181 /**
182  * rolls to next day. This can be used to roll to next day avoiding the daylight saving shift in time.
183  */
184 AjxDateUtil.rollToNextDay =
185 function(date) {
186     date.setHours(0,0,0,0);
187     date.setTime(date.getTime() + AjxDateUtil.MSEC_PER_DAY);
188 };
189 
190 // Computes the difference between now and <dateMSec>. Returns a string describing
191 // the difference
192 AjxDateUtil.computeDateDelta =
193 function(dateMSec) {
194 	var deltaMSec = (new Date()).getTime() - dateMSec;
195 	var durationStr = AjxDateUtil.computeDuration(deltaMSec);
196 	return durationStr ? (durationStr + " " + AjxMsg.ago) : null;
197 };
198 
199 // Computes the difference between now and <dateMSec>. Returns a simplified string describing
200 // the difference
201 AjxDateUtil.agoTime =
202 function(dateMSec) {
203 	var deltaMSec = (new Date()).getTime() - dateMSec;
204 	var durationStr = AjxDateUtil.computeDuration(deltaMSec, false, true);
205 	return durationStr ? (durationStr + " " + AjxMsg.ago) : null;
206 };
207 
208 
209 
210 // Returns a string describing the duration, which is in milliseconds.
211 AjxDateUtil.computeDuration =
212 function(duration, brief, simplified) {
213 	// bug fix #2203 - if delta is less than zero, dont bother computing
214 	if (duration < 0) return null;
215 
216 	var years =  Math.floor(duration / (AjxDateUtil.MSEC_PER_DAY * 365));
217 	if (years != 0)
218 		duration -= years * AjxDateUtil.MSEC_PER_DAY * 365;
219 	var months = Math.floor(duration / (AjxDateUtil.MSEC_PER_DAY * 30.42));
220 	if (months > 0)
221 		duration -= Math.floor(months * AjxDateUtil.MSEC_PER_DAY * 30.42);
222 	var days = Math.floor(duration / AjxDateUtil.MSEC_PER_DAY);
223 	if (days > 0)
224 		duration -= days * AjxDateUtil.MSEC_PER_DAY;
225 	var hours = Math.floor(duration / AjxDateUtil.MSEC_PER_HOUR);
226 	if (hours > 0) 
227 		duration -= hours * AjxDateUtil.MSEC_PER_HOUR;
228 	var mins = Math.floor(duration / 60000);
229 	if (mins > 0)
230 		duration -= mins * 60000;
231 	var secs = Math.floor(duration / 1000);
232 
233 	var formatter = brief ? AjxDurationFormatConcise : AjxDurationFormatVerbose;
234 	if (years > 0) {
235 		return simplified
236             ? formatter.formatYears(years)
237             : formatter.formatYears(years, months);
238 	} else if (months > 0) {
239 		return simplified
240             ? formatter.formatMonths(months)
241             : formatter.formatMonths(months, days);
242 	} else if (days > 0) {
243 		return simplified
244             ? formatter.formatDays(days)
245             : formatter.formatDays(days, hours);
246 	} else if (hours > 0) {
247 		return simplified
248             ? formatter.formatHours(hours)
249             : formatter.formatHours(hours, mins);
250 	} else if (mins > 0) {
251 		return simplified
252             ? formatter.formatMinutes(mins)
253             : formatter.formatMinutes(mins, secs);
254 	} else {
255 		return formatter.formatSeconds(secs);
256 	}
257 };
258 
259 AjxDateUtil.simpleComputeDateStr = 
260 function(date, stringToPrepend) {
261 	var dateStr = AjxDateUtil._dateFormat.format(date);
262 	return stringToPrepend ? stringToPrepend + dateStr : dateStr;
263 };
264 AjxDateUtil.simpleParseDateStr =
265 function(dateStr) {
266 	return AjxDateUtil._dateFormat.parse(dateStr);
267 };
268 
269 AjxDateUtil.simpleComputeDateTimeStr = 
270 function(date, stringToPrepend) {
271 	var dateTimeStr = AjxDateUtil._dateTimeFormat.format(date);
272 	return stringToPrepend ? stringToPrepend + dateTimeStr : dateTimeStr;
273 };
274 AjxDateUtil.simpleParseDateTimeStr =
275 function(dateTimeStr) {
276 	return AjxDateUtil._dateTimeFormat.parse(dateTimeStr);
277 };
278 
279 AjxDateUtil.longComputeDateStr = 
280 function(date) {
281 	var formatter = AjxDateFormat.getDateInstance(AjxDateFormat.FULL);
282 	return formatter.format(date);
283 }
284 
285 AjxDateUtil.computeDateStr =
286 function(now, dateMSec) {
287 	if (dateMSec == null)
288 		return "";
289 
290 	var date = new Date(dateMSec);
291 	if (now.getTime() - dateMSec < AjxDateUtil.MSEC_PER_DAY &&
292 		now.getDay() == date.getDay()) {
293 		return AjxDateUtil.computeTimeString(date);
294 	}
295 
296 	if (now.getFullYear() == date.getFullYear()) {
297 		return AjxDateUtil._dateFormatNoYear.format(date);
298 	}
299 
300 	return AjxDateUtil.simpleComputeDateStr(date);
301 };
302 
303 AjxDateUtil.computeDateStrNoYear =
304 function(date) {
305     return AjxDateUtil._dateFormatNoYear.format(date);
306 };
307 
308 // Example output: "Today, 9:44 AM" "Yesterday, 12:22 PM" "Sun, 1/11/01 1:11 PM"
309 AjxDateUtil.computeWordyDateStr =
310 function(now, dateMSec) {
311 	if (dateMSec == null) {
312 		return "";
313 	}
314 
315 	var date = new Date(dateMSec);
316 	if (now.getTime() - dateMSec < AjxDateUtil.MSEC_PER_DAY && now.getDay() == date.getDay()) {
317 		if (!AjxDateUtil._wordyDateToday) {
318 			AjxDateUtil._wordyDateToday = new AjxDateFormat(AjxMsg.formatWordyDateToday);
319 		}
320 		return AjxDateUtil._wordyDateToday.format(date);
321 	} else if ((now.getTime() - dateMSec) < (2 * AjxDateUtil.MSEC_PER_DAY) && (now.getDay() - 1) == date.getDay()) {
322 		if (!AjxDateUtil._wordyDateYesterday) {
323 			AjxDateUtil._wordyDateYesterday = new AjxDateFormat(AjxMsg.formatWordyDateYesterday);
324 		}
325 		return AjxDateUtil._wordyDateYesterday.format(date);
326 	} else {
327 		if (!AjxDateUtil._wordyDate) {
328 			AjxDateUtil._wordyDate = new AjxDateFormat(AjxMsg.formatWordyDate);
329 		}
330 		return AjxDateUtil._wordyDate.format(date);
331 	}
332 };
333 
334 /* returns true if dateString is a valid and understandable date string
335  * in compliance with the locale of the user ie. dd/mm/yy or mm/dd/yy etc.
336  * Also for date strings like 1/32/2000 (that roll over to 2/1/2000), false is returned.
337  */
338 AjxDateUtil.isValidSimpleDateStr =
339 function(str){
340         if(!str) {return false};
341         var dateValue = AjxDateUtil.getSimpleDateFormat().parse(str);
342         if (!dateValue) {return false};
343         var dateValueStr = AjxDateUtil.simpleComputeDateStr(dateValue);
344         return (str == dateValueStr);
345 }
346 
347 AjxDateUtil.computeTimeString =
348 function(date) {
349 	var formatter = AjxDateFormat.getTimeInstance(AjxDateFormat.SHORT);
350 	return formatter.format(date);
351 };
352 
353 AjxDateUtil.computeDateTimeString =
354 function(date) {
355 	var formatter = AjxDateFormat.getDateTimeInstance(AjxDateFormat.LONG);
356 	return formatter.format(date);
357 };
358 
359 AjxDateUtil._getHoursStr =
360 function(date, pad, useMilitary) {
361 	var myVal = date.getHours();
362 	if (!useMilitary) {
363 		myVal %= 12;
364 		if (myVal == 0) myVal = 12;
365 	}
366 	return pad ? AjxDateUtil._pad(myVal) : myVal;
367 };
368 
369 AjxDateUtil._getMinutesStr = 
370 function(date) {
371 	return AjxDateUtil._pad(date.getMinutes());
372 };
373 
374 AjxDateUtil._getSecondsStr = 
375 function(date) {
376 	return AjxDateUtil._pad(date.getSeconds());
377 };
378 
379 AjxDateUtil._getAMPM = 
380 function (date, upper) {
381 	var myHour = date.getHours();
382 	return (myHour < 12) ? (upper ? 'AM' : 'am') : (upper ? 'PM' : 'pm');
383 };
384 
385 AjxDateUtil._getMonthName = 
386 function(date, abbreviated) {
387 	return abbreviated
388 		? AjxDateUtil.MONTH_MEDIUM[date.getMonth()]
389 		: AjxDateUtil.MONTH_LONG[date.getMonth()];
390 };
391 
392 AjxDateUtil._getMonth = 
393 function(date, pad) {
394 	var myMonth = date.getMonth() + 1;
395 	if (pad) {
396 		return AjxDateUtil._pad(myMonth);
397 	} else {
398 		return myMonth;
399 	}
400 };
401 
402 AjxDateUtil._getDate = 
403 function(date, pad) {
404 	var myVal = date.getDate();
405 	return pad ? AjxDateUtil._pad(myVal) : myVal;
406 };
407 
408 AjxDateUtil._getWeekday =
409 function (date) {
410 	var myVal = date.getDay();
411 	return AjxDateUtil.WEEKDAY_LONG[myVal];
412 };
413 
414 // Returns "Mon", "Tue", etc.
415 AjxDateUtil._getWeekdayMedium =
416 function (date) {
417 	var myVal = date.getDay();
418 	return AjxDateUtil.WEEKDAY_MEDIUM[myVal];
419 };
420 
421 AjxDateUtil._getFullYear =
422 function(date) {
423 	return date.getFullYear();
424 };
425 
426 AjxDateUtil.getFirstDayOfWeek =
427 function (dt, startOfWeek) {
428     startOfWeek = startOfWeek || 0;
429     var dayOfWeekIndex = dt.getDay();
430     var dayOfWeek = (dayOfWeekIndex - startOfWeek + 7) % 7;
431     dt.setDate(dt.getDate() - dayOfWeek);
432     return dt;
433 };
434 
435 AjxDateUtil.getLastDayOfWeek =
436 function (dt, startOfWeek) {
437     startOfWeek = startOfWeek || 0;
438     var dayOfWeekIndex = dt.getDay();
439     var dayOfWeek = (dayOfWeekIndex - startOfWeek + 7) % 7;
440     dt.setDate(dt.getDate() - dayOfWeek + 6);
441     dt.setHours(23, 59, 59, 999);
442     return dt;
443 };
444 
445 AjxDateUtil.getWeekNumber =
446 function(date, firstDayOfWeek, janDate, isISO8601WeekNum) {
447 
448     // Setup Defaults
449     firstDayOfWeek = firstDayOfWeek || 0;
450     janDate = janDate || AjxDateUtil.WEEK_ONE_JAN_DATE;
451     date = date || new Date();
452 
453     date.setHours(12,0,0,0);
454     var targetDate = date,
455             startOfWeek,
456             endOfWeek;
457 
458     if (targetDate.getDay() === firstDayOfWeek) {
459         startOfWeek = targetDate;
460     } else {
461         startOfWeek = AjxDateUtil.getFirstDayOfWeek(targetDate, firstDayOfWeek);
462     }
463 
464     var startYear = startOfWeek.getFullYear(),
465             startTime = startOfWeek.getTime();
466 
467     // DST shouldn't be a problem here, math is quicker than setDate();
468     endOfWeek = new Date(startOfWeek.getTime() + 6*AjxDateUtil.MSEC_PER_DAY);
469 
470     var weekNum;
471 
472     if(!isISO8601WeekNum) {
473         if (startYear !== endOfWeek.getFullYear() && endOfWeek.getDate() >= janDate) {
474             weekNum = 1;
475         } else {
476             var weekOne = (new Date(startYear, 0, janDate));
477             weekOne.setHours(12,0,0,0);
478             var weekOneDayOne = AjxDateUtil.getFirstDayOfWeek(weekOne, firstDayOfWeek);
479 
480             // Round days to smoothen out 1 hr DST diff
481             var daysDiff  = Math.round((targetDate.getTime() - weekOneDayOne.getTime())/AjxDateUtil.MSEC_PER_DAY);
482 
483             // Calc. Full Weeks
484             var rem = daysDiff % 7;
485             var weeksDiff = (daysDiff - rem)/7;
486             weekNum = weeksDiff + 1;
487         }
488         return weekNum;
489     }else {
490 
491         var newYear = new Date(date.getFullYear(),0,1);
492         var day = newYear.getDay() - 1;
493         day = (day >= 0 ? day : day + 7);
494         var dayOftheYear = Math.floor((date.getTime()-newYear.getTime() - (date.getTimezoneOffset()-newYear.getTimezoneOffset())*60000)/AjxDateUtil.MSEC_PER_DAY) + 1;
495 
496         if(day < 4)
497         {
498             weekNum = Math.floor((dayOftheYear+day-1)/7) + 1;
499             if(weekNum > 52)
500             {
501                 var nxtYear = new Date(date.getFullYear() + 1,0,1);
502                 var nxtDay = nxtYear.getDay() - 1;
503                 nxtDay = nxtDay >= 0 ? nxtDay : nxtDay + 7;
504                 weekNum = nxtDay < 4 ? 1 : 53;
505             }
506         }else {
507             weekNum = Math.floor((dayOftheYear+day -1 )/7);
508             if(weekNum == 0)
509             {
510                 var prevYear = new Date(date.getFullYear()-1,0,1);
511                 var prevDay = prevYear.getDay()-1;
512                 prevDay = (prevDay >= 0 ? prevDay : prevDay + 7);
513                 weekNum = ( prevDay==3 || ( AjxDateUtil.isLeapYear(prevYear.getFullYear()) && prevDay==2 ) ) ? 53 : 52;
514             }
515         }
516         return weekNum;
517     }
518 };
519 
520 AjxDateUtil.getTimeStr = 
521 function(date, format) {
522 	var s = format;
523 	s = s.replace(/%d/g, AjxDateUtil._getDate(date, true));				// zero padded day of the month
524 	s = s.replace(/%D/g, AjxDateUtil._getDate(date, false));			// day of the month without padding
525 	s = s.replace(/%w/g, AjxDateUtil._getWeekday(date));				// day of the week
526 	s = s.replace(/%M/g, AjxDateUtil._getMonthName(date));				// full month name
527 	s = s.replace(/%t/g, AjxDateUtil._getMonthName(date, true));		// abbr. month name
528 	s = s.replace(/%n/g, AjxDateUtil._getMonth(date, true));		    // zero padded month
529 	s = s.replace(/%Y/g, AjxDateUtil._getFullYear(date));				// full year
530 	s = s.replace(/%h/g, AjxDateUtil._getHoursStr(date, false, false));	// non-padded hours
531 	s = s.replace(/%H/g, AjxDateUtil._getHoursStr(date, true, false ));	// padded hours
532 	s = s.replace(/%m/g, AjxDateUtil._getMinutesStr(date));				// padded minutes
533 	s = s.replace(/%s/g, AjxDateUtil._getSecondsStr(date));				// padded seconds
534 	s = s.replace(/%P/g, AjxDateUtil._getAMPM(date, true));				// upper case AM PM
535 	s = s.replace(/%p/g, AjxDateUtil._getAMPM(date, false));			// lower case AM PM
536 	return s;
537 };
538 
539 AjxDateUtil.getRoundedMins = 
540 function (date, roundTo) {
541 	var mins = date.getMinutes();
542 	if (mins != 0 && roundTo)
543 		mins = (Math.ceil( (mins/roundTo) )) * roundTo;
544 	return mins;
545 };
546 
547 AjxDateUtil.roundTimeMins = 
548 function(date, roundTo) {
549 	var mins = date.getMinutes();
550 	var hours = date.getHours();
551 	if (mins != 0 && roundTo){
552 		mins = (Math.ceil( (mins/roundTo) )) * roundTo;
553 		if (mins == 60) {
554 			mins = 0;
555 			hours++;
556 		}
557 		date.setMinutes(mins);
558 		date.setHours(hours);
559 	}
560 	return date;
561 };
562 
563 AjxDateUtil.isInRange = 
564 function(startTime1, endTime1, startTime2, endTime2) {
565 	return (startTime1 < endTime2 && endTime1 > startTime2);
566 }
567 
568 AjxDateUtil.getSimpleDateFormat =
569 function() {
570 	return AjxDateUtil._dateFormat;
571 };
572 
573 /**
574  * The following are helper routines for processing server date/time which comes
575  * in this format: YYYYMMDDTHHMMSSZ
576 */
577 AjxDateUtil.getServerDate = 
578 function(date) {
579 	if (!AjxDateUtil._serverDateFormatter) {
580 		AjxDateUtil._serverDateFormatter = new AjxDateFormat("yyyyMMdd");
581 	}
582 	return AjxDateUtil._serverDateFormatter.format(date);
583 };
584 
585 AjxDateUtil.getServerDateTime = 
586 function(date, useUTC) {
587 	var newDate = date;
588 	var formatter = null;
589 
590 	if (useUTC) {
591 		if (!AjxDateUtil._serverDateTimeFormatterUTC) {
592 			AjxDateUtil._serverDateTimeFormatterUTC = new AjxDateFormat("yyyyMMdd'T'HHmmss'Z'");
593 		}
594 		formatter = AjxDateUtil._serverDateTimeFormatterUTC;
595 		// add timezone offset to this UTC date
596 		newDate = new Date(date.getTime());
597 		newDate.setMinutes(newDate.getMinutes() + newDate.getTimezoneOffset());
598 	} else {
599 		if (!AjxDateUtil._serverDateTimeFormatter) {
600 			AjxDateUtil._serverDateTimeFormatter = new AjxDateFormat("yyyyMMdd'T'HHmmss");
601 		}
602 		formatter = AjxDateUtil._serverDateTimeFormatter;
603 	}
604 
605 	return formatter.format(newDate);
606 };
607 
608 AjxDateUtil.parseServerTime = 
609 function(serverStr, date, noSpecialUtcCase) {
610 	if (serverStr.charAt(8) == 'T') {
611 		var hh = parseInt(serverStr.substr(9,2), 10);
612 		var mm = parseInt(serverStr.substr(11,2), 10);
613 		var ss = parseInt(serverStr.substr(13,2), 10);
614 		if (!noSpecialUtcCase && serverStr.charAt(15) == 'Z') {
615 			mm += AjxTimezone.getOffset(AjxTimezone.DEFAULT, date);
616 		}
617 		date.setHours(hh, mm, ss, 0);
618 	}
619 	return date;
620 };
621 
622 AjxDateUtil.parseISO8601Date = function(s) {
623     var formatters = AjxDateUtil.__ISO8601_formats;
624     if (!formatters) {
625         formatters = AjxDateUtil.__ISO8601_formats = [
626             new AjxDateUtil.TZDFormat("yyyy-MM-dd'T'HH:mm:ss.SZ"),
627             new AjxDateUtil.TZDFormat("yyyy-MM-dd'T'HH:mm:ssZ"),
628             new AjxDateUtil.TZDFormat("yyyy-MM-dd'T'HH:mmZ"),
629             new AjxDateFormat("yyyy-MM-dd"),
630             new AjxDateFormat("yyyy-MM"),
631             new AjxDateFormat("yyyy")
632         ];
633     }
634     for (var i = 0; i < formatters.length; i++) {
635         var date = formatters[i].parse(s);
636         if (date) return date;
637     }
638     return null;
639 };
640 
641 AjxDateUtil.TZDFormat = function(pattern) {
642     if (arguments.length == 0) return;
643     AjxDateFormat.apply(this, arguments);
644     var segments = this._segments || [];
645     for (var i = 0; i < segments.length; i++) {
646         var segment = segments[i];
647         if (segment instanceof AjxDateFormat.TimezoneSegment) {
648             segments[i] = new AjxDateUtil.TZDSegment(segment.toSubPattern());
649         }
650     }
651 };
652 AjxDateUtil.TZDFormat.prototype = new AjxDateFormat;
653 AjxDateUtil.TZDFormat.prototype.constructor = AjxDateUtil.TZDFormat;
654 AjxDateUtil.TZDFormat.prototype.toString = function() { return "TZDFormat"; };
655 
656 AjxDateUtil.TZDSegment = function(pattern) {
657     if (arguments.length == 0) return;
658     AjxDateFormat.TimezoneSegment.apply(this, arguments);
659 };
660 AjxDateUtil.TZDSegment.prototype = new AjxDateFormat.TimezoneSegment;
661 AjxDateUtil.TZDSegment.prototype.constructor = AjxDateUtil.TZDSegment;
662 AjxDateUtil.TZDSegment.prototype.toString = function() { return "TZDSegment"; };
663 
664 AjxDateUtil.TZDSegment.prototype.parse = function(o, s, i) {
665     var m = /^(Z)|^(\+|\-)(\d\d):(\d\d)/.exec(s.substr(i));
666     if (m) {
667         var offset = new Date().getTimezoneOffset();
668         if (m[1]) o.timezone = offset;
669         else {
670             var hours = parseInt(m[3],10), mins = parseInt(m[4],10);
671             o.timezone = hours * 60 + mins;
672             if (m[2] != "-") o.timezone *= -1;
673             o.timezone -= offset;
674         }
675     }
676     return i + (m ? m[0].length : 0);
677 };
678 
679 AjxDateUtil.parseServerDateTime = 
680 function(serverStr, noSpecialUtcCase) {
681 	if (serverStr == null) return null;
682 
683 	var d = new Date();
684 	var yyyy = parseInt(serverStr.substr(0,4), 10);
685 	var MM = parseInt(serverStr.substr(4,2), 10);
686 	var dd = parseInt(serverStr.substr(6,2), 10);
687 	d.setFullYear(yyyy);
688 	d.setMonth(MM - 1);
689 	d.setMonth(MM - 1); // DON'T remove second call to setMonth (see bug #3839)
690 	d.setDate(dd);
691 	AjxDateUtil.parseServerTime(serverStr, d, noSpecialUtcCase);
692 	return d;
693 };
694 
695 AjxDateUtil._pad = 
696 function(n) {
697 	return n < 10 ? ('0' + n) : n;
698 };
699 
700 /**
701  * Returns the year portion of the given date as a YYYY string.
702  *
703  * @param {Date}    date    (optional, defaults to current date) a date
704  * @returns {string}    year as YYYY
705  */
706 AjxDateUtil.getYearStr = function(date) {
707     date = date || new Date();
708     return date.getFullYear() + "";
709 };
710 
711 AjxDurationFormatVerbose = function() { }
712 
713 AjxDurationFormatVerbose.formatYears =
714 function(years, months) {
715 	var deltaStr =  years + " ";
716 	deltaStr += (years > 1) ? AjxMsg.years : AjxMsg.year;
717 	if (years <= 3 && months > 0) {
718 		deltaStr += " " + months;
719 		deltaStr += " " + ((months > 1) ? AjxMsg.months : AjxMsg.months);
720 	}
721 	return deltaStr;
722 };
723 
724 AjxDurationFormatVerbose.formatMonths =
725 function(months, days) {
726 	var deltaStr =  months + " ";
727 	deltaStr += (months > 1) ? AjxMsg.months : AjxMsg.month;
728 	if (months <= 3 && days > 0) {
729 		deltaStr += " " + days;
730 		deltaStr += " " + ((days > 1) ? AjxMsg.days : AjxMsg.day);
731 	}
732 	return deltaStr;
733 };
734 
735 AjxDurationFormatVerbose.formatDays =
736 function(days, hours) {
737 	var deltaStr = days + " ";
738 	deltaStr += (days > 1) ? AjxMsg.days : AjxMsg.day;
739 	if (days <= 2 && hours > 0) {
740 		deltaStr += " " + hours;
741 		deltaStr += " " + ((hours > 1) ? AjxMsg.hours : AjxMsg.hour);
742 	}
743 	return deltaStr;
744 };
745 
746 AjxDurationFormatVerbose.formatHours =
747 function(hours, mins) {
748 	var deltaStr = hours + " ";
749 	deltaStr += (hours > 1) ? AjxMsg.hours : AjxMsg.hour;
750 	if (hours < 5 && mins > 0) {
751 		deltaStr += " " + mins;
752 		deltaStr += " " + ((mins > 1) ? AjxMsg.minutes : AjxMsg.minute);
753 	}
754 	return deltaStr;
755 };
756 
757 AjxDurationFormatVerbose.formatMinutes =
758 function(mins, secs) {
759 	var deltaStr = mins + " ";
760 	deltaStr += ((mins > 1) ? AjxMsg.minutes : AjxMsg.minute);
761 	if (mins < 5 && secs > 0) {
762 		deltaStr += " " + secs;
763 		deltaStr += " " + ((secs > 1) ? AjxMsg.seconds : AjxMsg.second);
764 	}
765 	return deltaStr;
766 };
767 
768 AjxDurationFormatVerbose.formatSeconds =
769 function(secs) {
770 	return (secs + " " + ((secs > 1) ? AjxMsg.seconds : AjxMsg.second));
771 };
772 
773 AjxDurationFormatConcise = function() { }
774 
775 AjxDurationFormatConcise.formatYears =
776 function(years, months) {
777 	return this._format(years, months);
778 };
779 
780 AjxDurationFormatConcise.formatMonths =
781 function(months, days) {
782 	return this._format(months, days);
783 };
784 
785 AjxDurationFormatConcise.formatDays =
786 function(days, hours) {
787 	return this._format(days, hours);
788 };
789 
790 AjxDurationFormatConcise.formatHours =
791 function(hours, mins) {
792 	return this._format(hours, mins);
793 };
794 
795 AjxDurationFormatConcise.formatMinutes =
796 function(mins, secs) {
797 	return this._format(mins, secs);
798 };
799 
800 AjxDurationFormatConcise.formatSeconds =
801 function(secs) {
802 	return this._format(0, secs);
803 };
804 
805 AjxDurationFormatConcise._format =
806 function(a, b) {
807 	var i = 0;
808 	var result = [];
809 	result[i++] = a;
810 	result[i++] = ':';
811 	if (b < 10) {
812 		result[i++] = '0';
813 	}
814 	result[i++] = b;
815 	return result.join('');
816 };
817 
818 /**
819  * Added more utility functions for date finding and navigating
820  */
821 
822 AjxDateUtil.SUNDAY = 0;
823 AjxDateUtil.MONDAY = 1;
824 AjxDateUtil.TUESDAY = 2;
825 AjxDateUtil.WEDNESDAY = 3;
826 AjxDateUtil.THURSDAY = 4;
827 AjxDateUtil.FRIDAY = 5;
828 AjxDateUtil.SATURDAY = 6;                                                                              
829 
830 /**
831  *
832  * @param fromThisDate The searching starts from this date.
833  * @param thisWeekday  The day to find ( eg. AjxDateUtil.SUNDAY)
834  * @param count Which occurence, like first, second.. has to be always positive
835  * 
836  */
837 AjxDateUtil.getDateForNextDay =
838 function(fromThisDate, thisWeekday, count) {
839 	count = count || 1;
840 	var r = new Date(fromThisDate);
841 	for (var i = 0; i < count; i++) {
842 		r = AjxDateUtil._getDateForNextWeekday(r, thisWeekday);
843 		if (i < count-1) {
844 			r.setDate(r.getDate() + 1);
845 		}
846 	}
847 	return r;
848 }
849 
850 /**
851  *
852  * @param fromThisDate The searching work week days starting from this date
853  * @param count Which occurence, like first, second.. has to be always positive
854  *
855  */
856 AjxDateUtil.getDateForNextWorkWeekDay =
857 function(fromThisDate, count) {
858 	count = count?count:1;
859 	var r = new Date(fromThisDate);
860 	for (var i = 0; i < count; i++) {
861 		r = AjxDateUtil._getDateForNextWorkWeekday(r);
862 		if (i < count-1) {
863 			r.setDate(r.getDate() + 1);
864 		}
865 	}
866 	return r;
867 }
868 
869 /**
870  *
871  * @param fromThisDate The starting point
872  * @param thisWeekday  The day to find
873  * @param count this many positions to navigate, if negative goes in reverse, if positive goes forward
874  */
875 AjxDateUtil.getDateForThisDay =
876 function(fromThisDate, thisWeekday, count) {
877 	if (count < 0 ) {
878 		return AjxDateUtil.getDateForPrevDay(fromThisDate, thisWeekday, -count);//-(-)  is plus
879 	} else {
880 		return AjxDateUtil.getDateForNextDay(fromThisDate, thisWeekday, count);
881 	}
882 }
883 
884 /**
885  *
886  * @param fromThisDate The starting point
887  * @param count this many positions to navigate, if negative goes in reverse, if positive goes forward
888  */
889 AjxDateUtil.getDateForThisWorkWeekDay =
890 function(fromThisDate, count) {
891 	if (count < 0 ) {
892 		return AjxDateUtil.getDateForPrevWorkWeekDay(fromThisDate, -count);		//-(-)  is plus
893 	}else{
894 		return AjxDateUtil.getDateForNextWorkWeekDay(fromThisDate, count);
895 	}
896 }
897 
898 /**
899  *
900  * @param fromThisDate The searching starts from this date in reverse direction. 
901  * @param thisWeekday  The day to find ( eg. AjxDateUtil.SUNDAY)
902  * @param count Which occurence, like first, second..has to be always positive
903  */
904 AjxDateUtil.getDateForPrevDay =
905 function(fromThisDate,thisWeekday,count) {
906 	count = count || 1;
907 	var r = new Date(fromThisDate);
908 	for (var i = 0; i < count; i++) {
909 		r = AjxDateUtil._getDateForPrevWeekday(r, thisWeekday);
910 		if (i < count-1) {
911 			r.setDate(r.getDate()-1);
912 		}
913 	}
914 	return r;
915 }
916 
917 /**
918  *
919  * @param fromThisDate The searching for work week days starting from this date in reverse direction.
920  * @param count Which occurence, like first, second..has to be always positive
921  */
922 
923 AjxDateUtil.getDateForPrevWorkWeekDay =
924 function(fromThisDate, count) {
925 	count = count || 1;
926 	var r = new Date(fromThisDate);
927 	for(var i = 0; i < count; i++) {
928 		r = AjxDateUtil._getDateForPrevWorkWeekday(r);
929 		if (i < count-1) {
930 			r.setDate(r.getDate()-1);
931 		}
932 	}
933 	return r;
934 }
935 
936 /**
937  * note - this deals with the format we save from Prefs page. Careful if using for other cases.
938  * @param value
939  * @return {String}
940  */
941 AjxDateUtil.dateLocal2GMT =
942 function(value) {
943 	if (!value) { return ""; }
944 
945 	var yr, mo, da, hr, mi, se; // really smart parsing.
946 	yr = parseInt(value.substr(0,  4), 10);
947 	mo = parseInt(value.substr(4,  2), 10);
948 	da = parseInt(value.substr(6,  2), 10);
949 	hr = parseInt(value.substr(8,  2), 10);
950 	mi = parseInt(value.substr(10, 2), 10);
951 	se = parseInt(value.substr(12, 2), 10);
952 	var date = new Date(yr, mo - 1, da, hr, mi, se, 0);
953 	yr = date.getUTCFullYear();
954 	mo = date.getUTCMonth() + 1;
955 	da = date.getUTCDate();
956 	hr = date.getUTCHours();
957 	mi = date.getUTCMinutes();
958 	se = date.getUTCSeconds();
959 	var a = [ yr, mo, da, hr, mi, se ];
960 	for (var i = a.length; --i > 0;) {
961 		var n = a[i];
962 		if (n < 10)
963 			a[i] = "0" + n;
964 	}
965 	return (a.join("") + "Z");
966 };
967 
968 /**
969  * note - this deals with the format we save from Prefs page. Careful if using for other cases.
970  * @param value
971  * @return {String}
972  */
973 AjxDateUtil.dateGMT2Local =
974 function(value) {
975 	if (!value) { return ""; }
976 
977 	var yr, mo, da, hr, mi, se; // really smart parsing.
978 	yr = parseInt(value.substr(0,  4), 10);
979 	mo = parseInt(value.substr(4,  2), 10);
980 	da = parseInt(value.substr(6,  2), 10);
981 	hr = parseInt(value.substr(8,  2), 10);
982 	mi = parseInt(value.substr(10, 2), 10);
983 	se = parseInt(value.substr(12, 2), 10);
984 	var date = new Date();
985 	date.setUTCMilliseconds(0);
986 	date.setUTCSeconds(se);
987 	date.setUTCMinutes(mi);
988 	date.setUTCHours(hr);
989 	date.setUTCDate(da);
990 	date.setUTCMonth(mo - 1);
991 	date.setUTCFullYear(yr);
992 	yr = date.getFullYear();
993 	mo = date.getMonth() + 1;
994 	da = date.getDate();
995 	hr = date.getHours();
996 	mi = date.getMinutes();
997 	se = date.getSeconds();
998 	var a = [yr, mo, da, hr, mi, se];
999 	for (var i = a.length; --i > 0;) {
1000 		var n = a[i];
1001 		if (n < 10)
1002 			a[i] = "0" + n;
1003 	}
1004 	return (a.join("") + "Z");
1005 };
1006 
1007 
1008 AjxDateUtil._getDateForNextWeekday =
1009 function(fromThisDate,thisWeekday) {
1010 	var newDate = new Date(fromThisDate);
1011 	var weekDay = fromThisDate.getDay();
1012 	if (weekDay == thisWeekday) {
1013 		return newDate;
1014 	}
1015 	var diff = (thisWeekday-weekDay);
1016 	if (diff > 0) {
1017 		newDate.setDate(fromThisDate.getDate() + diff);
1018 	} else {
1019 		newDate.setDate(fromThisDate.getDate() + (7 + diff));
1020 	}
1021 	return newDate;
1022 }
1023 
1024 AjxDateUtil._getDateForNextWorkWeekday =
1025 function(fromThisDate) {
1026 	var newDate = new Date(fromThisDate);
1027 	var weekDay = fromThisDate.getDay();
1028 	if (weekDay == AjxDateUtil.SUNDAY) {
1029 		newDate.setDate(fromThisDate.getDate()+1);
1030 	} else if (weekDay == AjxDateUtil.SATURDAY) {
1031 		newDate.setDate(fromThisDate.getDate()+2);
1032 	}
1033 	return newDate;
1034 }
1035 
1036 AjxDateUtil._getDateForPrevWeekday =
1037 function(fromThisDate, thisWeekday) {
1038 	var newDate = new Date(fromThisDate);
1039 	var weekDay = fromThisDate.getDay();
1040 	if (weekDay == thisWeekday) {
1041 		return newDate;
1042 	}
1043 	var diff = (weekDay-thisWeekday);
1044 	if (diff > 0) {
1045 		newDate.setDate(fromThisDate.getDate() - diff);
1046 	} else {
1047 		newDate.setDate(fromThisDate.getDate() - (7 + diff));
1048 	}
1049 	return newDate;
1050 }
1051 
1052 AjxDateUtil._getDateForPrevWorkWeekday =
1053 function(fromThisDate) {
1054 	var newDate = new Date(fromThisDate);
1055 	var weekDay = fromThisDate.getDay();
1056 	if (weekDay == AjxDateUtil.SUNDAY) {
1057 		newDate.setDate(fromThisDate.getDate() - 2);
1058 	} else if (weekDay == AjxDateUtil.SATURDAY) {
1059 		newDate.setDate(fromThisDate.getDate() - 1);
1060 	}
1061 	return newDate;
1062 }
1063 
1064 //
1065 // Date calculator functions
1066 //
1067 
1068 AjxDateUtil.calculate =
1069 function(rule, date) {
1070 	// initialize
1071 	if (!AjxDateUtil.__calculate_initialized) {
1072 		AjxDateUtil.__calculate_initialized = true;
1073 		AjxDateUtil.__calculate_init();
1074 	}
1075 
1076 	var now = date || new Date;
1077 	rule = rule.replace(/^\s*|\s*$/, "").replace(/\s*=\s*/g,"=").replace(/\s*,\s*/g,",");
1078 	var a = rule.split(/\s+/g);
1079 	var s, m, plusminus, number, type, amount, weekord, daynum;
1080 	for (var i = 0; i < a.length; i++) {
1081 		s = a[i];
1082 		// comment
1083 		if (s.match(AjxDateUtil.RE_COMMENT)) {
1084 			break;
1085 		}
1086 		// context date
1087 		if (s.match(AjxDateUtil.RE_NOW)) {
1088 			date = new Date(now.getTime());
1089 			continue;
1090 		}
1091 		// add
1092 		if (m = s.match(AjxDateUtil.RE_ADD_NUMBER)) {
1093 			plusminus = m[1];
1094 			number = AjxDateUtil.__calculate_parseInt(m[2]);
1095 			type = a[++i];
1096 			amount = plusminus == '+' ? number : number * -1;
1097 			AjxDateUtil.__calculate_add(date, type, amount);
1098 			continue;
1099 		}
1100 		// set
1101 		if (m = s.match(AjxDateUtil.RE_SET)) {
1102 			AjxDateUtil.__calculate_set(date, m[1], m[2]);
1103 			continue;
1104 		}
1105 		// try to parse as a date
1106 		date = AjxDateFormat.parse("yyyyy-MM-dd", s);
1107 		if (!date && (date = AjxDateFormat.parse("yyyy-MM-dd'T'hh:mm:ss'Z'", s))) {
1108 			date.setMinutes(date.getMinutes() - date.getTimezoneOffset());
1109 		}
1110 		if (!date) date = AjxDateFormat.parse("yyyy-MM-dd'T'HH:mm:ss", s);
1111 		if (!date) throw "invalid date pattern: \""+s+"\"";
1112 	}
1113 	return date;
1114 };
1115 
1116 //
1117 // Date calculator constants
1118 //
1119 
1120 AjxDateUtil.S_DAYNAME = [
1121 	AjxMsg["calc.dayname.sunday"],
1122 	AjxMsg["calc.dayname.monday"],
1123 	AjxMsg["calc.dayname.tuesday"],
1124 	AjxMsg["calc.dayname.wednesday"],
1125 	AjxMsg["calc.dayname.thursday"],
1126 	AjxMsg["calc.dayname.friday"],
1127 	AjxMsg["calc.dayname.saturday"]
1128 ].join("|");
1129 
1130 AjxDateUtil.S_MONTHNAME = [
1131 	AjxMsg["calc.monthname.january"],
1132 	AjxMsg["calc.monthname.february"],
1133 	AjxMsg["calc.monthname.march"],
1134 	AjxMsg["calc.monthname.april"],
1135 	AjxMsg["calc.monthname.may"],
1136 	AjxMsg["calc.monthname.june"],
1137 	AjxMsg["calc.monthname.july"],
1138 	AjxMsg["calc.monthname.august"],
1139 	AjxMsg["calc.monthname.september"],
1140 	AjxMsg["calc.monthname.october"],
1141 	AjxMsg["calc.monthname.november"],
1142 	AjxMsg["calc.monthname.december"]
1143 ].join("|");
1144 
1145 AjxDateUtil.S_WEEKORD = [
1146 	AjxMsg["calc.ordinal.first"],
1147 	AjxMsg["calc.ordinal.second"],
1148 	AjxMsg["calc.ordinal.third"],
1149 	AjxMsg["calc.ordinal.fourth"],
1150 	AjxMsg["calc.ordinal.fifth"],
1151 	AjxMsg["calc.ordinal.last"]
1152 ].join("|");
1153 
1154 AjxDateUtil.WEEKORD_RE = [
1155     new RegExp("(first|"+AjxMsg["calc.ordinal.first"]+")",  "i"),
1156     new RegExp("(second|"+AjxMsg["calc.ordinal.second"]+")", "i"),
1157     new RegExp("(third|"+AjxMsg["calc.ordinal.third"]+")",  "i"),
1158     new RegExp("(fourth|"+AjxMsg["calc.ordinal.fourth"]+")", "i"),
1159     new RegExp("(last|"+AjxMsg["calc.ordinal.last"]+")",   "i")
1160 ];
1161 
1162 // NOTE: Originally, the keywords for the date calculation rules
1163 //       were in the message bundle so that they could be translated.
1164 //       But while the keywords were translated, the rules were not
1165 //       updated to use the translated keywords. So none of the date
1166 //       matching worked in other languages. So I am reverting that
1167 //       decision and hard-coding all of the relevant keywords. The
1168 //       ordinals, day names, and month names still need to be
1169 //       translated, though.
1170 
1171 AjxMsg["calc.now"]	= "now";
1172 AjxMsg["calc.date"]	= "date";
1173 
1174 AjxMsg["calc.duration.year"]		= "year|years";
1175 AjxMsg["calc.duration.month"]		= "mons|month|months";
1176 AjxMsg["calc.duration.day"]			= "day|days";
1177 AjxMsg["calc.duration.hour"]		= "hour|hours";
1178 AjxMsg["calc.duration.minute"]		= "min|mins|minute|minutes";
1179 AjxMsg["calc.duration.week"]        = "week";
1180 AjxMsg["calc.duration.second"]		= "sec|secs|second|seconds";
1181 AjxMsg["calc.duration.millisecond"]	= "milli|millis|millisecond|milliseconds";
1182 
1183 AjxDateUtil.S_DURATION = [
1184 	AjxMsg["calc.duration.year"],
1185 	AjxMsg["calc.duration.month"],
1186     AjxMsg["calc.duration.week"],
1187 	AjxMsg["calc.duration.day"],
1188 	AjxMsg["calc.duration.hour"],
1189 	AjxMsg["calc.duration.minute"],
1190 	AjxMsg["calc.duration.second"],
1191 	AjxMsg["calc.duration.millisecond"]
1192 ].join("|");
1193 
1194 //
1195 // Date calculator private functions
1196 //
1197 
1198 AjxDateUtil.__calculate_init =
1199 function() {
1200 	AjxDateUtil.WEEKDAYS = {};
1201 	var weekdays = [
1202 		"sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"
1203 	];
1204 	for (var i = 0; i < weekdays.length; i++) {
1205 		var weekday = AjxMsg["calc.dayname."+weekdays[i]].split("|");
1206 		for (var j = 0; j < weekday.length; j++) {
1207 			AjxDateUtil.WEEKDAYS[weekday[j].toLowerCase()] = i;
1208 		}
1209 	}
1210 
1211 	AjxDateUtil.MONTHNAME2MONTHNUM = {};
1212 	var months = [
1213 		"january", "february", "march", "april", "may", "june",
1214 		"july", "august", "september", "october", "november", "december"
1215 	];
1216 	for (var i = 0; i < months.length; i++) {
1217 		var month = AjxMsg["calc.monthname."+months[i]].split("|");
1218 		for (var j = 0; j < month.length; j++) {
1219 			AjxDateUtil.MONTHNAME2MONTHNUM[month[j].toLowerCase()] = i;
1220 		}
1221 	}
1222 
1223 	AjxDateUtil.RE_YEAR = new RegExp("^("+AjxMsg["calc.duration.year"]+")$", "i");
1224 	AjxDateUtil.RE_MONTH = new RegExp("^("+AjxMsg["calc.duration.month"]+")$", "i");
1225 	AjxDateUtil.RE_WEEK = new RegExp("^("+AjxMsg["calc.duration.week"]+")$", "i");
1226 	AjxDateUtil.RE_DAY = new RegExp("^("+AjxMsg["calc.duration.day"]+")$", "i");
1227 	AjxDateUtil.RE_HOUR = new RegExp("^("+AjxMsg["calc.duration.hour"]+")$", "i");
1228 	AjxDateUtil.RE_MINUTE = new RegExp("^("+AjxMsg["calc.duration.minute"]+")$", "i");
1229 	AjxDateUtil.RE_SECOND = new RegExp("^("+AjxMsg["calc.duration.second"]+")$", "i");
1230 	AjxDateUtil.RE_MILLISECOND = new RegExp("^("+AjxMsg["calc.duration.millisecond"]+")$", "i");
1231 
1232 	AjxDateUtil.RE_DATE = new RegExp("^("+AjxMsg["calc.date"]+")$", "i");
1233 	
1234 	AjxDateUtil.RE_DAYNAME = new RegExp("^("+AjxDateUtil.S_DAYNAME+")$", "i");
1235 	AjxDateUtil.RE_MONTHNAME = new RegExp("^("+AjxDateUtil.S_MONTHNAME+")$", "i");
1236 	AjxDateUtil.RE_WEEKORD = new RegExp("^("+AjxDateUtil.S_WEEKORD+")$", "i");
1237 
1238 	AjxDateUtil.RE_COMMENT = /^#/;
1239 	AjxDateUtil.RE_NOW = new RegExp("^("+AjxMsg["calc.now"]+")$", "i");
1240 	AjxDateUtil.RE_ADD_NUMBER = new RegExp("^([+\\-])(\\d+)$", "i");
1241 	AjxDateUtil.RE_SET = new RegExp("^("+AjxDateUtil.S_DURATION+"|"+AjxMsg["calc.date"]+")=(.*)$", "i");
1242 };
1243 
1244 AjxDateUtil.__calculate_normalizeFullWidthDigit =
1245 function(digit) {
1246 	var charCode = "0".charCodeAt(0) + digit.charCodeAt(0) - "\uff10".charCodeAt(0);
1247 	return String.fromCharCode(charCode);
1248 };
1249 
1250 /** This is needed to handle asian full-width digits. */
1251 AjxDateUtil.__calculate_replaceFullWidthDigit =
1252 function($0, digit) {
1253 	return AjxDateUtil.__calculate_normalizeFullWidthDigit(digit);
1254 };
1255 
1256 AjxDateUtil.__calculate_parseInt =
1257 function(s) {
1258 	s = s.replace(/([\uFF10-\uFF19])/g, AjxDateUtil.__calculate_normalizeFullWidthDigit);
1259 	return parseInt(s, 10);
1260 };
1261 
1262 AjxDateUtil.__calculate_add =
1263 function(date, type, amount) {
1264 	if (type.match(AjxDateUtil.RE_YEAR)) {
1265 		date.setFullYear(date.getFullYear() + amount);
1266 		return;
1267 	}
1268 	if (type.match(AjxDateUtil.RE_MONTH)) {
1269 		var month = date.getMonth();
1270 		date.setMonth(month + amount);
1271 		// avoid roll
1272 		if (Math.abs(month + amount) % 12 != date.getMonth()) {
1273 			date.setDate(0);
1274 		}
1275 		return;
1276 	}
1277 	if (type.match(AjxDateUtil.RE_WEEK)) {
1278 		date.setDate(date.getDate() + amount * 7);
1279 		return;
1280 	}
1281 	if (type.match(AjxDateUtil.RE_DAY)) {
1282 		date.setDate(date.getDate() + amount);
1283 		return;
1284 	}
1285 	if (type.match(AjxDateUtil.RE_HOUR)) {
1286 		date.setHours(date.getHours() + amount);
1287 		return;
1288 	}
1289 	if (type.match(AjxDateUtil.RE_MINUTE)) {
1290 		date.setMinutes(date.getMinutes() + amount);
1291 		return;
1292 	}
1293 	if (type.match(AjxDateUtil.RE_SECOND)) {
1294 		date.setSeconds(date.getSeconds() + amount);
1295 		return;
1296 	}
1297 	if (type.match(AjxDateUtil.RE_MILLISECOND)) {
1298 		date.setMilliseconds(date.getMilliseconds() + amount);
1299 		return;
1300 	}
1301 	if (type.match(AjxDateUtil.RE_MONTHNAME)) {
1302 		var monthnum = AjxDateUtil.MONTHNAME2MONTHNUM[type.toLowerCase()];
1303 		if (monthnum < date.getMonth()) {
1304 			amount += amount > 0 ? 0 : 1;
1305 		}
1306 		else if (monthnum > date.getMonth()) {
1307 			amount += amount > 0 ? -1 : 0;
1308 		}
1309 		date.setFullYear(date.getFullYear() + amount, monthnum, 1);
1310 		return;
1311 	}
1312 	if (type.match(AjxDateUtil.RE_DAYNAME)) {
1313 		var daynum = AjxDateUtil.WEEKDAYS[type.toLowerCase()];
1314 		if (daynum < date.getDay()) {
1315 			amount += amount > 0 ? 0 : 1;
1316 		}
1317 		else if (daynum > date.getDay()) {
1318 			amount += amount > 0 ? -1 : 0;
1319 		}
1320 		date.setDate(date.getDate() + (daynum - date.getDay()) + 7 * amount);
1321 		return;
1322 	}
1323 	throw "unknown type: "+type;
1324 };
1325 
1326 AjxDateUtil.__calculate_add_ordinal =
1327 function() {
1328 	throw "TODO: not implemented";
1329 };
1330 
1331 AjxDateUtil.__calculate_set =
1332 function(date, type, value) {
1333 	var args = value.split(/,/);
1334 	//Add support for Japanese Heisei year format represented by H{year-number}
1335 	//The year is H23 in H23/12/31, means 2011/12/31; we get that by adding year 1988 to 23
1336 	//For example: H23 = 23 + 1988 = 2011(English year)
1337 	if(args[0].indexOf("H") == 0) {
1338 		args[0] = parseInt(args[0].replace("H", "")) + 1988;
1339 	}
1340 	if (type.match(AjxDateUtil.RE_YEAR)) {
1341 		args[0] = AjxDateUtil.__calculate_fullYear(args[0]); // year
1342 		if (args[1] != null) args[1] = AjxDateUtil.__calculate_month(args[1]); // month
1343 		if (args[2] != null) args[2] = parseInt(args[2], 10); // date
1344 		date.setFullYear.apply(date, args);
1345 		return;
1346 	}
1347 	if (type.match(AjxDateUtil.RE_MONTH)) {
1348 		args[0] = AjxDateUtil.__calculate_month(args[0]); // month
1349 		if (args[1] != null) args[1] = parseInt(args[1], 10); // date
1350 		date.setMonth.apply(date, args);
1351 		return;
1352 	}
1353     if (type.match(AjxDateUtil.RE_WEEK)) {
1354         var ord = AjxDateUtil.__calculate_week(args[0]); // week
1355         var day = args[1] ? AjxDateUtil.__calculate_day(args[1]) : date.getDay(); // day
1356 
1357         var target;
1358         if (ord != -1) {
1359             var firstday = new Date(date.getFullYear(), date.getMonth(), 1, 12, 0, 0, 0);
1360             var firstdow = firstday.getDay();
1361             var delta = firstdow - day;
1362 
1363             target = new Date(firstday.getTime());
1364             target.setDate(1 - delta);
1365             if (delta > 0) {
1366                 target.setDate(target.getDate() + 7);
1367             }
1368             target.setDate(target.getDate() + 7 * ord);
1369         }
1370         else {
1371             var lastday = new Date(date.getFullYear(), date.getMonth()+1, 0, 12, 0, 0, 0);
1372 
1373             target = new Date(lastday.getTime());
1374             target.setDate(target.getDate() - (target.getDay() - day));
1375             if (target.getMonth() != lastday.getMonth()) {
1376                 target.setDate(target.getDate() - 7);
1377             }
1378         }
1379 
1380         if (target && (date.getMonth() == target.getMonth())) {
1381             date.setTime(target.getTime());
1382         }
1383         return;
1384     }
1385 	if (type.match(AjxDateUtil.RE_DATE)) {
1386 		args[0] = parseInt(args[0], 10); // date
1387 		date.setDate.apply(date, args);
1388 		return;
1389 	}
1390 	if (type.match(AjxDateUtil.RE_HOUR)) {
1391 		args[0] = parseInt(args[0], 10); // hour
1392 		if (args[1] != null) args[1] = parseInt(args[1], 10); // minutes
1393 		if (args[2] != null) args[2] = parseInt(args[2], 10); // seconds
1394 		if (args[3] != null) args[3] = parseInt(args[3], 10); // milliseconds
1395 		date.setHours.apply(date, args);
1396 		return;
1397 	}
1398 	if (type.match(AjxDateUtil.RE_MINUTE)) {
1399 		args[0] = parseInt(args[0], 10); // minutes
1400 		if (args[1] != null) args[1] = parseInt(args[1], 10); // seconds
1401 		if (args[2] != null) args[2] = parseInt(args[2], 10); // milliseconds
1402 		date.setMinutes.apply(date, args);
1403 		return;
1404 	}
1405 	if (type.match(AjxDateUtil.RE_SECOND)) {
1406 		args[0] = parseInt(args[0], 10); // seconds
1407 		if (args[1] != null) args[1] = parseInt(args[1], 10); // milliseconds
1408 		date.setSeconds.apply(date, args);
1409 		return;
1410 	}
1411 	if (type.match(AjxDateUtil.RE_MILLISECOND)) {
1412 		date.setMilliseconds.apply(date, args); // milliseconds
1413 		return;
1414 	}
1415 	throw "unknown type: "+type;
1416 };
1417 
1418 AjxDateUtil.__calculate_fullYear =
1419 function(value) {
1420 	if (value.length == 2) {
1421 		var d = new Date;
1422 		d.setYear(parseInt(value, 10));
1423         var fullYear = d.getFullYear();
1424         if (fullYear <= AjxMsg.dateParsing2DigitStartYear) {
1425             value = String(fullYear + 100);
1426         }
1427         else {
1428             value = String(fullYear).substr(0,2) + value;
1429         }
1430 	}
1431 	return parseInt(value, 10);
1432 };
1433 
1434 AjxDateUtil.__calculate_month =
1435 function(value) {
1436 	var monthnum = AjxDateUtil.MONTHNAME2MONTHNUM[value.toLowerCase()];
1437 	return monthnum != null ? monthnum : parseInt(value, 10) - 1;
1438 };
1439 
1440 AjxDateUtil.__calculate_week = function(value) {
1441     for (var i = 0; i < AjxDateUtil.WEEKORD_RE.length; i++) {
1442         if (value.match(AjxDateUtil.WEEKORD_RE[i])) {
1443             if (i == AjxDateUtil.WEEKORD_RE.length - 1) {
1444                 return -1;
1445             }
1446             return i;
1447         }
1448     }
1449     return 0;
1450 };
1451 
1452 AjxDateUtil.__calculate_day =
1453 function(value) {
1454 	var daynum = AjxDateUtil.WEEKDAYS[value.toLowerCase()];
1455 	return daynum != null ? daynum : parseInt(value, 10);
1456 };
1457