waterml/src/main/java/ucar/nc2/ogc/erddap/util/ErddapEDUnits.java
public static String udunitsToUcum(String udunits) {
        if (udunits == null) {
            return null;
        }

        //is it a point in time? e.g., seconds since 1970-01-01T00:00:00T
        int sincePo = udunits.indexOf(" since ");
        if (sincePo > 0) {
            try {
                //test if really appropriate
                double baf[] = ErddapCalendar2.getTimeBaseAndFactor(udunits); //throws exception if trouble

                //use 'factor', since it is more forgiving than udunitsToUcum converter
                String u;
                if (Misc.nearlyEquals(baf[1], 0.001, 1e-6)) {  // Can't simply do "baf[1] == 0.001".
                    u = "ms";
                } else if (baf[1] == 1) {
                    u = "s";
                } else if (baf[1] == ErddapCalendar2.SECONDS_PER_MINUTE) {
                    u = "min";
                } else if (baf[1] == ErddapCalendar2.SECONDS_PER_HOUR) {
                    u = "h";
                } else if (baf[1] == ErddapCalendar2.SECONDS_PER_DAY) {
                    u = "d";
                } else if (baf[1] == 30 * ErddapCalendar2.SECONDS_PER_DAY) {  // mo_j ?
                    u = "mo";
                } else if (baf[1] == 360 * ErddapCalendar2.SECONDS_PER_DAY) {  // a_j ?
                    u = "a";
                } else {
                    u = udunitsToUcum(udunits.substring(0, sincePo)); //shouldn't happen, but weeks? microsec?
                }

                //make "s{since 1970-01-01T00:00:00T}
                return u + "{" + udunits.substring(sincePo + 1) + "}";
            } catch (Exception e) {
            }
        }

        //parse udunits and build ucum, till done
        StringBuilder ucum = new StringBuilder();
        int udLength = udunits.length();
        int po = 0;  //po is next position to be read
        while (po < udLength) {
            char ch = udunits.charAt(po);

            //letter  
            if (isUdunitsLetter(ch)) {     //includes 'µ' and '°'
                //find contiguous letters|_|digit (no '-') 
                int po2 = po + 1;
                while (po2 < udLength &&
                        (isUdunitsLetter(udunits.charAt(po2)) || udunits.charAt(po2) == '_' ||
                                ErddapString2.isDigit(udunits.charAt(po2)))) {
                    po2++;
                }
                String tUdunits = udunits.substring(po, po2);
                po = po2;

                //some udunits have internal digits, but none end in digits 
                //if it ends in digits, treat as exponent
                //find contiguous digits at end
                int firstDigit = tUdunits.length();
                while (firstDigit >= 1 && ErddapString2.isDigit(tUdunits.charAt(firstDigit - 1))) {
                    firstDigit--;
                }
                String exponent = tUdunits.substring(firstDigit);
                tUdunits = tUdunits.substring(0, firstDigit);
                String tUcum = oneUdunitsToUcum(tUdunits);

                //deal with PER -> / 
                if (tUcum.equals("/")) {
                    char lastUcum = ucum.length() == 0 ? '\u0000' : ucum.charAt(ucum.length() - 1);
                    if (lastUcum == '/') {
                        ucum.setCharAt(ucum.length() - 1, '.'); //2 '/' cancel out
                    } else if (lastUcum == '.') {
                        ucum.setCharAt(ucum.length() - 1, '/'); //  '/' replaces '.'
                    } else {
                        ucum.append('/');
                    }

                } else {
                    ucum.append(tUcum);
                }

                //add the exponent
                ucum.append(exponent);
                //catch -exponent as a number below

                continue;
            }

            //number
            if (ch == '-' || ErddapString2.isDigit(ch)) {
                //find contiguous digits
                int po2 = po + 1;
                while (po2 < udLength && ErddapString2.isDigit(udunits.charAt(po2))) {
                    po2++;
                }

                //decimal place + digit (not just .=multiplication)
                boolean hasDot = false;
                if (po2 < udLength - 1 && udunits.charAt(po2) == '.' && ErddapString2.isDigit(udunits.charAt(po2 + 1))) {
                    hasDot = true;
                    po2 += 2;
                    while (po2 < udLength && ErddapString2.isDigit(udunits.charAt(po2))) {
                        po2++;
                    }
                }

                //exponent?     e-  or e{digit}
                boolean hasE = false;
                if (po2 < udLength - 1 && Character.toLowerCase(udunits.charAt(po2)) == 'e' &&
                        (udunits.charAt(po2 + 1) == '-' || ErddapString2.isDigit(udunits.charAt(po2 + 1)))) {
                    hasE = true;
                    po2 += 2;
                    while (po2 < udLength && ErddapString2.isDigit(udunits.charAt(po2))) {
                        po2++;
                    }
                }
                String num = udunits.substring(po, po2);
                po = po2;

                //convert floating point to rational number
                if (hasDot || hasE) {
                    int rational[] = ErddapString2.toRational(ErddapString2.parseDouble(num));
                    if (rational[1] == Integer.MAX_VALUE) {
                        ucum.append(num); //ignore the trouble !!! ???
                    } else if (rational[1] == 0) //includes {0, 0}
                    {
                        ucum.append(rational[0]);
                    } else {
                        ucum.append(rational[0]).append(".10^").append(rational[1]);
                    }
                } else {
                    //just copy num
                    ucum.append(num);
                }

                continue;
            }

            //space or . or · (183) (multiplication)
            if (ch == ' ' || ch == '.' || ch == 183) {
                char lastUcum = ucum.length() == 0 ? '\u0000' : ucum.charAt(ucum.length() - 1);
                if (lastUcum == '/' || lastUcum == '.') {
                    //if last token was / or .,  do nothing
                } else {
                    ucum.append('.');
                }
                po++;
                continue;
            }

            // *  (multiplication * or exponent **)
            if (ch == '*') {
                po++;
                if (po < udLength && udunits.charAt(po) == '*') {
                    ucum.append('^');  // exponent: ** -> ^
                    po++;
                } else {
                    char lastUcum = ucum.length() == 0 ? '\u0000' : ucum.charAt(ucum.length() - 1);
                    if (lastUcum == '/' || lastUcum == '.') {
                        //if last token was / or .,  do nothing
                    } else {
                        ucum.append('.');
                    }
                }
                continue;
            }

            // /
            if (ch == '/') {
                po++;
                char lastUcum = ucum.length() == 0 ? '\u0000' : ucum.charAt(ucum.length() - 1);
                if (lastUcum == '/') {
                    ucum.setCharAt(ucum.length() - 1, '.'); //  2 '/' cancel out
                } else if (lastUcum == '.') {
                    ucum.setCharAt(ucum.length() - 1, '/'); //  '/' replaces '.'
                } else {
                    ucum.append('/');
                }

                continue;
            }

            // "
            if (ch == '\"') {
                po++;
                ucum.append("''");
                continue;
            }

            //otherwise, punctuation.   copy it
            ucum.append(ch);
            po++;
        }

        return ucum.toString();
    }
