// ************************** // JavaScript code for parsing ISO8601 Strings. // ************************** function isLetter(str) { return str.length === 1 && str.match(/[a-z]/i); } /** * get an integer, allowing a letter at the end. * @param val string value * @param deft int value to return if the string is not valid. -99 will throw exception * @return */ function getInt(val, deft) { if (val === undefined) { if (deft !== -99) return deft; else alert("bad digit"); } n = val.length - 1; if (isLetter(val.charAt(n))) { return parseInt(val.substring(0, n)); } else { return parseInt(val); } } /** * get the double, allowing a letter at the end. * @param val string value * @param deft double value to return if the string is not valid. -99 will throw exception * @return */ function getDouble(val, deft) { if (val === undefined) { if (deft !== -99) return deft; else alert("bad digit"); } n = val.length - 1; if (isLetter(val.charAt(n))) { return parseFloat(val.substring(0, n)); } else { return parseFloat(val); } } var simpleFloat = "\\d?\\.?\\d+"; var iso8601duration = "P(\\d+Y)?(\\d+M)?(\\d+D)?(T(\\d+H)?(\\d+M)?(" + simpleFloat + "S)?)?"; /** * returns a 7 element array with [year,mon,day,hour,min,sec,nanos] or [-9999]. * @param stringIn * @return [year,mon,day,hour,min,sec,nanos] */ function parseISO8601Duration(stringIn) { var iso8601DurationPattern = new RegExp(iso8601duration, 'g'); m = iso8601DurationPattern.exec(stringIn); if (m !== null) { dsec = getDouble(m[7], 0); sec = Math.floor(dsec); nanosec = Math.floor((dsec - sec) * 1e9); return [getInt(m[1], 0), getInt(m[2], 0), getInt(m[3], 0), getInt(m[5], 0), getInt(m[6], 0), sec, nanosec]; } else { alert("unable to parse: " + stringIn); } } /** * find the next instance of delim in str, where delim is * one of the chars in delims * @param str the string we are parsing * @param index the start index * @param delims string of delims ("-T:.Z") */ function nextToken(str, index, delims) { index = index + 1; if (index > str.length) return -1; while (index < str.length) { ch = str.charAt(index); if (delims.indexOf(ch) > -1) { break; } else { index = index + 1; } } return index; } /** * ISO8601 datum parser. This does not support 2-digit years, which * were removed in ISO 8601:2004. * * @param str the string we are parsing * @param result the int[7] result * @param lsd * @return the lsd */ function parseISO8601Datum(str, result, lsd) { delims = "-T:.Z"; dir = ""; DIR_FORWARD = "f"; DIR_REVERSE = "r"; want = 0; haveDelim = false; index = -1; index1 = nextToken(str, index, delims); while (index1 > -1) { if (haveDelim) { delim = str.charAt(index - 1); // delim is the delimiter before tok. if (index1 === str.length - 1) { // "Z" break; } } else { delim = ''; haveDelim = true; } tok = str.substring(index, index1); if (dir === "") { if (tok.length === 4) { // typical route iyear = parseInt(tok); result[0] = iyear; want = 1; dir = DIR_FORWARD; result[1] = 0; result[2] = 0; result[3] = 0; result[4] = 0; result[5] = 0; result[6] = 0; } else if (tok.length === 6) { want = lsd; if (want !== 6) alert("lsd must be 6"); result[want] = parseInt(tok.substring(0, 2)); want--; result[want] = parseInt(tok.substring(2, 4)); want--; result[want] = parseInt(tok.substring(4, 6)); want--; dir = DIR_REVERSE; } else if (tok.length === 7) { result[0] = parseInt(tok.substring(0, 4)); result[1] = 1; result[2] = parseInt(tok.substring(4, 7)); want = 3; dir = DIR_FORWARD; result[3] = 0; result[4] = 0; result[5] = 0; result[6] = 0; } else if (tok.length === 8) { result[0] = Integer.parseInt(tok.substring(0, 4)); result[1] = Integer.parseInt(tok.substring(4, 6)); result[2] = Integer.parseInt(tok.substring(6, 8)); want = 3; dir = DIR_FORWARD; result[3] = 0; result[4] = 0; result[5] = 0; result[6] = 0; } else { dir = DIR_REVERSE; want = lsd; // we are going to have to reverse these when we're done. i = parseInt(tok); result[want] = i; want--; } } else if (dir === DIR_FORWARD) { if (want === 1 && tok.length === 3) { // $j result[1] = 1; result[2] = parseInt(tok); want = 3; } else if (want === 3 && tok.length === 6) { result[want] = parseInt(tok.substring(0, 2)); want++; result[want] = parseInt(tok.substring(2, 4)); want++; result[want] = parseInt(tok.substring(4, 6)); want++; } else if (want === 3 && tok.length === 4) { result[want] = parseInt(tok.substring(0, 2)); want++; result[want] = parseInt(tok.substring(2, 4)); want++; } else { i = parseInt(tok); if (delim === '.' && want === 6) { n = 9 - tok.length; result[want] = i * Math.pow(10, n); } else { result[want] = i; } want++; } } else if (dir === DIR_REVERSE) { // what about 1200 in reverse? i = parseInt(tok); if (delim === '.') { n = 9 - tok.length; result[want] = i * Math.pow(10, n); } else { result[want] = i; } want--; } index = index1 + 1; index1 = nextToken(str, index, delims); } if (dir === DIR_REVERSE) { iu = want + 1; id = lsd; while (iu < id) { t = result[iu]; result[iu] = result[id]; result[id] = t; iu = iu + 1; id = id - 1; } } else { lsd = want - 1; } return lsd; } /** * returns the time found in an iso8601 string, or null. This supports * periods (durations) as in: 2007-03-01T13:00:00Z/P1Y2M10DT2H30M * Other examples: * 2007-03-01T13:00:00Z/2008-05-11T15:30:00Z * 2007-03-01T13:00:00Z/P1Y2M10DT2H30M * P1Y2M10DT2H30M/2008-05-11T15:30:00Z * 2007-03-01T00:00Z/P1D * 2012-100T02:00/03:45 * https://en.wikipedia.org/wiki/ISO_8601#Time_intervals * @param stringIn * @param result if non-null should be an int[14] to provide storage to routine. * @return int[14] with [Y,M,D,H,M,S,NS,Y,M,D,H,M,S,NS] */ function parseISO8601Range(stringIn, result) { parts = stringIn.split("/", 2); if ( parts.length!==2 ) { if ( parts[0].length<4 ) { throw Exception('time must have 4, 7, 8, or 10 digits'); } else if ( parts[0].length===4 ) { // YYYY stringIn= stringIn+'/P1Y'; } else if ( stringIn.length===7 ) { // YYYY-DD stringIn= stringIn+'/P1M'; } else if ( stringIn.length===8 ) { // YYYY-MMM stringIn= stringIn+'/P1D'; } else if ( stringIn.length===10 ) { // YYYY-MM-DD stringIn= stringIn+'/P1D'; } parts= stringIn.split("/",2); } d1 = parts[0].charAt(0) === 'P'; // true if it is a duration d2 = parts[1].charAt(0) === 'P'; lsd = -1; if (d1) { digits0 = parseISO8601Duration(parts[0]); } else if (parts[0] === 'now') { dd = new Date(); digits0 = [dd.getUTCFullYear(), dd.getUTCMonth() + 1, dd.getUTCDate(), dd.getUTCHours(), dd.getUTCMinutes(), dd.getUTCSeconds(), dd.getUTCMilliseconds() * 1000000]; } else if (parts[0].startsWith('now-')) { dd = new Date(); delta = parseISO8601Duration(parts[0].substring(4)); digits0 = [dd.getUTCFullYear(), dd.getUTCMonth() + 1, dd.getUTCDate(), dd.getUTCHours(), dd.getUTCMinutes(), dd.getUTCSeconds(), dd.getUTCMilliseconds() * 1000000]; for (j = 0; j < 7; j++) digits0[j] -= delta[j]; } else if (parts[0].startsWith('now+')) { dd = new Date(); delta = parseISO8601Duration(parts[0].substring(4)); digits0 = [dd.getUTCFullYear(), dd.getUTCMonth() + 1, dd.getUTCDate(), dd.getUTCHours(), dd.getUTCMinutes(), dd.getUTCSeconds(), dd.getUTCMilliseconds() * 1000000]; for (j = 0; j < 7; j++) digits0[j] += delta[j]; } else { digits0 = [0, 0, 0, 0, 0, 0, 0]; lsd = parseISO8601Datum(parts[0], digits0, lsd); for (j = lsd + 1; j < 3; j++) digits0[j] = 1; // month 1 is first month, not 0. day 1 } if (d2) { digits1 = parseISO8601Duration(parts[1]); } else if (parts[1] === 'now') { dd = new Date(); digits1 = [dd.getUTCFullYear(), dd.getUTCMonth() + 1, dd.getUTCDate(), dd.getUTCHours(), dd.getUTCMinutes(), dd.getUTCSeconds(), dd.getUTCMilliseconds() * 1000000]; } else if (parts[1].startsWith('now-')) { dd = new Date(); delta = parseISO8601Duration(parts[1].substring(4)); digits1 = [dd.getUTCFullYear(), dd.getUTCMonth() + 1, dd.getUTCDate(), dd.getUTCHours(), dd.getUTCMinutes(), dd.getUTCSeconds(), dd.getUTCMilliseconds() * 1000000]; for (j = 0; j < 7; j++) digits1[j] -= delta[j]; } else if (parts[1].startsWith('now+')) { dd = new Date(); delta = parseISO8601Duration(parts[1].substring(4)); digits1 = [dd.getUTCFullYear(), dd.getUTCMonth() + 1, dd.getUTCDate(), dd.getUTCHours(), dd.getUTCMinutes(), dd.getUTCSeconds(), dd.getUTCMilliseconds() * 1000000]; for (j = 0; j < 7; j++) digits1[j] += delta[j]; } else { if (d1) { digits1 = [0, 0, 0, 0, 0, 0, 0]; } else { digits1 = digits0.slice(0); // make a clone of the array } lsd = parseISO8601Datum(parts[1], digits1, lsd); for (j = lsd + 1; j < 3; j++) digits1[j] = 1; // month 1 is first month, not 0. day 1 } if (digits0 === null || digits1 === null) return null; if (d1) { for (i = 0; i < 7; i++) digits0[i] = digits1[i] - digits0[i]; } if (d2) { for (i = 0; i < 7; i++) digits1[i] = digits0[i] + digits1[i]; } if (result === undefined) { result = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; } for (i = 0; i < 7; i++) result[i] = digits0[i]; for (i = 0; i < 7; i++) result[i + 7] = digits1[i]; return result; } /** * returns the time found in the string. This can be an ISO8601 time * range string, or four-digit year, or other convenient forms. * Other examples: *