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