| "use strict"; |
| |
| function padWithZeros(vNumber, width) { |
| var numAsString = vNumber.toString(); |
| while (numAsString.length < width) { |
| numAsString = "0" + numAsString; |
| } |
| return numAsString; |
| } |
| |
| function addZero(vNumber) { |
| return padWithZeros(vNumber, 2); |
| } |
| |
| /** |
| * Formats the TimeOffset |
| * Thanks to http://www.svendtofte.com/code/date_format/ |
| * @private |
| */ |
| function offset(timezoneOffset) { |
| var os = Math.abs(timezoneOffset); |
| var h = String(Math.floor(os / 60)); |
| var m = String(os % 60); |
| if (h.length === 1) { |
| h = "0" + h; |
| } |
| if (m.length === 1) { |
| m = "0" + m; |
| } |
| return timezoneOffset < 0 ? "+" + h + m : "-" + h + m; |
| } |
| |
| function asString(format, date) { |
| if (typeof format !== "string") { |
| date = format; |
| format = module.exports.ISO8601_FORMAT; |
| } |
| if (!date) { |
| date = module.exports.now(); |
| } |
| |
| // Issue # 14 - Per ISO8601 standard, the time string should be local time |
| // with timezone info. |
| // See https://en.wikipedia.org/wiki/ISO_8601 section "Time offsets from UTC" |
| |
| var vDay = addZero(date.getDate()); |
| var vMonth = addZero(date.getMonth() + 1); |
| var vYearLong = addZero(date.getFullYear()); |
| var vYearShort = addZero(vYearLong.substring(2, 4)); |
| var vYear = format.indexOf("yyyy") > -1 ? vYearLong : vYearShort; |
| var vHour = addZero(date.getHours()); |
| var vMinute = addZero(date.getMinutes()); |
| var vSecond = addZero(date.getSeconds()); |
| var vMillisecond = padWithZeros(date.getMilliseconds(), 3); |
| var vTimeZone = offset(date.getTimezoneOffset()); |
| var formatted = format |
| .replace(/dd/g, vDay) |
| .replace(/MM/g, vMonth) |
| .replace(/y{1,4}/g, vYear) |
| .replace(/hh/g, vHour) |
| .replace(/mm/g, vMinute) |
| .replace(/ss/g, vSecond) |
| .replace(/SSS/g, vMillisecond) |
| .replace(/O/g, vTimeZone); |
| return formatted; |
| } |
| |
| function setDatePart(date, part, value, local) { |
| date['set' + (local ? '' : 'UTC') + part](value); |
| } |
| |
| function extractDateParts(pattern, str, missingValuesDate) { |
| // Javascript Date object doesn't support custom timezone. Sets all felds as |
| // GMT based to begin with. If the timezone offset is provided, then adjust |
| // it using provided timezone, otherwise, adjust it with the system timezone. |
| var local = pattern.indexOf('O') < 0; |
| var matchers = [ |
| { |
| pattern: /y{1,4}/, |
| regexp: "\\d{1,4}", |
| fn: function(date, value) { |
| setDatePart(date, 'FullYear', value, local); |
| } |
| }, |
| { |
| pattern: /MM/, |
| regexp: "\\d{1,2}", |
| fn: function(date, value) { |
| setDatePart(date, 'Month', (value - 1), local); |
| } |
| }, |
| { |
| pattern: /dd/, |
| regexp: "\\d{1,2}", |
| fn: function(date, value) { |
| setDatePart(date, 'Date', value, local); |
| } |
| }, |
| { |
| pattern: /hh/, |
| regexp: "\\d{1,2}", |
| fn: function(date, value) { |
| setDatePart(date, 'Hours', value, local); |
| } |
| }, |
| { |
| pattern: /mm/, |
| regexp: "\\d\\d", |
| fn: function(date, value) { |
| setDatePart(date, 'Minutes', value, local); |
| } |
| }, |
| { |
| pattern: /ss/, |
| regexp: "\\d\\d", |
| fn: function(date, value) { |
| setDatePart(date, 'Seconds', value, local); |
| } |
| }, |
| { |
| pattern: /SSS/, |
| regexp: "\\d\\d\\d", |
| fn: function(date, value) { |
| setDatePart(date, 'Milliseconds', value, local); |
| } |
| }, |
| { |
| pattern: /O/, |
| regexp: "[+-]\\d{3,4}|Z", |
| fn: function(date, value) { |
| if (value === "Z") { |
| value = 0; |
| } |
| var offset = Math.abs(value); |
| var timezoneOffset = (value > 0 ? -1 : 1 ) * ((offset % 100) + Math.floor(offset / 100) * 60); |
| // Per ISO8601 standard: UTC = local time - offset |
| // |
| // For example, 2000-01-01T01:00:00-0700 |
| // local time: 2000-01-01T01:00:00 |
| // ==> UTC : 2000-01-01T08:00:00 ( 01 - (-7) = 8 ) |
| // |
| // To make it even more confusing, the date.getTimezoneOffset() is |
| // opposite sign of offset string in the ISO8601 standard. So if offset |
| // is '-0700' the getTimezoneOffset() would be (+)420. The line above |
| // calculates timezoneOffset to matche Javascript's behavior. |
| // |
| // The date/time of the input is actually the local time, so the date |
| // object that was constructed is actually local time even thought the |
| // UTC setters are used. This means the date object's internal UTC |
| // representation was wrong. It needs to be fixed by substracting the |
| // offset (or adding the offset minutes as they are opposite sign). |
| // |
| // Note: the time zone has to be processed after all other fileds are |
| // set. The result would be incorrect if the offset was calculated |
| // first then overriden by the other filed setters. |
| date.setUTCMinutes(date.getUTCMinutes() + timezoneOffset); |
| } |
| } |
| ]; |
| |
| var parsedPattern = matchers.reduce( |
| function(p, m) { |
| if (m.pattern.test(p.regexp)) { |
| m.index = p.regexp.match(m.pattern).index; |
| p.regexp = p.regexp.replace(m.pattern, "(" + m.regexp + ")"); |
| } else { |
| m.index = -1; |
| } |
| return p; |
| }, |
| { regexp: pattern, index: [] } |
| ); |
| |
| var dateFns = matchers.filter(function(m) { |
| return m.index > -1; |
| }); |
| dateFns.sort(function(a, b) { |
| return a.index - b.index; |
| }); |
| |
| var matcher = new RegExp(parsedPattern.regexp); |
| var matches = matcher.exec(str); |
| if (matches) { |
| var date = missingValuesDate || module.exports.now(); |
| dateFns.forEach(function(f, i) { |
| f.fn(date, matches[i + 1]); |
| }); |
| |
| return date; |
| } |
| |
| throw new Error( |
| "String '" + str + "' could not be parsed as '" + pattern + "'" |
| ); |
| } |
| |
| function parse(pattern, str, missingValuesDate) { |
| if (!pattern) { |
| throw new Error("pattern must be supplied"); |
| } |
| |
| return extractDateParts(pattern, str, missingValuesDate); |
| } |
| |
| /** |
| * Used for testing - replace this function with a fixed date. |
| */ |
| function now() { |
| return new Date(); |
| } |
| |
| module.exports = asString; |
| module.exports.asString = asString; |
| module.exports.parse = parse; |
| module.exports.now = now; |
| module.exports.ISO8601_FORMAT = "yyyy-MM-ddThh:mm:ss.SSS"; |
| module.exports.ISO8601_WITH_TZ_OFFSET_FORMAT = "yyyy-MM-ddThh:mm:ss.SSSO"; |
| module.exports.DATETIME_FORMAT = "dd MM yyyy hh:mm:ss.SSS"; |
| module.exports.ABSOLUTETIME_FORMAT = "hh:mm:ss.SSS"; |