waterml/src/main/java/ucar/nc2/ogc/erddap/util/ErddapCalendar2.java
private static void parseN(String s, char separatorN[], int resultsN[]) {
        //ensure s starts with a digit
        if (s == null)
            s = "";
        s = s.trim();
        int sLength = s.length();
        if (sLength < 1 || !(s.charAt(0) == '-' || ErddapString2.isDigit(s.charAt(0)))) {
            resultsN[0] = Integer.MAX_VALUE;
            return;  
        }
        int po1, po2 = -1;
        //String2.log("parseN " + s);

        //search for digits, non-digit.   "1970-01-01T00:00:00.000-01:00"
        boolean mMode = s.charAt(0) == '-'; //initial '-' is required and included when evaluating number
        int nParts = separatorN.length;
        for (int part = 0; part < nParts; part++) {
            if (po2 + 1 < sLength) {
                //accumulate digits
                po1 = po2 + 1;
                po2 = po1;
                if (mMode) {
                    if (po2 < sLength && s.charAt(po2) == '-') po2++; 
                    else {resultsN[0] = Integer.MAX_VALUE; return; }
                }
                while (po2 < sLength && ErddapString2.isDigit(s.charAt(po2))) po2++; //digit

                //if no number, return; we're done
                if (po2 == po1)
                    return;
                if (part > 0 && separatorN[part - 1] == '.') {
                    resultsN[part] = ErddapMath2.roundToInt(1000 *
                            ErddapString2.parseDouble("0." + s.substring(po1, po2)));
                    //String2.log("  millis=" + resultsN[part]);
                } else {
                    resultsN[part] = ErddapString2.parseInt(s.substring(po1, po2));
                }

                //if invalid number, return trouble
                if (resultsN[part] == Integer.MAX_VALUE) {
                    resultsN[0] = Integer.MAX_VALUE;
                    return; 
                }

                //if no more source characters, we're done
                if (po2 >= sLength) {
                    //String2.log("  " + String2.toCSSVString(resultsN));
                    return;
                }

                //if invalid separator, stop trying to read more; return trouble
                mMode = false;
                char ch = s.charAt(po2);
                if (ch == ',') 
                    ch = '.';
                if (separatorN[part] == '\u0000') {

                } else if (separatorN[part] == '±') {
                    if (ch == '+') { //do nothing
                    }else if (ch == '-') {
                        po2--; //number starts with -
                        mMode = true;
                    } else {
                        resultsN[0] = Integer.MAX_VALUE; return; 
                    }
                } else if (ch != separatorN[part]) { //if not exact match ...
                    //if current part is ':' or '.' and not matched, try to skip forward to '±'
                    if ((separatorN[part] == ':' || separatorN[part] == '.') && 
                        part < nParts - 1) {
                        int pmPart = ErddapString2.indexOf(separatorN, '±', part + 1);
                        if (pmPart >= 0) {
                            //String2.log("  jump to +/-");
                            part = pmPart; 
                            if (ch == '+') { //do nothing
                            }else if (ch == '-') {
                                po2--; //number starts with -
                                mMode = true;
                            } else {
                                resultsN[0] = Integer.MAX_VALUE; return; 
                            }
                            continue;
                        } //if < 0, fall through to failure
                    }
                    resultsN[0] = Integer.MAX_VALUE;
                    return;  
                }
            }
        }
    }
private static void parseN(String s, char separatorN[], int resultsN[]) {
        //ensure s starts with a digit
        if (s == null)
            s = "";
        s = s.trim();
        int sLength = s.length();
        if (sLength < 1 || !(s.charAt(0) == '-' || ErddapString2.isDigit(s.charAt(0)))) {
            resultsN[0] = Integer.MAX_VALUE;
            return;  
        }
        int po1, po2 = -1;
        //String2.log("parseN " + s);

        //search for digits, non-digit.   "1970-01-01T00:00:00.000-01:00"
        boolean mMode = s.charAt(0) == '-'; //initial '-' is required and included when evaluating number
        int nParts = separatorN.length;
        for (int part = 0; part < nParts; part++) {
            if (po2 + 1 < sLength) {
                //accumulate digits
                po1 = po2 + 1;
                po2 = po1;
                if (mMode) {
                    if (po2 < sLength && s.charAt(po2) == '-') po2++; 
                    else {resultsN[0] = Integer.MAX_VALUE; return; }
                }
                while (po2 < sLength && ErddapString2.isDigit(s.charAt(po2))) po2++; //digit

                //if no number, return; we're done
                if (po2 == po1)
                    return;
                if (part > 0 && separatorN[part - 1] == '.') {
                    resultsN[part] = ErddapMath2.roundToInt(1000 *
                            ErddapString2.parseDouble("0." + s.substring(po1, po2)));
                    //String2.log("  millis=" + resultsN[part]);
                } else {
                    resultsN[part] = ErddapString2.parseInt(s.substring(po1, po2));
                }

                //if invalid number, return trouble
                if (resultsN[part] == Integer.MAX_VALUE) {
                    resultsN[0] = Integer.MAX_VALUE;
                    return; 
                }

                //if no more source characters, we're done
                if (po2 >= sLength) {
                    //String2.log("  " + String2.toCSSVString(resultsN));
                    return;
                }

                //if invalid separator, stop trying to read more; return trouble
                mMode = false;
                char ch = s.charAt(po2);
                if (ch == ',') 
                    ch = '.';
                if (separatorN[part] == '\u0000') {

                } else if (separatorN[part] == '±') {
                    if (ch == '+') { //do nothing
                    }else if (ch == '-') {
                        po2--; //number starts with -
                        mMode = true;
                    } else {
                        resultsN[0] = Integer.MAX_VALUE; return; 
                    }
                } else if (ch != separatorN[part]) { //if not exact match ...
                    //if current part is ':' or '.' and not matched, try to skip forward to '±'
                    if ((separatorN[part] == ':' || separatorN[part] == '.') && 
                        part < nParts - 1) {
                        int pmPart = ErddapString2.indexOf(separatorN, '±', part + 1);
                        if (pmPart >= 0) {
                            //String2.log("  jump to +/-");
                            part = pmPart; 
                            if (ch == '+') { //do nothing
                            }else if (ch == '-') {
                                po2--; //number starts with -
                                mMode = true;
                            } else {
                                resultsN[0] = Integer.MAX_VALUE; return; 
                            }
                            continue;
                        } //if < 0, fall through to failure
                    }
                    resultsN[0] = Integer.MAX_VALUE;
                    return;  
                }
            }
        }
    }
