in rhino/src/main/java/org/mozilla/javascript/NativeDate.java [960:1147]
private static double parseISOString(Context cx, String s) {
// we use a simple state machine to parse the input string
final int ERROR = -1;
final int YEAR = 0, MONTH = 1, DAY = 2;
final int HOUR = 3, MIN = 4, SEC = 5, MSEC = 6;
final int TZHOUR = 7, TZMIN = 8;
int state = YEAR;
// default values per [15.9.1.15 Date Time String Format]
int[] values = {1970, 1, 1, 0, 0, 0, 0, -1, -1};
boolean timeSpecified = false;
int yearlen = 4, yearmod = 1, tzmod = 1;
int i = 0, len = s.length();
if (len != 0) {
char c = s.charAt(0);
if (c == '+' || c == '-') {
// 15.9.1.15.1 Extended years
i += 1;
yearlen = 6;
yearmod = (c == '-') ? -1 : 1;
} else if (c == 'T' && cx.getLanguageVersion() < Context.VERSION_ES6) {
// time-only forms no longer in spec, but follow spidermonkey here
i += 1;
state = HOUR;
}
}
loop:
while (state != ERROR) {
if (state == MSEC) {
// milli secs are different, digit 2 and 3 are optional
int value = 0;
int digitsFound = 0;
for (; i < len; i++) {
char c = s.charAt(i);
if (c < '0' || c > '9') {
break;
}
// skip more digits
if (digitsFound < 3) {
value = 10 * value + (c - '0');
digitsFound++;
}
}
if (digitsFound == 0) {
state = ERROR;
break loop;
}
if (digitsFound < 3) {
value = value * (digitsFound == 1 ? 100 : 10);
}
values[state] = value;
if (i == len) {
// no timezone at all is correct here
break;
}
} else {
int m = i + (state == YEAR ? yearlen : 2);
if (m > len) {
state = ERROR;
break;
}
int value = 0;
for (; i < m; ++i) {
char c = s.charAt(i);
if (c < '0' || c > '9') {
state = ERROR;
break loop;
}
value = 10 * value + (c - '0');
}
values[state] = value;
if (i == len) {
// reached EOF, check for end state
switch (state) {
case HOUR:
case TZHOUR:
state = ERROR;
}
break;
}
}
char c = s.charAt(i++);
if (c == 'Z') {
// handle abbrevation for UTC timezone
values[TZHOUR] = 0;
values[TZMIN] = 0;
switch (state) {
case YEAR:
case MONTH:
case DAY:
case MIN:
case SEC:
case MSEC:
break;
default:
state = ERROR;
}
break;
}
// state transition
switch (state) {
case YEAR:
case MONTH:
state = (c == '-' ? state + 1 : c == 'T' ? HOUR : ERROR);
break;
case DAY:
state = (c == 'T' ? HOUR : ERROR);
break;
case HOUR:
state = (c == ':' ? MIN : ERROR);
timeSpecified = true;
break;
case TZHOUR:
// state = (c == ':' ? state + 1 : ERROR);
// Non-standard extension, https://bugzilla.mozilla.org/show_bug.cgi?id=682754
if (c != ':') {
// back off by one and try to read without ':' separator
i -= 1;
}
state = TZMIN;
break;
case MIN:
state = (c == ':' ? SEC : c == '+' || c == '-' ? TZHOUR : ERROR);
break;
case SEC:
state = (c == '.' ? MSEC : c == '+' || c == '-' ? TZHOUR : ERROR);
break;
case MSEC:
state = (c == '+' || c == '-' ? TZHOUR : ERROR);
break;
case TZMIN:
state = ERROR;
break;
}
if (state == TZHOUR) {
// save timezone modificator
tzmod = (c == '-') ? -1 : 1;
}
}
syntax:
{
// error or unparsed characters
if (state == ERROR || i != len) break syntax;
// check values
int year = values[YEAR], month = values[MONTH], day = values[DAY];
int hour = values[HOUR], min = values[MIN], sec = values[SEC], msec = values[MSEC];
int tzhour = values[TZHOUR], tzmin = values[TZMIN];
if (year > 275943 // ceil(1e8/365) + 1970 = 275943
|| (month < 1 || month > 12)
|| (day < 1 || day > DaysInMonth(year, month))
|| hour > 24
|| (hour == 24 && (min > 0 || sec > 0 || msec > 0))
|| min > 59
|| sec > 59
|| tzhour > 23
|| tzmin > 59) {
break syntax;
}
// valid ISO-8601 format, compute date in milliseconds
double date = date_msecFromDate(year * yearmod, month - 1, day, hour, min, sec, msec);
if (tzhour == -1) {
// Spec says to use UTC timezone, the following bug report says
// that local timezone was meant to be used. Stick with spec for now.
// https://bugs.ecmascript.org/show_bug.cgi?id=112
// date = internalUTC(date);
// browsers doing this now
if (timeSpecified) {
date -= cx.getTimeZone().getRawOffset() + DaylightSavingTA(cx, date);
}
} else {
date -= (tzhour * 60 + tzmin) * msPerMinute * tzmod;
}
if (date < -HalfTimeDomain || date > HalfTimeDomain) break syntax;
return date;
}
// invalid ISO-8601 format, return NaN
return ScriptRuntime.NaN;
}