public static String udunitsToUcum(String udunits) {
        if (udunits == null) {
            return null;
        }

        //is it a point in time? e.g., seconds since 1970-01-01T00:00:00T
        int sincePo = udunits.indexOf(" since ");
        if (sincePo > 0) {
            try {
                //test if really appropriate
                double baf[] = ErddapCalendar2.getTimeBaseAndFactor(udunits); //throws exception if trouble

                //use 'factor', since it is more forgiving than udunitsToUcum converter
                String u;
                if (Misc.nearlyEquals(baf[1], 0.001, 1e-6)) {  // Can't simply do "baf[1] == 0.001".
                    u = "ms";
                } else if (baf[1] == 1) {
                    u = "s";
                } else if (baf[1] == ErddapCalendar2.SECONDS_PER_MINUTE) {
                    u = "min";
                } else if (baf[1] == ErddapCalendar2.SECONDS_PER_HOUR) {
                    u = "h";
                } else if (baf[1] == ErddapCalendar2.SECONDS_PER_DAY) {
                    u = "d";
                } else if (baf[1] == 30 * ErddapCalendar2.SECONDS_PER_DAY) {  // mo_j ?
                    u = "mo";
                } else if (baf[1] == 360 * ErddapCalendar2.SECONDS_PER_DAY) {  // a_j ?
                    u = "a";
                } else {
                    u = udunitsToUcum(udunits.substring(0, sincePo)); //shouldn't happen, but weeks? microsec?
                }

                //make "s{since 1970-01-01T00:00:00T}
                return u + "{" + udunits.substring(sincePo + 1) + "}";
            } catch (Exception e) {
            }
        }

        //parse udunits and build ucum, till done
        StringBuilder ucum = new StringBuilder();
        int udLength = udunits.length();
        int po = 0;  //po is next position to be read
        while (po < udLength) {
            char ch = udunits.charAt(po);

            //letter  
            if (isUdunitsLetter(ch)) {     //includes 'µ' and '°'
                //find contiguous letters|_|digit (no '-') 
                int po2 = po + 1;
                while (po2 < udLength &&
                        (isUdunitsLetter(udunits.charAt(po2)) || udunits.charAt(po2) == '_' ||
                                ErddapString2.isDigit(udunits.charAt(po2)))) {
                    po2++;
                }
                String tUdunits = udunits.substring(po, po2);
                po = po2;

                //some udunits have internal digits, but none end in digits 
                //if it ends in digits, treat as exponent
                //find contiguous digits at end
                int firstDigit = tUdunits.length();
                while (firstDigit >= 1 && ErddapString2.isDigit(tUdunits.charAt(firstDigit - 1))) {
                    firstDigit--;
                }
                String exponent = tUdunits.substring(firstDigit);
                tUdunits = tUdunits.substring(0, firstDigit);
                String tUcum = oneUdunitsToUcum(tUdunits);

                //deal with PER -> / 
                if (tUcum.equals("/")) {
                    char lastUcum = ucum.length() == 0 ? '\u0000' : ucum.charAt(ucum.length() - 1);
                    if (lastUcum == '/') {
                        ucum.setCharAt(ucum.length() - 1, '.'); //2 '/' cancel out
                    } else if (lastUcum == '.') {
                        ucum.setCharAt(ucum.length() - 1, '/'); //  '/' replaces '.'
                    } else {
                        ucum.append('/');
                    }

                } else {
                    ucum.append(tUcum);
                }

                //add the exponent
                ucum.append(exponent);
                //catch -exponent as a number below

                continue;
            }

            //number
            if (ch == '-' || ErddapString2.isDigit(ch)) {
                //find contiguous digits
                int po2 = po + 1;
                while (po2 < udLength && ErddapString2.isDigit(udunits.charAt(po2))) {
                    po2++;
                }

                //decimal place + digit (not just .=multiplication)
                boolean hasDot = false;
                if (po2 < udLength - 1 && udunits.charAt(po2) == '.' && ErddapString2.isDigit(udunits.charAt(po2 + 1))) {
                    hasDot = true;
                    po2 += 2;
                    while (po2 < udLength && ErddapString2.isDigit(udunits.charAt(po2))) {
                        po2++;
                    }
                }

                //exponent?     e-  or e{digit}
                boolean hasE = false;
                if (po2 < udLength - 1 && Character.toLowerCase(udunits.charAt(po2)) == 'e' &&
                        (udunits.charAt(po2 + 1) == '-' || ErddapString2.isDigit(udunits.charAt(po2 + 1)))) {
                    hasE = true;
                    po2 += 2;
                    while (po2 < udLength && ErddapString2.isDigit(udunits.charAt(po2))) {
                        po2++;
                    }
                }
                String num = udunits.substring(po, po2);
                po = po2;

                //convert floating point to rational number
                if (hasDot || hasE) {
                    int rational[] = ErddapString2.toRational(ErddapString2.parseDouble(num));
                    if (rational[1] == Integer.MAX_VALUE) {
                        ucum.append(num); //ignore the trouble !!! ???
                    } else if (rational[1] == 0) //includes {0, 0}
                    {
                        ucum.append(rational[0]);
                    } else {
                        ucum.append(rational[0]).append(".10^").append(rational[1]);
                    }
                } else {
                    //just copy num
                    ucum.append(num);
                }

                continue;
            }

            //space or . or · (183) (multiplication)
            if (ch == ' ' || ch == '.' || ch == 183) {
                char lastUcum = ucum.length() == 0 ? '\u0000' : ucum.charAt(ucum.length() - 1);
                if (lastUcum == '/' || lastUcum == '.') {
                    //if last token was / or .,  do nothing
                } else {
                    ucum.append('.');
                }
                po++;
                continue;
            }

            // *  (multiplication * or exponent **)
            if (ch == '*') {
                po++;
                if (po < udLength && udunits.charAt(po) == '*') {
                    ucum.append('^');  // exponent: ** -> ^
                    po++;
                } else {
                    char lastUcum = ucum.length() == 0 ? '\u0000' : ucum.charAt(ucum.length() - 1);
                    if (lastUcum == '/' || lastUcum == '.') {
                        //if last token was / or .,  do nothing
                    } else {
                        ucum.append('.');
                    }
                }
                continue;
            }

            // /
            if (ch == '/') {
                po++;
                char lastUcum = ucum.length() == 0 ? '\u0000' : ucum.charAt(ucum.length() - 1);
                if (lastUcum == '/') {
                    ucum.setCharAt(ucum.length() - 1, '.'); //  2 '/' cancel out
                } else if (lastUcum == '.') {
                    ucum.setCharAt(ucum.length() - 1, '/'); //  '/' replaces '.'
                } else {
                    ucum.append('/');
                }

                continue;
            }

            // "
            if (ch == '\"') {
                po++;
                ucum.append("''");
                continue;
            }

            //otherwise, punctuation.   copy it
            ucum.append(ch);
            po++;
        }

        return ucum.toString();
    }
