/***************************************************************/ /* */ /* FUNCS.C */ /* */ /* This file contains the built-in functions used in */ /* expressions. */ /* */ /* This file is part of REMIND. */ /* Copyright (C) 1992-2021 by Dianne Skoll */ /* */ /***************************************************************/ #include "version.h" #include "config.h" #include #include #include #include #include #include #ifdef HAVE_SYS_FILE_H #include #endif #include #include #ifdef TM_IN_SYS_TIME #include #else #include #endif #ifndef R_OK #define R_OK 4 #define W_OK 2 #define X_OK 1 #endif #include "types.h" #include "globals.h" #include "protos.h" #include "err.h" #include "expr.h" /* Defines that used to be static variables */ #define Nargs (info->nargs) #define RetVal (info->retval) /* Function prototypes */ static int FADawn (func_info *); static int FADusk (func_info *); static int FAbs (func_info *); static int FAccess (func_info *); static int FAmpm (func_info *); static int FIsAny (func_info *); static int FArgs (func_info *); static int FAsc (func_info *); static int FBaseyr (func_info *); static int FChar (func_info *); static int FChoose (func_info *); static int FCoerce (func_info *); static int FCurrent (func_info *); static int FDate (func_info *); static int FDateTime (func_info *); static int FDatepart (func_info *); static int FDawn (func_info *); static int FDay (func_info *); static int FDaysinmon (func_info *); static int FDefined (func_info *); static int FDosubst (func_info *); static int FDusk (func_info *); static int FEasterdate (func_info *); static int FEvalTrig (func_info *); static int FFiledate (func_info *); static int FFiledatetime (func_info *); static int FFiledir (func_info *); static int FFilename (func_info *); static int FGetenv (func_info *); static int FHebdate (func_info *); static int FHebday (func_info *); static int FHebmon (func_info *); static int FHebyear (func_info *); static int FHour (func_info *); static int FIif (func_info *); static int FIndex (func_info *); static int FIsdst (func_info *); static int FIsleap (func_info *); static int FIsomitted (func_info *); static int FLanguage (func_info *); static int FLower (func_info *); static int FMax (func_info *); static int FMin (func_info *); static int FMinsfromutc (func_info *); static int FMinute (func_info *); static int FMon (func_info *); static int FMonnum (func_info *); static int FMoondate (func_info *); static int FMoondatetime (func_info *); static int FMoonphase (func_info *); static int FMoontime (func_info *); static int FNDawn (func_info *); static int FNDusk (func_info *); static int FNonomitted (func_info *); static int FNow (func_info *); static int FOrd (func_info *); static int FOstype (func_info *); static int FPlural (func_info *); static int FPsmoon (func_info *); static int FPsshade (func_info *); static int FRealCurrent (func_info *); static int FRealnow (func_info *); static int FRealtoday (func_info *); static int FSgn (func_info *); static int FShell (func_info *); static int FSlide (func_info *); static int FStrlen (func_info *); static int FSubstr (func_info *); static int FSunrise (func_info *); static int FSunset (func_info *); static int FTime (func_info *); static int FTimepart (func_info *); static int FToday (func_info *); static int FTrigback (func_info *); static int FTrigdate (func_info *); static int FTrigdatetime (func_info *); static int FTrigdelta (func_info *); static int FTrigduration (func_info *); static int FTrigeventduration(func_info *); static int FTrigeventstart (func_info *); static int FTrigfrom (func_info *); static int FTrigger (func_info *); static int FTrigpriority (func_info *); static int FTrigrep (func_info *); static int FTrigscanfrom (func_info *); static int FTrigtime (func_info *); static int FTrigtimedelta (func_info *); static int FTrigtimerep (func_info *); static int FTriguntil (func_info *); static int FTrigvalid (func_info *); static int FTypeof (func_info *); static int FTzconvert (func_info *); static int FUpper (func_info *); static int FValue (func_info *); static int FVersion (func_info *); static int FWeekno (func_info *); static int FWkday (func_info *); static int FWkdaynum (func_info *); static int FYear (func_info *); static int FShellescape (func_info *); static int CleanUpAfterFunc (func_info *); static int CheckArgs (BuiltinFunc *f, int nargs); static int SunStuff (int rise, double cosz, int jul); /* "Overload" the struct Operator definition */ #define NO_MAX 127 /* Caches for extracting months, days, years from dates - may improve performance slightly. */ static int CacheJul = -1; static int CacheYear, CacheMon, CacheDay; static int CacheHebJul = -1; static int CacheHebYear, CacheHebMon, CacheHebDay; /* We need access to the value stack */ extern Value ValStack[]; extern int ValStackPtr; /* Macro for accessing arguments from the value stack - args are numbered from 0 to (Nargs - 1) */ #define ARG(x) (ValStack[ValStackPtr - Nargs + (x)]) #define ARGV(x) ARG(x).v.val #define ARGSTR(x) ARG(x).v.str #define ASSERT_TYPE(x, t) if (ARG(x).type != t) return E_BAD_TYPE /* Macro for getting date part of a date or datetime value */ #define DATEPART(x) ((x).type == DATE_TYPE ? (x).v.val : ((x).v.val / MINUTES_PER_DAY)) /* Macro for getting time part of a time or datetime value */ #define TIMEPART(x) ((x).type == TIME_TYPE ? (x).v.val : ((x).v.val % MINUTES_PER_DAY)) #define HASDATE(x) ((x).type == DATE_TYPE || (x).type == DATETIME_TYPE) #define HASTIME(x) ((x).type == TIME_TYPE || (x).type == DATETIME_TYPE) /* Macro for copying a value while destroying original copy */ #define DCOPYVAL(x, y) ( (x) = (y), (y).type = ERR_TYPE ) /* Get at RetVal.v.val easily */ #define RETVAL info->retval.v.val /* Convenience macros */ #define UPPER(c) (islower(c) ? toupper(c) : c) #define LOWER(c) (isupper(c) ? tolower(c) : c) /* The array holding the built-in functions. */ BuiltinFunc Func[] = { /* Name minargs maxargs is_constant func */ { "abs", 1, 1, 1, FAbs }, { "access", 2, 2, 0, FAccess }, { "adawn", 0, 1, 0, FADawn}, { "adusk", 0, 1, 0, FADusk}, { "ampm", 1, 3, 1, FAmpm }, { "args", 1, 1, 0, FArgs }, { "asc", 1, 1, 1, FAsc }, { "baseyr", 0, 0, 1, FBaseyr }, { "char", 1, NO_MAX, 1, FChar }, { "choose", 2, NO_MAX, 1, FChoose }, { "coerce", 2, 2, 1, FCoerce }, { "current", 0, 0, 0, FCurrent }, { "date", 3, 3, 1, FDate }, { "datepart", 1, 1, 1, FDatepart }, { "datetime", 2, 5, 1, FDateTime }, { "dawn", 0, 1, 0, FDawn}, { "day", 1, 1, 1, FDay }, { "daysinmon", 2, 2, 1, FDaysinmon }, { "defined", 1, 1, 0, FDefined }, { "dosubst", 1, 3, 0, FDosubst }, { "dusk", 0, 1, 0, FDusk }, { "easterdate", 1, 1, 0, FEasterdate }, { "evaltrig", 1, 2, 0, FEvalTrig }, { "filedate", 1, 1, 0, FFiledate }, { "filedatetime", 1, 1, 0, FFiledatetime }, { "filedir", 0, 0, 0, FFiledir }, { "filename", 0, 0, 0, FFilename }, { "getenv", 1, 1, 0, FGetenv }, { "hebdate", 2, 5, 0, FHebdate }, { "hebday", 1, 1, 0, FHebday }, { "hebmon", 1, 1, 0, FHebmon }, { "hebyear", 1, 1, 0, FHebyear }, { "hour", 1, 1, 1, FHour }, { "iif", 1, NO_MAX, 1, FIif }, { "index", 2, 3, 1, FIndex }, { "isany", 1, NO_MAX, 1, FIsAny }, { "isdst", 0, 2, 0, FIsdst }, { "isleap", 1, 1, 1, FIsleap }, { "isomitted", 1, 1, 0, FIsomitted }, { "language", 0, 0, 1, FLanguage }, { "lower", 1, 1, 1, FLower }, { "max", 1, NO_MAX, 1, FMax }, { "min", 1, NO_MAX, 1, FMin }, { "minsfromutc", 0, 2, 0, FMinsfromutc }, { "minute", 1, 1, 1, FMinute }, { "mon", 1, 1, 1, FMon }, { "monnum", 1, 1, 1, FMonnum }, { "moondate", 1, 3, 0, FMoondate }, { "moondatetime", 1, 3, 0, FMoondatetime }, { "moonphase", 0, 2, 0, FMoonphase }, { "moontime", 1, 3, 0, FMoontime }, { "ndawn", 0, 1, 0, FNDawn}, { "ndusk", 0, 1, 0, FNDusk}, { "nonomitted", 2, NO_MAX, 0, FNonomitted }, { "now", 0, 0, 0, FNow }, { "ord", 1, 1, 1, FOrd }, { "ostype", 0, 0, 1, FOstype }, { "plural", 1, 3, 1, FPlural }, { "psmoon", 1, 4, 1, FPsmoon}, { "psshade", 1, 3, 1, FPsshade}, { "realcurrent", 0, 0, 0, FRealCurrent}, { "realnow", 0, 0, 0, FRealnow}, { "realtoday", 0, 0, 0, FRealtoday }, { "sgn", 1, 1, 1, FSgn }, { "shell", 1, 2, 0, FShell }, { "shellescape", 1, 1, 1, FShellescape }, { "slide", 2, NO_MAX, 0, FSlide }, { "strlen", 1, 1, 1, FStrlen }, { "substr", 2, 3, 1, FSubstr }, { "sunrise", 0, 1, 0, FSunrise}, { "sunset", 0, 1, 0, FSunset }, { "time", 2, 2, 1, FTime }, { "timepart", 1, 1, 1, FTimepart }, { "today", 0, 0, 0, FToday }, { "trigback", 0, 0, 0, FTrigback }, { "trigdate", 0, 0, 0, FTrigdate }, { "trigdatetime", 0, 0, 0, FTrigdatetime }, { "trigdelta", 0, 0, 0, FTrigdelta }, { "trigduration", 0, 0, 0, FTrigduration }, { "trigeventduration", 0, 0, 0, FTrigeventduration }, { "trigeventstart", 0, 0, 0, FTrigeventstart }, { "trigfrom", 0, 0, 0, FTrigfrom }, { "trigger", 1, 3, 0, FTrigger }, { "trigpriority", 0, 0, 0, FTrigpriority }, { "trigrep", 0, 0, 0, FTrigrep }, { "trigscanfrom", 0, 0, 0, FTrigscanfrom }, { "trigtime", 0, 0, 0, FTrigtime }, { "trigtimedelta",0, 0, 0, FTrigtimedelta }, { "trigtimerep", 0, 0, 0, FTrigtimerep }, { "triguntil", 0, 0, 0, FTriguntil }, { "trigvalid", 0, 0, 0, FTrigvalid }, { "typeof", 1, 1, 1, FTypeof }, { "tzconvert", 2, 3, 0, FTzconvert }, { "upper", 1, 1, 1, FUpper }, { "value", 1, 2, 0, FValue }, { "version", 0, 0, 1, FVersion }, { "weekno", 0, 3, 1, FWeekno }, { "wkday", 1, 1, 1, FWkday }, { "wkdaynum", 1, 1, 1, FWkdaynum }, { "year", 1, 1, 1, FYear } }; /* Need a variable here - Func[] array not really visible to outside. */ int NumFuncs = sizeof(Func) / sizeof(Operator) ; /***************************************************************/ /* */ /* CallFunc */ /* */ /* Call a function given a pointer to it, and the number */ /* of arguments supplied. */ /* */ /***************************************************************/ int CallFunc(BuiltinFunc *f, int nargs) { register int r = CheckArgs(f, nargs); int i; func_info info_obj; func_info *info = &info_obj; Nargs = nargs; RetVal.type = ERR_TYPE; if (DebugFlag & DB_PRTEXPR) { fprintf(ErrFp, "%s(", f->name); for (i=0; i "); if (r) { fprintf(ErrFp, "%s\n", ErrMsg[r]); return r; } } if (r) { Eprint("%s(): %s", f->name, ErrMsg[r]); return r; } r = (*(f->func))(info); if (r) { DestroyValue(RetVal); if (DebugFlag & DB_PRTEXPR) fprintf(ErrFp, "%s\n", ErrMsg[r]); else Eprint("%s(): %s", f->name, ErrMsg[r]); return r; } if (DebugFlag & DB_PRTEXPR) { PrintValue(&RetVal, ErrFp); fprintf(ErrFp, "\n"); } r = CleanUpAfterFunc(info); return r; } /***************************************************************/ /* */ /* CheckArgs */ /* */ /* Check that the right number of args have been supplied */ /* for a function. */ /* */ /***************************************************************/ static int CheckArgs(BuiltinFunc *f, int nargs) { if (nargs < f->minargs) return E_2FEW_ARGS; if (nargs > f->maxargs && f->maxargs != NO_MAX) return E_2MANY_ARGS; return OK; } /***************************************************************/ /* */ /* CleanUpAfterFunc */ /* */ /* Clean up the stack after a function call - remove */ /* args and push the new value. */ /* */ /***************************************************************/ static int CleanUpAfterFunc(func_info *info) { Value v; int i; for (i=0; itype != STR_TYPE) return E_BAD_TYPE; RetVal.type = INT_TYPE; size_t l = strlen(v->v.str); if (l > INT_MAX) return E_2HIGH; RETVAL = (int) l; return OK; } /***************************************************************/ /* */ /* FBaseyr - system base year */ /* */ /***************************************************************/ static int FBaseyr(func_info *info) { RetVal.type = INT_TYPE; RETVAL = BASE; return OK; } /***************************************************************/ /* */ /* FDate - make a date from year, month, day. */ /* */ /***************************************************************/ static int FDate(func_info *info) { int y, m, d; int ytemp, mtemp, dtemp; /* Any arg can be a date (in which case we use the corresponding component) or an integer */ if (HASDATE(ARG(0))) { FromJulian(DATEPART(ARG(0)), &ytemp, &mtemp, &dtemp); y = ytemp; } else { ASSERT_TYPE(0, INT_TYPE); y = ARGV(0); } if (HASDATE(ARG(1))) { FromJulian(DATEPART(ARG(1)), &ytemp, &mtemp, &dtemp); m = mtemp; } else { ASSERT_TYPE(1, INT_TYPE); m = ARGV(1) - 1; } if (HASDATE(ARG(2))) { FromJulian(DATEPART(ARG(2)), &ytemp, &mtemp, &dtemp); d = dtemp; } else { ASSERT_TYPE(2, INT_TYPE); d = ARGV(2); } if (!DateOK(y, m, d)) { return E_BAD_DATE; } RetVal.type = DATE_TYPE; RETVAL = Julian(y, m, d); return OK; } /***************************************************************/ /* */ /* FDateTime - make a datetime from one of these combos: */ /* DATE, TIME */ /* DATE, HOUR, MINUTE */ /* YEAR, MONTH, DAY, TIME */ /* YEAR, MONTH, DAY, HOUR, MINUTE */ /* */ /***************************************************************/ static int FDateTime(func_info *info) { int y, m, d; RetVal.type = DATETIME_TYPE; switch(Nargs) { case 2: if (ARG(0).type != DATE_TYPE || ARG(1).type != TIME_TYPE) return E_BAD_TYPE; RETVAL = (MINUTES_PER_DAY * ARGV(0)) + ARGV(1); return OK; case 3: if (ARG(0).type != DATE_TYPE || ARG(1).type != INT_TYPE || ARG(2).type != INT_TYPE) return E_BAD_TYPE; if (ARGV(1) < 0 || ARGV(2) < 0) return E_2LOW; if (ARGV(1) > 23 || ARGV(2) > 59) return E_2HIGH; RETVAL = (MINUTES_PER_DAY * ARGV(0)) + 60 * ARGV(1) + ARGV(2); return OK; case 4: if (ARG(0).type != INT_TYPE || ARG(1).type != INT_TYPE || ARG(2).type != INT_TYPE || ARG(3).type != TIME_TYPE) return E_BAD_TYPE; y = ARGV(0); m = ARGV(1) - 1; d = ARGV(2); if (!DateOK(y, m, d)) return E_BAD_DATE; RETVAL = Julian(y, m, d) * MINUTES_PER_DAY + ARGV(3); return OK; case 5: if (ARG(0).type != INT_TYPE || ARG(1).type != INT_TYPE || ARG(2).type != INT_TYPE || ARG(3).type != INT_TYPE || ARG(4).type != INT_TYPE) return E_BAD_TYPE; y = ARGV(0); m = ARGV(1) - 1; d = ARGV(2); if (!DateOK(y, m, d)) return E_BAD_DATE; if (ARGV(3) < 0 || ARGV(4) < 0) return E_2LOW; if (ARGV(3) > 23 || ARGV(4) > 59) return E_2HIGH; RETVAL = Julian(y, m, d) * MINUTES_PER_DAY + ARGV(3) * 60 + ARGV(4); return OK; default: return E_2MANY_ARGS; } } /***************************************************************/ /* */ /* FCoerce - type coercion function. */ /* */ /***************************************************************/ static int FCoerce(func_info *info) { char const *s; ASSERT_TYPE(0, STR_TYPE); s = ARGSTR(0); /* Copy the value of ARG(1) into RetVal, and make ARG(1) invalid so it won't be destroyed */ DCOPYVAL(RetVal, ARG(1)); if (! StrCmpi(s, "int")) return DoCoerce(INT_TYPE, &RetVal); else if (! StrCmpi(s, "date")) return DoCoerce(DATE_TYPE, &RetVal); else if (! StrCmpi(s, "time")) return DoCoerce(TIME_TYPE, &RetVal); else if (! StrCmpi(s, "string")) return DoCoerce(STR_TYPE, &RetVal); else if (! StrCmpi(s, "datetime")) return DoCoerce(DATETIME_TYPE, &RetVal); else return E_CANT_COERCE; } /***************************************************************/ /* */ /* FMax - select maximum from a list of args. */ /* */ /***************************************************************/ static int FMax(func_info *info) { Value *maxptr; int i; char type; maxptr = &ARG(0); type = maxptr->type; for (i=1; i maxptr->v.val) maxptr = &ARG(i); } else { if (strcmp(ARG(i).v.str, maxptr->v.str) > 0) maxptr = &ARG(i); } } DCOPYVAL(RetVal, *maxptr); return OK; } /***************************************************************/ /* */ /* FMin - select minimum from a list of args. */ /* */ /***************************************************************/ static int FMin(func_info *info) { Value *minptr; int i; char type; minptr = &ARG(0); type = minptr->type; for (i=1; iv.val) minptr = &ARG(i); } else { if (strcmp(ARG(i).v.str, minptr->v.str) < 0) minptr = &ARG(i); } } DCOPYVAL(RetVal, *minptr); return OK; } /***************************************************************/ /* */ /* FAsc - ASCII value of first char of string */ /* */ /***************************************************************/ static int FAsc(func_info *info) { ASSERT_TYPE(0, STR_TYPE); RetVal.type = INT_TYPE; RETVAL = *(ARGSTR(0)); return OK; } /***************************************************************/ /* */ /* FChar - build a string from ASCII values */ /* */ /***************************************************************/ static int FChar(func_info *info) { int i, len; /* Special case of one arg - if given ascii value 0, create empty string */ if (Nargs == 1) { ASSERT_TYPE(0, INT_TYPE); if (ARGV(0) < -128) return E_2LOW; if (ARGV(0) > 255) return E_2HIGH; len = ARGV(0) ? 2 : 1; RetVal.v.str = malloc(len); if (!RetVal.v.str) return E_NO_MEM; RetVal.type = STR_TYPE; *(RetVal.v.str) = ARGV(0); if (len>1) *(RetVal.v.str + 1) = 0; return OK; } RetVal.v.str = malloc(Nargs + 1); if (!RetVal.v.str) return E_NO_MEM; RetVal.type = STR_TYPE; for (i=0; i 255) { free(RetVal.v.str); RetVal.type = ERR_TYPE; return E_2HIGH; } *(RetVal.v.str + i) = ARG(i).v.val; } *(RetVal.v.str + Nargs) = 0; return OK; } /***************************************************************/ /* */ /* Functions for extracting the components of a date. */ /* */ /* FDay - get day of month */ /* FMonnum - get month (1-12) */ /* FYear - get year */ /* FWkdaynum - get weekday num (0 = Sun) */ /* FWkday - get weekday (string) */ /* FMon - get month (string) */ /* */ /***************************************************************/ static int FDay(func_info *info) { int y, m, d, v; if (!HASDATE(ARG(0))) return E_BAD_TYPE; v = DATEPART(ARG(0)); if (v == CacheJul) d = CacheDay; else { FromJulian(v, &y, &m, &d); CacheJul = v; CacheYear = y; CacheMon = m; CacheDay = d; } RetVal.type = INT_TYPE; RETVAL = d; return OK; } static int FMonnum(func_info *info) { int y, m, d, v; if (!HASDATE(ARG(0))) return E_BAD_TYPE; v = DATEPART(ARG(0)); if (v == CacheJul) m = CacheMon; else { FromJulian(v, &y, &m, &d); CacheJul = v; CacheYear = y; CacheMon = m; CacheDay = d; } RetVal.type = INT_TYPE; RETVAL = m+1; return OK; } static int FYear(func_info *info) { int y, m, d, v; if (!HASDATE(ARG(0))) return E_BAD_TYPE; v = DATEPART(ARG(0)); if (v == CacheJul) y = CacheYear; else { FromJulian(v, &y, &m, &d); CacheJul = v; CacheYear = y; CacheMon = m; CacheDay = d; } RetVal.type = INT_TYPE; RETVAL = y; return OK; } static int FWkdaynum(func_info *info) { int v; if (!HASDATE(ARG(0))) return E_BAD_TYPE; v = DATEPART(ARG(0)); RetVal.type = INT_TYPE; /* Correct so that 0 = Sunday */ RETVAL = (v+1) % 7; return OK; } static int FWkday(func_info *info) { char const *s; if (!HASDATE(ARG(0)) && ARG(0).type != INT_TYPE) return E_BAD_TYPE; if (ARG(0).type == INT_TYPE) { if (ARGV(0) < 0) return E_2LOW; if (ARGV(0) > 6) return E_2HIGH; /* Convert 0=Sun to 0=Mon */ ARGV(0)--; if (ARGV(0) < 0) ARGV(0) = 6; s = DayName[ARGV(0)]; } else s = DayName[DATEPART(ARG(0)) % 7]; return RetStrVal(s, info); } static int FMon(func_info *info) { char const *s; int y, m, d, v; if (!HASDATE(ARG(0)) && ARG(0).type != INT_TYPE) return E_BAD_TYPE; if (ARG(0).type == INT_TYPE) { m = ARGV(0) - 1; if (m < 0) return E_2LOW; if (m > 11) return E_2HIGH; } else { v = DATEPART(ARG(0)); if (v == CacheJul) m = CacheMon; else { FromJulian(v, &y, &m, &d); CacheJul = v; CacheYear = y; CacheMon = m; CacheDay = d; } } s = MonthName[m]; return RetStrVal(s, info); } /***************************************************************/ /* */ /* FHour - extract hour from a time */ /* FMinute - extract minute from a time */ /* FTime - create a time from hour and minute */ /* */ /***************************************************************/ static int FHour(func_info *info) { int v; if (!HASTIME(ARG(0))) return E_BAD_TYPE; v = TIMEPART(ARG(0)); RetVal.type = INT_TYPE; RETVAL = v / 60; return OK; } static int FMinute(func_info *info) { int v; if (!HASTIME(ARG(0))) return E_BAD_TYPE; v = TIMEPART(ARG(0)); RetVal.type = INT_TYPE; RETVAL = v % 60; return OK; } static int FTime(func_info *info) { int h, m; if (ARG(0).type != INT_TYPE || ARG(1).type != INT_TYPE) return E_BAD_TYPE; h = ARGV(0); m = ARGV(1); if (h<0 || m<0) return E_2LOW; if (h>23 || m>59) return E_2HIGH; RetVal.type = TIME_TYPE; RETVAL = h*60 + m; return OK; } /***************************************************************/ /* */ /* FAbs - absolute value */ /* FSgn - signum function */ /* */ /***************************************************************/ static int FAbs(func_info *info) { volatile int v; ASSERT_TYPE(0, INT_TYPE); v = ARGV(0); RetVal.type = INT_TYPE; RETVAL = (v < 0) ? (-v) : v; v = RETVAL; if (v < 0) return E_2HIGH; return OK; } static int FSgn(func_info *info) { int v; ASSERT_TYPE(0, INT_TYPE); v = ARGV(0); RetVal.type = INT_TYPE; if (v>0) RETVAL = 1; else if (v<0) RETVAL = -1; else RETVAL = 0; return OK; } /***************************************************************/ /* */ /* FAmpm - return a time as a string with "AM" or "PM" suffix */ /* */ /***************************************************************/ static int FAmpm(func_info *info) { int h, m; int yr=0, mo=0, da=0; char const *am = "AM"; char const *pm = "PM"; char const *ampm = NULL; char outbuf[128]; if (ARG(0).type != DATETIME_TYPE && ARG(0).type != TIME_TYPE) { return E_BAD_TYPE; } if (HASDATE(ARG(0))) { FromJulian(DATEPART(ARG(0)), &yr, &mo, &da); } if (Nargs >= 2) { ASSERT_TYPE(1, STR_TYPE); am = ARGSTR(1); if (Nargs >= 3) { ASSERT_TYPE(2, STR_TYPE); pm = ARGSTR(2); } } h = TIMEPART(ARG(0)) / 60; m = TIMEPART(ARG(0)) % 60; if (h <= 11) { /* AM */ if (h == 0) { if (ARG(0).type == DATETIME_TYPE) { snprintf(outbuf, sizeof(outbuf), "%04d%c%02d%c%02d%c12%c%02d", yr, DateSep, mo+1, DateSep, da, DateTimeSep, TimeSep, m); } else { snprintf(outbuf, sizeof(outbuf), "12%c%02d", TimeSep, m); } } else { if (ARG(0).type == DATETIME_TYPE) { snprintf(outbuf, sizeof(outbuf), "%04d%c%02d%c%02d%c%d%c%02d", yr, DateSep, mo+1, DateSep, da, DateTimeSep, h, TimeSep, m); } else { snprintf(outbuf, sizeof(outbuf), "%d%c%02d", h, TimeSep, m); } } ampm = am; } else { if (h > 12) { h -= 12; } if (ARG(0).type == DATETIME_TYPE) { snprintf(outbuf, sizeof(outbuf), "%04d%c%02d%c%02d%c%d%c%02d", yr, DateSep, mo+1, DateSep, da, DateTimeSep, h, TimeSep, m); } else { snprintf(outbuf, sizeof(outbuf), "%d%c%02d", h, TimeSep, m); } ampm = pm; } RetVal.type = STR_TYPE; RetVal.v.str = malloc(strlen(outbuf) + strlen(ampm) + 1); if (!RetVal.v.str) { RetVal.type = ERR_TYPE; return E_NO_MEM; } strcpy(RetVal.v.str, outbuf); strcat(RetVal.v.str, ampm); return OK; } /***************************************************************/ /* */ /* FOrd - returns a string containing ordinal number. */ /* */ /* EG - ord(2) == "2nd", etc. */ /* */ /***************************************************************/ static int FOrd(func_info *info) { int t, u, v; char const *s; char buf[32]; ASSERT_TYPE(0, INT_TYPE); v = ARGV(0); t = v % 100; if (t < 0) t = -t; u = t % 10; s = "th"; if (u == 1 && t != 11) s = "st"; if (u == 2 && t != 12) s = "nd"; if (u == 3 && t != 13) s = "rd"; sprintf(buf, "%d%s", v, s); return RetStrVal(buf, info); } /***************************************************************/ /* */ /* FPlural - pluralization function */ /* */ /* plural(n) --> "" or "s" */ /* plural(n, str) --> "str" or "strs" */ /* plural(n, str1, str2) --> "str1" or "str2" */ /* */ /***************************************************************/ static int FPlural(func_info *info) { ASSERT_TYPE(0, INT_TYPE); switch(Nargs) { case 1: if (ARGV(0) == 1) return RetStrVal("", info); else return RetStrVal("s", info); case 2: ASSERT_TYPE(1, STR_TYPE); if (ARGV(0) == 1) { DCOPYVAL(RetVal, ARG(1)); return OK; } RetVal.type = STR_TYPE; RetVal.v.str = malloc(strlen(ARGSTR(1))+2); if (!RetVal.v.str) { RetVal.type = ERR_TYPE; return E_NO_MEM; } strcpy(RetVal.v.str, ARGSTR(1)); strcat(RetVal.v.str, "s"); return OK; default: if (ARG(1).type != STR_TYPE || ARG(2).type != STR_TYPE) return E_BAD_TYPE; if (ARGV(0) == 1) DCOPYVAL(RetVal, ARG(1)); else DCOPYVAL(RetVal, ARG(2)); return OK; } } /***************************************************************/ /* */ /* FIsAny */ /* Return 1 if the first arg equals any subsequent arg, 0 */ /* otherwise. */ /* */ /***************************************************************/ static int FIsAny(func_info *info) { int i; RetVal.type = INT_TYPE; RETVAL = 0; for (i=1; iN, choose Nth value. Indexes always start */ /* from 1. */ /* */ /***************************************************************/ static int FChoose(func_info *info) { int v; ASSERT_TYPE(0, INT_TYPE); v = ARGV(0); if (v < 1) v = 1; if (v > Nargs-1) v = Nargs-1; DCOPYVAL(RetVal, ARG(v)); return OK; } /***************************************************************/ /* */ /* FVersion - version of Remind */ /* */ /***************************************************************/ static int FVersion(func_info *info) { return RetStrVal(VERSION, info); } /***************************************************************/ /* */ /* FOstype - the type of operating system */ /* (UNIX, OS/2, or MSDOS) */ /* */ /***************************************************************/ static int FOstype(func_info *info) { return RetStrVal("UNIX", info); } /***************************************************************/ /* */ /* FShellescape - escape shell meta-characters */ /* */ /***************************************************************/ static int FShellescape(func_info *info) { DynamicBuffer buf; int r; ASSERT_TYPE(0, STR_TYPE); DBufInit (&buf); if (ShellEscape(ARG(0).v.str, &buf) != OK) { DBufFree(&buf); return E_NO_MEM; } r = RetStrVal(DBufValue(&buf), info); DBufFree(&buf); return r; } /***************************************************************/ /* */ /* FUpper - convert string to upper-case */ /* FLower - convert string to lower-case */ /* */ /***************************************************************/ static int FUpper(func_info *info) { char *s; ASSERT_TYPE(0, STR_TYPE); DCOPYVAL(RetVal, ARG(0)); s = RetVal.v.str; while (*s) { *s = UPPER(*s); s++; } return OK; } static int FLower(func_info *info) { char *s; ASSERT_TYPE(0, STR_TYPE); DCOPYVAL(RetVal, ARG(0)); s = RetVal.v.str; while (*s) { *s = LOWER(*s); s++; } return OK; } /***************************************************************/ /* */ /* FToday - return the system's notion of "today" */ /* Frealtoday - return today's date as read from OS. */ /* FNow - return the system time (or time on cmd line.) */ /* FRealnow - return the true system time */ /* */ /***************************************************************/ static int FToday(func_info *info) { RetVal.type = DATE_TYPE; RETVAL = JulianToday; return OK; } static int FRealtoday(func_info *info) { RetVal.type = DATE_TYPE; RETVAL = RealToday; return OK; } static int FNow(func_info *info) { RetVal.type = TIME_TYPE; RETVAL = (int) ( SystemTime(0) / 60L ); return OK; } static int FRealnow(func_info *info) { RetVal.type = TIME_TYPE; RETVAL = (int) ( SystemTime(1) / 60L ); return OK; } static int FCurrent(func_info *info) { RetVal.type = DATETIME_TYPE; RETVAL = JulianToday * MINUTES_PER_DAY + (SystemTime(0) / 60); return OK; } static int FRealCurrent(func_info *info) { RetVal.type = DATETIME_TYPE; RETVAL = RealToday * MINUTES_PER_DAY + (SystemTime(1) / 60); return OK; } /***************************************************************/ /* */ /* FGetenv - get the value of an environment variable. */ /* */ /***************************************************************/ static int FGetenv(func_info *info) { ASSERT_TYPE(0, STR_TYPE); return RetStrVal(getenv(ARGSTR(0)), info); } /***************************************************************/ /* */ /* FValue */ /* */ /* Get the value of a variable. If a second arg is supplied, */ /* it is returned if variable is undefined. */ /* */ /***************************************************************/ static int FValue(func_info *info) { Var *v; ASSERT_TYPE(0, STR_TYPE); switch(Nargs) { case 1: return GetVarValue(ARGSTR(0), &RetVal, NULL, NULL); case 2: v = FindVar(ARGSTR(0), 0); if (!v) { DCOPYVAL(RetVal, ARG(1)); return OK; } else { return CopyValue(&RetVal, &v->v); } } return OK; } /***************************************************************/ /* */ /* FDefined */ /* */ /* Return 1 if a variable is defined, 0 if it is not. */ /* */ /***************************************************************/ static int FDefined(func_info *info) { ASSERT_TYPE(0, STR_TYPE); RetVal.type = INT_TYPE; if (FindVar(ARGSTR(0), 0)) RETVAL = 1; else RETVAL = 0; return OK; } /***************************************************************/ /* */ /* FTrigdate and FTrigtime */ /* */ /* Date and time of last trigger. These are stored in global */ /* vars. */ /* */ /***************************************************************/ static int FTrigdate(func_info *info) { if (LastTrigValid) { RetVal.type = DATE_TYPE; RETVAL = LastTriggerDate; } else { RetVal.type = INT_TYPE; RETVAL = 0; } return OK; } static int FTrigback(func_info *info) { RetVal.type = INT_TYPE; RETVAL = LastTrigger.back; return OK; } static int FTrigdelta(func_info *info) { RetVal.type = INT_TYPE; RETVAL = LastTrigger.delta; return OK; } static int FTrigtimedelta(func_info *info) { RetVal.type = INT_TYPE; RETVAL = LastTimeTrig.delta; return OK; } static int FTrigtimerep(func_info *info) { RetVal.type = INT_TYPE; RETVAL = LastTimeTrig.rep; return OK; } static int FTrigeventduration(func_info *info) { if (LastTrigger.eventduration == NO_TIME) { RetVal.type = INT_TYPE; RETVAL = -1; } else { RetVal.type = TIME_TYPE; RETVAL = LastTrigger.eventduration; } return OK; } static int FTrigeventstart(func_info *info) { if (LastTrigger.eventstart == NO_TIME) { RetVal.type = INT_TYPE; RETVAL = -1; } else { RetVal.type = DATETIME_TYPE; RETVAL = LastTrigger.eventstart; } return OK; } static int FTrigduration(func_info *info) { if (LastTimeTrig.duration == NO_TIME) { RetVal.type = INT_TYPE; RETVAL = -1; } else { RetVal.type = TIME_TYPE; RETVAL = LastTimeTrig.duration; } return OK; } static int FTrigrep(func_info *info) { RetVal.type = INT_TYPE; RETVAL = LastTrigger.rep; return OK; } static int FTrigpriority(func_info *info) { RetVal.type = INT_TYPE; RETVAL = LastTrigger.priority; return OK; } static int FTriguntil(func_info *info) { if (LastTrigger.until == NO_UNTIL) { RetVal.type = INT_TYPE; RETVAL = -1; } else { RetVal.type = DATE_TYPE; RETVAL = LastTrigger.until; } return OK; } static int FTrigscanfrom(func_info *info) { if (LastTrigger.scanfrom == NO_DATE) { RetVal.type = INT_TYPE; RETVAL = -1; } else { RetVal.type = DATE_TYPE; RETVAL = LastTrigger.scanfrom; } return OK; } static int FTrigfrom(func_info *info) { if (LastTrigger.from == NO_DATE) { RetVal.type = INT_TYPE; RETVAL = -1; } else { RetVal.type = DATE_TYPE; RETVAL = LastTrigger.from; } return OK; } static int FTrigvalid(func_info *info) { RetVal.type = INT_TYPE; RETVAL = LastTrigValid; return OK; } static int FTrigtime(func_info *info) { if (LastTriggerTime != NO_TIME) { RetVal.type = TIME_TYPE; RETVAL = LastTriggerTime; } else { RetVal.type = INT_TYPE; RETVAL = 0; } return OK; } static int FTrigdatetime(func_info *info) { if (!LastTrigValid) { RetVal.type = INT_TYPE; RETVAL = 0; } else if (LastTriggerTime != NO_TIME) { RetVal.type = DATETIME_TYPE; RETVAL = LastTriggerDate * MINUTES_PER_DAY + LastTriggerTime; } else { RetVal.type = DATE_TYPE; RETVAL = LastTriggerDate; } return OK; } /***************************************************************/ /* */ /* FDaysinmon */ /* */ /* Returns the number of days in mon,yr */ /* */ /***************************************************************/ static int FDaysinmon(func_info *info) { if (ARG(0).type != INT_TYPE || ARG(1).type != INT_TYPE) return E_BAD_TYPE; if (ARGV(0) > 12 || ARGV(0) < 1 || ARGV(1) < BASE || ARGV(1) > BASE+YR_RANGE) return E_DOMAIN_ERR; RetVal.type = INT_TYPE; RETVAL = DaysInMonth(ARGV(0)-1, ARGV(1)); return OK; } /***************************************************************/ /* */ /* FIsleap */ /* */ /* Return 1 if year is a leap year, zero otherwise. */ /* */ /***************************************************************/ static int FIsleap(func_info *info) { int y, m, d; if (ARG(0).type != INT_TYPE && !HASDATE(ARG(0))) return E_BAD_TYPE; /* If it's a date, extract the year */ if (HASDATE(ARG(0))) FromJulian(DATEPART(ARG(0)), &y, &m, &d); else y = ARGV(0); RetVal.type = INT_TYPE; RETVAL = IsLeapYear(y); return OK; } /***************************************************************/ /* */ /* FTrigger */ /* */ /* Put out a date in a format suitable for triggering. */ /* */ /***************************************************************/ static int FTrigger(func_info *info) { int y, m, d; int date, tim; char buf[128]; tim = NO_TIME; if (ARG(0).type != DATE_TYPE && ARG(0).type != DATETIME_TYPE) return E_BAD_TYPE; if (ARG(0).type == DATE_TYPE) { date = ARGV(0); } else { date = ARGV(0) / MINUTES_PER_DAY; tim = ARGV(0) % MINUTES_PER_DAY; } if (ARG(0).type == DATE_TYPE) { if (Nargs > 2) { /* Date Time UTCFlag */ if (ARG(0).type == DATETIME_TYPE) return E_BAD_TYPE; ASSERT_TYPE(2, INT_TYPE); ASSERT_TYPE(1, TIME_TYPE); tim = ARGV(1); if (ARGV(2)) { UTCToLocal(date, tim, &date, &tim); } } else if (Nargs > 1) { /* Date Time */ ASSERT_TYPE(1, TIME_TYPE); tim = ARGV(1); } } else { if (Nargs > 2) { return E_2MANY_ARGS; } else if (Nargs > 1) { /* DateTime UTCFlag */ ASSERT_TYPE(1, INT_TYPE); if (ARGV(1)) { UTCToLocal(date, tim, &date, &tim); } } } FromJulian(date, &y, &m, &d); if (tim != NO_TIME) { sprintf(buf, "%d %s %d AT %02d:%02d", d, EnglishMonthName[m], y, tim/60, tim%60); } else { sprintf(buf, "%d %s %d", d, EnglishMonthName[m], y); } return RetStrVal(buf, info); } /***************************************************************/ /* */ /* FShell */ /* */ /* The shell function. */ /* */ /* If run is disabled, will not be executed. */ /* */ /***************************************************************/ static int FShell(func_info *info) { DynamicBuffer buf; int ch, r; FILE *fp; /* For compatibility with previous versions of Remind, which used a static buffer for reading results from shell() command */ int maxlen = 511; DBufInit(&buf); if (RunDisabled) return E_RUN_DISABLED; ASSERT_TYPE(0, STR_TYPE); if (Nargs >= 2) { ASSERT_TYPE(1, INT_TYPE); maxlen = ARGV(1); } /* Don't allow maxlen to exceed the maximum length of a string variable */ if (MaxStringLen > 0) { if (maxlen <= 0 || maxlen > MaxStringLen) { maxlen = MaxStringLen; } } fp = popen(ARGSTR(0), "r"); if (!fp) return E_IO_ERR; while (1) { ch = getc(fp); if (ch == EOF) { break; } if (isspace(ch)) ch = ' '; if (DBufPutc(&buf, (char) ch) != OK) { pclose(fp); DBufFree(&buf); return E_NO_MEM; } if (maxlen > 0 && DBufLen(&buf) >= (size_t) maxlen) { break; } } /* Delete trailing newline (converted to space) */ if (DBufLen(&buf) && DBufValue(&buf)[DBufLen(&buf)-1] == ' ') { DBufValue(&buf)[DBufLen(&buf)-1] = 0; } /* XXX Should we consume remaining output from cmd? */ pclose(fp); r = RetStrVal(DBufValue(&buf), info); DBufFree(&buf); return r; } /***************************************************************/ /* */ /* FIsomitted */ /* */ /* Is a date omitted? */ /* */ /***************************************************************/ static int FIsomitted(func_info *info) { int r; if (!HASDATE(ARG(0))) return E_BAD_TYPE; RetVal.type = INT_TYPE; r = IsOmitted(DATEPART(ARG(0)), 0, NULL, &RETVAL); return r; } /***************************************************************/ /* */ /* FSubstr */ /* */ /* The substr function. We destroy the value on the stack. */ /* */ /***************************************************************/ static int FSubstr(func_info *info) { char *s; char const *t; int start, end; if (ARG(0).type != STR_TYPE || ARG(1).type != INT_TYPE) return E_BAD_TYPE; if (Nargs == 3 && ARG(2).type != INT_TYPE) return E_BAD_TYPE; s = ARGSTR(0); start = 1; while (start < ARGV(1)) { if (!*s) break; s++; start++; } if (Nargs == 2 || !*s) return RetStrVal(s, info); end = start; t = s; while (end <= ARGV(2)) { if (!*s) break; s++; end++; } *s = 0; return RetStrVal(t, info); } /***************************************************************/ /* */ /* FIndex */ /* */ /* The index of one string embedded in another. */ /* */ /***************************************************************/ static int FIndex(func_info *info) { char const *s; int start; if (ARG(0).type != STR_TYPE || ARG(1).type != STR_TYPE || (Nargs == 3 && ARG(2).type != INT_TYPE)) return E_BAD_TYPE; s = ARGSTR(0); /* If 3 args, bump up the start */ if (Nargs == 3) { start = 1; while (start < ARGV(2)) { if (!*s) break; s++; start++; } } /* Find the string */ s = strstr(s, ARGSTR(1)); RetVal.type = INT_TYPE; if (!s) { RETVAL = 0; return OK; } RETVAL = (s - ARGSTR(0)) + 1; return OK; } /***************************************************************/ /* */ /* FIif */ /* */ /* The IIF function. */ /* */ /***************************************************************/ static int FIif(func_info *info) { int istrue; int arg; if (!(Nargs % 2)) return E_IIF_ODD; for (arg=0; arg DBufValue(&buf) && *s != '/') s--; if (*s == '/') { *s = 0; r = RetStrVal(DBufValue(&buf), info); } else r = RetStrVal(".", info); DBufFree(&buf); return r; } /***************************************************************/ /* */ /* FAccess */ /* */ /* The UNIX access() system call. */ /* */ /***************************************************************/ static int FAccess(func_info *info) { int amode; char const *s; if (ARG(0).type != STR_TYPE || (ARG(1).type != INT_TYPE && ARG(1).type != STR_TYPE)) return E_BAD_TYPE; if (ARG(1).type == INT_TYPE) amode = ARGV(1); else { amode = 0; s = ARGSTR(1); while (*s) { switch(*s++) { case 'r': case 'R': amode |= R_OK; break; case 'w': case 'W': amode |= W_OK; break; case 'x': case 'X': amode |= X_OK; break; } } } RetVal.type = INT_TYPE; RETVAL = access(ARGSTR(0), amode); return OK; } /***************************************************************/ /* */ /* FTypeof */ /* */ /* Implement the typeof() function. */ /* */ /***************************************************************/ static int FTypeof(func_info *info) { switch(ARG(0).type) { case INT_TYPE: return RetStrVal("INT", info); case DATE_TYPE: return RetStrVal("DATE", info); case TIME_TYPE: return RetStrVal("TIME", info); case STR_TYPE: return RetStrVal("STRING", info); case DATETIME_TYPE: return RetStrVal("DATETIME", info); default: return RetStrVal("ERR", info); } } /***************************************************************/ /* */ /* FLanguage */ /* */ /* Implement the language() function. */ /* */ /***************************************************************/ static int FLanguage(func_info *info) { return RetStrVal(L_LANGNAME, info); } /***************************************************************/ /* */ /* FArgs */ /* */ /* Implement the args() function. */ /* */ /***************************************************************/ static int FArgs(func_info *info) { ASSERT_TYPE(0, STR_TYPE); RetVal.type = INT_TYPE; RETVAL = UserFuncExists(ARGSTR(0)); return OK; } /***************************************************************/ /* */ /* FDosubst */ /* */ /* Implement the dosubst() function. */ /* */ /***************************************************************/ static int FDosubst(func_info *info) { int jul, tim, r; DynamicBuffer buf; DBufInit(&buf); jul = NO_DATE; tim = NO_TIME; ASSERT_TYPE(0, STR_TYPE); if (Nargs >= 2) { if (ARG(1).type == DATETIME_TYPE) { jul = DATEPART(ARG(1)); tim = TIMEPART(ARG(1)); } else { ASSERT_TYPE(1, DATE_TYPE); jul = ARGV(1); } if (Nargs >= 3) { if (ARG(1).type == DATETIME_TYPE) { return E_2MANY_ARGS; } ASSERT_TYPE(2, TIME_TYPE); tim = ARGV(2); } } if ((r=DoSubstFromString(ARGSTR(0), &buf, jul, tim))) return r; r = RetStrVal(DBufValue(&buf), info); DBufFree(&buf); return r; } /***************************************************************/ /* */ /* FHebdate */ /* FHebday */ /* FHebmon */ /* FHebyear */ /* */ /* Hebrew calendar support functions */ /* */ /***************************************************************/ static int FHebdate(func_info *info) { int year, day, mon, jahr; int mout, dout; int ans, r; int adarbehave; if (ARG(0).type != INT_TYPE || ARG(1).type != STR_TYPE) return E_BAD_TYPE; day = ARGV(0); mon = HebNameToNum(ARGSTR(1)); if (mon < 0) return E_BAD_HEBDATE; if (Nargs == 2) { r = GetNextHebrewDate(JulianToday, mon, day, 0, 0, &ans); if (r) return r; RetVal.type = DATE_TYPE; RETVAL = ans; return OK; } if (Nargs == 5) { ASSERT_TYPE(4, INT_TYPE); adarbehave = ARGV(4); if (adarbehave < 0) return E_2LOW; if (adarbehave > 2) return E_2HIGH; } else adarbehave = 0; if (Nargs >= 4) { ASSERT_TYPE(3, INT_TYPE); jahr = ARGV(3); if (jahr < 0) return E_2LOW; if (jahr > 2) { r = ComputeJahr(jahr, mon, day, &jahr); if (r) return r; } } else jahr = 0; if (ARG(2).type == INT_TYPE) { year = ARGV(2); r = GetValidHebDate(year, mon, day, 0, &mout, &dout, jahr); if (r) return r; r = HebToJul(year, mout, dout); if (r<0) return E_DATE_OVER; RETVAL = r; RetVal.type = DATE_TYPE; return OK; } else if (HASDATE(ARG(2))) { r = GetNextHebrewDate(DATEPART(ARG(2)), mon, day, jahr, adarbehave, &ans); if (r) return r; RETVAL = ans; RetVal.type = DATE_TYPE; return OK; } else return E_BAD_TYPE; } static int FHebday(func_info *info) { int y, m, d, v; if (!HASDATE(ARG(0))) return E_BAD_TYPE; v = DATEPART(ARG(0)); if (v == CacheHebJul) d = CacheHebDay; else { JulToHeb(v, &y, &m, &d); CacheHebJul = v; CacheHebYear = y; CacheHebMon = m; CacheHebDay = d; } RetVal.type = INT_TYPE; RETVAL = d; return OK; } static int FHebmon(func_info *info) { int y, m, d, v; if (!HASDATE(ARG(0))) return E_BAD_TYPE; v = DATEPART(ARG(0)); if (v == CacheHebJul) { m = CacheHebMon; y = CacheHebYear; } else { JulToHeb(v, &y, &m, &d); CacheHebJul = v; CacheHebYear = y; CacheHebMon = m; CacheHebDay = d; } return RetStrVal(HebMonthName(m, y), info); } static int FHebyear(func_info *info) { int y, m, d, v; if (!HASDATE(ARG(0))) return E_BAD_TYPE; v = DATEPART(ARG(0)); if (v == CacheHebJul) y = CacheHebYear; else { JulToHeb(v, &y, &m, &d); CacheHebJul = v; CacheHebYear = y; CacheHebMon = m; CacheHebDay = d; } RetVal.type = INT_TYPE; RETVAL = y; return OK; } /****************************************************************/ /* */ /* FEasterdate - calc. easter Sunday from a year. */ /* */ /* from The Art of Computer Programming Vol 1. */ /* Fundamental Algorithms */ /* by Donald Knuth. */ /* */ /* Donated by Michael Salmon - thanks! */ /* */ /* I haven't examined this in detail, but I *think* int */ /* arithmetic is fine, even on 16-bit machines. */ /* */ /****************************************************************/ static int FEasterdate(func_info *info) { int y, m, d; int g, c, x, z, e, n; if (ARG(0).type == INT_TYPE) { y = ARGV(0); if (y < BASE) return E_2LOW; else if (y > BASE+YR_RANGE) return E_2HIGH; } else if (HASDATE(ARG(0))) { FromJulian(DATEPART(ARG(0)), &y, &m, &d); /* We just want the year */ } else return E_BAD_TYPE; do { g = (y % 19) + 1; /* golden number */ c = (y / 100) + 1; /* century */ x = (3 * c)/4 - 12; /* correction for non-leap year centuries */ z = (8 * c + 5)/25 - 5; /* special constant for moon sync */ d = (5 * y)/4 - x - 10; /* find sunday */ e = (11 * g + 20 + z - x) % 30; /* calc epact */ if ( e < 0 ) e += 30; if ( e == 24 || (e == 25 && g > 11)) e++; n = 44 - e; /* find full moon */ if ( n < 21 ) n += 30; /* after 21st */ d = n + 7 - (d + n)%7; /* calc sunday after */ if (d <= 31) m = 2; else { d = d - 31; m = 3; } RetVal.type = DATE_TYPE; RETVAL = Julian(y, m, d); y++; } while (HASDATE(ARG(0)) && RETVAL < DATEPART(ARG(0))); return OK; } /***************************************************************/ /* */ /* FIsdst and FMinsfromutc */ /* */ /* Check whether daylight saving time is in effect, and */ /* get minutes from UTC. */ /* */ /***************************************************************/ static int FTimeStuff (int wantmins, func_info *info); static int FIsdst(func_info *info) { return FTimeStuff(0, info); } static int FMinsfromutc(func_info *info) { return FTimeStuff(1, info); } static int FTimeStuff(int wantmins, func_info *info) { int jul, tim; int mins, dst; jul = JulianToday; tim = 0; if (Nargs >= 1) { if (!HASDATE(ARG(0))) return E_BAD_TYPE; jul = DATEPART(ARG(0)); if (HASTIME(ARG(0))) { tim = TIMEPART(ARG(0)); } if (Nargs >= 2) { if (HASTIME(ARG(0))) return E_2MANY_ARGS; ASSERT_TYPE(1, TIME_TYPE); tim = ARGV(1); } } if (CalcMinsFromUTC(jul, tim, &mins, &dst)) return E_MKTIME_PROBLEM; RetVal.type = INT_TYPE; if (wantmins) RETVAL = mins; else RETVAL = dst; return OK; } /***************************************************************/ /* */ /* Sunrise and sunset functions. */ /* */ /* Algorithm from "Almanac for computers for the year 1978" */ /* by L. E. Doggett, Nautical Almanac Office, USNO. */ /* */ /* This code also uses some ideas found in programs written */ /* by Michael Schwartz and Marc T. Kaufman. */ /* */ /***************************************************************/ #ifdef PI #undef PI #endif #define PI 3.14159265358979323846 #define DEGRAD (PI/180.0) #define RADDEG (180.0/PI) static int SunStuff(int rise, double cosz, int jul) { int mins, hours; int year, mon, day; double M, L, sinDelta, cosDelta, a, a_hr, cosH, t, H, T; double latitude, longdeg, UT, local; /* Get offset from UTC */ if (CalculateUTC) { if (CalcMinsFromUTC(jul, 12*60, &mins, NULL)) { Eprint(ErrMsg[E_MKTIME_PROBLEM]); return NO_TIME; } } else mins = MinsFromUTC; /* Get latitude and longitude */ longdeg = -Longitude; latitude = DEGRAD * Latitude; FromJulian(jul, &year, &mon, &day); /* Following formula on page B6 exactly... */ t = (double) jul; if (rise) { t += (6.0 + longdeg/15.0) / 24.0; } else { t += (18.0 + longdeg/15.0) / 24.0; } /* Mean anomaly of sun starting from 1 Jan 1990 */ /* NOTE: This assumes that BASE = 1990!!! */ #if BASE != 1990 #error Sun calculations assume a BASE of 1990! #endif t = 0.9856002585 * t; M = t + 357.828757; /* In degrees */ /* Make sure M is in the range [0, 360) */ M -= (floor(M/360.0) * 360.0); /* Sun's true longitude */ L = M + 1.916*sin(DEGRAD*M) + 0.02*sin(2*DEGRAD*M) + 283.07080214; if (L > 360.0) L -= 360.0; /* Tan of sun's right ascension */ a = RADDEG * atan2(0.91746*sin(DEGRAD*L), cos(DEGRAD*L)); if (a<0) { a += 360.0; } a_hr = a / 15.0; /* Sine of sun's declination */ sinDelta = 0.39782 * sin(DEGRAD*L); cosDelta = sqrt(1 - sinDelta*sinDelta); /* Cosine of sun's local hour angle */ cosH = (cosz - sinDelta * sin(latitude)) / (cosDelta * cos(latitude)); if (cosH < -1.0) { /* Summer -- permanent daylight */ if (rise) return NO_TIME; else return -NO_TIME; } if (cosH > 1.0) { /* Winter -- permanent darkness */ if (rise) return -NO_TIME; else return NO_TIME; } H = RADDEG * acos(cosH); if (rise) H = 360.0 - H; t -= 360.0*floor(t/360.0); T = (H-t) / 15.0 + a_hr - 6.726637276; if (T >= 24.0) T -= 24.0; else if (T < 0.0) T+= 24.0; UT = T + longdeg / 15.0; local = UT + (double) mins / 60.0; if (local < 0.0) local += 24.0; else if (local >= 24.0) local -= 24.0; /* Round off local time to nearest minute */ local = floor(local * 60.0 + 0.5) / 60.0; hours = (int) local; mins = (int) ((local - hours) * 60.0); /* Sometimes, we get roundoff error. Check for "reasonableness" of answer. */ if (rise) { /* Sunrise so close to midnight it wrapped around -- permament light */ if (hours >= 23) return NO_TIME; } else { /* Sunset so close to midnight it wrapped around -- permament light */ if (hours <= 1) return -NO_TIME; } return hours*60 + mins; } /***************************************************************/ /* */ /* Sunrise and Sunset functions. */ /* */ /***************************************************************/ static int FSun(int rise, func_info *info) { int jul = JulianToday; /* Assignment below is not necessary, but it silences a GCC warning about a possibly-uninitialized variable */ double cosz = 0.0; int r; if (rise == 0 || rise == 1) { /* Sunrise and sunset : cos(90 degrees + 50 arcminutes) */ cosz = -0.01454389765158243; } else if (rise == 2 || rise == 3) { /* Civil twilight: cos(96 degrees) */ cosz = -0.10452846326765333; } else if (rise == 4 || rise == 5) { /* Nautical twilight: cos(102 degrees) */ cosz = -0.20791169081775912; } else if (rise == 6 || rise == 7) { /* Astronomical twilight: cos(108 degrees) */ cosz = -0.30901699437494734; } if (Nargs >= 1) { if (!HASDATE(ARG(0))) return E_BAD_TYPE; jul = DATEPART(ARG(0)); } r = SunStuff(rise % 2, cosz, jul); if (r == NO_TIME) { RETVAL = 0; RetVal.type = INT_TYPE; } else if (r == -NO_TIME) { RETVAL = MINUTES_PER_DAY; RetVal.type = INT_TYPE; } else { RETVAL = r; RetVal.type = TIME_TYPE; } return OK; } static int FSunrise(func_info *info) { return FSun(1, info); } static int FSunset(func_info *info) { return FSun(0, info); } static int FDawn(func_info *info) { return FSun(3, info); } static int FDusk(func_info *info) { return FSun(2, info); } static int FNDawn(func_info *info) { return FSun(5, info); } static int FNDusk(func_info *info) { return FSun(4, info); } static int FADawn(func_info *info) { return FSun(7, info); } static int FADusk(func_info *info) { return FSun(6, info); } /***************************************************************/ /* */ /* FFiledate */ /* */ /* Return modification date of a file */ /* */ /***************************************************************/ static int FFiledate(func_info *info) { struct stat statbuf; struct tm *t1; RetVal.type = DATE_TYPE; ASSERT_TYPE(0, STR_TYPE); if (stat(ARGSTR(0), &statbuf)) { RETVAL = 0; return OK; } t1 = localtime(&(statbuf.st_mtime)); if (t1->tm_year + 1900 < BASE) RETVAL=0; else RETVAL=Julian(t1->tm_year+1900, t1->tm_mon, t1->tm_mday); return OK; } /***************************************************************/ /* */ /* FFiledatetime */ /* */ /* Return modification datetime of a file */ /* */ /***************************************************************/ static int FFiledatetime(func_info *info) { struct stat statbuf; struct tm *t1; RetVal.type = DATETIME_TYPE; ASSERT_TYPE(0, STR_TYPE); if (stat(ARGSTR(0), &statbuf)) { RETVAL = 0; return OK; } t1 = localtime(&(statbuf.st_mtime)); if (t1->tm_year + 1900 < BASE) RETVAL=0; else RETVAL = MINUTES_PER_DAY * Julian(t1->tm_year+1900, t1->tm_mon, t1->tm_mday) + t1->tm_hour * 60 + t1->tm_min; return OK; } /***************************************************************/ /* */ /* FPsshade */ /* */ /* Canned PostScript code for shading a calendar square */ /* */ /***************************************************************/ static int psshade_warned = 0; static int FPsshade(func_info *info) { char psbuff[256]; char *s = psbuff; int i; /* 1 or 3 args */ if (Nargs != 1 && Nargs != 3) return E_2MANY_ARGS; for (i=0; i 100) return E_2HIGH; } if (!psshade_warned) { psshade_warned = 1; Eprint("psshade() is deprecated; use SPECIAL SHADE instead."); FreshLine = 1; } sprintf(s, "/_A LineWidth 2 div def "); s += strlen(s); sprintf(s, "_A _A moveto "); s += strlen(s); sprintf(s, "BoxWidth _A sub _A lineto BoxWidth _A sub BoxHeight _A sub lineto "); s += strlen(s); if (Nargs == 1) { sprintf(s, "_A BoxHeight _A sub lineto closepath %d 100 div setgray fill 0.0 setgray", ARGV(0)); } else { sprintf(s, "_A BoxHeight _A sub lineto closepath %d 100 div %d 100 div %d 100 div setrgbcolor fill 0.0 setgray", ARGV(0), ARGV(1), ARGV(2)); } return RetStrVal(psbuff, info); } /***************************************************************/ /* */ /* FPsmoon */ /* */ /* Canned PostScript code for generating moon phases */ /* */ /***************************************************************/ static int psmoon_warned = 0; static int FPsmoon(func_info *info) { char psbuff[512]; char sizebuf[30]; char fontsizebuf[30]; char *s = psbuff; char const *extra = NULL; int size = -1; int fontsize = -1; ASSERT_TYPE(0, INT_TYPE); if (ARGV(0) < 0) return E_2LOW; if (ARGV(0) > 3) return E_2HIGH; if (Nargs > 1) { ASSERT_TYPE(1, INT_TYPE); if (ARGV(1) < -1) return E_2LOW; size = ARGV(1); if (Nargs > 2) { ASSERT_TYPE(2, STR_TYPE); extra = ARGSTR(2); if (Nargs > 3) { ASSERT_TYPE(3, INT_TYPE); if (ARGV(3) <= 0) return E_2LOW; fontsize = ARGV(3); } } } if (!psmoon_warned) { psmoon_warned = 1; Eprint("psmoon() is deprecated; use SPECIAL MOON instead."); FreshLine = 1; } if (size > 0) { sprintf(sizebuf, "%d", size); } else { strcpy(sizebuf, "DaySize 2 div"); } if (fontsize > 0) { sprintf(fontsizebuf, "%d", fontsize); } else { strcpy(fontsizebuf, "EntrySize"); } sprintf(s, "gsave 0 setgray newpath Border %s add BoxHeight Border sub %s sub", sizebuf, sizebuf); s += strlen(s); sprintf(s, " %s 0 360 arc closepath", sizebuf); s += strlen(s); switch(ARGV(0)) { case 0: sprintf(s, " fill"); s += strlen(s); break; case 2: sprintf(s, " stroke"); s += strlen(s); break; case 1: sprintf(s, " stroke"); s += strlen(s); sprintf(s, " newpath Border %s add BoxHeight Border sub %s sub", sizebuf, sizebuf); s += strlen(s); sprintf(s, " %s 90 270 arc closepath fill", sizebuf); s += strlen(s); break; default: sprintf(s, " stroke"); s += strlen(s); sprintf(s, " newpath Border %s add BoxHeight Border sub %s sub", sizebuf, sizebuf); s += strlen(s); sprintf(s, " %s 270 90 arc closepath fill", sizebuf); s += strlen(s); break; } if (extra) { sprintf(s, " Border %s add %s add Border add BoxHeight border sub %s sub %s sub moveto /EntryFont findfont %s scalefont setfont (%s) show", sizebuf, sizebuf, sizebuf, sizebuf, fontsizebuf, extra); s += strlen(s); } sprintf(s, " grestore"); return RetStrVal(psbuff, info); } /***************************************************************/ /* */ /* FMoonphase */ /* */ /* Phase of moon for specified date/time. */ /* */ /***************************************************************/ static int FMoonphase(func_info *info) { int date, time; switch(Nargs) { case 0: date = JulianToday; time = 0; break; case 1: if (!HASDATE(ARG(0))) return E_BAD_TYPE; date = DATEPART(ARG(0)); if (HASTIME(ARG(0))) { time = TIMEPART(ARG(0)); } else { time = 0; } break; case 2: if (ARG(0).type == DATETIME_TYPE) return E_2MANY_ARGS; if (ARG(0).type != DATE_TYPE && ARG(1).type != TIME_TYPE) return E_BAD_TYPE; date = ARGV(0); time = ARGV(1); break; default: return E_SWERR; } RetVal.type = INT_TYPE; RETVAL = MoonPhase(date, time); return OK; } /***************************************************************/ /* */ /* FMoondate */ /* */ /* Hunt for next occurrence of specified moon phase */ /* */ /***************************************************************/ static int MoonStuff (int want_time, func_info *info); static int FMoondate(func_info *info) { return MoonStuff(DATE_TYPE, info); } static int FMoontime(func_info *info) { return MoonStuff(TIME_TYPE, info); } static int FMoondatetime(func_info *info) { return MoonStuff(DATETIME_TYPE, info); } static int MoonStuff(int type_wanted, func_info *info) { int startdate, starttim; int d, t; startdate = JulianToday; starttim = 0; ASSERT_TYPE(0, INT_TYPE); if (ARGV(0) < 0) return E_2LOW; if (ARGV(0) > 3) return E_2HIGH; if (Nargs >= 2) { if (!HASDATE(ARG(1))) return E_BAD_TYPE; startdate = DATEPART(ARG(1)); if (HASTIME(ARG(1))) { starttim = TIMEPART(ARG(1)); } if (Nargs >= 3) { if (HASTIME(ARG(1))) return E_2MANY_ARGS; ASSERT_TYPE(2, TIME_TYPE); starttim = ARGV(2); } } HuntPhase(startdate, starttim, ARGV(0), &d, &t); RetVal.type = type_wanted; switch(type_wanted) { case TIME_TYPE: RETVAL = t; break; case DATE_TYPE: RETVAL = d; break; case DATETIME_TYPE: RETVAL = d * MINUTES_PER_DAY + t; break; default: return E_BAD_TYPE; } return OK; } static int FTimepart(func_info *info) { if (!HASTIME(ARG(0))) return E_BAD_TYPE; RetVal.type = TIME_TYPE; RETVAL = TIMEPART(ARG(0)); return OK; } static int FDatepart(func_info *info) { if (!HASDATE(ARG(0))) return E_BAD_TYPE; RetVal.type = DATE_TYPE; RETVAL = DATEPART(ARG(0)); return OK; } #ifndef HAVE_SETENV /* This is NOT a general-purpose replacement for setenv. It's only * used for the timezone stuff! */ static int setenv(char const *varname, char const *val, int overwrite) { static char tzbuf[256]; if (strcmp(varname, "TZ")) { fprintf(stderr, "built-in setenv can only be used with TZ\n"); abort(); } if (!overwrite) { fprintf(stderr, "built-in setenv must have overwrite=1\n"); abort(); } if (strlen(val) > 250) { return -1; } sprintf(tzbuf, "%s=%s", varname, val); return(putenv(tzbuf)); } #endif #ifndef HAVE_UNSETENV /* This is NOT a general-purpose replacement for unsetenv. It's only * used for the timezone stuff! */ static void unsetenv(char const *varname) { static char tzbuf[8]; if (strcmp(varname, "TZ")) { fprintf(stderr, "built-in unsetenv can only be used with TZ\n"); abort(); } sprintf(tzbuf, "%s", varname); putenv(tzbuf); } #endif /***************************************************************/ /* */ /* FTz */ /* */ /* Conversion between different timezones. */ /* */ /***************************************************************/ static int tz_set_tz(char const *tz) { int r; if (tz == NULL) { unsetenv("TZ"); r = 0; } else { r = setenv("TZ", tz, 1); } tzset(); return r; } static int tz_convert(int year, int month, int day, int hour, int minute, char const *src_tz, char const *tgt_tz, struct tm *tm) { int r; time_t t; struct tm *res; char const *old_tz; /* init tm struct */ tm->tm_sec = 0; tm->tm_min = minute; tm->tm_hour = hour; tm->tm_mday = day; tm->tm_mon = month; tm->tm_year = year - 1900; tm->tm_wday = 0; /* ignored by mktime */ tm->tm_yday = 0; /* ignored by mktime */ tm->tm_isdst = -1; /* information not available */ /* backup old TZ env var */ old_tz = getenv("TZ"); if (tgt_tz == NULL) { tgt_tz = old_tz; } /* set source TZ */ r = tz_set_tz(src_tz); if (r == -1) { return -1; } /* create timestamp in UTC */ t = mktime(tm); if (t == (time_t) -1) { tz_set_tz(old_tz); return -1; } /* set target TZ */ r = tz_set_tz(tgt_tz); if (r == -1) { tz_set_tz(old_tz); return -1; } /* convert to target TZ */ res = localtime_r(&t, tm); /* restore old TZ */ tz_set_tz(old_tz); /* return result */ if (res == NULL) { return -1; } else { return 1; } } static int FTzconvert(func_info *info) { int year, month, day, hour, minute, r; int jul, tim; struct tm tm; if (ARG(0).type != DATETIME_TYPE || ARG(1).type != STR_TYPE) return E_BAD_TYPE; if (Nargs == 3 && ARG(2).type != STR_TYPE) return E_BAD_TYPE; FromJulian(DATEPART(ARG(0)), &year, &month, &day); r = TIMEPART(ARG(0)); hour = r / 60; minute = r % 60; if (Nargs == 2) { r = tz_convert(year, month, day, hour, minute, ARGSTR(1), NULL, &tm); } else { r = tz_convert(year, month, day, hour, minute, ARGSTR(1), ARGSTR(2), &tm); } if (r == -1) return E_CANT_CONVERT_TZ; jul = Julian(tm.tm_year + 1900, tm.tm_mon, tm.tm_mday); tim = tm.tm_hour * 60 + tm.tm_min; RetVal.type = DATETIME_TYPE; RETVAL = jul * MINUTES_PER_DAY + tim; return OK; } static int FSlide(func_info *info) { int r, omit, d, i, localomit, amt; Token tok; if (!HASDATE(ARG(0))) return E_BAD_TYPE; ASSERT_TYPE(1, INT_TYPE); d = DATEPART(ARG(0)); amt = ARGV(1); if (amt > 1000000) return E_2HIGH; if (amt < -1000000) return E_2LOW; localomit = 0; for (i=2; i 0) { while(amt) { d++; r = IsOmitted(d, localomit, NULL,&omit); if (r) return r; if (!omit) amt--; } } else { while(amt) { d--; if (d < 0) return E_DATE_OVER; r = IsOmitted(d, localomit, NULL,&omit); if (r) return r; if (!omit) amt++; } } RetVal.type = DATE_TYPE; RETVAL = d; return OK; } static int FNonomitted(func_info *info) { int d1, d2, ans, localomit, i; int omit, r; Token tok; if (!HASDATE(ARG(0)) || !HASDATE(ARG(1))) { return E_BAD_TYPE; } d1 = DATEPART(ARG(0)); d2 = DATEPART(ARG(1)); if (d2 < d1) return E_2LOW; localomit = 0; for (i=2; i= 1) { if (!HASDATE(ARG(0))) return E_BAD_TYPE; jul = DATEPART(ARG(0)); } if (Nargs >= 2) { ASSERT_TYPE(1, INT_TYPE); if (ARGV(1) < 0) return E_2LOW; if (ARGV(1) > 6) return E_2HIGH; wkstart = ARGV(1); /* Convert 0=Sun to 0=Mon */ wkstart--; if (wkstart < 0) wkstart = 6; if (Nargs >= 3) { ASSERT_TYPE(2, INT_TYPE); if (ARGV(2) < 1) return E_2LOW; if (ARGV(2) > 31) return E_2HIGH; daystart = ARGV(2); } } RetVal.type = INT_TYPE; /* If start day is 7, first week starts after Jan, otherwise after Dec. */ if (daystart <= 7) { monstart = 0; } else { monstart = 11; } FromJulian(jul, &y, &m, &d); /* Try this year */ candidate = Julian(y, monstart, daystart); while((candidate % 7) != wkstart) candidate++; if (candidate <= jul) { RETVAL = ((jul - candidate) / 7) + 1; return OK; } if (y-1 < BASE) return E_DATE_OVER; /* Must be last year */ candidate = Julian(y-1, monstart, daystart); while((candidate % 7) != wkstart) candidate++; if (candidate <= jul) { RETVAL = ((jul - candidate) / 7) + 1; return OK; } if (y-2 < BASE) return E_DATE_OVER; /* Holy cow! */ candidate = Julian(y-2, monstart, daystart); while((candidate % 7) != wkstart) candidate++; RETVAL = ((jul - candidate) / 7) + 1; return OK; } static int FEvalTrig(func_info *info) { Parser p; Trigger trig; TimeTrig tim; int jul, scanfrom; int r; ASSERT_TYPE(0, STR_TYPE); if (Nargs >= 2) { if (!HASDATE(ARG(1))) return E_BAD_TYPE; scanfrom = DATEPART(ARG(1)); } else { scanfrom = NO_DATE; } CreateParser(ARGSTR(0), &p); p.allownested = 0; r = ParseRem(&p, &trig, &tim, 0); if (r) return r; if (trig.typ != NO_TYPE) { FreeTrig(&trig); return E_PARSE_ERR; } if (scanfrom == NO_DATE) { jul = ComputeTrigger(trig.scanfrom, &trig, &tim, &r, 0); } else { /* Hokey... */ if (trig.scanfrom != JulianToday) { Eprint("Warning: SCANFROM is ignored in two-argument form of evaltrig()"); } jul = ComputeTrigger(scanfrom, &trig, &tim, &r, 0); } if (r == E_CANT_TRIG && trig.maybe_uncomputable) { r = 0; jul = -1; } FreeTrig(&trig); if (r) return r; if (jul < 0) { RetVal.type = INT_TYPE; RETVAL = jul; } else if (tim.ttime == NO_TIME) { RetVal.type = DATE_TYPE; RETVAL = jul; } else { RetVal.type = DATETIME_TYPE; RETVAL = (MINUTES_PER_DAY * jul) + tim.ttime; } return OK; }