/* parse.c ********************************************************************************
* parse.c
*
* This set of routines parses date and time strings in a variety of formats.
*
* RL_BOOL Jul_ParseDT(RL_CHAR *string, RL_INT4 *dutc, RL_FLT8 *secs)
* converts a date/time string to seconds relative to J2000 TAI.
* RL_BOOL Jul_ParseDate(RL_CHAR *string, RL_INT4 *dutc)
* converts a date string to day relative to J2000.
* RL_BOOL Jul_ParseTime(RL_CHAR *string, RL_BOOL isleap, RL_FLT8 *secs)
* converts a time string to seconds into the given day.
*
* Mark Showalter, PDS Ring-Moon Systems Node, December 1995
* Revised 11/96 by MRS with minor updates.
* Revised 6/98 by MRS to conform to RingLib naming standards.
*******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "julian.h"
#include "fortran.h"
/***********************************************************
* Data structures
***********************************************************/
#define MAX_TOKENS 20
#define TOKEN_LEN 79
typedef struct Token_struct {
RL_CHAR string[TOKEN_LEN + 1];
RL_CHAR delim;
RL_BOOL is_alpha;
RL_INT4 number;
} TOKEN;
/***********************************************************
* Prototypes of internal functions
***********************************************************/
/* To interpret date/time formats */
static RL_BOOL ZJul_DTTokens RL_PROTO((TOKEN *tokens, RL_INT4 ntokens,
RL_CHAR *pref,
RL_INT4 *dutc, RL_FLT8 *secs));
/* To interpret date formats */
static RL_BOOL ZJul_DateTokens RL_PROTO((TOKEN *tokens, RL_INT4 ntokens,
RL_CHAR *pref, RL_INT4 *dutc));
static RL_BOOL ZJul_TestYMD RL_PROTO((TOKEN *ytoken, TOKEN *mtoken,
TOKEN *dtoken, RL_INT4 *year,
RL_INT4 *month, RL_INT4 *day));
static RL_BOOL ZJul_TestYear RL_PROTO((TOKEN *token, RL_INT4 *year));
static RL_BOOL ZJul_TestMonth RL_PROTO((TOKEN *token, RL_INT4 *month));
static RL_BOOL ZJul_TestDay RL_PROTO((TOKEN *token, RL_INT4 year,
RL_INT4 month, RL_INT4 *day));
static RL_BOOL ZJul_TestDOY RL_PROTO((TOKEN *token, RL_INT4 year,
RL_INT4 *doy));
/* To interpret time formats */
static RL_BOOL ZJul_TimeTokens RL_PROTO((TOKEN *tokens, RL_INT4 ntokens,
RL_BOOL isleap, RL_FLT8 *secs));
static RL_BOOL ZJul_TestHMS RL_PROTO((TOKEN *token, RL_INT4 minval,
RL_INT4 maxval, RL_INT4 *value));
/* To interpret Julian date formats */
static RL_BOOL ZJul_JDTokens RL_PROTO((TOKEN *tokens, RL_INT4 ntokens,
RL_FLT8 *tai));
/* To manipulate tokens */
static RL_INT4 ZJul_FillTokens RL_PROTO((RL_CHAR *string, TOKEN *tokens,
RL_INT4 max_tokens));
static RL_CHAR *ZJul_NextToken RL_PROTO((RL_CHAR *string, TOKEN *token));
static RL_BOOL ZJul_TestDelim RL_PROTO((RL_INT4 delim, RL_CHAR *choices));
static RL_BOOL ZJul_FracToken RL_PROTO((TOKEN *token, RL_FLT8 *frac));
/*
********************************************************************************
* EXPORTED USER ROUTINES
********************************************************************************
*$ Component_name:
* Jul_ParseDT
*$ Abstract:
* Interprets a character string as a date and time.
*$ Keywords:
* JULIAN, TIME, PARSING
* C, PUBLIC
*$ Declarations:
* RL_BOOL Jul_ParseDT(string, pref, dutc, secs)
* RL_CHAR *string, *pref;
* RL_INT4 *dutc;
* RL_FLT8 *secs;
*$ Inputs:
* *string the character string to parse.
* *pref optional three-character string giving the preferred
* order for year, month and day values (see
* Jul_ParseDate).
*$ Outputs:
* *dutc inferred days relative to January 1, 2000.
* *secs inferred seconds into day.
*$ Returns:
* TRUE if a valid string interpretation was found; FALSE otherwise.
*$ Side_effects:
* none
*$ Detailed_description:
* This function interprets a character string as a date and time.
*
* A string beginning with "JD" is interpreted as a Julian date; a string
* beginning with "MJD" is interpreted as a Modified Julian Date.
*
* Otherwise, the string is first separated into a sequence of tokens
* separated by delimiters. A token consists of any sequence of letters
* or digits (but not a mixture of both). A delimiter is any punctuation
* or a single letter separating numeric tokens. Blanks are not
* significant except as delimiters when nothing else is present.
*
* The string is parsed by testing each possible division between date and
* time tokens until it finds one that produces both a valid date
* according to Jul_ParseDate() and a valid time according to
* Jul_ParseTime(). It gives preference for the longest possible date
* token sequence; it also gives preference for dates before times.
*
* If no valid date/time interpretation is found, it returns FALSE.
*$ External_references:
* Jul_ParseDate(), Jul_ParseTime()
*$ Examples:
* none
*$ Error_handling:
* It returns FALSE if no valid interpretation is found; no error message
* is printed.
*$ Limitations:
* none
*$ Author_and_institution:
* Mark R. Showalter
* PDS Ring-Moon Systems Node
* NASA/Ames Research Center
*$ Version_and_date:
* 1.0: December 1995
*$ Change_history:
* none
*******************************************************************************/
RL_BOOL Jul_ParseDT(string, pref, dutc, secs)
RL_CHAR *string, *pref;
RL_INT4 *dutc;
RL_FLT8 *secs;
{
TOKEN tokens[MAX_TOKENS];
RL_INT4 ntokens;
RL_BOOL status;
RL_FLT8 tai;
/* Interpret string as a sequence of tokens */
ntokens = ZJul_FillTokens(string, tokens, MAX_TOKENS);
if (ntokens > MAX_TOKENS) return FALSE;
/* Check JD/MJD format */
status = ZJul_JDTokens(tokens, ntokens, &tai);
if (status) {
*dutc = Jul_DUTCofTAI(tai, secs);
return TRUE;
}
/* Check final delimiter */
status = ZJul_TestDelim(tokens[ntokens-1].delim, " THMSZ.");
if (!status) return FALSE;
/* Parse date/time string */
status = ZJul_DTTokens(tokens, ntokens, pref, dutc, secs);
if (!status) return FALSE;
return TRUE;
}
/*
********************************************************************************
*$ Component_name:
* Jul_ParseDate
*$ Abstract:
* Interprets a character string as a date.
*$ Keywords:
* JULIAN, TIME, PARSING
* C, PUBLIC
*$ Declarations:
* RL_BOOL Jul_ParseDate(string, pref, dutc)
* RL_CHAR *string, *pref;
* RL_INT4 *dutc;
*$ Inputs:
* *string the character string to parse.
* *pref optional three-character string giving the preferred
* order for year, month and day values (see below).
*$ Outputs:
* *dutc inferred days relative to January 1, 2000.
*$ Returns:
* TRUE if a valid string interpretation was found; FALSE otherwise.
*$ Side_effects:
* none
*$ Detailed_description:
* This function interprets a character string as a date.
*
* The string is first separated into a sequence of tokens separated by
* delimiters. A token consists of any sequence of letters or digits (but
* not a mixture of both). A delimiter is any punctuation or a single
* letter separating numeric tokens. Blanks are not signficant except as
* delimiters when nothing else is present.
*
* A single token is interpreted as either a four-digit year or as an
* eight-digit merged date of the form "yyyymmdd". A pair of tokens is
* interpreted as a year plus day-of-year in that order.
*
* A set of three tokens is interpreted as a month, day and year in some
* order. Year values must be four digits or else 0-49 (which is
* interpreted as 2000-2049) or 50-99 (which is interpreted as 1950-1999).
* Months are integers 1-12 or English names January-December, abbreviated
* to three or more letters). Days are integers between 1 and 31 (or less,
* depending on the month). The sequence of tokens is interpreted in the
* preferred order first, then as month-day-year, day-month-year, and
* year-month-day until a valid interpretation is found.
*
* The pref argument, if used, enables the user to specify the preferred
* ordering for year, month, and day. It is a three-character string
* containing a 'Y', 'M' and 'D', where the order of the three indicates
* the preferred ordering for the year, month and day, respectively.
*
* Valid delimiters are "/", "-", "." or blank. For three tokens, the
* delimiters between the tokens must be the same except for the special
* case "month day, year" where a comma is allowed after the second token.
* The final delimiter must be blank.
*
* If no valid interpretation is found, the function returns FALSE.
*$ External_references:
* many
*$ Examples:
* none
*$ Error_handling:
* It returns FALSE if no valid interpretation is found; no error message
* is printed.
*$ Limitations:
* none
*$ Author_and_institution:
* Mark R. Showalter
* PDS Ring-Moon Systems Node
* NASA/Ames Research Center
*$ Version_and_date:
* 1.0: December 1995
*$ Change_history:
* none
*******************************************************************************/
RL_BOOL Jul_ParseDate(string, pref, dutc)
RL_CHAR *string, *pref;
RL_INT4 *dutc;
{
TOKEN tokens[MAX_TOKENS];
RL_INT4 ntokens;
RL_BOOL status;
/* Convert to tokens */
ntokens = ZJul_FillTokens(string, tokens, MAX_TOKENS);
if (ntokens > MAX_TOKENS) return FALSE;
/* Check final delimiter */
status = ZJul_TestDelim(tokens[ntokens-1].delim, " ");
if (!status) return FALSE;
/* Parse date string */
status = ZJul_DateTokens(tokens, ntokens, pref, dutc);
return status;
}
/*
********************************************************************************
*$ Component_name:
* Jul_ParseTime
*$ Abstract:
* Interprets a character string as a time.
*$ Keywords:
* JULIAN, TIME, PARSING
* C, PUBLIC
*$ Declarations:
* RL_BOOL Jul_ParseTime(string, isleap, secs)
* RL_CHAR *string;
* RL_BOOL isleap;
* RL_FLT8 *secs;
*$ Inputs:
* string pointer to the character string to parse.
* isleap TRUE if the day has a leap second; FALSE otherwise.
*$ Outputs:
* *dutc inferred seconds into day.
*$ Returns:
* TRUE if a valid string interpretation was found; FALSE otherwise.
*$ Side_effects:
* none
*$ Detailed_description:
* This function interprets a character string as a time.
*
* The string is first separated into a sequence of tokens separated by
* delimiters. A token consists of any sequence of letters or digits (but
* not a mixture of both). A delimiter is any punctuation or a single
* letter separating numeric tokens. Blanks are not signficant except as
* delimiters when nothing else is present.
*
* Tokens are interpreted as hour, minute, second and millisecond in that
* order although not all a required; the last token may have a fractional
* part. The string may end in "AM" or "PM", or else "Z" to be compatible
* with PDS time formats. Valid delimiters are colons or blanks.
*
* Individual tokens may also end in "H", "M" or "S" to indicate hours,
* minutes or seconds respectively. This enables the user to express a
* time in minutes (without hours) or seconds (without hours and minutes).
*
* If no valid interpretation is found, the function returns FALSE.
*$ External_references:
* many
*$ Examples:
* none
*$ Error_handling:
* It returns FALSE if no valid interpretation is found; no error message
* is printed.
*$ Limitations:
* none
*$ Author_and_institution:
* Mark R. Showalter
* PDS Ring-Moon Systems Node
* NASA/Ames Research Center
*$ Version_and_date:
* 1.0: December 1995
*$ Change_history:
* none
*******************************************************************************/
RL_BOOL Jul_ParseTime(string, isleap, secs)
RL_CHAR *string;
RL_BOOL isleap;
RL_FLT8 *secs;
{
TOKEN tokens[MAX_TOKENS];
RL_INT4 ntokens;
RL_BOOL status;
/* Convert to tokens */
ntokens = ZJul_FillTokens(string, tokens, MAX_TOKENS);
if (ntokens > MAX_TOKENS) return FALSE;
/* Check final delimiter */
status = ZJul_TestDelim(tokens[ntokens-1].delim, "HMSZ ");
if (!status) return FALSE;
/* Parse time string */
status = ZJul_TimeTokens(tokens, ntokens, isleap, secs);
return status;
}
/*
********************************************************************************
* INTERNAL FUNCTION TO INTERPRET DATE/TIME FORMATS
********************************************************************************
* RL_INT4 ZJul_DTTokens(TOKEN *tokens, RL_INT4 ntokens, RL_CHAR *pref,
* RL_INT4 *dutc, RL_FLT8 *secs)
*
* This internal function interprets a sequence of tokens as a date/time
* combination. It returns TRUE if a valid date/time combination was found;
* FALSE otherise.
*
* Input:
* tokens[0...ntokens-1] array of tokens.
* *pref preferred order for year, month and day; see
* Jul_ParseDate().
*
* Output:
* *dutc day relative to J2000.
* *secs seconds into day.
*
* Return: TRUE if a valid sequence of tokens was found;
* FALSE otherwise.
*******************************************************************************/
static RL_BOOL ZJul_DTTokens(tokens, ntokens, pref, dutc, secs)
TOKEN *tokens;
RL_INT4 ntokens, *dutc;
RL_FLT8 *secs;
RL_CHAR* pref;
{
RL_INT4 n, dtemp;
RL_FLT8 stemp;
RL_BOOL dstat, sstat;
/* Consider date-time orderings */
for (n=ntokens; n>=0; n--) {
dstat = ZJul_DateTokens(tokens+0, n, pref, &dtemp);
if (!dstat) continue;
sstat = ZJul_TimeTokens(tokens+n, ntokens-n,
Jul_IsLeapDay(dtemp), &stemp);
if (sstat) goto DONE;
}
/* Consider time-date orderings */
for (n=0; n<=ntokens; n++) {
dstat = ZJul_DateTokens(tokens+n, ntokens-n, pref, &dtemp);
if (!dstat) continue;
sstat = ZJul_TimeTokens(tokens+0, n,
Jul_IsLeapDay(dtemp), &stemp);
if (sstat) goto DONE;
}
return FALSE;
/* Valid interpretation found */
DONE:
*dutc = dtemp;
*secs = stemp;
return TRUE;
}
/*
********************************************************************************
* INTERNAL FUNCTIONS TO INTERPRET DATES
********************************************************************************
* RL_INT4 ZJul_DateTokens(TOKEN *tokens, RL_INT4 ntokens, RL_CHAR *pref,
* RL_INT4 *dutc)
*
* This internal function interprets a sequence of tokens as a date. It returns
* a status value and, if status >= 0, the date inferred.
*
* Input:
* tokens[0...ntokens-1] array of tokens.
* *pref optional preferred year/month/day ordering.
*
* Output:
* *dutc day relative to J2000.
*
* Return: TRUE if a valid interpretation was found;
* FALSE otherwise.
*******************************************************************************/
static RL_BOOL ZJul_DateTokens(tokens, ntokens, pref, dutc)
TOKEN *tokens;
RL_CHAR *pref;
RL_INT4 ntokens, *dutc;
{
RL_INT4 year, month, day, y, m, d, i;
RL_BOOL status, MDYonly;
RL_CHAR string[20], test;
/* Test final delimiter */
status = ZJul_TestDelim(tokens[ntokens-1].delim, " T/-:.");
if (!status) return FALSE;
switch (ntokens) {
/***********************************************************
* A single token must be either a year or "YYYYMMDD" format.
***********************************************************/
case 1:
/* Test for a year value */
status = ZJul_TestYear(tokens+0, &year);
if (status) {
*dutc = Jul_DUTCofYMD(year, 1, 1);
return TRUE;
}
/* Test for a "YYYYMMDD" numeric value */
if (tokens[0].is_alpha) return FALSE;
if (strlen(tokens[0].string) != 8) return FALSE;
string[0] = tokens[0].string[4];
string[1] = tokens[0].string[5];
string[2] = ' ';
string[3] = tokens[0].string[6];
string[4] = tokens[0].string[7];
string[5] = ',';
string[6] = tokens[0].string[0];
string[7] = tokens[0].string[1];
string[8] = tokens[0].string[2];
string[9] = tokens[0].string[3];
string[10] = tokens[0].delim;
string[11] = '\0';
return Jul_ParseDate(string, "MDY", dutc);
/***********************************************************
* Two tokens must be year plus day-of-year
***********************************************************/
case 2:
status = ZJul_TestDelim(tokens[0].delim, "/-. ");
if (!status) return FALSE;
status = ZJul_TestYear(tokens+0, &year);
if (!status) return FALSE;
status = ZJul_TestDOY(tokens+1, year, &day);
if (!status) return FALSE;
*dutc = Jul_DUTCofYMD(year, 1, day);
return TRUE;
/***********************************************************
* Three tokens must be year, month and day in some order
***********************************************************/
case 3:
/* Test first delimiter */
status = ZJul_TestDelim(tokens[0].delim, " /-.");
if (!status) return FALSE;
/* Delimiters must match except when using "month day, year" format */
MDYonly = (tokens[0].delim != tokens[1].delim);
if (MDYonly) {
if (tokens[0].delim != ' ') return FALSE;
if (tokens[1].delim != ',') return FALSE;
}
/* Test preferred order */
if (!MDYonly && pref != NULL && *pref != '\0') {
y = -1;
m = -1;
d = -1;
for (i=0; i<3; i++) {
test = toupper(pref[i]);
if (test == 'Y') y = i;
if (test == 'M') m = i;
if (test == 'D') d = i;
}
if (y < 0 || m < 0 || d < 0) return FALSE;
status = ZJul_TestYMD(tokens+y, tokens+m, tokens+d,
&year, &month, &day);
if (status) {
*dutc = Jul_DUTCofYMD(year, month, day);
return TRUE;
}
}
/* Test MDY order */
status = ZJul_TestYMD(tokens+2, tokens+0, tokens+1,
&year, &month, &day);
if (status) {
*dutc = Jul_DUTCofYMD(year, month, day);
return TRUE;
}
if (MDYonly) return FALSE;
/* Test DMY order */
status = ZJul_TestYMD(tokens+2, tokens+1, tokens+0,
&year, &month, &day);
if (status) {
*dutc = Jul_DUTCofYMD(year, month, day);
return TRUE;
}
/* Test YMD order */
status = ZJul_TestYMD(tokens+0, tokens+1, tokens+2,
&year, &month, &day);
if (status) {
*dutc = Jul_DUTCofYMD(year, month, day);
return TRUE;
}
return FALSE;
/***********************************************************
* The number of tokens must be 1-3.
***********************************************************/
default:
return FALSE;
}
}
/*
********************************************************************************
* RL_BOOL ZJul_TestYMD(TOKEN *ytoken, TOKEN *mtoken, TOKEN *dtoken,
* RL_INT4 *year, RL_INT4 *month, RL_INT4 *day)
*
* This internal function tests a set of three tokens to determine if they
* comprise a valid year, month and day. It returns TRUE if they do, FALSE if
* they don't.
*
* Input:
* ytoken pointer to the year token.
* mtoken pointer to the month token.
* dtoken pointer to the day token.
*
* Output:
* *year inferred year 1900-2100.
* *month inferred month 1-12.
* *day inferred day 1-31.
*
* Return: TRUE if all three tokens are valid; FALSE otherwise.
*******************************************************************************/
static RL_BOOL ZJul_TestYMD(ytoken, mtoken, dtoken, year, month, day)
TOKEN *ytoken, *mtoken, *dtoken;
RL_INT4 *year, *month, *day;
{
RL_BOOL status;
status = ZJul_TestYear(ytoken, year);
if (!status) return FALSE;
status = ZJul_TestMonth(mtoken, month);
if (!status) return FALSE;
status = ZJul_TestDay(dtoken, *year, *month, day);
if (!status) return FALSE;
return TRUE;
}
/*
********************************************************************************
* RL_BOOL ZJul_TestYear(TOKEN *token, RL_INT4 *year)
*
* This internal function tests a token to determine if it is a valid year. It
* returns TRUE if the token is valid and FALSE otherwise.
*
* Input:
* token token structure.
*
* Output:
* *month year value if a valid token is found.
*
* Return: TRUE if valid; FALSE otherwise.
*******************************************************************************/
static RL_BOOL ZJul_TestYear(token, year)
TOKEN *token;
RL_INT4 *year;
{
RL_INT4 temp;
if (token->is_alpha) return FALSE;
temp = token->number;
/* Years between 1000 and 9999 are accepted exactly */
if (temp >= 1000 && temp <= 9999) {
*year = temp;
return TRUE;
}
/* Years 50-99 are treated as 1950-1999 */
if (temp >= 50 && temp <= 99) {
*year = temp + 1900;
return TRUE;
}
/* Years 0-49 are treated as 2000-2049 */
if (temp >= 0 && temp <= 49) {
*year = temp + 2000;
return TRUE;
}
return FALSE;
}
/*
********************************************************************************
* RL_INT4 ZJul_TestMonth(TOKEN *token, RL_INT4 *month)
*
* This internal function tests a token to determine if it is a valid month. It
* returns TRUE if it is valid, FALSE otherwise.
*
* Input:
* token token structure.
*
* Output:
* *month month value 1-12 if a valid token is found.
*
* Return: TRUE if token was valid; FALSE otherwise.
*******************************************************************************/
static RL_BOOL ZJul_TestMonth(token, month)
TOKEN *token;
RL_INT4 *month;
{
RL_INT4 temp, len;
static RL_CHAR *(month_names[12]) =
{"JANUARY", "FEBRUARY", "MARCH", "APRIL",
"MAY", "JUNE", "JULY", "AUGUST",
"SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER"};
/* Test character strings */
if (token->is_alpha) {
len = strlen(token->string);
if (len < 3) return FALSE;
for (temp = 0; temp < 12; temp++) {
if (strncmp(month_names[temp], token->string, len) == 0)
break;
}
if (temp >= 12) return FALSE;
*month = temp + 1;
return TRUE;
}
/* Test numeric values */
if (token->number <= 0 || token->number > 12) return FALSE;
*month = token->number;
return TRUE;
}
/*
********************************************************************************
* RL_BOOL ZJul_TestDay(TOKEN *token, RL_INT4 year, RL_INT4 month, RL_INT4 *day)
*
* This internal function tests a token to determine if it is a valid day-of-
* month. It returns TRUE if it is valid, FALSE otherwise.
*
* Input:
* token token structure.
* year year 1900-2100.
* month month value 1-12
*
* Output:
* *doy day-of-month value 1-31 if a valid token is found.
*
* Return: TRUE if token was valid; FALSE otherwise.
*******************************************************************************/
static RL_BOOL ZJul_TestDay(token, year, month, day)
TOKEN *token;
RL_INT4 year, month, *day;
{
if (token->is_alpha) return FALSE;
if (token->number < 1) return FALSE;
if (token->number <= 28 || token->number <= Jul_MonthDays(year,month)) {
*day = token->number;
return TRUE;
}
return FALSE;
}
/*
********************************************************************************
* RL_BOOL ZJul_TestDOY(TOKEN *token, RL_INT4 year, RL_INT4 *doy)
*
* This internal function tests a token to determine if it is a valid day-of-
* year. It returns TRUE if it is valid; FALSE otherwise.
*
* Input:
* token token structure.
* year year 1900-2100.
*
* Output:
* *doy day-of-year value 1-366 if a valid token is found.
*
* Return: TRUE if token was valid; FALSE otherwise.
*******************************************************************************/
static RL_BOOL ZJul_TestDOY(token, year, doy)
TOKEN *token;
RL_INT4 year, *doy;
{
if (token->is_alpha) return FALSE;
if (token->number < 1) return FALSE;
if (token->number <= 365 || token->number == Jul_YearDays(year)) {
*doy = token->number;
return TRUE;
}
return FALSE;
}
/*
********************************************************************************
* INTERNAL FUNCTIONS TO INTERPRET TIME FORMATS
********************************************************************************
* RL_BOOL ZJul_TimeTokens(TOKEN *tokens, RL_INT4 ntokens, RL_BOOL isleap,
* RL_FLT8 *secs)
*
* This internal function interprets a sequence of tokens as a time-of-day.
*
* Input:
* tokens[0...ntokens-1] array of tokens to interpret.
* isleap TRUE if this is a leap day; FALSE otherwise.
*
* Output:
* *secs number of seconds into day.
*
* Return: TRUE if a valid sequence of tokens was found;
* FALSE otherwise.
*******************************************************************************/
static RL_BOOL ZJul_TimeTokens(tokens, ntokens, isleap, secs)
TOKEN *tokens;
RL_INT4 ntokens;
RL_BOOL isleap;
RL_FLT8 *secs;
{
RL_INT4 hour, minute, second, millisec, ampm, index, maxsecs;
RL_BOOL status;
RL_FLT8 frac, scale;
RL_CHAR last;
hour = 0;
minute = 0;
second = 0;
millisec = 0;
frac = 0.;
/*******************************************************************************
* Handle empty token string
*******************************************************************************/
if (ntokens == 0) {
*secs = 0.;
return TRUE;
}
/*******************************************************************************
* Test whether the last token is AM or PM
*******************************************************************************/
if (strcmp(tokens[ntokens-1].string, "AM") == 0) ampm = 1;
else if (strcmp(tokens[ntokens-1].string, "PM") == 0) ampm = 2;
else ampm = 0;
if (ampm) {
ntokens--;
status = ZJul_TestDelim(tokens[ntokens-1].delim, " :/-");
if (!status) return FALSE;
}
/*******************************************************************************
* Check for fractional part in last token
*******************************************************************************/
if (ntokens >= 2 && tokens[ntokens-2].delim == '.') {
status = ZJul_FracToken(tokens + ntokens-1, &frac);
if (!status) return FALSE;
if (tokens[ntokens-1].delim == '.') return FALSE;
tokens[ntokens-2].delim = tokens[ntokens-1].delim;
ntokens--;
}
if (tokens[ntokens-1].delim == '.') tokens[ntokens-1].delim = ' ';
/*******************************************************************************
* Test token count and final delimiter
*******************************************************************************/
if (ntokens < 1) return FALSE;
if (ampm) status = ZJul_TestDelim(tokens[ntokens-1].delim, "HMS -/:");
else status = ZJul_TestDelim(tokens[ntokens-1].delim, "HMS -/:Z");
if (!status) return FALSE;
last = ' ';
/*******************************************************************************
* Interpret hours
*******************************************************************************/
index = 0;
if (tokens[index].delim == 'M') goto MINUTES;
if (tokens[index].delim == 'S') goto SECONDS;
status = ZJul_TestDelim(tokens[index].delim, "H: Z");
if (!status) return FALSE;
if (ampm) {
status = ZJul_TestHMS(tokens+index, 1, 13, &hour);
if (hour >= 12) hour -= 12;
if (ampm == 2) hour += 12;
}
else {
status = ZJul_TestHMS(tokens+index, 0, 24, &hour);
}
if (!status) return FALSE;
last = 'H';
isleap = (isleap && hour == 23);
index++;
if (index >= ntokens) goto DONE;
/*******************************************************************************
* Interpret minutes
*******************************************************************************/
MINUTES:
if (tokens[index].delim == 'S') goto SECONDS;
status = ZJul_TestDelim(tokens[index].delim, "M: Z");
if (!status) return FALSE;
if (last == 'H') status = ZJul_TestHMS(tokens+index, 0, 60, &minute);
else status = ZJul_TestHMS(tokens+index, 0, 1440, &minute);
if (!status) return FALSE;
last = 'M';
isleap = (isleap && minute + 60*hour == 1439);
index++;
if (index >= ntokens) goto DONE;
/*******************************************************************************
* Interpret seconds
*******************************************************************************/
SECONDS:
status = ZJul_TestDelim(tokens[index].delim, "S: Z");
if (!status) return FALSE;
if (last == 'M') maxsecs = 60 + (isleap ? 1:0);
else if (last == 'H') maxsecs = 3600 + (isleap ? 1:0);
else maxsecs = 86400 + (isleap ? 1:0);
status = ZJul_TestHMS(tokens+index, 0, maxsecs, &second);
if (!status) return FALSE;
last = 'S';
index++;
if (index >= ntokens) goto DONE;
/*******************************************************************************
* Interpret milliseconds
*******************************************************************************/
status = ZJul_TestDelim(tokens[index].delim, " Z");
if (!status) return FALSE;
status = ZJul_TestHMS(tokens+index, 0, 1000, &millisec);
if (!status) return FALSE;
last = 'Z';
index++;
if (index >= ntokens) goto DONE;
/* Any additional token constitutes an error */
return FALSE;
/*******************************************************************************
* Assemble number of seconds
*******************************************************************************/
DONE:
*secs = millisec/1000. + (RL_FLT8) (second + 60*(minute + 60*hour));
if (frac != 0) {
if (last == 'H') scale = 3600. + (isleap ? 1:0);
else if (last == 'M') scale = 60. + (isleap ? 1:0);
else if (last == 'S') scale = 1.;
else scale = 0.001;
*secs += frac * scale;
}
return TRUE;
}
/*
********************************************************************************
* RL_BOOL ZJul_TestHMS(TOKEN *token, RL_INT4 minval, RL_INT4 maxval,
* RL_INT4 *value);
*
* This internal extracts an integer token and confirms that it is in the allowed
* range.
*
* Input:
* token pointer to the token.
* minval lower limit on value (inclusive).
* maxval upper limit on value (exclusive).
*
* Output:
* *value numeric value inferred.
*
* Return: TRUE if it is a valid token; FALSE othewise.
*******************************************************************************/
static RL_BOOL ZJul_TestHMS(token, minval, maxval, value)
TOKEN *token;
RL_INT4 minval, maxval, *value;
{
/* Interpret numeric value */
if (token->is_alpha) return FALSE;
/* Test numeric value */
if (token->number < minval || token->number >= maxval) return FALSE;
*value = token->number;
return TRUE;
}
/*
********************************************************************************
* INTERNAL FUNCTION TO INTEPRET JULIAN DATE FORMATS
********************************************************************************
* RL_BOOL ZJul_JDTokens(TOKEN *tokens, RL_INT4 ntokens, RL_FLT8 *tai)
*
* This internal function interprets a sequence of tokens as a Julian Date or a
* Modified Julian Date.
*
* Input:
* tokens[0...ntokens-1] array of tokens to interpret.
*
* Output:
* *tai on success, seconds from J2000 TAI.
*
* Return: TRUE if a valid sequence of tokens was found;
* FALSE otherwise.
*******************************************************************************/
static RL_BOOL ZJul_JDTokens(tokens, ntokens, tai)
TOKEN *tokens;
RL_INT4 ntokens;
RL_FLT8 *tai;
{
RL_INT4 isJD, isMJD;
RL_FLT8 value, frac;
RL_BOOL status;
/* Test for JD or MJD prefix */
if (!tokens[0].is_alpha) return FALSE;
isJD = (strcmp(tokens[0].string, "JD") == 0);
isMJD = (strcmp(tokens[0].string, "MJD") == 0);
if (!isJD && !isMJD) return FALSE;
status = ZJul_TestDelim(tokens[0].delim, " -");
if (!status) return FALSE;
/* Interpret number */
switch (ntokens) {
case 2:
if (tokens[1].is_alpha) return FALSE;
status = ZJul_TestDelim(tokens[1].delim, " .");
if (!status) return FALSE;
value = (RL_FLT8) tokens[1].number;
break;
case 3:
if (tokens[1].is_alpha) return FALSE;
if (tokens[1].delim != '.') return FALSE;
if (tokens[2].delim != ' ') return FALSE;
status = ZJul_FracToken(tokens+2, &frac);
if (!status) return FALSE;
value = (RL_FLT8) tokens[1].number + frac;
break;
default:
return FALSE;
}
/* Convert numeric value to TAI */
if (isMJD) *tai = Jul_TAIofMJD(value, JUL_UTC_TYPE);
else *tai = Jul_TAIofJD(value, JUL_UTC_TYPE);
return TRUE;
}
/*
********************************************************************************
* INTERNAL FUNCTIONS FOR MANIPULATING TOKENS
********************************************************************************
* RL_INT4 ZJul_FillTokens(RL_CHAR *string, TOKEN *tokens, RL_INT4 max_tokens)
*
* This internal function parses a character string into a sequence of tokens.
*
* Input:
* string null-terminated character string.
* tokens[0...max_tokens-1]
* array of destination tokens.
*
* Output:
* tokens[0...] updated tokens.
*
* Return: number of tokens written. If >max_tokens, then not
* enough destination tokens were provided so part of the
* string was not parsed.
*******************************************************************************/
static RL_INT4 ZJul_FillTokens(string, tokens, max_tokens)
RL_CHAR *string;
TOKEN *tokens;
RL_INT4 max_tokens;
{
RL_CHAR *s;
RL_INT4 ntokens;
s = string;
for (ntokens=0; ntokens<MAX_TOKENS; ntokens++) {
if (*s == '\0') break;
s = ZJul_NextToken(s, tokens + ntokens);
}
return ntokens;
}
/*
********************************************************************************
* RL_CHAR *ZJul_NextToken(RL_CHAR *string, TOKEN *token)
*
* This internal function extracts the first token and delimiter from the given
* input string and then returns a pointer to the string following the delimiter.
* A token consists of an uninterrupted sequence of either numeric or alphabetic
* characters (but not a mixture of each). A single letter is treated as a
* delimiter, not a token. Alphabetic characters are converted to upper case.
* A blank delimiter is returned at the end of the string, or wherever the
* delimiter is merely a transition from alphabetic to numeric or back.
*
* Input:
* string input string;
*
* Output:
* *token token found.
*
* Return: pointer to the beginning of the next token in the
* string, which may be the string's terminal '\0'.
*******************************************************************************/
static RL_CHAR *ZJul_NextToken(string, token)
RL_CHAR *string;
TOKEN *token;
{
RL_CHAR *s;
RL_INT4 i;
/* Skip over leading whitespace */
s = string;
while (isspace(*s)) s++;
/* Copy a numeric string and (possibly alphabetic) delimiter */
if (isdigit(*s)) {
for (i=0; i<TOKEN_LEN; i++) {
if (!isdigit(*s)) break;
token->string[i] = *s;
s++;
}
token->string[i] = '\0';
token->is_alpha = FALSE;
token->number = atol(token->string);
while (isspace(*s)) s++;
if (*s == '\0' || isdigit(*s) ||
(isalpha(*s) && isalpha(s[1]))) {
token->delim = ' ';
}
else {
token->delim = toupper(*s);
for (s++; isspace(*s); s++) ;
}
}
/* Copy an alphabetic string and delimiter */
else if (isalpha(*s) && isalpha(s[1])) {
for (i=0; i<TOKEN_LEN; i++) {
if (!isalpha(*s)) break;
token->string[i] = toupper(*s);
s++;
}
token->string[i] = '\0';
token->is_alpha = TRUE;
while (isspace(*s)) s++;
if (*s == '\0' || isalpha(*s) || isdigit(*s)) {
token->delim = ' ';
}
else {
token->delim = *s;
for (s++; isspace(*s); s++) ;
}
}
/* Otherwise, copy an empty token with delimiter */
else {
token->string[0] = '\0';
token->is_alpha = TRUE;
if (*s == '\0') {
token->delim = ' ';
}
else {
token->delim = toupper(*s);
for (s++; isspace(*s); s++) ;
}
}
return s;
}
/*
********************************************************************************
* RL_BOOL ZJul_TestDelim(RL_INT4 delim, RL_CHAR *choices)
*
* This internal function tests a delimiter against a set of choices. It returns
* TRUE if the delimiter is found in the set; otherwise it returns FALSE.
*
* Input:
* delim delimiter character to test.
* choices null-terminated string of delimiter choices.
*
* Return: TRUE if the delimiter is found in the set of choices;
* FALSE otherwise.
*******************************************************************************/
static RL_BOOL ZJul_TestDelim(delim, choices)
RL_INT4 delim;
RL_CHAR *choices;
{
RL_CHAR *c;
for (c = choices; *c != '\0'; c++) {
if (*c == delim) return TRUE;
}
return FALSE;
}
/*
********************************************************************************
* RL_INT4 ZJul_FracToken(TOKEN tokens, RL_FLT8 *frac)
*
* This internal function interprets a token as the fractional part of a double-
* precision number.
*
* Input:
* token pointer to a pair of tokens.
*
* Output:
* *frac fractional value found on successful conversion.
*
* Return: TRUE if a valid interpretation was found; FALSE
* otherwise.
*******************************************************************************/
static RL_BOOL ZJul_FracToken(token, frac)
TOKEN *token;
RL_FLT8 *frac;
{
RL_CHAR string[TOKEN_LEN + 2];
if (token->is_alpha) return FALSE;
string[0] = '.';
strcpy(string+1, token->string);
*frac = atof(string);
return TRUE;
}
/*
********************************************************************************
* FORTRAN INTERFACE ROUTINES
********************************************************************************
*$ Component_name:
* FJul_ParseDT
*$ Abstract:
* Interprets a character string as a date and time.
*$ Keywords:
* JULIAN, TIME, PARSING
* FORTRAN, PUBLIC
*$ Declarations:
* logical*4 function FJul_ParseDT(string, pref, dutc, secs)
* character*(*) string, pref
* integer*4 dutc
* real*8 secs
*$ Inputs:
* string character string to parse.
* pref optional three-character string giving the preferred
* order for year, month and day values (see
* FJul_ParseDate).
*$ Outputs:
* dutc inferred days relative to January 1, 2000.
* secs inferred seconds into day.
*$ Returns:
* .TRUE. if a valid string interpretation was found; .FALSE. otherwise.
*$ Side_effects:
* none
*$ Detailed_description:
* This function interprets a character string as a date and time.
*
* A string beginning with "JD" is interpreted as a Julian date; a string
* beginning with "MJD" is interpreted as a Modified Julian Date.
*
* Otherwise, the string is first separated into a sequence of tokens
* separated by delimiters. A token consists of any sequence of letters
* or digits (but not a mixture of both). A delimiter is any punctuation
* or a single letter separating numeric tokens. Blanks are not
* significant except as delimiters when nothing else is present.
*
* The string is parsed by testing each possible division between date and
* time tokens until it finds one that produces both a valid date
* according to FJul_ParseDate() and a valid time according to
* FJul_ParseTime(). It gives preference for the longest possible date
* token sequence; it also gives preference for dates before times.
*
* If no valid date/time interpretation is found, it returns .FALSE.
*$ External_references:
* Jul_ParseDT(), FORT_Cstring()
*$ Examples:
* none
*$ Error_handling:
* It returns .FALSE. if no valid interpretation is found; no error message
* is printed.
*$ Limitations:
* none
*$ Author_and_institution:
* Mark R. Showalter
* PDS Ring-Moon Systems Node
* NASA/Ames Research Center
*$ Version_and_date:
* 1.0: December 1995
*$ Change_history:
* none
********************************************************************************
* Note: GJul_ParseDT() defined here is the intermediary routine between
* FJul_ParseDT() and Jul_ParseDT(), allowing for the fact that strings cannot
* be passed directly between FORTRAN and C. See fjulian.for for the rest of
* the code.
*
* subroutine GJul_ParseDT(string, pref, dutc, secs)
* byte string, pref
* integer*4 dutc
* real*8 secs
*******************************************************************************/
RL_INT4 FORTRAN_NAME(gjul_parsedt) (string, pref, dutc, secs)
RL_CHAR *string, *pref;
RL_INT4 *dutc;
RL_FLT8 *secs;
{
FORT_Init();
return (Jul_ParseDT(string,pref,dutc,secs) ? FTRUE:FFALSE);
}
/*
********************************************************************************
*$ Component_name:
* FJul_ParseDate
*$ Abstract:
* Interprets a character string as a date.
*$ Keywords:
* JULIAN, TIME, PARSING
* FORTRAN, PUBLIC
*$ Declarations:
* logical*4 function FJul_ParseDate(string, pref, dutc)
* character*(*) string, pref
* integer*4 dutc
*$ Inputs:
* string the character string to parse.
* pref optional three-character string giving the preferred
* order for year, month and day values (see below).
*$ Outputs:
* dutc inferred days relative to January 1, 2000.
*$ Returns:
* .TRUE. if a valid string interpretation was found; .FALSE. otherwise.
*$ Side_effects:
* none
*$ Detailed_description:
* This function interprets a character string as a date.
*
* The string is first separated into a sequence of tokens separated by
* delimiters. A token consists of any sequence of letters or digits (but
* not a mixture of both). A delimiter is any punctuation or a single
* letter separating numeric tokens. Blanks are not signficant except as
* delimiters when nothing else is present.
*
* A single token is interpreted as either a four-digit year or as an
* eight-digit merged date of the form "yyyymmdd". A pair of tokens is
* interpreted as a year plus day-of-year in that order.
*
* A set of three tokens is interpreted as a month, day and year in some
* order. Year values must be four digits or else 0-49 (which is
* interpreted as 2000-2049) or 50-99 (which is interpreted as 1950-1999).
* Months are integers 1-12 or English names January-December, abbreviated
* to three or more letters). Days are integers between 1 and 31 (or less,
* depending on the month). The sequence of tokens is interpreted in the
* preferred order first, then as month-day-year, day-month-year, and
* year-month-day until a valid interpretation is found.
*
* The pref argument, if used, enables the user to specify the preferred
* ordering for year, month, and day. It is a three-character string
* containing a "Y", "M" and "D", where the order of the three indicates
* the preferred ordering for the year, month and day, respectively.
*
* Valid delimiters are "/", "-", "." or blank. For three tokens, the
* delimiters between the tokens must be the same except for the special
* case "month day, year" where a comma is allowed after the second token.
* The final delimiter may be a "T" to be compatible with PDS date formats.
*
* If no valid interpretation is found, the function returns .FALSE.
*$ External_references:
* Jul_ParseDate(), FORT_Cstring()
*$ Examples:
* none
*$ Error_handling:
* It returns .FALSE. if no valid interpretation is found; no error message
* is printed.
*$ Limitations:
* none
*$ Author_and_institution:
* Mark R. Showalter
* PDS Ring-Moon Systems Node
* NASA/Ames Research Center
*$ Version_and_date:
* 1.0: December 1995
*$ Change_history:
* none
********************************************************************************
* Note: GJul_ParseDate() defined here is the intermediary routine between
* FJul_ParseDate() and Jul_ParseDate(), allowing for the fact that strings
* cannot be passed directly between FORTRAN and C. See fjulian.for for the
* rest of the code.
*
* subroutine GJul_ParseDate(string, pref, dutc)
* byte string, pref
* integer*4 dutc
*******************************************************************************/
RL_INT4 FORTRAN_NAME(gjul_parsedate) (string, pref, dutc)
RL_CHAR *string, *pref;
RL_INT4 *dutc;
{
FORT_Init();
return (Jul_ParseDate(string,pref,dutc) ? FTRUE:FFALSE);
}
/*
********************************************************************************
*$ Component_name:
* FJul_ParseTime
*$ Abstract:
* Interprets a character string as a time.
*$ Keywords:
* JULIAN, TIME, PARSING
* FORTRAN, PUBLIC
*$ Declarations:
* logical*4 function FJul_ParseTime(string, isleap, secs)
* character*(*) string
* logical*4 isleap
* real*8 secs
*$ Inputs:
* string the character string to parse.
* isleap .TRUE. if the day has a leap second; .FALSE. otherwise.
*$ Outputs:
* dutc inferred seconds into day.
*$ Returns:
* .TRUE. if a valid string interpretation was found; .FALSE. otherwise.
*$ Side_effects:
* none
*$ Detailed_description:
* This function interprets a character string as a time.
*
* The string is first separated into a sequence of tokens separated by
* delimiters. A token consists of any sequence of letters or digits (but
* not a mixture of both). A delimiter is any punctuation or a single
* letter separating numeric tokens. Blanks are not signficant except as
* delimiters when nothing else is present.
*
* Tokens are interpreted as hour, minute, second and millisecond in that
* order although not all a required; the last token may have a fractional
* part. The string may end in "AM" or "PM", or else "Z" to be compatible
* with PDS time formats. Valid delimiters are colons or blanks.
*
* Individual tokens may also end in "H", "M" or "S" to indicate hours,
* minutes or seconds respectively. This enables the user to express a
* time in minutes (without hours) or seconds (without hours and minutes).
*
* If no valid interpretation is found, the function returns .FALSE.
*$ External_references:
* Jul_ParseTime(), FORT_Cstring()
*$ Examples:
* none
*$ Error_handling:
* It returns .FALSE. if no valid interpretation is found; no error message
* is printed.
*$ Limitations:
* none
*$ Author_and_institution:
* Mark R. Showalter
* PDS Ring-Moon Systems Node
* NASA/Ames Research Center
*$ Version_and_date:
* 1.0: November 1995
*$ Change_history:
* none
********************************************************************************
* Note: GJul_ParseTime() defined here is the intermediary routine between
* FJul_ParseTime() and Jul_ParseTime(), allowing for the fact that strings
* cannot be passed directly between FORTRAN and C. See fjulian.for for the
* rest of the code.
*
* subroutine GJul_ParseTime(string, isleap, secs)
* byte string
* logical*4 isleap
* real*8 secs
*******************************************************************************/
RL_INT4 FORTRAN_NAME(gjul_parsetime) (string, isleap, secs)
RL_CHAR *string;
RL_INT4 *isleap;
RL_FLT8 *secs;
{
FORT_Init();
return (Jul_ParseTime(string, (RL_BOOL) *isleap, secs) ? FTRUE:FFALSE);
}
/*******************************************************************************
*/