#if COPYRIGHT //------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ #endif Date._appendPreOrPostMatch = function(preMatch, strBuilder) { // appends pre- and post- token match strings while removing escaped characters. // Returns a single quote count which is used to determine if the token occurs // in a string literal. var quoteCount = 0; var escaped = false; for (var i = 0, il = preMatch.length; i < il; i++) { var c = preMatch.charAt(i); switch (c) { case '\'': if (escaped) strBuilder.append("'"); else quoteCount++; escaped = false; break; case '\\': if (escaped) strBuilder.append("\\"); escaped = !escaped; break; default: strBuilder.append(c); escaped = false; break; } } return quoteCount; } Date._expandFormat = function(dtf, format) { // expands unspecified or single character date formats into the full pattern. if (!format) { format = "F"; } var len = format.length; if (len === 1) { switch (format) { case "d": return dtf.ShortDatePattern; case "D": return dtf.LongDatePattern; case "t": return dtf.ShortTimePattern; case "T": return dtf.LongTimePattern; case "f": return dtf.LongDatePattern + " " + dtf.ShortTimePattern; case "F": return dtf.FullDateTimePattern; case "M": case "m": return dtf.MonthDayPattern; case "s": return dtf.SortableDateTimePattern; case "Y": case "y": return dtf.YearMonthPattern; default: throw Error.format(Sys.Res.formatInvalidString); } } else if ((len === 2) && (format.charAt(0) === "%")) { // Dev10 580024: %X escape format -- intended as a custom format string that // is only one character, not a built-in format. format = format.charAt(1); } return format; } Date._expandYear = function(dtf, year) { // expands 2-digit year into 4 digits. var now = new Date(), era = Date._getEra(now); if (year < 100) { var curr = Date._getEraYear(now, dtf, era); year += curr - (curr % 100); if (year > dtf.Calendar.TwoDigitYearMax) { year -= 100; } } return year; } Date._getEra = function(date, eras) { if (!eras) return 0; var start, ticks = date.getTime(); for (var i = 0, l = eras.length; i < l; i += 4) { start = eras[i+2]; if ((start === null) || (ticks >= start)) { return i; } } return 0; } Date._getEraYear = function(date, dtf, era, sortable) { var year = date.getFullYear(); if (!sortable && dtf.eras) { // convert normal gregorian year to era-shifted gregorian // year by subtracting the era offset year -= dtf.eras[era + 3]; } return year; } Date._getParseRegExp = function(dtf, format) { // converts a format string into a regular expression with groups that // can be used to extract date fields from a date string. // check for a cached parse regex. if (!dtf._parseRegExp) { dtf._parseRegExp = {}; } else if (dtf._parseRegExp[format]) { return dtf._parseRegExp[format]; } // expand single digit formats, then escape regular expression characters. var expFormat = Date._expandFormat(dtf, format); expFormat = expFormat.replace(/([\^\$\.\*\+\?\|\[\]\(\)\{\}])/g, "\\\\$1"); var regexp = new Sys.StringBuilder("^"); var groups = []; var index = 0; var quoteCount = 0; var tokenRegExp = Date._getTokenRegExp(); var match; // iterate through each date token found. while ((match = tokenRegExp.exec(expFormat)) !== null) { var preMatch = expFormat.slice(index, match.index); index = tokenRegExp.lastIndex; // don't replace any matches that occur inside a string literal. quoteCount += Date._appendPreOrPostMatch(preMatch, regexp); if ((quoteCount%2) === 1) { regexp.append(match[0]); continue; } // add a regex group for the token. switch (match[0]) { case 'dddd': case 'ddd': case 'MMMM': case 'MMM': case 'gg': case 'g': regexp.append("(\\D+)"); break; case 'tt': case 't': regexp.append("(\\D*)"); break; case 'yyyy': regexp.append("(\\d{4})"); break; case 'fff': regexp.append("(\\d{3})"); break; case 'ff': regexp.append("(\\d{2})"); break; case 'f': regexp.append("(\\d)"); break; case 'dd': case 'd': case 'MM': case 'M': case 'yy': case 'y': case 'HH': case 'H': case 'hh': case 'h': case 'mm': case 'm': case 'ss': case 's': regexp.append("(\\d\\d?)"); break; case 'zzz': regexp.append("([+-]?\\d\\d?:\\d{2})"); break; case 'zz': case 'z': regexp.append("([+-]?\\d\\d?)"); break; case '/': regexp.append("(\\" + dtf.DateSeparator + ")"); break; #if DEBUGINTERNAL default: Sys.Debug.fail("Invalid date format pattern"); #endif } Array.add(groups, match[0]); } Date._appendPreOrPostMatch(expFormat.slice(index), regexp); regexp.append("$"); // allow whitespace to differ when matching formats. var regexpStr = regexp.toString().replace(/\s+/g, "\\s+"); var parseRegExp = {'regExp': regexpStr, 'groups': groups}; // cache the regex for this format. dtf._parseRegExp[format] = parseRegExp; return parseRegExp; } Date._getTokenRegExp = function() { // regular expression for matching dateTime tokens in format strings. return /\/|dddd|ddd|dd|d|MMMM|MMM|MM|M|yyyy|yy|y|hh|h|HH|H|mm|m|ss|s|tt|t|fff|ff|f|zzz|zz|z|gg|g/g; } Date.parseLocale = function(value, formats) { /// Creates a date from a locale-specific string representation. /// A locale-specific string that can parse to a date. /// Custom formats to match. /// return Date._parse(value, Sys.CultureInfo.CurrentCulture, arguments); } Date.parseInvariant = function(value, formats) { /// Creates a date from its string representation. /// A string that can parse to a date. /// Custom formats to match. /// return Date._parse(value, Sys.CultureInfo.InvariantCulture, arguments); } Date._parse = function(value, cultureInfo, args) { // args is a params array with value as the first item, followed by custom formats. // try parse with custom formats. var i, l, date, format, formats, custom = false; for (i = 1, l = args.length; i < l; i++) { format = args[i]; if (format) { custom = true; date = Date._parseExact(value, format, cultureInfo); if (date) return date; } } // try parse with culture formats. if (! custom) { formats = cultureInfo._getDateTimeFormats(); for (i = 0, l = formats.length; i < l; i++) { date = Date._parseExact(value, formats[i], cultureInfo); if (date) return date; } } return null; } Date._parseExact = function(value, format, cultureInfo) { // try to parse the date string value by matching against the format string // while using the specified culture for date field names. value = value.trim(); var dtf = cultureInfo.dateTimeFormat, // convert date formats into regular expressions with groupings. // use the regexp to determine the input format and extract the date fields. parseInfo = Date._getParseRegExp(dtf, format), match = new RegExp(parseInfo.regExp).exec(value); // DevDiv 124696: Return null to avoid Firefox warning "does not always return a value" if (match === null) return null; // found a date format that matches the input. var groups = parseInfo.groups, era = null, year = null, month = null, date = null, weekDay = null, hour = 0, hourOffset, min = 0, sec = 0, msec = 0, tzMinOffset = null, pmHour = false; // iterate the format groups to extract and set the date fields. for (var j = 0, jl = groups.length; j < jl; j++) { var matchGroup = match[j+1]; if (matchGroup) { switch (groups[j]) { case 'dd': case 'd': // Day of month. date = parseInt(matchGroup, 10); // check that date is generally in valid range, also checking overflow below. if ((date < 1) || (date > 31)) return null; break; case 'MMMM': // Month, long name. month = cultureInfo._getMonthIndex(matchGroup); if ((month < 0) || (month > 11)) return null; break; case 'MMM': // Month, short name. month = cultureInfo._getAbbrMonthIndex(matchGroup); if ((month < 0) || (month > 11)) return null; break; case 'M': case 'MM': // Month. month = parseInt(matchGroup, 10) - 1; if ((month < 0) || (month > 11)) return null; break; case 'y': case 'yy': // 2-Digit Year. year = Date._expandYear(dtf,parseInt(matchGroup, 10)); if ((year < 0) || (year > 9999)) return null; break; case 'yyyy': // 4-Digit Year. year = parseInt(matchGroup, 10); if ((year < 0) || (year > 9999)) return null; break; case 'h': case 'hh': // Hours (12-hour clock). hour = parseInt(matchGroup, 10); if (hour === 12) hour = 0; if ((hour < 0) || (hour > 11)) return null; break; case 'H': case 'HH': // Hours (24-hour clock). hour = parseInt(matchGroup, 10); if ((hour < 0) || (hour > 23)) return null; break; case 'm': case 'mm': // Minutes. min = parseInt(matchGroup, 10); if ((min < 0) || (min > 59)) return null; break; case 's': case 'ss': // Seconds. sec = parseInt(matchGroup, 10); if ((sec < 0) || (sec > 59)) return null; break; case 'tt': case 't': // AM/PM designator. var upperToken = matchGroup.toUpperCase(); pmHour = (upperToken === dtf.PMDesignator.toUpperCase()); if (!pmHour && (upperToken !== dtf.AMDesignator.toUpperCase())) return null; break; case 'f': // Deciseconds. msec = parseInt(matchGroup, 10) * 100; if ((msec < 0) || (msec > 999)) return null; break; case 'ff': // Centiseconds. msec = parseInt(matchGroup, 10) * 10; if ((msec < 0) || (msec > 999)) return null; break; case 'fff': // Milliseconds. msec = parseInt(matchGroup, 10); if ((msec < 0) || (msec > 999)) return null; break; case 'dddd': // Day of week. weekDay = cultureInfo._getDayIndex(matchGroup); if ((weekDay < 0) || (weekDay > 6)) return null; break; case 'ddd': // Day of week. weekDay = cultureInfo._getAbbrDayIndex(matchGroup); if ((weekDay < 0) || (weekDay > 6)) return null; break; case 'zzz': // Time zone offset in +/- hours:min. var offsets = matchGroup.split(/:/); if (offsets.length !== 2) return null; hourOffset = parseInt(offsets[0], 10); if ((hourOffset < -12) || (hourOffset > 13)) return null; var minOffset = parseInt(offsets[1], 10); if ((minOffset < 0) || (minOffset > 59)) return null; tzMinOffset = (hourOffset * 60) + (matchGroup.startsWith('-')? -minOffset : minOffset); break; case 'z': case 'zz': // Time zone offset in +/- hours. hourOffset = parseInt(matchGroup, 10); if ((hourOffset < -12) || (hourOffset > 13)) return null; tzMinOffset = hourOffset * 60; break; case 'g': case 'gg': var eraName = matchGroup; if (!eraName || !dtf.eras) return null; eraName = eraName.toLowerCase().trim(); for (var i = 0, l = dtf.eras.length; i < l; i += 4) { if (eraName === dtf.eras[i + 1].toLowerCase()) { era = i; break; } } // could not find an era with that name if (era === null) return null; break; } } } var result = new Date(), defaultYear, convert = dtf.Calendar.convert; if (convert) { defaultYear = convert.fromGregorian(result)[0]; } else { defaultYear = result.getFullYear(); } if (year === null) { year = defaultYear; } else if (dtf.eras) { // year must be shifted to normal gregorian year // but not if year was not specified, its already normal gregorian // per the main if clause above. year += dtf.eras[(era || 0) + 3]; } // Dev10 747832: set default day and month to 1 and January to be consistent with .Net behavior. if (month === null) { month = 0; } if (date === null) { date = 1; } // now have year, month, and date, but in the culture's calendar. // convert to gregorian if necessary if (convert) { result = convert.toGregorian(year, month, date); // conversion failed, must be an invalid match if (result === null) return null; } else { // have to set year, month and date together to avoid overflow based on current date. result.setFullYear(year, month, date); // check to see if date overflowed for specified month (only checked 1-31 above). if (result.getDate() !== date) return null; // invalid day of week. if ((weekDay !== null) && (result.getDay() !== weekDay)) { return null; } } // if pm designator token was found make sure the hours fit the 24-hour clock. if (pmHour && (hour < 12)) { hour += 12; } result.setHours(hour, min, sec, msec); if (tzMinOffset !== null) { // adjust timezone to utc before applying local offset. var adjustedMin = result.getMinutes() - (tzMinOffset + result.getTimezoneOffset()); // Safari limits hours and minutes to the range of -127 to 127. We need to use setHours // to ensure both these fields will not exceed this range. adjustedMin will range // somewhere between -1440 and 1500, so we only need to split this into hours. result.setHours(result.getHours() + parseInt(adjustedMin/60, 10), adjustedMin%60); } return result; } Date.prototype.format = function(format) { /// Format a date using the invariant culture. /// Format string. /// Formatted date. return this._toFormattedString(format, Sys.CultureInfo.InvariantCulture); } Date.prototype.localeFormat = function(format) { /// Format a date using the current culture. /// Format string. /// Formatted date. return this._toFormattedString(format, Sys.CultureInfo.CurrentCulture); } Date.prototype._toFormattedString = function(format, cultureInfo) { var dtf = cultureInfo.dateTimeFormat, convert = dtf.Calendar.convert; if (!format || !format.length || (format === 'i')) { if (cultureInfo && cultureInfo.name.length) { if (convert) { // non-gregorian calendar, so we cannot use built-in toLocaleString() return this._toFormattedString(dtf.FullDateTimePattern, cultureInfo); } else { var eraDate = new Date(this.getTime()); var era = Date._getEra(this, dtf.eras); eraDate.setFullYear(Date._getEraYear(this, dtf, era)); return eraDate.toLocaleString(); } } else { return this.toString(); } } var eras = dtf.eras, sortable = (format === "s"); format = Date._expandFormat(dtf, format); // Start with an empty string var ret = new Sys.StringBuilder(); var hour; function addLeadingZero(num) { if (num < 10) { return '0' + num; } return num.toString(); } function addLeadingZeros(num) { if (num < 10) { return '00' + num; } if (num < 100) { return '0' + num; } return num.toString(); } function padYear(year) { if (year < 10) { return '000' + year; } else if (year < 100) { return '00' + year; } else if (year < 1000) { return '0' + year; } return year.toString(); } var foundDay, checkedDay, dayPartRegExp = /([^d]|^)(d|dd)([^d]|$)/g; function hasDay() { if (foundDay || checkedDay) { return foundDay; } foundDay = dayPartRegExp.test(format); checkedDay = true; return foundDay; } var quoteCount = 0, tokenRegExp = Date._getTokenRegExp(), converted; if (!sortable && convert) { converted = convert.fromGregorian(this); } for (;;) { // Save the current index var index = tokenRegExp.lastIndex; // Look for the next pattern var ar = tokenRegExp.exec(format); // Append the text before the pattern (or the end of the string if not found) var preMatch = format.slice(index, ar ? ar.index : format.length); quoteCount += Date._appendPreOrPostMatch(preMatch, ret); if (!ar) break; // do not replace any matches that occur inside a string literal. if ((quoteCount%2) === 1) { ret.append(ar[0]); continue; } function getPart(date, part) { if (converted) { return converted[part]; } switch (part) { case 0: return date.getFullYear(); case 1: return date.getMonth(); case 2: return date.getDate(); } } switch (ar[0]) { case "dddd": // Day of the week, using the full name ret.append(dtf.DayNames[this.getDay()]); break; case "ddd": //Day of the week, as a three-letter abbreviation ret.append(dtf.AbbreviatedDayNames[this.getDay()]); break; case "dd": foundDay = true; // Day of month, with leading zero for single-digit days ret.append(addLeadingZero(getPart(this, 2))); break; case "d": foundDay = true; // Day of month, without leading zero for single-digit days ret.append(getPart(this, 2)); break; case "MMMM": // Month, using the full name ret.append((dtf.MonthGenitiveNames && hasDay()) ? dtf.MonthGenitiveNames[getPart(this, 1)] : dtf.MonthNames[getPart(this, 1)]); break; case "MMM": // Month, as a three-letter abbreviation ret.append((dtf.AbbreviatedMonthGenitiveNames && hasDay()) ? dtf.AbbreviatedMonthGenitiveNames[getPart(this, 1)] : dtf.AbbreviatedMonthNames[getPart(this, 1)]); break; case "MM": // Month, as digits, with leading zero for single-digit months ret.append(addLeadingZero(getPart(this, 1) + 1)); break; case "M": // Month, as digits, with no leading zero for single-digit months ret.append(getPart(this, 1) + 1); break; case "yyyy": // Year represented by four full digits ret.append(padYear(converted ? converted[0] : Date._getEraYear(this, dtf, Date._getEra(this, eras), sortable))); break; case "yy": // Year, as two digits, with leading zero for years less than 10 ret.append(addLeadingZero((converted ? converted[0] : Date._getEraYear(this, dtf, Date._getEra(this, eras), sortable)) % 100)); break; case "y": // Year, as two digits, but with no leading zero for years less than 10 ret.append((converted ? converted[0] : Date._getEraYear(this, dtf, Date._getEra(this, eras), sortable)) % 100); break; case "hh": // Hours with leading zero for single-digit hours, using 12-hour clock hour = this.getHours() % 12; if (hour === 0) hour = 12; ret.append(addLeadingZero(hour)); break; case "h": // Hours with no leading zero for single-digit hours, using 12-hour clock hour = this.getHours() % 12; if (hour === 0) hour = 12; ret.append(hour); break; case "HH": // Hours with leading zero for single-digit hours, using 24-hour clock ret.append(addLeadingZero(this.getHours())); break; case "H": // Hours with no leading zero for single-digit hours, using 24-hour clock ret.append(this.getHours()); break; case "mm": // Minutes with leading zero for single-digit minutes ret.append(addLeadingZero(this.getMinutes())); break; case "m": // Minutes with no leading zero for single-digit minutes ret.append(this.getMinutes()); break; case "ss": // Seconds with leading zero for single-digit seconds ret.append(addLeadingZero(this.getSeconds())); break; case "s": // Seconds with no leading zero for single-digit seconds ret.append(this.getSeconds()); break; case "tt": // Multicharacter am/pm indicator ret.append((this.getHours() < 12) ? dtf.AMDesignator : dtf.PMDesignator); break; case "t": // One character am/pm indicator ("a" or "p") ret.append(((this.getHours() < 12) ? dtf.AMDesignator : dtf.PMDesignator).charAt(0)); break; case "f": // Deciseconds ret.append(addLeadingZeros(this.getMilliseconds()).charAt(0)); break; case "ff": // Centiseconds ret.append(addLeadingZeros(this.getMilliseconds()).substr(0, 2)); break; case "fff": // Milliseconds ret.append(addLeadingZeros(this.getMilliseconds())); break; case "z": // Time zone offset, no leading zero hour = this.getTimezoneOffset() / 60; ret.append(((hour <= 0) ? '+' : '-') + Math.floor(Math.abs(hour))); break; case "zz": // Time zone offset with leading zero hour = this.getTimezoneOffset() / 60; ret.append(((hour <= 0) ? '+' : '-') + addLeadingZero(Math.floor(Math.abs(hour)))); break; case "zzz": // Time zone offset with leading zero hour = this.getTimezoneOffset() / 60; ret.append(((hour <= 0) ? '+' : '-') + addLeadingZero(Math.floor(Math.abs(hour))) + // Dev10 580009: Server has hard coded ":" separator, rather than using dtf.TimeSeparator // Repeated here for consistency, plus ":" was already assumed in date parsing. ":" + addLeadingZero(Math.abs(this.getTimezoneOffset() % 60))); break; case "g": case "gg": if (dtf.eras) { ret.append(dtf.eras[Date._getEra(this, eras) + 1]); } break; case "/": ret.append(dtf.DateSeparator); break; #if DEBUGINTERNAL default: Sys.Debug.fail("Invalid date format pattern"); #endif } } return ret.toString(); }