waterml/src/main/java/ucar/nc2/ogc/erddap/util/ErddapCalendar2.java
public static GregorianCalendar parseISODateTime(GregorianCalendar gc, 
        String s) {

        if (s == null)
            s = "";
        boolean negative = s.startsWith("-");
        if (negative) 
            s = s.substring(1);
        if (s.length() < 1 || !ErddapString2.isDigit(s.charAt(0)))
            throw new RuntimeException(
                    ErddapString2.ERROR + " in parseISODateTime: for first character of dateTime='" + s + "' isn't a digit!");
        if (gc == null)
            throw new RuntimeException(ErddapString2.ERROR + " in parseISODateTime: gc is null!");

        //default ymdhmsmom     year is the only required value
        int ymdhmsmom[] = {Integer.MAX_VALUE, 1, 1, 0, 0, 0, 0, 0, 0};


        //remove trailing Z or "UTC"
        s = s.trim();
        if (Character.toLowerCase(s.charAt(s.length() - 1)) == 'z') 
            s = s.substring(0, s.length() - 1).trim();
        if (s.length() >= 3) {
            String last3 = s.substring(s.length() - 3).toLowerCase();
            if (last3.equals("utc") || last3.equals("gmt"))
                s = s.substring(0, s.length() - 3).trim();
        }

        //if e.g., 1970-01-01 00:00:00 0:00, change ' ' to '+' (first ' '->'+' is irrelevant)
        s = ErddapString2.replaceAll(s, ' ', '+');

        //separators (\u0000=any non-digit)
        char separator[] = {'-','-','\u0000',':',':','.','±', ':', '\u0000'};
        parseN(s, separator, ymdhmsmom);
        if (ymdhmsmom[0] == Integer.MAX_VALUE)
            throw new RuntimeException(
                    ErddapString2.ERROR + " in parseISODateTime: dateTime='" + s + "' has an invalid format!");

        //do time zone adjustment
        //String2.log("#7=" + ymdhmsmom[7] + " #8=" + ymdhmsmom[8]);
        if (ymdhmsmom[7] != 0)
            ymdhmsmom[3] -= ymdhmsmom[7];
        if (ymdhmsmom[8] != 0) 
            ymdhmsmom[4] -= ymdhmsmom[7] < 0? -ymdhmsmom[8] : ymdhmsmom[8];

        //set gc      month -1 since gc month is 0..
        gc.set((negative? -1 : 1) * ymdhmsmom[0], ymdhmsmom[1] - 1, ymdhmsmom[2], 
            ymdhmsmom[3], ymdhmsmom[4], ymdhmsmom[5]);
        gc.set(MILLISECOND, ymdhmsmom[6]);
        gc.get(YEAR); //force recalculations

        return gc;
    }
