Well... I think I've worked this out. I've appended the code below but if
anybody wants a copy let me know.
This handles (barring bugs!) the following subset of ISO datetime stamps:
YYYY (eg 1997)
YYYY-MM (eg 1997-07)
YYYY-MM-DD (eg 1997-07-16)
YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20Z)
YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30Z)
YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45Z)
The code works well so far... but there are some caveats:
+) The RegExs at play here don't do much validation. For example while they
won't match a month of "14" they will match a month of "2" and a date of
"31". This will also only honors years from 1600 on (I've no desire to play
with Gregorian vrs Julian dates).
I've split out the expression fragments and maybe someday will add the
look-aheads required to do day of month validation... but I doubt I'll ever
bother with leap year validation. The rule here is "good enough for what I
need not perfect".
+) I've been leniant (and a little lazy) on the element separators. While
they are required you can use slashes, dashes, dots or colons. For
milliseconds you can use a dot or a comma. The ISO8601 standard is dashes
to separate date parts, colons to separate time parts and a dot (decimal) to
separate milliseconds.
+) If the input does not validate to a date a null is returned.
+) If there is no timezone information the date returned will be in local
time.
+) If the UTC indicator exists (a trailing "Z") then the date returned will
be in UTC time.
+) If timezone offset information exists it will be used to convert the
input to the local time.
If anybody finds any errors in this I'd much appreciate hearing about them!
Jim Davis
// Convert Dates from iso8601
function ISO8601ToDate(CurDate) {
// Set the fragment expressions
var S = "[\\-/:.]";
var Yr = "((?:1[6-9]|[2-9][0-9])[0-9]{2})";
var Mo = S + "((?:1[012])|(?:0[1-9])|[1-9])";
var Dy = S + "((?:3[01])|(?:[12][0-9])|(?:0[1-9])|[1-9])";
var Hr = "(2[0-4]|[01]?[0-9])";
var Mn = S + "([0-5]?[0-9])";
var Sd = "(?:" + S + "([0-5]?[0-9])(?:[.,]([0-9]+))?)?";
var TZ = "(?
Z)|(?
[\+\-])(1[012]|[0]?[0-9])(?::?([0-5]?[0-9]))?))?";
// RegEx the input
// First check: Just date parts (month and day are optional)
// Second check: Full date plus time (seconds, milliseconds and TimeZone
info are optional)
var TF;
if ( TF = new RegExp("^" + Yr + "(?:" + Mo + "(?:" + Dy + ")?)?" +
"$").exec(CurDate) ) {} else if ( TF = new RegExp("^" + Yr + Mo + Dy + "T" +
Hr + Mn + Sd + TZ + "$").exec(CurDate) ) {};
// If the date couldn't be parsed, return null
if ( !TF ) {
return null;
};
// Default the Time Fragments if they're not present
if ( !TF[2] ) { TF[2] = 1 } else { TF[2] = TF[2] - 1 };
if ( !TF[3] ) { TF[3] = 1 };
if ( !TF[4] ) { TF[4] = 0 };
if ( !TF[5] ) { TF[5] = 0 };
if ( !TF[6] ) { TF[6] = 0 };
if ( !TF[7] ) { TF[7] = 0 };
if ( !TF[8] ) { TF[8] = null };
if ( TF[9] != "-" && TF[9] != "+" ) { TF[9] = null };
if ( !TF[10] ) { TF[10] = 0 } else { TF[10] = TF[9] + TF[10] };
if ( !TF[11] ) { TF[11] = 0 } else { TF[11] = TF[9] + TF[11] };
// If there's no timezone info the data is local time
if ( !TF[8] && !TF[9] ) {
return new Date(TF[1], TF[2], TF[3], TF[4], TF[5], TF[6], TF[7]);
};
// If the UTC indicator is set the date is UTC
if ( TF[8] == "Z" ) {
return new Date(Date.UTC(TF[1], TF[2], TF[3], TF[4], TF[5], TF[6],
TF[7]));
};
// If the date has a timezone offset
if ( TF[9] == "-" || TF[9] == "+" ) {
// Get current Timezone information
var CurTZ = new Date().getTimezoneOffset();
var CurTZh = TF[10] - ((CurTZ >= 0 ? "-" : "+") +
Math.floor(Math.abs(CurTZ) / 60))
var CurTZm = TF[11] - ((CurTZ >= 0 ? "-" : "+") + (Math.abs(CurTZ) % 60))
// Return the date
return new Date(TF[1], TF[2], TF[3], TF[4] - CurTZh, TF[5] - CurTZm,
TF[6], TF[7]);
};
};