private static double parseISOString()

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;
    }