public static GregorianCalendar parseISODateTime(GregorianCalendar gc, 
        String s) {

        if (s == null)
            s = "";
        boolean negative = s.startsWith("-");
        if (negative) 
            s = s.substring(1);
        if (s.length() < 1 || !ErddapString2.isDigit(s.charAt(0)))
            throw new RuntimeException(
                    ErddapString2.ERROR + " in parseISODateTime: for first character of dateTime='" + s + "' isn't a digit!");
        if (gc == null)
            throw new RuntimeException(ErddapString2.ERROR + " in parseISODateTime: gc is null!");

        //default ymdhmsmom     year is the only required value
        int ymdhmsmom[] = {Integer.MAX_VALUE, 1, 1, 0, 0, 0, 0, 0, 0};


        //remove trailing Z or "UTC"
        s = s.trim();
        if (Character.toLowerCase(s.charAt(s.length() - 1)) == 'z') 
            s = s.substring(0, s.length() - 1).trim();
        if (s.length() >= 3) {
            String last3 = s.substring(s.length() - 3).toLowerCase();
            if (last3.equals("utc") || last3.equals("gmt"))
                s = s.substring(0, s.length() - 3).trim();
        }

        //if e.g., 1970-01-01 00:00:00 0:00, change ' ' to '+' (first ' '->'+' is irrelevant)
        s = ErddapString2.replaceAll(s, ' ', '+');

        //separators (\u0000=any non-digit)
        char separator[] = {'-','-','\u0000',':',':','.','±', ':', '\u0000'};
        parseN(s, separator, ymdhmsmom);
        if (ymdhmsmom[0] == Integer.MAX_VALUE)
            throw new RuntimeException(
                    ErddapString2.ERROR + " in parseISODateTime: dateTime='" + s + "' has an invalid format!");

        //do time zone adjustment
        //String2.log("#7=" + ymdhmsmom[7] + " #8=" + ymdhmsmom[8]);
        if (ymdhmsmom[7] != 0)
            ymdhmsmom[3] -= ymdhmsmom[7];
        if (ymdhmsmom[8] != 0) 
            ymdhmsmom[4] -= ymdhmsmom[7] < 0? -ymdhmsmom[8] : ymdhmsmom[8];

        //set gc      month -1 since gc month is 0..
        gc.set((negative? -1 : 1) * ymdhmsmom[0], ymdhmsmom[1] - 1, ymdhmsmom[2], 
            ymdhmsmom[3], ymdhmsmom[4], ymdhmsmom[5]);
        gc.set(MILLISECOND, ymdhmsmom[6]);
        gc.get(YEAR); //force recalculations

        return gc;
    }
