mirror of
https://github.com/Unidata/netcdf-c.git
synced 2024-12-27 08:49:16 +08:00
c37cc13dca
Enables ncdump -t (-i) to recognize a wider variety of time related units and calendar names. This brings ncdump closer to what it advertises in its man page regarding its understanding of udunits compliant time units.
1189 lines
30 KiB
C
1189 lines
30 KiB
C
/*********************************************************************
|
|
* Copyright 2018, University Corporation for Atmospheric Research
|
|
* See netcdf/COPYRIGHT file for copying and redistribution conditions.
|
|
* $Id: nctime.c,v 1.9 2010/05/05 22:15:39 dmh Exp $
|
|
*********************************************************************/
|
|
|
|
/*
|
|
* This code was extracted with permission from the CDMS time
|
|
* conversion and arithmetic routines developed by Bob Drach, Lawrence
|
|
* Livermore National Laboratory as part of the cdtime library. Russ
|
|
* Rew of the UCAR Unidata Program made changes and additions to
|
|
* support the "-t" option of the netCDF ncdump utility, including a
|
|
* 366-day climate calendar.
|
|
*
|
|
* For the complete time conversion and climate calendar facilities of
|
|
* the CDMS library, get the original sources from LLNL.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <assert.h>
|
|
#include "nctime.h"
|
|
|
|
static const cdCompTime ZA = {1582, 10, 5, 0.0};
|
|
static const cdCompTime ZB = {1582, 10, 15, 0.0};
|
|
|
|
static int cuErrOpts; /* Error options */
|
|
static int cuErrorOccurred = 0; /* True iff cdError was called */
|
|
|
|
#define CD_DEFAULT_BASEYEAR "1979" /* Default base year for relative time (no 'since' clause) */
|
|
#define VALCMP(a,b) ((a)<(b)?-1:(b)<(a)?1:0)
|
|
|
|
/* forward declarations */
|
|
static void CdMonthDay(int *doy, CdTime *date);
|
|
static void CdDayOfYear(CdTime *date, int *doy);
|
|
static void cdComp2Rel(cdCalenType timetype, cdCompTime comptime, char* relunits, double* reltime);
|
|
static void cdRel2CompMixed(double reltime, cdUnitTime unit, cdCompTime basetime, cdCompTime *comptime);
|
|
static void cdRel2Comp(cdCalenType timetype, char* relunits, double reltime, cdCompTime* comptime);
|
|
|
|
/* Trim trailing whitespace, up to n characters. */
|
|
/* If no whitespace up to the last character, set */
|
|
/* the last character to null, else set the first */
|
|
/* whitespace character to null. */
|
|
static void
|
|
cdTrim(char* s, int n)
|
|
{
|
|
char* c;
|
|
|
|
if(s==NULL)
|
|
return;
|
|
for(c=s; *c && c<s+n-1 && !isspace((int)*c); c++);
|
|
*c='\0';
|
|
return;
|
|
}
|
|
|
|
static void
|
|
cdError(char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
|
|
cuErrorOccurred = 1;
|
|
if(cuErrOpts & CU_VERBOSE){
|
|
va_start(args,fmt);
|
|
fprintf(stderr, "CDMS error: ");
|
|
vfprintf(stderr, fmt, args);
|
|
fprintf(stderr, "\n");
|
|
va_end(args);
|
|
}
|
|
if(cuErrOpts & CU_FATAL)
|
|
exit(1);
|
|
return;
|
|
}
|
|
|
|
#define ISLEAP(year,timeType) ((timeType & Cd366) || (((timeType) & CdHasLeap) && (!((year) % 4) && (((timeType) & CdJulianType) || (((year) % 100) || !((year) % 400))))))
|
|
|
|
static const int mon_day_cnt_normal[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
|
|
static const int mon_day_cnt_leap[12] = {31,29,31,30,31,30,31,31,30,31,30,31};
|
|
static const int days_sum[12] = {0,31,59,90,120,151,181,212,243,273,304,334};
|
|
|
|
static const int* mon_day_cnt;
|
|
|
|
/* Compute month and day from year and day-of-year.
|
|
*
|
|
* Input:
|
|
* doy (int) (day-of-year)
|
|
* date->year (long) (year since 0 BC)
|
|
* date->timeType (CdTimetype) (time type)
|
|
* date->baseYear base year for relative times
|
|
* Output:
|
|
* date->month (short) (month in year)
|
|
* date->day (short) (day in month)
|
|
*
|
|
*
|
|
* Derived from NRL NEONS V3.6.
|
|
*/
|
|
|
|
static void
|
|
CdMonthDay(int *doy, CdTime *date)
|
|
{
|
|
int i; /* month counter */
|
|
int idoy; /* day of year counter */
|
|
long year;
|
|
|
|
if ((idoy = *doy) < 1) {
|
|
date->month = 0;
|
|
date->day = 0;
|
|
return;
|
|
}
|
|
|
|
if(!(date->timeType & CdChronCal)) /* Ignore year for Clim calendar */
|
|
year = 0;
|
|
else if(!(date->timeType & CdBase1970)) /* year is offset from base for relative time */
|
|
year = date->baseYear + date->year;
|
|
else
|
|
year = date->year;
|
|
|
|
if (ISLEAP(year,date->timeType)) {
|
|
mon_day_cnt = mon_day_cnt_leap;
|
|
} else {
|
|
mon_day_cnt = mon_day_cnt_normal;
|
|
}
|
|
date->month = 0;
|
|
for (i = 0; i < 12; i++) {
|
|
int delta;
|
|
(date->month)++;
|
|
date->day = (short)idoy;
|
|
delta = ((date->timeType & Cd365) || (date->timeType & Cd366) ? (mon_day_cnt[date->month-1]) : 30);
|
|
idoy -= delta;
|
|
if(idoy <= 0)
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Compute day-of-year from year, month and day
|
|
*
|
|
* Input:
|
|
* date->year (long) (year since 0 BC)
|
|
* date->month (short) (month in year)
|
|
* date->day (short) (day in month)
|
|
* date->baseYear base year for relative times
|
|
* Output: doy (int) (day-of-year)
|
|
*
|
|
* Derived from NRL NEONS V3.6
|
|
*/
|
|
|
|
static void
|
|
CdDayOfYear(CdTime *date, int *doy)
|
|
{
|
|
int leap_add = 0; /* add 1 day if leap year */
|
|
int month; /* month */
|
|
long year;
|
|
|
|
month = date->month;
|
|
if (month < 1 || month > 12) {
|
|
cdError( "Day-of-year error; month: %d\n", month);
|
|
month = 1;
|
|
}
|
|
|
|
if(!(date->timeType & CdChronCal)) /* Ignore year for Clim calendar */
|
|
year = 0;
|
|
else if(!(date->timeType & CdBase1970)) /* year is offset from base for relative time */
|
|
year = date->baseYear + date->year;
|
|
else
|
|
year = date->year;
|
|
|
|
if (ISLEAP(year,date->timeType) && month > 2) leap_add = 1;
|
|
if( ((date->timeType) & Cd365) || ((date->timeType) & Cd366) ) {
|
|
*doy = days_sum[month-1] + date->day + leap_add ;
|
|
} else { /* date->timeType & Cd360 */
|
|
*doy = 30*(month-1) + date->day + leap_add ;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Convert epochal time (hours since 00 jan 1, 1970)
|
|
* to human time (structured)
|
|
*
|
|
* Input:
|
|
* etime = epochal time representation
|
|
* timeType = time type (e.g., CdChron, CdClim, etc.) as defined in cdms.h
|
|
* baseYear = base real, used for relative time types only
|
|
*
|
|
* Output: htime = human (structured) time representation
|
|
*
|
|
* Derived from NRL Neons V3.6
|
|
*/
|
|
void
|
|
Cde2h(double etime, CdTimeType timeType, long baseYear, CdTime *htime)
|
|
{
|
|
long ytemp; /* temporary year holder */
|
|
int yr_day_cnt; /* count of days in year */
|
|
int doy; /* day of year */
|
|
int daysInLeapYear; /* number of days in a leap year */
|
|
int daysInYear; /* days in non-leap year */
|
|
|
|
doy = (int) floor(etime / 24.) + 1;
|
|
htime->hour = etime - (double) (doy - 1) * 24.;
|
|
|
|
/* Correct for goofy floor func on J90 */
|
|
if(htime->hour >= 24.){
|
|
doy += 1;
|
|
htime->hour -= 24.;
|
|
}
|
|
|
|
htime->baseYear = (timeType & CdBase1970) ? 1970 : baseYear;
|
|
if(!(timeType & CdChronCal)) htime->baseYear = 0; /* Set base year to 0 for Clim */
|
|
if(timeType & Cd366) {
|
|
daysInLeapYear = 366;
|
|
daysInYear = 366;
|
|
} else {
|
|
daysInLeapYear = (timeType & Cd365) ? 366 : 360;
|
|
daysInYear = (timeType & Cd365) ? 365 : 360;
|
|
}
|
|
|
|
if (doy > 0) {
|
|
for (ytemp = htime->baseYear; ; ytemp++) {
|
|
yr_day_cnt = ISLEAP(ytemp,timeType) ? daysInLeapYear : daysInYear;
|
|
if (doy <= yr_day_cnt) break;
|
|
doy -= yr_day_cnt;
|
|
}
|
|
} else {
|
|
for (ytemp = htime->baseYear-1; ; ytemp--) {
|
|
yr_day_cnt = ISLEAP(ytemp,timeType) ? daysInLeapYear : daysInYear;
|
|
doy += yr_day_cnt;
|
|
if (doy > 0) break;
|
|
}
|
|
}
|
|
htime->year = (timeType & CdBase1970) ? ytemp : (ytemp - htime->baseYear);
|
|
if(!(timeType & CdChronCal)) htime->year = 0; /* Set year to 0 for Clim */
|
|
htime->timeType = timeType;
|
|
CdMonthDay(&doy,htime);
|
|
|
|
return;
|
|
}
|
|
|
|
/* Add 'nDel' times 'delTime' to epochal time 'begEtm',
|
|
* return the result in epochal time 'endEtm'.
|
|
*/
|
|
static void
|
|
CdAddDelTime(double begEtm, long nDel, CdDeltaTime delTime, CdTimeType timeType,
|
|
long baseYear, double *endEtm)
|
|
{
|
|
double delHours;
|
|
long delMonths, delYears;
|
|
CdTime bhtime, ehtime;
|
|
|
|
extern void Cde2h(double etime, CdTimeType timeType, long baseYear, CdTime *htime);
|
|
extern void Cdh2e(CdTime *htime, double *etime);
|
|
|
|
switch(delTime.units){
|
|
case CdYear:
|
|
delMonths = 12;
|
|
break;
|
|
case CdSeason:
|
|
delMonths = 3;
|
|
break;
|
|
case CdMonth:
|
|
delMonths = 1;
|
|
break;
|
|
case CdWeek:
|
|
delHours = 168.0;
|
|
break;
|
|
case CdDay:
|
|
delHours = 24.0;
|
|
break;
|
|
case CdHour:
|
|
delHours = 1.0;
|
|
break;
|
|
case CdMinute:
|
|
delHours = 1./60.;
|
|
break;
|
|
case CdSecond:
|
|
delHours = 1./3600.;
|
|
break;
|
|
default:
|
|
cdError("Invalid delta time units: %d\n",delTime.units);
|
|
return;
|
|
}
|
|
|
|
switch(delTime.units){
|
|
case CdYear: case CdSeason: case CdMonth:
|
|
Cde2h(begEtm,timeType,baseYear,&bhtime);
|
|
delMonths = delMonths * nDel * delTime.count + bhtime.month - 1;
|
|
delYears = (delMonths >= 0 ? (delMonths/12) : (delMonths+1)/12 - 1);
|
|
ehtime.year = bhtime.year + delYears;
|
|
ehtime.month = (short)(delMonths - (12 * delYears) + 1);
|
|
ehtime.day = 1;
|
|
ehtime.hour = 0.0;
|
|
ehtime.timeType = timeType;
|
|
ehtime.baseYear = !(timeType & CdChronCal) ? 0 :
|
|
(timeType & CdBase1970) ? 1970 : baseYear; /* base year is 0 for Clim, */
|
|
/* 1970 for Chron, */
|
|
/* or input base year for Rel */
|
|
Cdh2e(&ehtime,endEtm);
|
|
break;
|
|
case CdWeek: case CdDay: case CdHour: case CdMinute: case CdSecond:
|
|
delHours = delHours * (double)(nDel * delTime.count);
|
|
*endEtm = begEtm + delHours;
|
|
break;
|
|
default: break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Parse relative units, returning the unit and base component time. */
|
|
/* Function returns 1 if error, 0 on success */
|
|
int
|
|
cdParseRelunits(cdCalenType timetype, char* relunits, cdUnitTime* unit, cdCompTime* base_comptime)
|
|
{
|
|
char charunits[CD_MAX_RELUNITS];
|
|
char basetime_1[CD_MAX_CHARTIME];
|
|
char basetime_2[CD_MAX_CHARTIME];
|
|
char basetime[2 * CD_MAX_CHARTIME + 1];
|
|
int nconv1, nconv2, nconv;
|
|
|
|
/* Parse the relunits */
|
|
/* Allow ISO-8601 "T" date-time separator as well as blank separator */
|
|
nconv1 = sscanf(relunits,"%s since %[^T]T%s",charunits,basetime_1,basetime_2);
|
|
if(nconv1==EOF || nconv1==0){
|
|
cdError("Error on relative units conversion, string = %s\n",relunits);
|
|
return 1;
|
|
}
|
|
nconv2 = sscanf(relunits,"%s since %s %s",charunits,basetime_1,basetime_2);
|
|
if(nconv2==EOF || nconv2==0){
|
|
cdError("Error on relative units conversion, string = %s\n",relunits);
|
|
return 1;
|
|
}
|
|
if(nconv1 < nconv2) {
|
|
nconv = nconv2;
|
|
} else {
|
|
nconv = sscanf(relunits,"%s since %[^T]T%s",charunits,basetime_1,basetime_2);
|
|
}
|
|
/* Get the units */
|
|
cdTrim(charunits,CD_MAX_RELUNITS);
|
|
if(!strncasecmp(charunits,"sec",3) || !strcasecmp(charunits,"s")){
|
|
*unit = cdSecond;
|
|
}
|
|
else if(!strncasecmp(charunits,"min",3) || !strcasecmp(charunits,"mn")){
|
|
*unit = cdMinute;
|
|
}
|
|
else if(!strncasecmp(charunits,"hour",4) || !strcasecmp(charunits,"hr")){
|
|
*unit = cdHour;
|
|
}
|
|
else if(!strncasecmp(charunits,"day",3) || !strcasecmp(charunits,"dy")){
|
|
*unit = cdDay;
|
|
}
|
|
else if(!strncasecmp(charunits,"week",4) || !strcasecmp(charunits,"wk")){
|
|
*unit = cdWeek;
|
|
}
|
|
else if(!strncasecmp(charunits,"month",5) || !strcasecmp(charunits,"mo")){
|
|
*unit = cdMonth;
|
|
}
|
|
else if(!strncasecmp(charunits,"season",6)){
|
|
*unit = cdSeason;
|
|
}
|
|
else if(!strncasecmp(charunits,"year",4) || !strcasecmp(charunits,"yr")){
|
|
if(!(timetype & cdStandardCal)){
|
|
cdError("Error on relative units conversion: climatological units cannot be 'years'.\n");
|
|
return 1;
|
|
}
|
|
*unit = cdYear;
|
|
}
|
|
else {
|
|
cdError("Error on relative units conversion: invalid units = %s\n",charunits);
|
|
return 1;
|
|
}
|
|
|
|
/* Build the basetime, if any (default is 1979), */
|
|
/* or month 1 for climatological time. */
|
|
if(nconv == 1){
|
|
if(timetype & cdStandardCal)
|
|
strcpy(basetime,CD_DEFAULT_BASEYEAR);
|
|
else
|
|
strcpy(basetime,"1");
|
|
}
|
|
/* Convert the basetime to component, then epochal (hours since 1970) */
|
|
else{
|
|
if(nconv == 2){
|
|
cdTrim(basetime_1,CD_MAX_CHARTIME);
|
|
strcpy(basetime,basetime_1);
|
|
}
|
|
else{
|
|
cdTrim(basetime_1,CD_MAX_CHARTIME);
|
|
cdTrim(basetime_2,CD_MAX_CHARTIME);
|
|
sprintf(basetime,"%s %s",basetime_1,basetime_2);
|
|
}
|
|
}
|
|
|
|
cdChar2Comp(timetype, basetime, base_comptime);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ca - cb in Gregorian calendar */
|
|
/* Result is in hours. */
|
|
static double
|
|
cdDiffGregorian(cdCompTime ca, cdCompTime cb){
|
|
|
|
double rela, relb;
|
|
|
|
cdComp2Rel(cdStandard, ca, "hours", &rela);
|
|
cdComp2Rel(cdStandard, cb, "hours", &relb);
|
|
return (rela - relb);
|
|
}
|
|
|
|
/* Return -1, 0, 1 as ca is less than, equal to, */
|
|
/* or greater than cb, respectively. */
|
|
static int
|
|
cdCompCompare(cdCompTime ca, cdCompTime cb){
|
|
|
|
int test;
|
|
|
|
if ((test = VALCMP(ca.year, cb.year)))
|
|
return test;
|
|
else if ((test = VALCMP(ca.month, cb.month)))
|
|
return test;
|
|
else if ((test = VALCMP(ca.day, cb.day)))
|
|
return test;
|
|
else
|
|
return (VALCMP(ca.hour, cb.hour));
|
|
}
|
|
|
|
/* ca - cb in Julian calendar. Result is in hours. */
|
|
static double
|
|
cdDiffJulian(cdCompTime ca, cdCompTime cb){
|
|
|
|
double rela, relb;
|
|
|
|
cdComp2Rel(cdJulian, ca, "hours", &rela);
|
|
cdComp2Rel(cdJulian, cb, "hours", &relb);
|
|
return (rela - relb);
|
|
}
|
|
|
|
/* ca - cb in mixed Julian/Gregorian calendar. */
|
|
/* Result is in hours. */
|
|
static double
|
|
cdDiffMixed(cdCompTime ca, cdCompTime cb)
|
|
{
|
|
double result;
|
|
|
|
if (cdCompCompare(cb, ZB) == -1){
|
|
if (cdCompCompare(ca, ZB) == -1) {
|
|
result = cdDiffJulian(ca, cb);
|
|
}
|
|
else {
|
|
result = cdDiffGregorian(ca, ZB) + cdDiffJulian(ZA, cb);
|
|
}
|
|
}
|
|
else {
|
|
if (cdCompCompare(ca, ZB) == -1){
|
|
result = cdDiffJulian(ca, ZA) + cdDiffGregorian(ZB, cb);
|
|
}
|
|
else {
|
|
result = cdDiffGregorian(ca, cb);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* Divide ('endEtm' - 'begEtm') by 'delTime',
|
|
* return the integer portion of the result in 'nDel'.
|
|
*/
|
|
static void
|
|
CdDivDelTime(double begEtm, double endEtm, CdDeltaTime delTime, CdTimeType timeType,
|
|
long baseYear, long *nDel)
|
|
{
|
|
double delHours, frange;
|
|
long delMonths, range;
|
|
CdTime bhtime, ehtime;
|
|
int hoursInYear;
|
|
|
|
extern void Cde2h(double etime, CdTimeType timeType, long baseYear, CdTime *htime);
|
|
|
|
switch(delTime.units){
|
|
case CdYear:
|
|
delMonths = 12;
|
|
break;
|
|
case CdSeason:
|
|
delMonths = 3;
|
|
break;
|
|
case CdMonth:
|
|
delMonths = 1;
|
|
break;
|
|
case CdWeek:
|
|
delHours = 168.0;
|
|
break;
|
|
case CdDay:
|
|
delHours = 24.0;
|
|
break;
|
|
case CdHour:
|
|
delHours = 1.0;
|
|
break;
|
|
case CdMinute:
|
|
delHours = 1./60.;
|
|
break;
|
|
case CdSecond:
|
|
delHours = 1./3600.;
|
|
break;
|
|
default:
|
|
cdError("Invalid delta time units: %d\n",delTime.units);
|
|
return;
|
|
}
|
|
|
|
switch(delTime.units){
|
|
case CdYear: case CdSeason: case CdMonth:
|
|
delMonths *= delTime.count;
|
|
Cde2h(begEtm,timeType,baseYear,&bhtime);
|
|
Cde2h(endEtm,timeType,baseYear,&ehtime);
|
|
if(timeType & CdChronCal){ /* Chron and Rel time */
|
|
range = 12*(ehtime.year - bhtime.year)
|
|
+ (ehtime.month - bhtime.month);
|
|
}
|
|
else{ /* Clim time, ignore year */
|
|
range = (ehtime.month - bhtime.month);
|
|
if(range < 0) range += 12;
|
|
}
|
|
*nDel = abs((int)range)/delMonths;
|
|
break;
|
|
case CdWeek: case CdDay: case CdHour: case CdMinute: case CdSecond:
|
|
delHours *= (double)delTime.count;
|
|
if(timeType & CdChronCal){ /* Chron and Rel time */
|
|
frange = fabs(endEtm - begEtm);
|
|
}
|
|
else{ /* Clim time, ignore year, but */
|
|
/* wraparound relative to hours-in-year*/
|
|
frange = endEtm - begEtm;
|
|
if(timeType & Cd366) {
|
|
hoursInYear = 8784;
|
|
} else {
|
|
hoursInYear = (timeType & Cd365) ? 8760. : 8640.;
|
|
}
|
|
/* Normalize frange to interval [0,hoursInYear) */
|
|
if(frange < 0.0 || frange >= hoursInYear)
|
|
frange -= hoursInYear * floor(frange/hoursInYear);
|
|
}
|
|
*nDel = (long)((frange + 1.e-10*delHours)/delHours);
|
|
break;
|
|
default: break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Value is in hours. Translate to units. */
|
|
static double
|
|
cdFromHours(double value, cdUnitTime unit){
|
|
double result;
|
|
|
|
switch(unit){
|
|
case cdSecond:
|
|
result = value * 3600.0;
|
|
break;
|
|
case cdMinute:
|
|
result = value * 60.0;
|
|
break;
|
|
case cdHour:
|
|
result = value;
|
|
break;
|
|
case cdDay:
|
|
result = value/24.0;
|
|
break;
|
|
case cdWeek:
|
|
result = value/168.0;
|
|
break;
|
|
case cdMonth:
|
|
case cdSeason:
|
|
case cdYear:
|
|
case cdFraction:
|
|
default:
|
|
cdError("Error on conversion from hours to vague unit");
|
|
result = 0;
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
/* Map to old timetypes */
|
|
static int
|
|
cdToOldTimetype(cdCalenType newtype, CdTimeType* oldtype)
|
|
{
|
|
switch(newtype){
|
|
case cdStandard:
|
|
*oldtype = CdChron;
|
|
break;
|
|
case cdJulian:
|
|
*oldtype = CdJulianCal;
|
|
break;
|
|
case cdNoLeap:
|
|
*oldtype = CdChronNoLeap;
|
|
break;
|
|
case cd360:
|
|
*oldtype = CdChron360;
|
|
break;
|
|
case cd366:
|
|
*oldtype = CdChron366;
|
|
break;
|
|
case cdClim:
|
|
*oldtype = CdClim;
|
|
break;
|
|
case cdClimLeap:
|
|
*oldtype = CdClimLeap;
|
|
break;
|
|
case cdClim360:
|
|
*oldtype = CdClim360;
|
|
break;
|
|
default:
|
|
cdError("Error on relative units conversion, invalid timetype = %d",newtype);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Convert human time to epochal time (hours since 00 jan 1, 1970)
|
|
*
|
|
* Input: htime = human time representation
|
|
*
|
|
* Output: etime = epochal time representation
|
|
*
|
|
* Derived from NRL Neons V3.6
|
|
*/
|
|
void
|
|
Cdh2e(CdTime *htime, double *etime)
|
|
{
|
|
long ytemp, year; /* temporary year holder */
|
|
int day_cnt; /* count of days */
|
|
int doy; /* day of year */
|
|
long baseYear; /* base year for epochal time */
|
|
int daysInLeapYear; /* number of days in a leap year */
|
|
int daysInYear; /* days in non-leap year */
|
|
|
|
CdDayOfYear(htime,&doy);
|
|
|
|
day_cnt = 0;
|
|
|
|
baseYear = ((htime->timeType) & CdBase1970) ? 1970 : htime->baseYear;
|
|
year = ((htime->timeType) & CdBase1970) ? htime->year : (htime->year + htime->baseYear);
|
|
if(!((htime->timeType) & CdChronCal)) baseYear = year = 0; /* set year and baseYear to 0 for Clim */
|
|
if((htime->timeType) & Cd366) {
|
|
daysInLeapYear = 366;
|
|
daysInYear = 366;
|
|
} else {
|
|
daysInLeapYear = ((htime->timeType) & Cd365) ? 366 : 360;
|
|
daysInYear = ((htime->timeType) & Cd365) ? 365 : 360;
|
|
}
|
|
|
|
if (year > baseYear) {
|
|
for (ytemp = year - 1; ytemp >= baseYear; ytemp--) {
|
|
day_cnt += ISLEAP(ytemp,htime->timeType) ? daysInLeapYear : daysInYear;
|
|
}
|
|
} else if (year < baseYear) {
|
|
for (ytemp = year; ytemp < baseYear; ytemp++) {
|
|
day_cnt -= ISLEAP(ytemp,htime->timeType) ? daysInLeapYear : daysInYear;
|
|
}
|
|
}
|
|
*etime = (double) (day_cnt + doy - 1) * 24. + htime->hour;
|
|
return;
|
|
}
|
|
|
|
/* Validate the component time, return 0 if valid, 1 if not */
|
|
static int
|
|
cdValidateTime(cdCalenType timetype, cdCompTime comptime)
|
|
{
|
|
NC_UNUSED(timetype);
|
|
if(comptime.month<1 || comptime.month>12){
|
|
cdError("Error on time conversion: invalid month = %hd\n",comptime.month);
|
|
return 1;
|
|
}
|
|
if(comptime.day<1 || comptime.day>31){
|
|
cdError("Error on time conversion: invalid day = %hd\n",comptime.day);
|
|
return 1;
|
|
}
|
|
if(comptime.hour<0.0 || comptime.hour>24.0){
|
|
cdError("Error on time conversion: invalid hour = %lf\n",comptime.hour);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
cdChar2Comp(cdCalenType timetype, char* chartime, cdCompTime* comptime)
|
|
{
|
|
double sec;
|
|
int ihr, imin, nconv;
|
|
long year;
|
|
short day;
|
|
short month;
|
|
|
|
comptime->year = CD_NULL_YEAR;
|
|
comptime->month = CD_NULL_MONTH;
|
|
comptime->day = CD_NULL_DAY;
|
|
comptime->hour = CD_NULL_HOUR;
|
|
|
|
if(timetype & cdStandardCal){
|
|
nconv = sscanf(chartime,"%ld-%hd-%hd %d:%d:%lf",&year,&month,&day,&ihr,&imin,&sec);
|
|
if(nconv==EOF || nconv==0){
|
|
cdError("Error on character time conversion, string = %s\n",chartime);
|
|
return;
|
|
}
|
|
if(nconv >= 1){
|
|
comptime->year = year;
|
|
}
|
|
if(nconv >= 2){
|
|
comptime->month = month;
|
|
}
|
|
if(nconv >= 3){
|
|
comptime->day = day;
|
|
}
|
|
if(nconv >= 4){
|
|
if(ihr<0 || ihr>23){
|
|
cdError("Error on character time conversion: invalid hour = %d\n",ihr);
|
|
return;
|
|
}
|
|
comptime->hour = (double)ihr;
|
|
}
|
|
if(nconv >= 5){
|
|
if(imin<0 || imin>59){
|
|
cdError("Error on character time conversion: invalid minute = %d\n",imin);
|
|
return;
|
|
}
|
|
comptime->hour += (double)imin/60.;
|
|
}
|
|
if(nconv >= 6){
|
|
if(sec<0.0 || sec>60.0){
|
|
cdError("Error on character time conversion: invalid second = %lf\n",sec);
|
|
return;
|
|
}
|
|
comptime->hour += sec/3600.;
|
|
}
|
|
}
|
|
else{ /* Climatological */
|
|
nconv = sscanf(chartime,"%hd-%hd %d:%d:%lf",&month,&day,&ihr,&imin,&sec);
|
|
if(nconv==EOF || nconv==0){
|
|
cdError("Error on character time conversion, string = %s",chartime);
|
|
return;
|
|
}
|
|
if(nconv >= 1){
|
|
comptime->month = month;
|
|
}
|
|
if(nconv >= 2){
|
|
comptime->day = day;
|
|
}
|
|
if(nconv >= 3){
|
|
if(ihr<0 || ihr>23){
|
|
cdError("Error on character time conversion: invalid hour = %d\n",ihr);
|
|
return;
|
|
}
|
|
comptime->hour = (double)ihr;
|
|
}
|
|
if(nconv >= 4){
|
|
if(imin<0 || imin>59){
|
|
cdError("Error on character time conversion: invalid minute = %d\n",imin);
|
|
return;
|
|
}
|
|
comptime->hour += (double)imin/60.;
|
|
}
|
|
if(nconv >= 5){
|
|
if(sec<0.0 || sec>60.0){
|
|
cdError("Error on character time conversion: invalid second = %lf\n",sec);
|
|
return;
|
|
}
|
|
comptime->hour += sec/3600.;
|
|
}
|
|
}
|
|
(void)cdValidateTime(timetype,*comptime);
|
|
return;
|
|
}
|
|
|
|
/* Convert ct to relunits (unit, basetime) */
|
|
/* in the mixed Julian/Gregorian calendar. */
|
|
/* unit is anything but year, season, month. unit and basetime are */
|
|
/* from the parsed relunits. Return result in reltime. */
|
|
static void
|
|
cdComp2RelMixed(cdCompTime ct, cdUnitTime unit, cdCompTime basetime, double *reltime){
|
|
|
|
double hourdiff;
|
|
|
|
hourdiff = cdDiffMixed(ct, basetime);
|
|
*reltime = cdFromHours(hourdiff, unit);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
cdComp2Rel(cdCalenType timetype, cdCompTime comptime, char* relunits, double* reltime)
|
|
{
|
|
cdCompTime base_comptime;
|
|
CdDeltaTime deltime;
|
|
CdTime humantime;
|
|
CdTimeType old_timetype;
|
|
cdUnitTime unit;
|
|
double base_etm, etm, delta;
|
|
long ndel, hoursInYear;
|
|
|
|
/* Parse the relunits */
|
|
if(cdParseRelunits(timetype, relunits, &unit, &base_comptime))
|
|
return;
|
|
|
|
/* Handle mixed Julian/Gregorian calendar */
|
|
if (timetype == cdMixed){
|
|
switch(unit){
|
|
case cdWeek: case cdDay: case cdHour: case cdMinute: case cdSecond:
|
|
cdComp2RelMixed(comptime, unit, base_comptime, reltime);
|
|
return;
|
|
case cdYear: case cdSeason: case cdMonth:
|
|
timetype = cdStandard;
|
|
break;
|
|
case cdFraction:
|
|
cdError("invalid unit in conversion");
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
/* Convert basetime to epochal */
|
|
humantime.year = base_comptime.year;
|
|
humantime.month = base_comptime.month;
|
|
humantime.day = base_comptime.day;
|
|
humantime.hour = base_comptime.hour;
|
|
humantime.baseYear = 1970;
|
|
/* Map to old-style timetype */
|
|
if(cdToOldTimetype(timetype,&old_timetype))
|
|
return;
|
|
humantime.timeType = old_timetype;
|
|
Cdh2e(&humantime,&base_etm);
|
|
|
|
/* Map end time to epochal */
|
|
humantime.year = comptime.year;
|
|
humantime.month = comptime.month;
|
|
humantime.day = comptime.day;
|
|
humantime.hour = comptime.hour;
|
|
Cdh2e(&humantime,&etm);
|
|
/* Calculate relative time value for months or hours */
|
|
deltime.count = 1;
|
|
/* Coverity[MIXED_ENUMS] */
|
|
deltime.units = (CdTimeUnit)unit;
|
|
switch(unit){
|
|
case cdWeek: case cdDay: case cdHour: case cdMinute: case cdSecond:
|
|
delta = etm - base_etm;
|
|
if(!(timetype & cdStandardCal)){ /* Climatological time */
|
|
hoursInYear = (timetype & cd365Days) ? 8760. : (timetype & cdHasLeap) ? 8784. : 8640.;
|
|
/* Normalize delta to interval [0,hoursInYear) */
|
|
if(delta < 0.0 || delta >= hoursInYear) {
|
|
double down = ((double)delta)/((double)hoursInYear);
|
|
down = floor(down);
|
|
down = down * (double)hoursInYear;
|
|
delta = delta - down;
|
|
}
|
|
}
|
|
break;
|
|
case cdYear: case cdSeason: case cdMonth:
|
|
CdDivDelTime(base_etm, etm, deltime, old_timetype, 1970, &ndel);
|
|
break;
|
|
case cdFraction:
|
|
cdError("invalid unit in conversion");
|
|
break;
|
|
default: break;
|
|
}
|
|
|
|
/* Convert to output units */
|
|
switch(unit){
|
|
case cdSecond:
|
|
*reltime = 3600.0 * delta;
|
|
break;
|
|
case cdMinute:
|
|
*reltime = 60.0 * delta;
|
|
break;
|
|
case cdHour:
|
|
*reltime = delta;
|
|
break;
|
|
case cdDay:
|
|
*reltime = delta/24.0;
|
|
break;
|
|
case cdWeek:
|
|
*reltime = delta/168.0;
|
|
break;
|
|
case cdMonth: case cdSeason: case cdYear: /* Already in correct units */
|
|
if(timetype & cdStandardCal)
|
|
*reltime = (base_etm <= etm) ? (double)ndel : (double)(-ndel);
|
|
else /* Climatological time is already normalized*/
|
|
*reltime = (double)ndel;
|
|
break;
|
|
default:
|
|
cdError("invalid unit in conversion");
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* Add (value,unit) to comptime. */
|
|
/* value is in hours. */
|
|
/* calendar is anything but cdMixed. */
|
|
static void
|
|
cdCompAdd(cdCompTime comptime, double value, cdCalenType calendar, cdCompTime *result){
|
|
|
|
double reltime;
|
|
|
|
cdComp2Rel(calendar, comptime, "hours", &reltime);
|
|
reltime += value;
|
|
cdRel2Comp(calendar, "hours", reltime, result);
|
|
return;
|
|
}
|
|
|
|
/* Add value in hours to ct, in the mixed Julian/Gregorian
|
|
* calendar. */
|
|
static void
|
|
cdCompAddMixed(cdCompTime ct, double value, cdCompTime *result){
|
|
|
|
double xj, xg;
|
|
|
|
if (cdCompCompare(ct, ZB) == -1){
|
|
xj = cdDiffJulian(ZA, ct);
|
|
if (value <= xj){
|
|
cdCompAdd(ct, value, cdJulian, result);
|
|
}
|
|
else {
|
|
cdCompAdd(ZB, value-xj, cdStandard, result);
|
|
}
|
|
}
|
|
else {
|
|
xg = cdDiffGregorian(ZB, ct);
|
|
if (value > xg){
|
|
cdCompAdd(ct, value, cdStandard, result);
|
|
}
|
|
else {
|
|
cdCompAdd(ZA, value-xg, cdJulian, result);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Return value expressed in hours. */
|
|
static double
|
|
cdToHours(double value, cdUnitTime unit){
|
|
|
|
double result = 0;
|
|
|
|
switch(unit){
|
|
case cdSecond:
|
|
result = value/3600.0;
|
|
break;
|
|
case cdMinute:
|
|
result = value/60.0;
|
|
break;
|
|
case cdHour:
|
|
result = value;
|
|
break;
|
|
case cdDay:
|
|
result = 24.0 * value;
|
|
break;
|
|
case cdWeek:
|
|
result = 168.0 * value;
|
|
break;
|
|
default:
|
|
cdError("invalid unit in conversion");
|
|
break;
|
|
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* Convert relative time (reltime, unit, basetime) to comptime in the
|
|
* mixed Julian/Gregorian calendar. unit is anything but year, season,
|
|
* month. unit and basetime are from the parsed relunits. Return
|
|
* result in comptime. */
|
|
static void
|
|
cdRel2CompMixed(double reltime, cdUnitTime unit, cdCompTime basetime, cdCompTime *comptime){
|
|
|
|
reltime = cdToHours(reltime, unit);
|
|
cdCompAddMixed(basetime, reltime, comptime);
|
|
return;
|
|
}
|
|
|
|
|
|
static void
|
|
cdRel2Comp(cdCalenType timetype, char* relunits, double reltime, cdCompTime* comptime)
|
|
{
|
|
CdDeltaTime deltime;
|
|
CdTime humantime;
|
|
CdTimeType old_timetype;
|
|
cdCompTime base_comptime;
|
|
cdUnitTime unit, baseunits;
|
|
double base_etm, result_etm;
|
|
double delta;
|
|
long idelta;
|
|
|
|
/* Parse the relunits */
|
|
if(cdParseRelunits(timetype, relunits, &unit, &base_comptime))
|
|
return;
|
|
|
|
if (timetype == cdMixed){
|
|
switch(unit){
|
|
case cdWeek: case cdDay: case cdHour: case cdMinute: case cdSecond:
|
|
cdRel2CompMixed(reltime, unit, base_comptime, comptime);
|
|
return;
|
|
case cdYear: case cdSeason: case cdMonth:
|
|
timetype = cdStandard;
|
|
break;
|
|
case cdFraction:
|
|
cdError("invalid unit in conversion");
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
baseunits =cdBadUnit;
|
|
switch(unit){
|
|
case cdSecond:
|
|
delta = reltime/3600.0;
|
|
baseunits = cdHour;
|
|
break;
|
|
case cdMinute:
|
|
delta = reltime/60.0;
|
|
baseunits = cdHour;
|
|
break;
|
|
case cdHour:
|
|
delta = reltime;
|
|
baseunits = cdHour;
|
|
break;
|
|
case cdDay:
|
|
delta = 24.0 * reltime;
|
|
baseunits = cdHour;
|
|
break;
|
|
case cdWeek:
|
|
delta = 168.0 * reltime;
|
|
baseunits = cdHour;
|
|
break;
|
|
case cdMonth:
|
|
idelta = (long)(reltime + (reltime<0 ? -1.e-10 : 1.e-10));
|
|
baseunits = cdMonth;
|
|
break;
|
|
case cdSeason:
|
|
idelta = (long)(3.0 * reltime + (reltime<0 ? -1.e-10 : 1.e-10));
|
|
baseunits = cdMonth;
|
|
break;
|
|
case cdYear:
|
|
idelta = (long)(12 * reltime + (reltime<0 ? -1.e-10 : 1.e-10));
|
|
baseunits = cdMonth;
|
|
break;
|
|
default:
|
|
cdError("invalid unit in conversion");
|
|
break;
|
|
}
|
|
|
|
deltime.count = 1;
|
|
/* Coverity[MIXED_ENUMS] */
|
|
deltime.units = (CdTimeUnit)baseunits;
|
|
|
|
humantime.year = base_comptime.year;
|
|
humantime.month = base_comptime.month;
|
|
humantime.day = base_comptime.day;
|
|
humantime.hour = base_comptime.hour;
|
|
humantime.baseYear = 1970;
|
|
/* Map to old-style timetype */
|
|
if(cdToOldTimetype(timetype,&old_timetype))
|
|
return;
|
|
humantime.timeType = old_timetype;
|
|
|
|
Cdh2e(&humantime,&base_etm);
|
|
/* If months, seasons, or years, */
|
|
if(baseunits == cdMonth){
|
|
|
|
/* Calculate new epochal time from integer months. */
|
|
/* Convert back to human, then comptime. */
|
|
/* For zero reltime, just return the basetime*/
|
|
if(reltime != 0.0){
|
|
CdAddDelTime(base_etm,idelta,deltime,old_timetype,1970,&result_etm);
|
|
Cde2h(result_etm, old_timetype, 1970, &humantime);
|
|
}
|
|
}
|
|
/* Calculate new epochal time. */
|
|
/* Convert back to human, then comptime. */
|
|
else if(baseunits == cdHour){
|
|
Cde2h(base_etm+delta, old_timetype, 1970, &humantime);
|
|
|
|
}
|
|
comptime->year = humantime.year;
|
|
comptime->month = humantime.month;
|
|
comptime->day = humantime.day;
|
|
comptime->hour = humantime.hour;
|
|
|
|
return;
|
|
}
|
|
|
|
/* rkr: output as ISO 8601 strings */
|
|
static void
|
|
cdComp2Iso(cdCalenType timetype, int separator, cdCompTime comptime, char* time)
|
|
{
|
|
double dtmp, sec;
|
|
int ihr, imin, isec;
|
|
int nskip;
|
|
|
|
if(cdValidateTime(timetype,comptime))
|
|
return;
|
|
|
|
ihr = (int)comptime.hour;
|
|
dtmp = 60.0 * (comptime.hour - (double)ihr);
|
|
imin = (int)dtmp;
|
|
sec = 60.0 * (dtmp - (double)imin);
|
|
isec = (int)sec;
|
|
|
|
if(sec == isec)
|
|
if(isec == 0)
|
|
if(imin == 0)
|
|
if(ihr == 0)
|
|
nskip = 4;
|
|
else
|
|
nskip = 3;
|
|
else
|
|
nskip = 2;
|
|
else
|
|
nskip = 1;
|
|
else
|
|
nskip = 0;
|
|
|
|
if(timetype & cdStandardCal){
|
|
switch (nskip) {
|
|
case 0: /* sec != 0 && (int)sec != sec */
|
|
sprintf(time,"%4.4ld-%2.2hd-%2.2hd%c%2.2d:%2.2d:%lf",
|
|
comptime.year,comptime.month,comptime.day,separator,ihr,imin,sec);
|
|
break;
|
|
case 1:
|
|
sprintf(time,"%4.4ld-%2.2hd-%2.2hd%c%2.2d:%2.2d:%2.2d",
|
|
comptime.year,comptime.month,comptime.day,separator,ihr,imin,isec);
|
|
break;
|
|
case 2:
|
|
sprintf(time,"%4.4ld-%2.2hd-%2.2hd%c%2.2d:%2.2d",
|
|
comptime.year,comptime.month,comptime.day,separator,ihr,imin);
|
|
break;
|
|
case 3:
|
|
sprintf(time,"%4.4ld-%2.2hd-%2.2hd%c%2.2d",
|
|
comptime.year,comptime.month,comptime.day,separator,ihr);
|
|
break;
|
|
case 4:
|
|
sprintf(time,"%4.4ld-%2.2hd-%2.2hd",
|
|
comptime.year,comptime.month,comptime.day);
|
|
break;
|
|
}
|
|
}
|
|
else { /* Climatological */
|
|
switch (nskip) {
|
|
case 0: /* sec != 0 && (int)sec != sec */
|
|
sprintf(time,"%2.2hd-%2.2hd%c%2.2d:%2.2d:%lf",
|
|
comptime.month,comptime.day,separator,ihr,imin,sec);
|
|
break;
|
|
case 1:
|
|
sprintf(time,"%2.2hd-%2.2hd%c%2.2d:%2.2d:%2.2d",
|
|
comptime.month,comptime.day,separator,ihr,imin,isec);
|
|
break;
|
|
case 2:
|
|
sprintf(time,"%2.2hd-%2.2hd%c%2.2d:%2.2d",
|
|
comptime.month,comptime.day,separator,ihr,imin);
|
|
break;
|
|
case 3:
|
|
sprintf(time,"%2.2hd-%2.2hd%c%2.2d",
|
|
comptime.month,comptime.day,separator,ihr);
|
|
break;
|
|
case 4:
|
|
sprintf(time,"%2.2hd-%2.2hd",
|
|
comptime.month,comptime.day);
|
|
break;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* rkr: added for output closer to ISO 8601 */
|
|
void
|
|
cdRel2Iso(cdCalenType timetype, char* relunits, int separator, double reltime, char* chartime)
|
|
{
|
|
cdCompTime comptime;
|
|
|
|
cdRel2Comp(timetype, relunits, reltime, &comptime);
|
|
cdComp2Iso(timetype, separator, comptime, chartime);
|
|
|
|
return;
|
|
}
|
|
|
|
int
|
|
cdSetErrOpts(int opts)
|
|
{
|
|
int old = cuErrOpts;
|
|
cuErrOpts = opts;
|
|
return old;
|
|
}
|