[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: 20000223: FCALLSCFUN4, FCALLSCFUN8 problem in uniunits installation



Jianfu Pan,

>Date: Tue, 22 Feb 2000 10:17:35 -0500 (EST)
>From: Jianfu Pan <address@hidden>
>Organization: NASA/GSFC
>To: address@hidden (Steve Emmerson)
>Subject: Re: 20000218: FCALLSCFUN4, FCALLSCFUN8 problem in uniunits 
>installation
>Keywords: 200002181939.MAA10304

In the above message, you wrote:

>    Sorry for the late response.  Here are the answers to your questions:
> 
>       1.  A Fortran "REAL";
> 
>           size is 8 bytes.
> >
> >     2.  A Fortran "DOUBLE PRECISION";
> 
>           16 bytes.
> >
> >     3.  A C "float"
> 
>           8 bytes.
> >
> >     4.  A C "double"
> 
>           8 bytes.

Thanks.

I've enclosed a modified version of the file "lib/utlib.c" which should
solve your problem.  Please replace your version with it and try to
build the package.

Please let me know if this helps.

Regards,
Steve Emmerson   <http://www.unidata.ucar.edu>

--------Begin lib/utlib c
/*
 * $Id: utlib.c,v 1.18 1999/09/08 16:18:53 steve Exp $
 *
 * Support functions for the units(3) library.
 */

/*LINTLIBRARY*/

#ifndef _XOPEN_SOURCE
#   define _XOPEN_SOURCE
#endif
#ifndef _ANSI_C_SOURCE
#   define _ANSI_C_SOURCE
#endif

#include <udposix.h>
#include <stddef.h>             /* for ptrdiff_t */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <math.h>
#include <assert.h>
#include <search.h>
#include <math.h>               /* for modf(), floor(), log10(), ceil() */
#include <limits.h>             /* for *PATH_MAX */
#include <float.h>              /* for DBL_DIG */
#include "cfortran.h"           /* for FORTRAN support */
#include "udunits.h"
#include "utprivate.h"

/*
 * cfortran.h support for unit arguments:
 */
/* C-returning-to-FORTRAN: */
#define PUNIT_cfFZ(UN,LN)               utUnit* fcallsc(UN,LN)(void
/* The following might be wrong and require modification for the next
 * version of cfortran.h */
#define PUNIT_cfINT(N,A,B,X,Y,Z)        SIMPLE_cfINT(N,A,B,X,Y,Z)
#define PUNIT_cfUU(PUNIT,A0)            utUnit* A0
#define PUNIT_cfL                       A0 =
#define PUNIT_cfI                       return A0;
#define PUNIT_cfK

/* FORTRAN-calling-C: */
#define PPUNIT_cfV(  T,A,B,F)       SIMPLE_cfV(T,A,B,F)
#define PPUNIT_cfSEP(T,  B)         SIMPLE_cfSEP(T,B)
#define PPUNIT_cfINT(N,A,B,X,Y,Z)   SIMPLE_cfINT(N,A,B,X,Y,Z)
#define PPUNIT_cfSTR(N,T,A,B,C,D,E) SIMPLE_cfSTR(N,T,A,B,C,D,E)
#define PPUNIT_cfCC( T,A,B)         SIMPLE_cfCC(T,A,B)
#define PPUNIT_cfAA( T,A,B)         PPUNIT_cfB(T,A) /* Argument B not used. */
#define PPUNIT_cfU(  T,A)           PPUNIT_cfN(T,A)
#define PPUNIT_cfN(  T,A)           utUnit** A
#define PPUNIT_cfT(M,I,A,B,D)       *A

/* Output, Fortran character buffer: */
#define CBUF_cfINT(N,A,B,X,Y,Z)         STRING_cfINT(N,A,B,X,Y,Z)
#define CBUF_cfSEP(T,  B)               STRING_cfSEP(T,B)
#define CBUF_cfN(  T,A)                 STRING_cfN(T,A)
#define CBUF_cfSTR(N,T,A,B,C,D,E)       STRING_cfSTR(N,T,A,B,C,D,E)
#if defined(vmsFortran)
#   define CBUF_cfT(M,I,A,B,D)          A->dsc$a_pointer, A->dsc$w_length
#elif defined(CRAYFortran)
#   define CBUF_cfT(M,I,A,B,D)          _fcdtocp(A), _fcdlen(A)
#else
#   define CBUF_cfT(M,I,A,B,D)          A, D
#endif

#ifndef PATH_MAX
#   define PATH_MAX     _POSIX_PATH_MAX
#endif

#undef  DUPSTR
#define DUPSTR(s)       strcpy((char*)malloc(strlen(s)+1), s)
#undef  ABS
#define ABS(a)          ((a) < 0 ? -(a) : (a))
#undef  MIN
#define MIN(a,b)        ((a) < (b) ? (a) : (b))
#undef  MAX
#define MAX(a,b)        ((a) > (b) ? (a) : (b))

typedef struct {
    char        *name;
    int         nchr;
    int         HasPlural;
    utUnit      unit;
} UnitEntry;

typedef struct {
    char        *name;          /* prefix string (e.g. "milli") */
    UtFactor    factor;         /* corresponding multiplying factor */
    short       nchar;          /* size of prefix string excluding EOS */
} PrefixEntry;

/*
 *  Prefix table in the order required by the prefix-entry comparison function.
 *  The names are Based on ANSI/IEEE Std 260-1978 (a.k.a. ANSI Y10.19-1969) -- 
 *  reaffirmed 1985.  The names must be unique.
 *
 *  NB: The short-prefix symbol corresponding to the prefix "micro" has been 
 *  changed here from the standard one (the Greek letter "mu") to the symbol 
 *  "u".
 */
#define PE(name, factor)    {name, factor, sizeof(name)-1}
static PrefixEntry      PrefixTable[]   = {
    PE("E",     1e18),
    PE("G",     1e9),
    PE("M",     1e6),
    PE("P",     1e15),
    PE("T",     1e12),
    PE("Y",     1e24),
    PE("Z",     1e21),
    PE("a",     1e-18),
    PE("atto",  1e-18),
    PE("c",     1e-2),
    PE("centi", 1e-2),
    PE("d",     1e-1),
    PE("da",    1e1),
    PE("deca",  1e1),   /* Spelling according to "ISO 2955: Information
                         * processing -- Representation of SI and other units
                         * in systems with limited character sets". */
    PE("deci",  1e-1),
    PE("deka",  1e1),   /* Spelling according to "ASTM Designation: E 380 - 85:
                         * Standard for METRIC PRACTICE" and "ANSI/IEEE Std
                         * 260-1978 (Reaffirmed 1985): IEEE Standard Letter 
                         * Symbols for Units of Measurement". */
    PE("exa",   1e18),
    PE("f",     1e-15),
    PE("femto", 1e-15),
    PE("giga",  1e9),
    PE("h",     1e2),
    PE("hecto", 1e2),
    PE("k",     1e3),
    PE("kilo",  1e3),
    PE("m",     1e-3),
    PE("mega",  1e6),
    PE("micro", 1e-6),
    PE("milli", 1e-3),
    PE("n",     1e-9),
    PE("nano",  1e-9),
    PE("p",     1e-12),
    PE("peta",  1e15),
    PE("pico",  1e-12),
    PE("tera",  1e12),
    PE("u",     1e-6),
    PE("y",     1e-24),
    PE("yocto", 1e-24),
    PE("yotta", 1e24),
    PE("z",     1e-21),
    PE("zepto", 1e-21),
    PE("zetta", 1e21),
    NULL
};

static void     *root           = NULL;
static int      initialized     = 0;    /* module initialized = no */
static int      NumberBaseUnits = 0;    /* number of base units */
static int      HaveStdTimeUnit = 0;    /* standard time unit set? */
static char     *input_buf;             /* scanner input buffer */
static char     *input_ptr;             /* scanner input position */
static char     *unput_ptr;             /* position in unput() buffer */
static char     unput_buf[512];         /* scanner unput() buffer */
static char     linebuf[512];           /* input units-specification buffer */
static char     BaseName[UT_MAXNUM_BASE_QUANTITIES][UT_NAMELEN];
static FILE     *UtFile;                /* input units-file */
static char     UnitsFilePath[PATH_MAX];/* input units-file path */
static utUnit   StdTimeUnit;            /* standard time unit for determining
                                         * if a unit structure refers to a unit
                                         * of time
                                         */


/*
 * The following two functions convert between Julian day number and
 * Gregorian/Julian dates (Julian dates are used prior to October 15,
 * 1582; Gregorian dates are used after that).  Julian day number 0 is
 * midday, January 1, 4713 BCE.  The Gregorian calendar was adopted
 * midday, October 15, 1582.
 *
 * Author: Robert Iles, March 1994
 *
 * C Porter: Steve Emmerson, October 1995
 *
 * Original: http://www.nag.co.uk:70/nagware/Examples/calendar.f90
 *
 * There is no warranty on this code.
 */


/*
 * Convert a Julian day number to a Gregorian/Julian date.
 */
void
julday_to_gregdate(julday, year, month, day)
    unsigned long       julday;         /* Julian day number to convert */
    int                 *year;          /* Gregorian year (out) */
    int                 *month;         /* Gregorian month (1-12) (out) */
    int                 *day;           /* Gregorian day (1-31) (out) */
{
#if INT_MAX <= 0X7FFF
    long        ja, jb, jd;
#else
    int         ja, jb, jd;
#endif
    int         jc;
    int         je, iday, imonth, iyear;
    double      xc;

    if (julday < 2299161)
        ja = julday;
    else
    {
        int     ia = ((julday - 1867216) - 0.25) / 36524.25;

        ja = julday + 1 + ia - (int)(0.25 * ia);
    }

    jb = ja + 1524;
    xc = ((jb - 2439870) - 122.1) / 365.25;
    jc = 6680.0 + xc;
    jd = 365 * jc + (int)(0.25 * jc);
    je = (int)((jb - jd) / 30.6001);

    iday = (int)(jb - jd - (int)(30.6001 * je));

    imonth = je - 1;
    if (imonth > 12)
        imonth -= 12;

    iyear = jc - 4715;
    if (imonth > 2)
        iyear -= 1;
    if (iyear <= 0)
        iyear -= 1;

    *year = iyear;
    *month = imonth;
    *day = iday;
}


/*
 * Convert a Gregorian/Julian date to a Julian day number.
 *
 * The Gregorian calendar was adopted midday, October 15, 1582.
 */
unsigned long
gregdate_to_julday(year, month, day)
    int         year;   /* Gregorian year */
    int         month;  /* Gregorian month (1-12) */
    int         day;    /* Gregorian day (1-31) */
{
#if INT_MAX <= 0X7FFF
    long                igreg = 15 + 31 * (10 + (12 * 1582));
    long                iy;     /* signed, origin 0 year */
    long                ja;     /* Julian century */
    long                jm;     /* Julian month */
    long                jy;     /* Julian year */
#else
    int                 igreg = 15 + 31 * (10 + (12 * 1582));
    int                 iy;     /* signed, origin 0 year */
    int                 ja;     /* Julian century */
    int                 jm;     /* Julian month */
    int                 jy;     /* Julian year */
#endif
    unsigned long       julday; /* returned Julian day number */

    /*
     * Because there is no 0 BC or 0 AD, assume the user wants the start of 
     * the common era if they specify year 0.
     */
    if (year == 0)
        year = 1;

    iy = year;
    if (year < 0)
        iy++;
    if (month > 2)
    {
        jy = iy;
        jm = month + 1;
    }
    else
    {
        jy = iy - 1;
        jm = month + 13;
    }

    /*
     *  Note: SLIGHTLY STRANGE CONSTRUCTIONS REQUIRED TO AVOID PROBLEMS WITH
     *        OPTIMISATION OR GENERAL ERRORS UNDER VMS!
     */
    julday = day + (int)(30.6001 * jm);
    if (jy >= 0)
    {
        julday += 365 * jy;
        julday += 0.25 * jy;
    }
    else
    {
        double          xi = 365.25 * jy;

        if ((int)xi != xi)
            xi -= 1;
        julday += (int)xi;
    }
    julday += 1720995;

    if (day + (31* (month + (12 * iy))) >= igreg)
    {
        ja = jy/100;
        julday -= ja;
        julday += 2;
        julday += ja/4;
    }

    return julday;
}


/*
 * Encode a date as a double-precision value.
 */
    double
utencdate(year, month, day)
    int         year;
    int         month;
    int         day;
{
    return ((long)gregdate_to_julday(year, month, day) - 
            (long)gregdate_to_julday(2001, 1, 1)) * 86400.0;
}


/*
 * Encode a time as a double-precision value.
 */
    double
utencclock(hours, minutes, seconds)
    int         hours;
    int         minutes;
    double      seconds;
{
    return (hours*60 + minutes)*60 + seconds;
}


/*
 * Decompose a value into a set of values accounting for uncertainty.
 */
    static void
decomp(value, uncer, nbasis, basis, count)
    double      value;
    double      uncer;          /* >= 0 */
    int         nbasis;
    double      *basis;         /* all values > 0 */
    double      *count;
{
    int         i;

    for (i = 0; i < nbasis; i++)
    {
        double  r = fmod(value, basis[i]);      /* remainder */

        /* Adjust remainder to minimum magnitude. */
        if (ABS(2*r) > basis[i])
            r += r > 0
                    ? -basis[i]
                    :  basis[i];

        if (ABS(r) <= uncer)
        {
            /* The value equals a basis multiple within the uncertainty. */
            double      half = value < 0 ? -basis[i]/2 : basis[i]/2;
            modf((value+half)/basis[i], count+i);
            break;
        }

        value = basis[i] * modf(value/basis[i], count+i);
    }

    for (i++; i < nbasis; i++)
        count[i] = 0;
}


/*
 * Decode a time from a double-precision value.
 */
    static void
dectime(value, year, month, day, hour, minute, second)
    double      value;
    int         *year;
    int         *month;
    int         *day;
    int         *hour;
    int         *minute;
    float       *second;
{
    long        days;
    long        hours;
    long        minutes;
    double      seconds;
    double      uncer;          /* uncertainty of input value */
    typedef union
    {
        double      vec[7];
        struct
        {
            double      days;
            double      hours12;
            double      hours;
            double      minutes10;
            double      minutes;
            double      seconds10;
            double      seconds;
        }           ind;
    } Basis;
    Basis       counts;
    static Basis        basis;

    basis.ind.days = 86400;
    basis.ind.hours12 = 43200;
    basis.ind.hours = 3600;
    basis.ind.minutes10 = 600;
    basis.ind.minutes = 60;
    basis.ind.seconds10 = 10;
    basis.ind.seconds = 1;

    uncer = ldexp(value < 0 ? -value : value, -DBL_MANT_DIG);

    days = floor(value/86400.0);
    value -= days * 86400.0;            /* make positive excess */

    decomp(value, uncer, sizeof(basis.vec)/sizeof(basis.vec[0]),
           basis.vec, counts.vec);

    days += counts.ind.days;
    hours = (int)counts.ind.hours12 * 12 + (int)counts.ind.hours;
    minutes = (int)counts.ind.minutes10 * 10 + (int)counts.ind.minutes;
    seconds = (int)counts.ind.seconds10 * 10 + counts.ind.seconds;

    *second = seconds;
    *minute = minutes;
    *hour = hours;
    julday_to_gregdate(gregdate_to_julday(2001, 1, 1) + days, year, month, day);
}


/*
 * Indicate whether or not the given unit structure refers to a unit of time.
 *
 * This function returns:
 *      1       unit structure is a unit of time;
 *      0       unit structure is not a unit of time.
 */
    int
utIsTime(up)
    const utUnit        *up;
{
    int         status;

    if (!initialized || !HaveStdTimeUnit) {
        status  = 0;
    } else {
        int             iquan;

        for (iquan = 0; iquan < UT_MAXNUM_BASE_QUANTITIES; ++iquan)
            if (up->power[iquan] != StdTimeUnit.power[iquan])
                break;

        status  = iquan == UT_MAXNUM_BASE_QUANTITIES;
    }

    return status;
}


/*
 * FORTRAN interface to the above function.
 */
FCALLSCFUN1(INT,utIsTime,UTTIME,uttime,
    PPUNIT)


/*
 * Indicate whether or not the given unit structure has an origin.
 *
 * This function returns:
 *      1       unit structure has an origin;
 *      0       unit structure doesn't have an origin.
 */
    int
utHasOrigin(up)
    const utUnit        *up;
{
    return initialized && up->hasorigin;
}


/*
 * FORTRAN interface to the above function.
 */
FCALLSCFUN1(INT,utHasOrigin,UTORIGIN,utorigin,
    PPUNIT)


/*
 *  Clear a unit structure by setting it to the dimensionless identity
 *  unit structure.
 *
 *  This function returns a pointer to the unit structure.
 */
    utUnit*
utClear(unit)
    utUnit              *unit;
{
    register int        iquan;

    unit->hasorigin     = 0;
    unit->origin        = 0.0;
    unit->factor        = 1.0;

    for (iquan = 0; iquan < UT_MAXNUM_BASE_QUANTITIES; ++iquan)
        unit->power[iquan] = 0;

    return unit;
}


/*
 * FORTRAN interface to the above function.
 */
FCALLSCSUB1(utClear,UTCLR,utclr,
    PPUNIT)


/*
 *  Copy a unit structure.
 *
 *  This function returns a pointer to the destination unit structure.
 */
    utUnit*
utCopy(source, dest)
    const utUnit        *source;
    utUnit              *dest;
{
    assert(source != NULL);
    assert(dest != NULL);

    *dest       = *source;

    return dest;
}


/*
 * FORTRAN interface to the above function.
 */
FCALLSCSUB2(utCopy,UTCPY,utcpy,
    PPUNIT,PPUNIT)


/*
 *  Set the exponent of the given base quantity to unity.
 *
 *  This function returns:
 *       NULL   error;
 *      !NULL   success (points to the input unit structure).
 */
    utUnit*
utSetPower(unit, position)
    utUnit      *unit;
    int         position;
{
    utUnit      *result = NULL;

    if (position < 0 || position >= UT_MAXNUM_BASE_QUANTITIES) {
        (void) fprintf(stderr, 
          "udunits(3): %d is an invalid quantity index.  Valid range is 0-%d\n",
              position, UT_MAXNUM_BASE_QUANTITIES);
    } else {
        unit->power[position]   = 1;
        result  = unit;
    }

    return result;
}


/* 
 *  Shift the origin of a unit structure.
 *
 *  This function returns a pointer to the destination unit structure.
 */
    utUnit*
utShift(source, amount, result)
    utUnit      *source;
    double      amount;
    utUnit      *result;
{
    assert(source != NULL);
    assert(result != NULL);

    (void) utCopy(source, result);
    result->origin      = source->origin + amount*result->factor;
    result->hasorigin   = 1;

    return result;
}


/*
 * FORTRAN interface to the above function.
 */
FCALLSCSUB3(utShift,UTORIG,utorig,
    PPUNIT,DOUBLE,PPUNIT)


/* 
 *  Scale a "unit" structure.
 *
 *  This function returns a pointer to the destination unit structure.
 */
    utUnit*
utScale(source, factor, result)
    utUnit     *source;
    double      factor;
    utUnit      *result;
{
    assert(source != NULL);
    assert(result != NULL);

    (void) utCopy(source, result);

    result->factor      *= factor;
    result->origin      *= factor;

    return result;
}


/*
 * FORTRAN interface to the above function.
 */
FCALLSCSUB3(utScale,UTSCAL,utscal,
    PPUNIT,DOUBLE,PPUNIT)


/* 
 *  Multiply two unit-structures.
 *
 *  This function returns
 *      NULL                    failure;
 *      pointer to `result'     success.
 */
    utUnit*
utMultiply(term1, term2, result)
    utUnit     *term1, *term2, *result;
{
    utUnit      *res    = NULL;

    if (term2->hasorigin && term1->hasorigin) {
        (void) fprintf(stderr, 
                   "udunits(3): Can't multiply units with origins\n");
    } else {
        int         iquan;

        result->factor          = term1->factor * term2->factor;
        result->origin          = term1->hasorigin
                                    ? term1->origin * term2->factor
                                    : term2->origin * term1->factor;
        result->hasorigin       = term1->hasorigin || term2->hasorigin;

        for (iquan = 0; iquan < UT_MAXNUM_BASE_QUANTITIES; ++iquan)
            result->power[iquan]    = term1->power[iquan] + term2->power[iquan];

        res     = result;
    }

    return res;
}


/*
 * FORTRAN interface to the above function.
 */
FCALLSCSUB3(utMultiply,UTMULT,utmult,
    PPUNIT,PPUNIT,PPUNIT)


/*
 *  Form the reciprocal of an internal unit specification.
 *
 *  This function returns
 *      NULL                    failure;
 *      pointer to `result'     success.
 */
    utUnit*
utInvert(source, result)
    utUnit     *source, *result;
{
    utUnit      *res    = NULL;

    if (source->hasorigin) {
        (void) fprintf(stderr, 
                   "udunits(3): Can't invert a unit with an origin\n");
    } else {
        int         iquan;

        result->factor          = 1./source->factor;
        result->origin          = 0.0;
        result->hasorigin       = 0;

        for (iquan = 0; iquan < UT_MAXNUM_BASE_QUANTITIES; ++iquan)
            result->power[iquan]  = -source->power[iquan];

        res     = result;
    }

    return res;
}


/*
 * FORTRAN interface to the above function.
 */
FCALLSCSUB2(utInvert,UTINV,utinv,
    PPUNIT,PPUNIT)


/* 
 *  Divide two unit-structures.
 *
 *  This function returns
 *      NULL                    failure;
 *      pointer to `result'     success.
 */
    utUnit*
utDivide(numer, denom, result)
    utUnit     *numer, *denom, *result;
{
    utUnit      *res    = NULL;

    if (denom->hasorigin && numer->hasorigin) {
        (void) fprintf(stderr, 
                   "udunits(3): Can't divide units with origins\n");
    } else {
        int         iquan;

        result->factor          = numer->factor / denom->factor;
        result->origin          = numer->origin;
        result->hasorigin       = numer->hasorigin;

        for (iquan = 0; iquan < UT_MAXNUM_BASE_QUANTITIES; ++iquan)
            result->power[iquan]    = numer->power[iquan] - denom->power[iquan];

        res     = result;
    }

    return res;
}


/*
 * FORTRAN interface to the above function.
 */
FCALLSCSUB3(utDivide,UTDIV,utdiv,
    PPUNIT,PPUNIT,PPUNIT)


/* 
 *  Raise a unit-structure to a given power.
 *
 *  This function returns
 *      NULL                    failure;
 *      pointer to `result'     success.
 */
    utUnit*
utRaise(source, power, result)
    utUnit      *source;
    int         power;
    utUnit      *result;
{
    utUnit      *res    = NULL;

    if (source->hasorigin) {
        (void) fprintf(stderr, 
           "udunits(3): Can't exponentiate a unit with an origin\n");
    } else {
        int         iquan;

        result->factor          = pow(source->factor, (double)power);
        result->origin          = 0.0;
        result->hasorigin       = 0;

        for (iquan = 0; iquan < UT_MAXNUM_BASE_QUANTITIES; ++iquan)
            result->power[iquan]    = source->power[iquan] * power;

        res     = result;
    }

    return res;
}


/*
 * FORTRAN interface to the above function.
 */
FCALLSCSUB3(utRaise,UTEXP,utexp,
    PPUNIT,INT,PPUNIT)


/*
 *  Open a units-file.
 *
 *  This function returns:
 *       0              success
 *      UT_ENOFILE      couldn't open file
 */
    static int
OpenUnits(filename)
    char        *filename;
{
    int         status;                 /* return status */

    if ((UtFile = fopen(filename, "r")) == NULL) {
        (void) fprintf(stderr, 
                       "udunits(3): Couldn't open units database \"%s\": ", 
                       filename);
        perror("");
        status  = UT_ENOFILE;
    } else {
        UtLineno        = 0;
        linebuf[sizeof(linebuf)-1]      = 0;
        status  = 0;
    }

    return status;
}


/*
 * Close a units file.
 *
 * This function returns void.
 */
    static void
CloseUnits()
{
    if (UtFile != NULL) {
        (void)fclose(UtFile);
        UtFile  = NULL;
    }
}


/*
 *  Decode a unit specification.
 *
 *  This function returns:
 *       0              success
 *      UT_ESYNTAX      syntax error
 *      UT_EUNKNOWN     unknown specification
 */
    static int
DecodeUnit(spec, unit)
    char        *spec;
    utUnit      *unit;
{
    char        specbuf[512];

    /* Remove leading whitespace */
    while (isspace(*spec))
        ++spec;

    (void) strncpy(specbuf, spec, sizeof(specbuf));
    specbuf[sizeof(specbuf)-1]  = 0;

    /* Remove trailing whitespace */
    {
        char    *cp;
        for (cp = specbuf + strlen(specbuf);
            cp > specbuf && isspace(cp[-1]);
            --cp)
            ;   /* EMPTY */
        *cp = 0;
    }

    input_ptr   = input_buf     = specbuf;
    unput_ptr   = unput_buf;

    (void)utClear(FinalUnit = unit);

    UnitNotFound        = 0;
    utrestart((FILE*)0);
    return utparse() == 0 ? 0 : UnitNotFound ? UT_EUNKNOWN : UT_ESYNTAX;
}


/*
 *  Scan (decode) the next entry in the units file.
 *  If the specification is for a base unit, then the returned unit-structure
 *  is cleared except that the value of the next available exponent slot is 
 *  set to one.  A base unit is the unit for a fundamental (base) physical
 *  quantity (e.g. the unit `meter' for the base physical quantity `length').
 *
 *  This function returns:
 *      0               success
 *      UT_EOF          end-of-file encountered
 *      UT_EIO          I/O error
 *      UT_ESYNTAX      syntax error
 *      UT_EUNKNOWN     unknown specification
 *      UT_EALLOC       allocation failure
 */
    static int
ScanUnit(name, sizename, unit, HasPlural)
    char        *name;                  /* name buffer */
    size_t      sizename;               /* size of name buffer */
    utUnit      *unit;                  /* unit specification */
    int         *HasPlural;             /* specification has a plural form? */
{
    int         status  = 0;            /* return status = success */

    for (;;) {
        char            *cp;
        static char     WhiteSpace[]    = " \t";

        ++UtLineno;
        if (fgets(linebuf, (int)sizeof(linebuf), UtFile) == NULL) {
            if (feof(UtFile)) {
                status  = UT_EOF;
            } else {
                (void) perror("fgets()");
                status  = UT_EIO;
            }
            break;
        }
        
        if (linebuf[strlen(linebuf)-1] != '\n') {
            (void) fprintf(stderr, 
                       "udunits(3): Input-line longer than %lu-byte buffer\n",
                           (unsigned long)sizeof(linebuf));
            status      = UT_ESYNTAX;
            break;
        }

        /* Truncate at comment character */
        if ((cp = strchr(linebuf, '#')) != NULL)
            *cp = 0;

        /* Trim trailing whitespace */
        for (cp = linebuf + strlen(linebuf); cp > linebuf; --cp)
             if (!isspace(cp[-1]))
                break;
        *cp     = 0;

        /* Find first "black" character */
        cp      = linebuf + strspn(linebuf, WhiteSpace);

        if (*cp != 0) {
            int         n       = strcspn(cp, WhiteSpace);
            char        buf[512];

            assert(sizeof(buf) > strlen(linebuf));
            assert((size_t)sizename > strlen(linebuf));

            (void)strncpy(name, cp, n); name[n] = 0;

            cp  += n;
            cp  += strspn(cp, WhiteSpace);
            n   = strcspn(cp, WhiteSpace);
            (void)strncpy(buf, cp, n); name[n]  = 0;

            if (strcmp(buf, "P") == 0) {
                *HasPlural      = 1;
            } else if (strcmp(buf, "S") == 0) {
                *HasPlural      = 0;
            } else {
                (void) fprintf(stderr,
                       "udunits(3): Invalid single/plural indicator \"%s\"\n", 
                       buf);
                status  = UT_ESYNTAX;
                break;
            }

            cp  += n;
            cp  += strspn(cp, WhiteSpace);
            (void)strcpy(buf, cp);

            if (buf[0] == 0) {
                (void)utClear(unit);
                if (utSetPower(unit, NumberBaseUnits) == NULL) {
                    (void) fprintf(stderr, 
                                   "udunits(3): Couldn't set base unit #%d\n", 
                                   NumberBaseUnits);
                    status      = UT_EALLOC;
                } else {
                    (void)strncpy(BaseName[NumberBaseUnits], name, 
                                  UT_NAMELEN-1);
                    ++NumberBaseUnits;
                }
            } else {
                if ((status = DecodeUnit(buf, unit)) != 0) {
                    (void) fprintf(stderr, 
                   "udunits(3): Couldn't decode \"%s\" definition \"%s\"\n", 
                           name, buf);
                }
            }
            break;
        }                                       /* if not a layout line */
    }                                           /* input-line loop */

    if (status != 0 && status != UT_EOF)
        (void) fprintf(stderr, "udunits(3): Error occurred at line %d\n", 
                       UtLineno);

    return status;
}


/*
 * Read and decode the entries in the units-file, adding them to the units-
 * table.
 *
 * This function returns:
 *      0               success
 *      UT_ENOFILE      no units-file
 *      UT_ESYNTAX      syntax error
 *      UT_EUNKNOWN     unknown specification
 *      UT_EALLOC       allocation failure
 *      UT_EIO          I/O error
 */
    static int
ReadUnits(path)
    char        *path;
{
    int         status;                         /* return status */

    if ((status = OpenUnits(path)) == 0) {
        for (;;) {
            int         HasPlural;
            char        name[512];
            utUnit      unit;

            if ((status = ScanUnit(name, sizeof(name), &unit, 
                                   &HasPlural)) == UT_EOF) {
                status  = 0;
                break;
            } else if (status == 0) {
                if ((status = utAdd(name, HasPlural, &unit)) != 0) {
                    (void) fprintf(stderr,
                           "udunits(3): Couldn't add \"%s\" to units-table\n",
                                   name);
                    break;
                }
            } else {
                (void) fprintf(stderr,
                               "udunits(3): Couldn't read units-file \"%s\"\n",
                               path);
                break;
            }
        }

        CloseUnits();
    }                                           /* units-file opened */

    return status;
}


/*
 *  Initialize the units(3) package.
 *
 *  This function returns:
 *      0               success
 *      UT_ENOFILE      no units-file
 *      UT_ESYNTAX      syntax error in units-file
 *      UT_EUNKNOWN     unknown specification in units-file
 *      UT_EIO          units-file I/O error
 *      UT_EALLOC       allocation failure
 */
    int
utInit(path)
    const char  *path;
{
    int         status;
    char        pathbuf[PATH_MAX+1];

    if (path == NULL || path[0] == 0) {
        path    = getenv("UDUNITS_PATH");
        if (path == NULL || path[0] == 0)
            path = strcpy(pathbuf, UT_DEFAULT_PATH);
    }

    if (initialized && strcmp(path, UnitsFilePath) == 0) {
        (void) fprintf(stderr, 
                "udunits(3): Already initialized from file \"%s\"\n",
                path);
        status  = 0;
    } else {
        utTerm();

        status  = ReadUnits(path);

        if (status == 0) {
            (void) strncpy(UnitsFilePath, path, sizeof(UnitsFilePath)-1);
            initialized = 1;
            if (utScan("second", &StdTimeUnit) == 0) {
                int     iquan;
                int     seen_one = 0;

                for (iquan = 0; iquan < UT_MAXNUM_BASE_QUANTITIES; ++iquan) {
                    if  (StdTimeUnit.power[iquan] != 0) {
                        if (seen_one)
                            break;
                        seen_one = 1;
                    }
                }
                HaveStdTimeUnit = seen_one && 
                                  iquan == UT_MAXNUM_BASE_QUANTITIES;
            }
        } else {
            utTerm();
        }
    }

    /* (void) fprintf(stderr, "utInit(): returning %d\n", status); */

    return status;
}


/*
 * FORTRAN interface to the above function.
 */
FCALLSCFUN1(INT,utInit,UTOPEN,utopen,
    STRING)


/*
 *  Decode a unit specification.
 *
 *  This function returns:
 *      0               success
 *      UT_ENOINIT      the package hasn't been initialized
 *      UT_EUNKNOWN     unknown specification
 *      UT_ESYNTAX      syntax error
 *      UT_EINVALID     NULL unit argument
 */
    int
utScan(spec, up)
    const char *spec;
    utUnit     *up;
{
    int         status;                    /* return status = success */

    if (spec == NULL) {
        status  = UT_EUNKNOWN;
    } else {
        if (up == NULL) {
            status      = UT_EINVALID;
        } else {
            if (!initialized) {
                (void) fprintf(stderr, 
                        "udunits(3): Package hasn't been initialized\n");
                status  = UT_ENOINIT;
            } else {
                utLexReset();
                status  = DecodeUnit(spec, up);
            }
        }
    }

    return status;
}


/*
 * FORTRAN interface to the above function.
 */
FCALLSCFUN2(INT,utScan,UTDEC,utdec,
    STRING,PPUNIT)


/*
 *  Encode a unit-structure into a formatted unit-secification.
 *
 *  This function returns:
 *      0               success
 *      UT_ENOINIT      the package hasn't been initialized
 *      UT_EINVALID     the unit-structure is invalid
 *
 *  On error, the string argument is set to the nil-pointer.
 */
    int
utPrint(unit, s)
    register const utUnit       *unit;
    char                        **s;
{
    int                         status;

    if (!initialized) {
        (void) fprintf(stderr,
                       "udunits(3): Package hasn't been initialized\n");
        *s      = NULL;
        status  = UT_ENOINIT;

    } else {
        if (unit->factor == 0.0) {
            *s          = NULL;
            status      = UT_EINVALID;

        } else {
            register int         iquan;
            register char       *buf    = linebuf;

            *buf        = 0;

            /*  Print the scale factor if it's non-unity. */
            if (unit->factor != 1.0) {
                (void)sprintf(buf, "%.*g ", DBL_DIG, unit->factor);
                buf     += strlen(buf);
            }

            /*  Append the dimensions in terms of base quanitities. */
            for (iquan = 0; iquan < UT_MAXNUM_BASE_QUANTITIES; ++iquan) {
                if (unit->power[iquan] != 0) {
                    if (unit->power[iquan] == 1) {
                        (void)sprintf(buf, "%s ", BaseName[iquan]);
                    } else {
                        (void)sprintf(buf, "%s%d ", BaseName[iquan],
                                      unit->power[iquan]);
                    }
                    buf += strlen(buf);
                }
            }

            /*  Append the origin-shift if it exists. */
            if (unit->hasorigin) {
                if (utIsTime(unit)) {
                    int         year, month, day;
                    int         hours;
                    int         minutes;
                    int         ndigsec;        /* precision of seconds in
                                                 * number of digits */
                    float       seconds;

                    dectime(unit->origin, &year, &month, &day, &hours,
                            &minutes, &seconds);
                    (void)sprintf(buf-1, "s since %d-%02d-%02d %02d:%02d ",
                                  year, month, day,
                                  hours, minutes);
                    buf += strlen(buf);
                    ndigsec     = DBL_DIG -
                        (int) ceil(log10(ABS(unit->origin /
                                                 utencclock(0, 0, 1.0))));
                    ndigsec     = MIN(ndigsec, DBL_DIG);
                    if (ndigsec > 0) {
                        int     precision = MAX(0, ndigsec-2);

#if 0
                        double  integral_secs;
                        double  fractional_secs = modf(seconds, &integral_secs);

                        if (precision <= 0)
                            (void) sprintf(buf-1, ":%02d", (int)integral_secs);
                        else
                            (void) sprintf(buf-1, ":%02d.%.*f", 
                                           (int)integral_secs, precision, 
                                           fractional_secs);
#endif

                        (void)sprintf(buf-1, ":%0*.*f ", precision+3,
                                      precision, seconds);
                    }
                    (void) strcat(buf, "UTC ");
                } else {
                    (void)sprintf(buf, "@ %.*g ", DBL_DIG, unit->origin);
                }
                buf     += strlen(buf);
            }

            /*  Remove trailing space. */
            if (buf > linebuf)
                buf[-1] = 0;

            *s          = linebuf;
            status      = 0;
        }                                       /* unit-structure is valid */
    }                                           /* package is initialized */

    return status;
}


/*
 * FORTRAN helper function for the above.
 */
static int
utPrint_help(unit, buf, size)
    utUnit      *unit;
    char        *buf;
    size_t      size;
{
    char        *s;
    int         status  = utPrint(unit, &s);

    if (status == 0)
    {
        size_t  len = strlen(s);

        if (len <= size)
        {
            (void) memcpy(buf, s, len);
            (void) memset(buf+len, ' ', size-len);
        }
        else
        {
            (void) memcpy(buf, s, size);
            status      = UT_ENOROOM;
        }
    }

    return status;
}


/*
 * FORTRAN interface to the above functionality.
 */
FCALLSCFUN2(INT,utPrint_help,UTENC,utenc,
    PPUNIT,CBUF)


/*
 *  Compare two strings.
 *
 *  This routine is used instead of strncmp(3) in order to obtain greater
 *  control over the returned value.
 *
 *  This function returns the index of the first, non-matching character or
 *  the length of the strings if all characters match.
 */
    static ptrdiff_t
mystrcmp(s1, s2)
    register char       *s1, *s2;
{
    register char       *start  = s1;

    for (; *s1 != 0 && *s2 != 0 && *s1 == *s2; ++s1, ++s2)
        continue;

    return s1 - start;
}


/*
 *  Compare two nodes in the units-table by comparing their names.
 *
 *  This function returns:
 *      <0      first argument less than second
 *      =0      first and second arguments are equal
 *      >0      first argument greater than second
 */
    static int
CompareNodes(node1, node2)
    const void  *node1;
    const void  *node2;
{
    /*
     * The following 2 lines might cause lint(1) to emit warnings about
     * "possible pointer alignment".  Thse may safely be ignored.
     */
    register char       *name1  = ((UnitEntry*)node1)->name;
    register char       *name2  = ((UnitEntry*)node2)->name;
    register int        status  = name1[0] - name2[0];  /* quick comparison */

    if (status == 0) {
        /*
         * Quick comparison failed.  Perform a long comparison.
         */
        status  = mystrcmp(name1, name2);
        status  = name1[status] - name2[status];
    }

    return status;
}


/*
 *  Compare a target and a node in the units-table by comparing their names.
 *  Allow the target name to be the plural of the node name if appropriate.
 *
 *  This function returns:
 *      <0      first argument less than second
 *      =0      first and second arguments are equal
 *      >0      first argument greater than second
 */
    static int
FindNodes(node1, node2)
    const void  *node1;
    const void  *node2;
{
    /*
     * The following 2 lines might cause lint(1) to emit warnings about
     * "possible pointer alignment".  Thse may safely be ignored.
     */
    register char               *name1  = ((UnitEntry*)node1)->name;
    register char               *name2  = ((UnitEntry*)node2)->name;
    register int                status  = name1[0] - name2[0];
                                                /* quick comparison */

    if (status == 0) {
        /*
         * Quick comparison failed.  Perform a long comparison.
         */
        int     i       = mystrcmp(name1, name2);

        status  = name1[i] - name2[i];

        if (status == 's') {
            /*
             * The following 2 lines might cause lint(1) to emit warnings about
             * "possible pointer alignment".  Thse may safely be ignored.
             */
            UnitEntry   *n1     = (UnitEntry*)node1;
            UnitEntry   *n2     = (UnitEntry*)node2;

            if (n2->HasPlural && i == n2->nchr && n2->nchr+1 == n1->nchr)
                status  = 0;
        }
    }

    return status;
}


/*
 *  Create a new node.
 *
 *  This function returns:
 *      !=NULL  success
 *      ==NULL  failure
 */
    static UnitEntry*
CreateNode(name, HasPlural, unit)
    char        *name;
    int         HasPlural;
    utUnit      *unit;
{
    int         nchr    = strlen(name);
    UnitEntry   *node   = NULL;

    if (nchr+1 > UT_NAMELEN) {
        (void) fprintf(stderr,
                       "udunits(3): The name \"%s\" is too long\n", name);
    } else {
        /*
         * The following line might cause lint(1) to emit a warning about
         * "possible pointer alignment".  This may safely be ignored.
         */
        node    = (UnitEntry*)malloc(sizeof(UnitEntry));

        if (node == NULL) {
            (void) fprintf(stderr, 
                "udunits(3): Couldn't allocate new entry\n");
        } else {
            if ((node->name = DUPSTR(name)) == NULL) {
                (void) fprintf(stderr, 
                        "udunits(3): Couldn't duplicate name\n");
                (void) free((char*)node);
            } else {
                node->nchr      = strlen(node->name);
                node->HasPlural = HasPlural;
                (void) utCopy(unit, &node->unit);
            }
        }
    }

    return node;
}


/*
 * Copy construct a node (C++ terminology).
 */
    static UnitEntry*
CopyCtorNode(node)
    const UnitEntry     *node;
{
    return CreateNode(node->name, node->HasPlural, &node->unit);
}


/*
 *  Destroy a node.
 *
 *  This function returns void.
 */
    static void
DestroyNode(node)
    UnitEntry   *node;
{
    if (node != NULL) {
        if (node->name != NULL)
            /*
             * The following line might cause lint(1) to emit a warning about
             * "possible pointer alignment".  This may safely be ignored.
             */
            (void) free((char*)node->name);
        (void) free((char*)node);
    }
}


/*
 * Assign one node to another.
 */
    static void
AssignNode(to, from)
    UnitEntry           *to;
    const UnitEntry     *from;
{
    DestroyNode(to);
    to->name = DUPSTR(from->name);
    to->nchr = from->nchr;
    to->HasPlural = from->HasPlural;
    to->unit = from->unit;
}
    

/*
 *  Add a unit-structure to the units-table.
 *
 *  This function returns:
 *      0               success
 *      UT_EALLOC       memory allocation failure
 */
    int
utAdd(name,  HasPlural, unit)
    char        *name;
    int         HasPlural;
    utUnit      *unit;
{
    int         status  = 0;                    /* return status = success */
    UnitEntry   *nodep  = CreateNode(name, HasPlural, unit);

    if (nodep == NULL)
        status = UT_EALLOC;
    else
    {
        UnitEntry       **found = (UnitEntry**)tsearch((void*)nodep, &root, 
                                                       CompareNodes);

        if (found == NULL)
        {
            (void) fprintf(stderr, 
                "udunits(3): Couldn't expand units-table for unit \"%s\"\n",
                name);
            status      = UT_EALLOC;
            DestroyNode(nodep);
        }
        else
        if (*found != nodep)
        {
            (void) fprintf(stderr, "udunits(3): Replacing unit \"%s\"\n", name);
            DestroyNode(*found);
            *found = nodep;
        }
    }

    return status;
}


/*
 *  Find the entry in the units-table corresponding to a given name.  
 *  If an entry isn't found, try again using the singular form, if
 *  appropriate.
 *
 *  This function returns:
 *       NULL   entry not found;
 *      !NULL   entry found.
 */
    static UnitEntry*
FindUnit(name)
    char        *name;
{
    UnitEntry   node;
    UnitEntry   **found;

    node.name   = (char*)name;
    node.nchr   = strlen(name);

    /*
     * The following line might cause lint(1) to emit a warning about
     * "possible pointer alignment".  This may safely be ignored.
     */
    found       = (UnitEntry**)tfind((void*)&node, &root, FindNodes);

    if (found == NULL) {
        /*
         * Not found.  If appropriate, try again with singular form.
         */
        if (node.nchr > 1 && node.name[node.nchr-1] == 's') {
            char        buf[UT_NAMELEN];

            assert(sizeof(buf) > node.nchr-1);

            node.name   = strncpy(buf, name, --node.nchr);
            node.name[node.nchr]        = 0;

            found       = (UnitEntry**)tfind((void*)&node, &root, 
            /*
             * The following line might cause lint(1) to emit a warning about
             * "possible pointer alignment".  This may safely be ignored.
             */
                                             FindNodes);

            /*
             * Ensure that a plural form is allowed.
             */
            if (found != NULL && !(*found)->HasPlural)
                found   = NULL;
        }
    }

    return found == NULL ? NULL : *found;
}


/*
 *  Find the entry in a prefix-table corresponding to a possible
 *  prefix.  A linear-search of the prefix-table is performed.
 *  An attempt is made to insure that the longest, possible, matching
 *  entry is returned.
 *
 *  NB: A binary-search of the table is not possible because, for example,
 *  the prefix-entry for the input specification "mzmeters" (where "mz" is 
 *  a made-up prefix) would be after the entry "micro", but the prefix-entry 
 *  corresponding to the specification "mm" (i.e. "m") lies before "micro".
 *  Thus, the binary-search comparison-function can't indicate which direction
 *  to go.
 *
 *  This function returns:
 *       NULL   not found
 *      !NULL   found
 */
    static PrefixEntry*
FindPrefix(spec)
    char        *spec;
{
    PrefixEntry                 *found  = NULL;
    register PrefixEntry        *entry;

    for (entry = PrefixTable; entry->name != NULL; ++entry) {
        register int    status;

        if (entry->name[0] - spec[0] < 0 ||
                (status = strncmp(entry->name, spec, entry->nchar)) < 0)
            continue;

        if (status > 0)
            break;

        if (found == NULL || found->nchar < entry->nchar)
            found       = entry;
    }

    return found;
}


/*
 *  Return the unit-structure corresponding to a unit-specification.
 *
 *  NB:
 *      It is permissible for the specification to consist solely
 *      of a prefix (e.g "milli").
 *
 *      An empty specification returns a dimensionless, unity unit-structure.
 *
 *      On failure, the output unit-structure is unmodified.
 *
 *  This function returns:
 *      0               found (the output unit-structure is set).
 *      UT_ENOINIT      the units-table hasn't been initialized
 *      UT_EUNKNOWN     not found
 */
    int
utFind(spec, up)
    char        *spec;
    utUnit      *up;
{
    int         status  = 0;            /* return status = found */
    UnitEntry   *entry  = NULL;
    double      factor  = 1;

    if (root == NULL) {
        (void) fprintf(stderr, "udunits(3): Units-table is empty\n");
        status  = UT_ENOINIT;
    } else {
        while (*spec != 0) {
            PrefixEntry *PrefixEnt;

            /*
             *  See if the specification is an isolated unit (e.g. "meter").
             *  We're done if it is.
             */
            if ((entry = FindUnit(spec)) != NULL)
                break;

            /*
             *  See if the specification has a multiplying prefix.  If 
             *  so, then use the prefix's dimensionless value, skip 
             *  over the prefix characters, and rescan.
             */

            if ((PrefixEnt = FindPrefix(spec)) != NULL) {
                factor  *= PrefixEnt->factor;
                spec    += strlen(PrefixEnt->name);
                continue;
            }
            
            status      = UT_EUNKNOWN;
            break;
        }                                       /* while something to decode */
    }                                           /* units-table is initialized */

    if (status == 0)
        (void)utScale(entry == NULL ? utClear(up) : &entry->unit,
                      factor, up);

    return status;
}


/*
 *  Compute the conversion factor between two unit-structures.
 *
 *  This function returns:
 *      0               success.
 *      UT_ENOINIT      the units-table hasn't been initialized
 *      UT_EINVALID     a structure is invalid
 *      UT_ECONVERT     the structures are not convertable
 */
    int
utConvert(from, to, slope, intercept)
    const utUnit        *from;
    const utUnit        *to;
    double              *slope, *intercept;
{
    int         status  = 0;

    if (!initialized) {
        (void) fprintf(stderr, 
                       "udunits(3): Package hasn't been initialized\n");
        status  = UT_ENOINIT;
    } else {
        if (from->factor == 0.0 || to->factor == 0.0) {
            status      = UT_EINVALID;
        } else {
            register int        iquan;

            for (iquan = 0; iquan < UT_MAXNUM_BASE_QUANTITIES; ++iquan)
                if (from->power[iquan] != to->power[iquan]) {
                    status      = UT_ECONVERT;
                    break;
                }

            if (status == 0) {
                /*
                 * Allow convertions between units with origins (e.g.
                 * Celsius) and those without (e.g. Kelvin) by using
                 * the fact that those without origins have a zero
                 * origin value.
                 */
                *slope  = from->factor / to->factor;
                *intercept      = (from->origin - to->origin) / to->factor;
            }
        }
    }                                           /* package is initialized */

    return status;
}


    static int
f_utConvert(from, to, slope, intercept)
    const utUnit        *from;
    const utUnit        *to;
    DOUBLE_PRECISION    *slope;
    DOUBLE_PRECISION    *intercept;
{
    double      tmpSlope;
    double      tmpIntercept;
    int         status;
    status = utConvert(from, to, &tmpSlope, &tmpIntercept);
    *slope = tmpSlope;
    *intercept = tmpIntercept;
    return status;
}


/*
 * FORTRAN interface to the above functionality.
 */
FCALLSCFUN4(INT,f_utConvert,UTCVT,utcvt,
    PPUNIT,PPUNIT,PDOUBLE,PDOUBLE)


/*
 * Convert a Gregorian/Julian date and time into a temporal value.
 *
 * Returns:
 *      0               success
 *      UT_EINVALID     not a unit of time
 *      UT_ENOINIT      the units-table hasn't been initialized
 */
    int
utInvCalendar(year, month, day, hour, minute, second, unit, value)
    int         year;
    int         month;
    int         day;
    int         hour;
    int         minute;
    double      second;
    utUnit      *unit;
    double      *value;
{
    int         status;

    if (!utIsTime(unit) || !unit->hasorigin) {
        status  = UT_EINVALID;
    } else {
        *value  = (utencdate(year, month, day) + 
                   utencclock(hour, minute, second) - unit->origin) /
                  unit->factor;
        status  = 0;
    }

    return status;
}


    static int
f_utInvCalendar(year, month, day, hour, minute, second, unit, value)
    int                 year;
    int                 month;
    int                 day;
    int                 hour;
    int                 minute;
    double              second;
    utUnit              *unit;
    DOUBLE_PRECISION    *value;
{
    double      tmpValue;
    int         status =
        utInvCalendar(year, month, day, hour, minute, second, unit, &tmpValue);
    *value = tmpValue;
    return status;
}


/*
 * FORTRAN interface to the above function.
 */
FCALLSCFUN8(INT,f_utInvCalendar,UTICALTIME,uticaltime,
    INT,INT,INT,INT,
            INT,FLOAT,PPUNIT,PDOUBLE)


/*
 * Convert a temporal value into UTC Gregorian/Julian date and time.
 *
 * Returns:
 *      0:              success
 *      UT_EINVALID:    not a unit of time
 */
    int
utCalendar(value, unit, year, month, day, hour, minute, second)
    double      value;
    utUnit      *unit;
    int         *year;
    int         *month;
    int         *day;
    int         *hour;
    int         *minute;
    float       *second;
{
    int         status;
    float       sec;

    if (!utIsTime(unit) || !unit->hasorigin) {
        status  = UT_EINVALID;
    } else {
        dectime(unit->origin + value*unit->factor, year, month, day, hour,
                minute, &sec);
        *second = sec;
        status  = 0;
    }

    return status;
}


/*
 * FORTRAN interface to the above function.
 */
FCALLSCFUN8(INT,utCalendar,UTCALTIME,utcaltime,
    DOUBLE,PPUNIT,PINT,PINT,PINT,PINT,PINT,PFLOAT)


/*
 * Create a unit structure.
 */
    utUnit*
utNew()
{
    return utClear((utUnit*)malloc(sizeof(utUnit)));
}


/*
 * FORTRAN interface to the above functionality.
 */
FCALLSCFUN0(PUNIT,utNew,UTMAKE,utmake)


/*
 * Destroy a unit structure that was created by utNew().
 */
    void
utDestroy(unit)
    utUnit      *unit;
{
    if (unit != NULL)
        free((voidp)unit);
}


/*
 * FORTRAN interface to the above functionality.
 */
FCALLSCSUB1(utDestroy,UTFREE,utfree,
    PPUNIT)


/*
 *  Free allocated nodes.
 */
    static void
NodeDeleter(node, order, level)
    const void  *node;
    VISIT       order;
    /*ARGSUSED*/
    int         level;
{
    if (order == leaf || order == endorder)
    {
        UnitEntry       *entry = *(UnitEntry**)node;

        (void)tdelete(entry, &root, CompareNodes);
        DestroyNode(entry);
    }
}


/*
 *  Terminate use of this package.
 *
 *  This function returns void.
 */
    void
utTerm()
{
    if (root != NULL) {
        twalk(root, NodeDeleter);
        root            = NULL;
    }
    initialized = 0;
    NumberBaseUnits     = 0;
    UnitsFilePath[0]    = 0;
    HaveStdTimeUnit     = 0;
}


/*
 * FORTRAN interface to the above functionality.
 */
FCALLSCSUB0(utTerm,UTCLS,utcls)


/*
 * Return the next character to the scanner.
 */
    int
utinput()
{
    return unput_buf < unput_ptr
                ? *--unput_ptr
                : *input_ptr == 0
                    ? EOF
                    : *input_ptr++;
}


/*
 * Save the given character to be returned by the next call to utinput().
 */
    void
utunput(c)
    int         c;
{
    *unput_ptr++        = c;
}


/*
 * LEX "wrap-up" routine.  Indicate no more input.
 */
    int
utwrap()
{
    return 1;
}


/*
 *  YACC error routine:
 */
    void
uterror(s)
    char                *s;
{
    register int        i;

    (void) fprintf(stderr, "udunits(3): %s:\n", s);
    (void) fputs(input_buf, stderr);
    (void) putc('\n', stderr);
    for (i = 0; i < input_ptr - input_buf; ++i)
        (void) putc(' ', stderr);
    (void) fputs("^\n", stderr);
}