getData( 
            URL server, 
            String id, 
            String parameters,
            String startTime,
            String endTime ) throws IOException, JSONException {
        return getDataCSV(server, id, parameters, startTime, endTime);
    }
    
    /**
     * return the time as milliseconds since 1970-01-01T00:00Z.  This
     * does not include leap seconds.  For example, in Jython:
     * 
     * {@code
     * from org.hapiserver.HapiClient import *
     * x= toMillisecondsSince1970('2000-01-02T00:00:00.0Z')
     * print x / 86400000   # 10958.0 days
     * print x % 86400000   # and no milliseconds
     * }
     * 
     * @param time
     * @return number of non-leap-second milliseconds since 1970-01-01T00:00Z.
     */
    public static long toMillisecondsSince1970( String time ) {
        TemporalAccessor ta = DateTimeFormatter.ISO_INSTANT.parse(time);
        Instant i = Instant.from(ta);
        Date d = Date.from(i);
        return d.getTime();
    }
    
    /**
     * fast parser requires that each character of string is a digit.
     * @param s
     * @return 
     */
    private static int parseInt( String s ) {
        int result;
        switch ( s.length() ) {
            case 2:
                result= 10 * ( s.charAt(0)-48 ) + (s.charAt(1)-48);
                return result;
            case 3:
                result= 100 * ( s.charAt(0)-48 )
                        + 10 * ( s.charAt(1)-48 ) + (s.charAt(2)-48);
                return result;
            default:        
                result=0;
                for ( int i=0; i2400 ) {
            throw new IllegalArgumentException("year must be between 1800 and 2400");
        }
        return (year % 4)==0 && ( year%400==0 || year%100!=0 );
    }
    
    /**
     * return the doy of year of the month and day for the year.  For example,
     * 
     * {@code
     * from org.hapiserver.HapiClient import *
     * print dayOfYear( 2000, 5, 29 ) # 150
     * }
     * 
     * @param year the year
     * @param month the month, from 1 to 12.
     * @param day the day in the month.
     * @return the day of year.
     */
    public static int dayOfYear( int year, int month, int day ) {
        if ( month==1 ) {
            return day;
        }
        if ( month<1 ) throw new IllegalArgumentException("month must be greater than 0.");
        if ( month>12 ) throw new IllegalArgumentException("month must be less than 12.");
        int leap= isLeapYear(year) ? 1: 0;
        return DAY_OFFSET[leap][month] + day;
    }
    
    /**
     * normalize the decomposed time by expressing day of year and month
     * and day of month, and moving hour="24" into the next day.
     * @param time 
     */
    private static void normalizeTime( int[] time ) {
        if ( time[3]==24 ) {
            time[2]+= 1;
            time[3]= 0;
        }
        
        if ( time[3]>24 ) throw new IllegalArgumentException("time[3] is greater than 24 (hours)");
        if ( time[1]>12 ) throw new IllegalArgumentException("time[1] is greater than 12 (months)");
        
        int leap= isLeapYear(time[0]) ? 1: 0;
        
        int d= DAYS_IN_MONTH[leap][time[1]];
        while ( time[2]>d ) {
            time[1]++;
            time[2]-= d;
            d= DAYS_IN_MONTH[leap][time[1]];
            if ( time[1]>12 ) throw new IllegalArgumentException("time[2] is too big");
        }
        
    }
    
    /**
     * return array [ year, months, days, hours, minutes, seconds, nanoseconds ]
     * @param time
     * @return the decomposed time
     */
    public static int[] isoTimeToArray( String time ) {
        int[] result;
        if ( time.length()==4 ) {
            result= new int[] { Integer.parseInt(time), 1, 1, 0, 0, 0, 0 };
        } else {
            if ( time.length()<8 ) throw new IllegalArgumentException("time must have 4 or greater than 7 elements");
            if ( time.charAt(8)=='T' ) {
                result= new int[] { 
                    parseInt(time.substring(0,4)),
                    1, 
                    parseInt(time.substring(5,8)),  // days
                    0, 
                    0, 
                    0,
                    0 
                };
                time= time.substring(9);
            } else {
                result= new int[] { 
                    parseInt(time.substring(0,4)), 
                    parseInt(time.substring(5,7)),
                    parseInt(time.substring(8,10)),
                    0,
                    0,
                    0,
                    0
                };
                time= time.substring(11);
            }
            if ( time.endsWith("Z") ) time= time.substring(0,time.length()-1);
            if ( time.length()>=2 ) {
                result[3]= parseInt(time.substring(0,2));
            }
            if ( time.length()>=5 ) {
                result[4]= parseInt(time.substring(3,5));
            }
            if ( time.length()>=8 ) {
                result[5]= parseInt(time.substring(6,8));
            } 
            if ( time.length()>9 ) {
                result[6]= (int)( Math.pow( 10, 18-time.length() ) ) * parseInt(time.substring(9));
            }
            normalizeTime(result);
        }
        return result;        
    }
    
    
}