public static GregorianCalendar parseISODateTime ( GregorianCalendar gc , String s ) { if ( s == null ) s = "" ; boolean negative = s . startsWith ( "-" ) ; if ( negative ) s = s . substring ( 1 ) ; if ( s . length ( ) < 1 || ! ErddapString2 . isDigit ( s . charAt ( 0 ) ) ) throw new RuntimeException ( ErddapString2 . ERROR + " in parseISODateTime: for first character of dateTime='" + s + "' isn't a digit!" ) ; if ( gc == null ) throw new RuntimeException ( ErddapString2 . ERROR + " in parseISODateTime: gc is null!" ) ; //default ymdhmsmom     year is the only required value int ymdhmsmom [ ] = { Integer . MAX_VALUE , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 } ; //remove trailing Z or "UTC" s = s . trim ( ) ; if ( Character . toLowerCase ( s . charAt ( s . length ( ) - 1 ) ) == ' ' ) s = s . substring ( 0 , s . length ( ) - 1 ) . trim ( ) ; if ( s . length ( ) >= 3 ) { String last3 = s . substring ( s . length ( ) - 3 ) . toLowerCase ( ) ; if ( last3 . equals ( "utc" ) || last3 . equals ( "gmt" ) ) s = s . substring ( 0 , s . length ( ) - 3 ) . trim ( ) ; } //if e.g., 1970-01-01 00:00:00 0:00, change ' ' to '+' (first ' '->'+' is irrelevant) s = ErddapString2 . replaceAll ( s , ' ' , ' ' ) ; //separators (\u0000=any non-digit) char separator [ ] = { ' ' , ' ' , ' ' , ' ' , ' ' , ' ' , ' , : , \ } ; parseN ( s , separator , ymdhmsmom ) ; if ( ymdhmsmom [ 0 ] == Integer . MAX_VALUE ) throw new RuntimeException ( ErddapString2 . ERROR + " in parseISODateTime: dateTime='" + s + "' has an invalid format!" ) ; //do time zone adjustment //String2.log("#7=" + ymdhmsmom[7] + " #8=" + ymdhmsmom[8]); if ( ymdhmsmom [ 7 ] != 0 ) ymdhmsmom [ 3 ] -= ymdhmsmom [ 7 ] ; if ( ymdhmsmom [ 8 ] != 0 ) ymdhmsmom [ 4 ] -= ymdhmsmom [ 7 ] < 0 ? - ymdhmsmom [ 8 ] : ymdhmsmom [ 8 ] ; //set gc      month -1 since gc month is 0.. gc . set ( ( negative ? - 1 : 1 ) * ymdhmsmom [ 0 ] , ymdhmsmom [ 1 ] - 1 , ymdhmsmom [ 2 ] , ymdhmsmom [ 3 ] , ymdhmsmom [ 4 ] , ymdhmsmom [ 5 ] ) ; gc . set ( MILLISECOND , ymdhmsmom [ 6 ] ) ; gc . get ( YEAR ) ; //force recalculations return gc ; }
This converts an ISO date time string ([-]YYYY-MM-DDTHH:MM:SS.SSS±ZZ:ZZ) into
a GregorianCalendar object.
<br>It is lenient; so Jan 32 is converted to Feb 1;
<br>The 'T' may be any non-digit.
<br>The time zone can be omitted.
<br>The parts at the end of the time can be omitted.
<br>If there is no time, the end parts of the date can be omitted.  Year is required.
<br>This tries hard to be tolerant of non-valid formats (e.g., "1971-1-2", "1971-01")
<br>As of 11/9/2006, NO LONGER TRUE: If year is 0..49, it is assumed to be 2000..2049.
<br>As of 11/9/2006, NO LONGER TRUE: If year is 50..99, it is assumed to be 1950..1999.
<br>If the string is too short, the end of "1970-01-01T00:00:00.000Z" will be added (effectively).
<br>If the string is too long, the excess will be ignored.
<br>If a required separator is incorrect, it is an error.
<br>If the date is improperly formatted, it returns null.
<br>Timezone "Z" or "" is treated as "-00:00" (UTC/Zulu time)
<br>Timezones: e.g., 2007-01-02T03:04:05-01:00 is same as 2007-01-02T04:04:05

@param gc a GregorianCalendar object. The dateTime will be interpreted
as being in gc's time zone.
Timezone info is relative to the gc's time zone.
@param s the dateTimeString in the ISO format (YYYY-MM-DDTHH:MM:SS.SSS±ZZ:ZZ
or -YYYY-MM-DDTHH:MM:SS.SSS±ZZ:ZZ for years B.C.)
For years B.C., use calendar2Year = 1 - BCYear.
Note that BCYears are 1..., so 1 BC is calendar2Year 0 (or 0000),
and 2 BC is calendar2Year -1 (or -0001).
This supports SS.SSS and SS,SSS (which ISO 8601 prefers!).
@return the same GregorianCalendar object, but with the date info
@throws RuntimeException if trouble (e.g., gc is null or s is null or
not at least #)
This converts an ISO date time string ( [ - ] YYYY - MM - DDTHH : MM : SS . SSS±ZZ : ZZ ) into a GregorianCalendar object . <br > It is lenient ; so Jan 32 is converted to Feb 1 ; <br > The T may be any non - digit . <br > The time zone can be omitted . <br > The parts at the end of the time can be omitted . <br > If there is no time the end parts of the date can be omitted . Year is required . <br > This tries hard to be tolerant of non - valid formats ( e . g . 1971 - 1 - 2 1971 - 01 ) <br > As of 11 / 9 / 2006 NO LONGER TRUE : If year is 0 .. 49 it is assumed to be 2000 .. 2049 . <br > As of 11 / 9 / 2006 NO LONGER TRUE : If year is 50 .. 99 it is assumed to be 1950 .. 1999 . <br > If the string is too short the end of 1970 - 01 - 01T00 : 00 : 00 . 000Z will be added ( effectively ) . <br > If the string is too long the excess will be ignored . <br > If a required separator is incorrect it is an error . <br > If the date is improperly formatted it returns null . <br > Timezone Z or is treated as - 00 : 00 ( UTC / Zulu time ) <br > Timezones : e . g . 2007 - 01 - 02T03 : 04 : 05 - 01 : 00 is same as 2007 - 01 - 02T04 : 04 : 05