/*
******************************************************************************** * 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); } /********************************************************************************/