public static String udunitsToUcum ( String udunits ) { if ( udunits == null ) { return null ; } //is it a point in time? e.g., seconds since 1970-01-01T00:00:00T int sincePo = udunits . indexOf ( " since " ) ; if ( sincePo > 0 ) { try { //test if really appropriate double baf [ ] = ErddapCalendar2 . getTimeBaseAndFactor ( udunits ) ; //throws exception if trouble //use 'factor', since it is more forgiving than udunitsToUcum converter String u ; if ( Misc . nearlyEquals ( baf [ 1 ] , 0.001 , 1e-6 ) ) { // Can't simply do "baf[1] == 0.001". u = "ms" ; } else if ( baf [ 1 ] == 1 ) { u = "s" ; } else if ( baf [ 1 ] == ErddapCalendar2 . SECONDS_PER_MINUTE ) { u = "min" ; } else if ( baf [ 1 ] == ErddapCalendar2 . SECONDS_PER_HOUR ) { u = "h" ; } else if ( baf [ 1 ] == ErddapCalendar2 . SECONDS_PER_DAY ) { u = "d" ; } else if ( baf [ 1 ] == 30 * ErddapCalendar2 . SECONDS_PER_DAY ) { // mo_j ? u = "mo" ; } else if ( baf [ 1 ] == 360 * ErddapCalendar2 . SECONDS_PER_DAY ) { // a_j ? u = "a" ; } else { u = udunitsToUcum ( udunits . substring ( 0 , sincePo ) ) ; //shouldn't happen, but weeks? microsec? } //make "s{since 1970-01-01T00:00:00T} return u + "{" + udunits . substring ( sincePo + 1 ) + "}" ; } catch ( Exception e ) { } } //parse udunits and build ucum, till done StringBuilder ucum = new StringBuilder ( ) ; int udLength = udunits . length ( ) ; int po = 0 ; //po is next position to be read while ( po < udLength ) { char ch = udunits . charAt ( po ) ; //letter   if ( isUdunitsLetter ( ch ) ) { //includes 'µ' and '°' //find contiguous letters|_|digit (no '-')  int po2 = po + 1 ; while ( po2 < udLength && ( isUdunitsLetter ( udunits . charAt ( po2 ) ) || udunits . charAt ( po2 ) == ' ' || ErddapString2 . isDigit ( udunits . charAt ( po2 ) ) ) ) { po2 ++ ; } String tUdunits = udunits . substring ( po , po2 ) ; po = po2 ; //some udunits have internal digits, but none end in digits  //if it ends in digits, treat as exponent //find contiguous digits at end int firstDigit = tUdunits . length ( ) ; while ( firstDigit >= 1 && ErddapString2 . isDigit ( tUdunits . charAt ( firstDigit - 1 ) ) ) { firstDigit -- ; } String exponent = tUdunits . substring ( firstDigit ) ; tUdunits = tUdunits . substring ( 0 , firstDigit ) ; String tUcum = oneUdunitsToUcum ( tUdunits ) ; //deal with PER -> /  if ( tUcum . equals ( "/" ) ) { char lastUcum = ucum . length ( ) == 0 ? ' ' : ucum . charAt ( ucum . length ( ) - 1 ) ; if ( lastUcum == ' ' ) { ucum . setCharAt ( ucum . length ( ) - 1 , ' ' ) ; //2 '/' cancel out } else if ( lastUcum == ' ' ) { ucum . setCharAt ( ucum . length ( ) - 1 , ' ' ) ; //  '/' replaces '.' } else { ucum . append ( ' ' ) ; } } else { ucum . append ( tUcum ) ; } //add the exponent ucum . append ( exponent ) ; //catch -exponent as a number below continue ; } //number if ( ch == ' ' || ErddapString2 . isDigit ( ch ) ) { //find contiguous digits int po2 = po + 1 ; while ( po2 < udLength && ErddapString2 . isDigit ( udunits . charAt ( po2 ) ) ) { po2 ++ ; } //decimal place + digit (not just .=multiplication) boolean hasDot = false ; if ( po2 < udLength - 1 && udunits . charAt ( po2 ) == ' ' && ErddapString2 . isDigit ( udunits . charAt ( po2 + 1 ) ) ) { hasDot = true ; po2 += 2 ; while ( po2 < udLength && ErddapString2 . isDigit ( udunits . charAt ( po2 ) ) ) { po2 ++ ; } } //exponent?     e-  or e{digit} boolean hasE = false ; if ( po2 < udLength - 1 && Character . toLowerCase ( udunits . charAt ( po2 ) ) == ' ' && ( udunits . charAt ( po2 + 1 ) == ' ' || ErddapString2 . isDigit ( udunits . charAt ( po2 + 1 ) ) ) ) { hasE = true ; po2 += 2 ; while ( po2 < udLength && ErddapString2 . isDigit ( udunits . charAt ( po2 ) ) ) { po2 ++ ; } } String num = udunits . substring ( po , po2 ) ; po = po2 ; //convert floating point to rational number if ( hasDot || hasE ) { int rational [ ] = ErddapString2 . toRational ( ErddapString2 . parseDouble ( num ) ) ; if ( rational [ 1 ] == Integer . MAX_VALUE ) { ucum . append ( num ) ; //ignore the trouble !!! ??? } else if ( rational [ 1 ] == 0 ) //includes {0, 0} { ucum . append ( rational [ 0 ] ) ; } else { ucum . append ( rational [ 0 ] ) . append ( ".10^" ) . append ( rational [ 1 ] ) ; } } else { //just copy num ucum . append ( num ) ; } continue ; } //space or . or · (183) (multiplication) if ( ch == ' ' || ch == ' ' || ch == 183 ) { char lastUcum = ucum . length ( ) == 0 ? ' ' : ucum . charAt ( ucum . length ( ) - 1 ) ; if ( lastUcum == ' ' || lastUcum == ' ' ) { //if last token was / or .,  do nothing } else { ucum . append ( ' ' ) ; } po ++ ; continue ; } // *  (multiplication * or exponent **) if ( ch == ' ' ) { po ++ ; if ( po < udLength && udunits . charAt ( po ) == ' ' ) { ucum . append ( ' ' ) ; // exponent: ** -> ^ po ++ ; } else { char lastUcum = ucum . length ( ) == 0 ? ' ' : ucum . charAt ( ucum . length ( ) - 1 ) ; if ( lastUcum == ' ' || lastUcum == ' ' ) { //if last token was / or .,  do nothing } else { ucum . append ( ' ' ) ; } } continue ; } // / if ( ch == ' ' ) { po ++ ; char lastUcum = ucum . length ( ) == 0 ? ' ' : ucum . charAt ( ucum . length ( ) - 1 ) ; if ( lastUcum == ' ' ) { ucum . setCharAt ( ucum . length ( ) - 1 , ' ' ) ; //  2 '/' cancel out } else if ( lastUcum == ' ' ) { ucum . setCharAt ( ucum . length ( ) - 1 , ' ' ) ; //  '/' replaces '.' } else { ucum . append ( ' ' ) ; } continue ; } // " if ( ch == ' ' ) { po ++ ; ucum . append ( "''" ) ; continue ; } //otherwise, punctuation.   copy it ucum . append ( ch ) ; po ++ ; } return ucum . toString ( ) ; }
This converts UDUnits to UCUM.
<br>UDUnits: http://www.unidata.ucar.edu/software/udunits/udunits-1/etc/udunits.dat
http://www.unidata.ucar.edu/software/udunits/udunits-2/udunits2.html
I worked with v 2.1.9
<br>UCUM: http://unitsofmeasure.org/ucum.html
I worked with Version: 1.8, $Revision: 28894 $
<p/>
<p>UDUnits supports lots of aliases (short and long)
and plurals (usually by adding 's').
These all get reduced to UCUM's short and canonical-only units.
<p/>
<p>Notes:
<ul>
<li>This method is a strictly case sensitive.
The only UDUnits that should be capitalized (other than acronyms) are
Btu, Gregorian..., Julian..., PI.
<br>The only UDUnits that may be capitalized are
Celsius, Fahrenheit, Kelvin, Rankine.
<li>For "10 to the", UCUM allows 10* or 10^. This method uses 10^.
<li>NTU becomes {ntu}.
<li>PSU or psu becomes {psu}.
</ul>
<p/>
return the UDUnits converted to UCUM.
null returns null. "" returns "".
throws Exception if trouble.
This converts UDUnits to UCUM . <br > UDUnits : http : // www . unidata . ucar . edu / software / udunits / udunits - 1 / etc / udunits . dat http : // www . unidata . ucar . edu / software / udunits / udunits - 2 / udunits2 . html I worked with v 2 . 1 . 9 <br > UCUM : http : // unitsofmeasure . org / ucum . html I worked with Version : 1 . 8 $Revision : 28894 $ <p / > <p > UDUnits supports lots of aliases ( short and long ) and plurals ( usually by adding s ) . These all get reduced to UCUM s short and canonical - only units . <p / > <p > Notes : <ul > <li > This method is a strictly case sensitive . The only UDUnits that should be capitalized ( other than acronyms ) are Btu Gregorian ... Julian ... PI . <br > The only UDUnits that may be capitalized are Celsius Fahrenheit Kelvin Rankine . <li > For 10 to the UCUM allows 10 * or 10^ . This method uses 10^ . <li > NTU becomes { ntu } . <li > PSU or psu becomes { psu } . < / ul > <p / > return the UDUnits converted to UCUM . null returns null . returns . throws Exception if trouble .