private static void parseN ( String s , char separatorN [ ] , int resultsN [ ] ) { //ensure s starts with a digit if ( s == null ) s = "" ; s = s . trim ( ) ; int sLength = s . length ( ) ; if ( sLength < 1 || ! ( s . charAt ( 0 ) == ' ' || ErddapString2 . isDigit ( s . charAt ( 0 ) ) ) ) { resultsN [ 0 ] = Integer . MAX_VALUE ; return ; } int po1 , po2 = - 1 ; //String2.log("parseN " + s); //search for digits, non-digit.   "1970-01-01T00:00:00.000-01:00" boolean mMode = s . charAt ( 0 ) == ' ' ; //initial '-' is required and included when evaluating number int nParts = separatorN . length ; for ( int part = 0 ; part < nParts ; part ++ ) { if ( po2 + 1 < sLength ) { //accumulate digits po1 = po2 + 1 ; po2 = po1 ; if ( mMode ) { if ( po2 < sLength && s . charAt ( po2 ) == ' ' ) po2 ++ ; else { resultsN [ 0 ] = Integer . MAX_VALUE ; return ; } } while ( po2 < sLength && ErddapString2 . isDigit ( s . charAt ( po2 ) ) ) po2 ++ ; //digit //if no number, return; we're done if ( po2 == po1 ) return ; if ( part > 0 && separatorN [ part - 1 ] == ' ' ) { resultsN [ part ] = ErddapMath2 . roundToInt ( 1000 * ErddapString2 . parseDouble ( "0." + s . substring ( po1 , po2 ) ) ) ; //String2.log("  millis=" + resultsN[part]); } else { resultsN [ part ] = ErddapString2 . parseInt ( s . substring ( po1 , po2 ) ) ; } //if invalid number, return trouble if ( resultsN [ part ] == Integer . MAX_VALUE ) { resultsN [ 0 ] = Integer . MAX_VALUE ; return ; } //if no more source characters, we're done if ( po2 >= sLength ) { //String2.log("  " + String2.toCSSVString(resultsN)); return ; } //if invalid separator, stop trying to read more; return trouble mMode = false ; char ch = s . charAt ( po2 ) ; if ( ch == ' ' ) ch = ' ' ; if ( separatorN [ part ] == ' ' ) { } else if ( separatorN [ part ] == ' ) if ( ch == ' ' ) { //do nothing } else if ( ch == ' ' ) { po2 -- ; //number starts with - mMode = true ; } else { resultsN [ 0 ] = Integer . MAX_VALUE ; return ; } } else if ( ch != separatorN [ part ] ) { //if not exact match ... //if current part is ':' or '.' and not matched, try to skip forward to '±' if ( ( separatorN [ part ] == ' ' || separatorN [ part ] == ' ' ) && part < nParts - 1 ) { int pmPart = ErddapString2 . indexOf ( separatorN , ' , art  ) ; if ( pmPart >= 0 ) { //String2.log("  jump to +/-"); part = pmPart ; if ( ch == ' ' ) { //do nothing } else if ( ch == ' ' ) { po2 -- ; //number starts with - mMode = true ; } else { resultsN [ 0 ] = Integer . MAX_VALUE ; return ; } continue ; } //if < 0, fall through to failure } resultsN [ 0 ] = Integer . MAX_VALUE ; return ; } } } }
This parses n int values from s and stores results in resultsN (or leaves
items in resultsN untouched if no value available).

@param s the date time string
@param separatorN is the separators (use "\u0000" to match any non-digit).
(± matches + or - and that becomes part of the number)
(. matches . or , (the European decimal point))
@param resultsN should initially have the defaults and
will receive the results.  If trouble, resultsN[0] will be Integer.MAX_VALUE,
so caller can throw exception with good error message.
This parses n int values from s and stores results in resultsN ( or leaves items in resultsN untouched if no value available ) .