/*****************************************************************************/ /* gmt.c Greenwich Mean Time related functions. A little convoluted because SYS$TIMEZONE_DIFFERENTIAL was often unreliable (at least administered) under VMS earlier than V7.0, and WASD's stated objective of support back to VMS V6.0, hence the HTTPD$GMT hack used by WASD. COPYRIGHT --------- Copyright (C) 2005-2024 Mark G.Daniel This program, comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under the conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version. VERSION HISTORY --------------- 01-FEB-2005 MGD initial */ /*****************************************************************************/ #ifdef SOYMAIL_VMS_V7 #undef _VMS_V6_SOURCE #define _VMS_V6_SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif #pragma nomember_alignment /* standard C header files */ #include #include #include #include #include #include #include /* VMS related header files */ #include #include #include #include /* application header file */ #include #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) !(((x) & STS$M_SUCCESS)) #define BOOL int #define TRUE 1 #define FALSE 0 #define FI_LI __FILE__, __LINE__ /******************/ /* global storage */ /******************/ BOOL GmtTimeAhead; long GmtSecondsOffset; int64 GmtTimeDelta64; char GmtTimeString [32], GmtVmsTimeString [32]; /**************/ /* prototypes */ /**************/ int lib$add_times (__unknown_params); int lib$cvt_vectim (__unknown_params); int lib$day_of_week (__unknown_params); int lib$mult_delta_time (__unknown_params); int lib$sub_times (__unknown_params); int sys$bintim (__unknown_params); int sys$fao (__unknown_params); int sys$gettim (__unknown_params); int sys$numtim (__unknown_params); int sys$trnlnm (__unknown_params); /********************/ /* external storage */ /********************/ extern BOOL Debug, WatchEnabled; /*****************************************************************************/ /* Create an HTTP format Greenwich Mean Time (UTC) time string in the storage pointed at by 'TimeString', e.g. "Fri, 25 Aug 1995 17:32:40 GMT" (RFC 1123). This must be at least 31 characters capacity. If 'BinTimePtr' is NULL the time string represents the current time. If it points to a quadword, VMS time value the string represents that time. */ int GmtBinTimeToString ( char *TimeString, int64 *Time64Ptr, unsigned long UnixTime ) { static char *DayNames [] = { "", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; static char *MonthName [] = { "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static $DESCRIPTOR (HttpTimeFaoDsc, "!AZ, !2ZW !AZ !4ZW !2ZW:!2ZW:!2ZW GMT"); static $DESCRIPTOR (TimeStringDsc, ""); int status; int64 Time64, GmTime64; unsigned short Length; unsigned short NumTime [7]; unsigned long DayOfWeek; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GmtBinTimeToString()\n"); if (UnixTime) GmtUnixToVmsBinTime (UnixTime, &GmTime64); else if (!Time64Ptr) sys$gettim (&GmTime64); else GmTime64 = *Time64Ptr; status = GmtTimeAdjust (TRUE, &GmTime64); if (VMSnok (status)) return (status); status = sys$numtim (&NumTime, &GmTime64); if (Debug) fprintf (stdout, "sys$numtim() %%X%08.08X %d %d %d %d %d %d %d\n", status, NumTime[0], NumTime[1], NumTime[2], NumTime[3], NumTime[4], NumTime[5], NumTime[6]); status = lib$day_of_week (&GmTime64, &DayOfWeek); if (VMSnok (status)) return (status); if (Debug) fprintf (stdout, "lib$day_of_week() %%X%08.08X is %d\n", status, DayOfWeek); /* set the descriptor address and size of the resultant time string */ TimeStringDsc.dsc$w_length = 30; TimeStringDsc.dsc$a_pointer = TimeString; status = sys$fao (&HttpTimeFaoDsc, &Length, &TimeStringDsc, DayNames[DayOfWeek], NumTime[2], MonthName[NumTime[1]], NumTime[0], NumTime[3], NumTime[4], NumTime[5]); if (VMSnok (status)) { if (Debug) fprintf (stdout, "sys$fao() %%X%08.08X\n", status); TimeString[0] = '\0'; } else TimeString[Length] = '\0'; if (Debug) fprintf (stdout, "|%s|\n", TimeString); return (status); } /*****************************************************************************/ /* */ /* we've discovered it! */ #define DISCOVER_VMS_AT_1970 0 int GmtUnixToVmsBinTime ( unsigned long UnixTime, int64 *Time64Ptr ) { #if DISCOVER_VMS_AT_1970 static $DESCRIPTOR (UnixEpoch, "1-JAN-1970 00:00:00.00"); #endif /* DISCOVER_VMS_AT_1970 */ static int64 OneSecondDelta64 = -10000000; static int64 BinTime1970 = 1273708544 + ((uint64)8164711 << 32); int status; int64 UnixTime64; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GmtUnixToVmsBinTime()\n"); #if DISCOVER_VMS_AT_1970 sys$bintim (&UnixEpoch, &BinTime1970); /* shows the binary time at 1970 */ if (Debug) fprintf (stdout, "%d %d\n", BinTime1970[0], BinTime1970[1]); #endif /* DISCOVER_VMS_AT_1970 */ UnixTime64 = OneSecondDelta64; status = lib$mult_delta_time (&UnixTime64, &UnixTime64); if (Debug) fprintf (stdout, "lib$mult_delta_time() %%X%08.08X\n", status); status = lib$add_times (&UnixTime64, &BinTime1970, Time64Ptr); if (Debug) fprintf (stdout, "lib$add_times() %%X%08.08X\n", status); return (status); } /*****************************************************************************/ /* Given a string such as "Fri, 25 Aug 1995 17:32:40 GMT" (RFC 1123), or "Friday, 25-Aug-1995 17:32:40 GMT" (RFC 1036), create an internal, local, binary time with the current GMT offset. See complementary function GmtBinToString(). Returns a VMS status. */ int GmtStringToBinTime ( char *TimeString, int64 *Time64Ptr ) { static char *MonthName [] = { "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; int status; char *tptr; unsigned short Length; unsigned short NumTime [7] = { 0,0,0,0,0,0,0 }; unsigned long DayOfWeek; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GmtStringToBinTime() |%s|\n", TimeString); tptr = TimeString; /* hunt straight for the comma after the weekday name! */ while (*tptr && *tptr != ',') tptr++; if (*tptr) tptr++; /* span white space between weekday name and date */ while (*tptr && isspace(*tptr)) tptr++; if (Debug) fprintf (stdout, "tptr |%s|\n", tptr); if (!*tptr) return (STS$K_ERROR); /* get the date and then skip to month name */ if (isdigit(*tptr)) NumTime[2] = atoi (tptr); while (*tptr && isdigit(*tptr)) tptr++; while (*tptr && (*tptr == '-' || isspace(*tptr))) tptr++; if (Debug) fprintf (stdout, "tptr |%s|\n", tptr); if (!*tptr) return (STS$K_ERROR); /* get the month number from the name and skip to the year */ for (NumTime[1] = 1; NumTime[1] <= 12; NumTime[1]++) if (!strncmp (tptr, MonthName[NumTime[1]], 3)) break; if (NumTime[1] > 12) return (STS$K_ERROR); while (*tptr && isalpha(*tptr)) tptr++; while (*tptr && (*tptr == '-' || isspace(*tptr))) tptr++; if (Debug) fprintf (stdout, "tptr |%s|\n", tptr); if (!*tptr) return (STS$K_ERROR); /* get the year and then skip to the hour */ if (isdigit(*tptr)) { NumTime[0] = atoi (tptr); if (NumTime[0] < 100) NumTime[0] += 1900; } while (*tptr && isdigit(*tptr)) tptr++; while (*tptr && isspace(*tptr)) tptr++; if (Debug) fprintf (stdout, "tptr |%s|\n", tptr); if (!*tptr) return (STS$K_ERROR); /* get the hour, minute and second */ if (isdigit(*tptr)) NumTime[3] = atoi (tptr); while (*tptr && isdigit(*tptr)) tptr++; if (*tptr == ':') tptr++; if (isdigit(*tptr)) NumTime[4] = atoi (tptr); while (*tptr && isdigit(*tptr)) tptr++; if (*tptr == ':') tptr++; if (isdigit(*tptr)) NumTime[5] = atoi (tptr); while (*tptr && isdigit(*tptr)) tptr++; if (*tptr == ':') tptr++; if (!*tptr) return (STS$K_ERROR); /* the only thing remaining should be the "GMT" */ while (*tptr && isspace(*tptr)) tptr++; if (Debug) fprintf (stdout, "tptr |%s|\n", tptr); if (strncmp (tptr, "GMT", 3)) return (STS$K_ERROR); /*******************************************/ /* convert what looks like legitimate GMT! */ /*******************************************/ if (Debug) fprintf (stdout, "NumTime[] %d %d %d %d %d %d %d\n", NumTime[0], NumTime[1], NumTime[2], NumTime[3], NumTime[4], NumTime[5], NumTime[6]); status = lib$cvt_vectim (&NumTime, Time64Ptr); if (VMSnok (status)) return (status); if (Debug) fprintf (stdout, "lib$cvt_vectim() %%X%08.08X\n", status); return (GmtTimeAdjust (FALSE, Time64Ptr)); } /*****************************************************************************/ /* Determine the offset from GMT (UTC) using either the HTTPD$GMT or SYS$TIMEZONE_DIFFERENTIAL logicals. If HTTPD$GMT is not defined time should be set from the timezone differential logical. */ int GmtSetDelta () { static BOOL UseTimezoneDifferential = FALSE; int status; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GmtSetDelta()\n"); if (!UseTimezoneDifferential) { status = GmtSetDeltaFromHttpdGmt(); /* return if error and that error was not that the name did not exist */ if (VMSok (status) || status != SS$_NOLOGNAM) return (status); } UseTimezoneDifferential = TRUE; GmtSetDeltaFromTimezone(); return (status); } /*****************************************************************************/ /* The SYS$TIMEZONE_DIFFERENTIAL logical contains the number of seconds offset from GMT (UTC) as a positive (ahead) or negative (behind) number. Set the 'GmtTimeString' global storage to a "+hh:mm" or "-hh:mm" string equivalent, and the 'GmtTimeAhead' global boolean and 'GmtVmsTimeString' delta-time global string. */ int GmtSetDeltaFromTimezone () { static unsigned short Length; static $DESCRIPTOR (TimezoneLogicalNameDsc, "SYS$TIMEZONE_DIFFERENTIAL"); static $DESCRIPTOR (LnmSystemDsc, "LNM$SYSTEM"); static $DESCRIPTOR (GmtTimeStringFaoDsc, "!AZ!2ZL:!2ZL"); static $DESCRIPTOR (GmtVmsTimeStringFaoDsc, "0 !2ZL:!2ZL"); static $DESCRIPTOR (GmtTimeStringDsc, GmtTimeString); static struct { short int buf_len; short int item; void *buf_addr; unsigned short *ret_len; } LnmItems [] = { { sizeof(GmtTimeString)-1, LNM$_STRING, GmtTimeString, &Length }, { 0,0,0,0 } }; int status; long Hours, Minutes, Seconds; char *SignPtr; $DESCRIPTOR (GmtVmsTimeStringDsc, GmtVmsTimeString); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GmtSetDeltaFromTimezone()\n"); status = sys$trnlnm (0, &LnmSystemDsc, &TimezoneLogicalNameDsc, 0, &LnmItems); if (Debug) fprintf (stdout, "sys$trnlnm() %%X%08.08X\n", status); if (VMSnok (status)) return (status); GmtTimeString[Length] = '\0'; if (Debug) fprintf (stdout, "GmtTimeString |%s|\n", GmtTimeString); GmtSecondsOffset = Seconds = atol(GmtTimeString); if (Debug) fprintf (stdout, "GmtSecondsOffset: %d\n", GmtSecondsOffset); if (Seconds < 0) { GmtTimeAhead = FALSE; Seconds = -Seconds; SignPtr = "-"; } else { GmtTimeAhead = TRUE; SignPtr = "+"; } Hours = Seconds / 3600; Minutes = (Seconds - Hours * 3600) / 60; if (Debug) fprintf (stdout, "%d %s%d:%d\n", Seconds, SignPtr, Hours, Minutes); sys$fao (&GmtTimeStringFaoDsc, &Length, &GmtTimeStringDsc, SignPtr, Hours, Minutes); GmtTimeString[Length] = '\0'; if (Debug) fprintf (stdout, "GmtTimeString |%s|\n", GmtTimeString); sys$fao (&GmtVmsTimeStringFaoDsc, &Length, &GmtVmsTimeStringDsc, Hours, Minutes); GmtVmsTimeString[Length] = '\0'; if (Debug) fprintf (stdout, "GmtVmsTimeString |%s|\n", GmtVmsTimeString); GmtVmsTimeStringDsc.dsc$w_length = Length; status = sys$bintim (&GmtVmsTimeStringDsc, &GmtTimeDelta64); if (VMSnok (status)) return (status); if (GmtTimeDelta64) return (status); /* time must have been zero, make it one one-hundreth of a second */ GmtTimeDelta64 = -100000; return (SS$_NORMAL); } /*****************************************************************************/ /* Translate the logical HTTPD$GMT (defined to be something like "+10:30" or "- 01:15") and convert it into a delta time structure and store in 'GmtTimeDelta64'. Store whether it is in advance or behind GMT in boolean 'GmtTimeAhead'. Store the logical string in 'GmtTimeString'. */ int GmtSetDeltaFromHttpdGmt () { static unsigned short Length; static $DESCRIPTOR (GmtLogicalNameDsc, "HTTPD$GMT"); static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV"); static struct { short int buf_len; short int item; void *buf_addr; unsigned short *ret_len; } LnmItems [] = { { sizeof(GmtTimeString)-1, LNM$_STRING, GmtTimeString, &Length }, { 0,0,0,0 } }; int status, hours, minutes; char *cptr, *sptr; $DESCRIPTOR (GmtVmsTimeStringDsc, GmtVmsTimeString); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GmtSetDeltaFromHttpdGmt()\n"); status = sys$trnlnm (0, &LnmFileDevDsc, &GmtLogicalNameDsc, 0, &LnmItems); if (Debug) fprintf (stdout, "sys$trnlnm() %%X%08.08X\n", status); if (VMSnok (status)) return (status); GmtTimeString[Length] = '\0'; if (Debug) fprintf (stdout, "GmtTimeString |%s|\n", GmtTimeString); if (GmtTimeString[0] == '$') return (SS$_NORMAL); if (*(cptr = GmtTimeString) == '-') GmtTimeAhead = FALSE; else GmtTimeAhead = TRUE; if (*cptr == '+' || *cptr == '-') cptr++; sptr = GmtVmsTimeString; *sptr++ = '0'; *sptr++ = ' '; while (*cptr) *sptr++ = *cptr++; *sptr = '\0'; if (Debug) fprintf (stdout, "GmtVmsTimeString |%s|\n", GmtVmsTimeString); cptr = GmtVmsTimeString; if (*cptr == '+' || *cptr == '-') cptr++; hours = atol(cptr); while (*sptr && *cptr != ':') cptr++; if (*cptr == ':') cptr++; minutes = atol(cptr); GmtSecondsOffset = hours * 3600 + minutes * 60; if (!GmtTimeAhead) GmtSecondsOffset = -GmtSecondsOffset; if (Debug) fprintf (stdout, "GmtSecondsOffset: %d\n", GmtSecondsOffset); GmtVmsTimeStringDsc.dsc$w_length = sptr - GmtVmsTimeString; if (VMSnok (status)) return (status); status = sys$bintim (&GmtVmsTimeStringDsc, &GmtTimeDelta64); if (GmtTimeDelta64) return (status); /* time must have been zero, make it one one-hundreth of a second */ GmtTimeDelta64 = -100000; return (SS$_NORMAL); } /*****************************************************************************/ /* The GMT is generated by calculating an offset using 'GmtTimeDelta64' and boolean 'GmtTimeAhead'. Adjust either to or from GMT. */ int GmtTimeAdjust ( BOOL ToGmTime, int64 *Time64Ptr ) { int status; int64 AdjustedTime64; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GmtTimeAdjust() ToGmTime: %d\n", ToGmTime); if (GmtTimeDelta64) GmtSetDelta (); if ((ToGmTime && GmtTimeAhead) || (!ToGmTime && !GmtTimeAhead)) { /* to GMT from local and ahead of GMT, or to local from GMT and behind */ status = lib$sub_times (Time64Ptr, &GmtTimeDelta64, &AdjustedTime64); if (Debug) fprintf (stdout, "lib$sub_times() %%X%08.08X\n", status); } else { /* to GMT from local and behind GMT, or to local from GMT and ahead */ status = lib$add_times (Time64Ptr, &GmtTimeDelta64, &AdjustedTime64); if (Debug) fprintf (stdout, "lib$add_times() %%X%08.08X\n", status); } if (Debug) { unsigned short Length; char String [64]; $DESCRIPTOR (AdjustedTimeFaoDsc, "AdjustedTime64: |!%D|\n"); $DESCRIPTOR (StringDsc, String); sys$fao (&AdjustedTimeFaoDsc, &Length, &StringDsc, &AdjustedTime64); String[Length] = '\0'; fprintf (stdout, "%s", String); } *Time64Ptr = AdjustedTime64; return (status); } /*****************************************************************************/