/*                                                               -*- C -*-
** Astrolog (Version 4.40) File: general.c
**
** IMPORTANT NOTICE: The graphics database and chart display routines
** used in this program are Copyright (C) 1991-1995 by Walter D. Pullen
** (astara@u.washington.edu). Permission is granted to freely use and
** distribute these routines provided one doesn't sell, restrict, or
** profit from them in any way. Modification is allowed provided these
** notices remain with any altered or edited versions of the program.
**
** The main planetary calculation routines used in this program have
** been Copyrighted and the core of this program is basically a
** conversion to C of the routines created by James Neely as listed in
** Michael Erlewine's 'Manual of Computer Programming for Astrologers',
** available from Matrix Software. The copyright gives us permission to
** use the routines for personal use but not to sell them or profit from
** them in any way.
**
** The PostScript code within the core graphics routines are programmed
** and Copyright (C) 1992-1993 by Brian D. Willoughby
** (brianw@sounds.wa.com). Conditions are identical to those above.
**
** The extended accurate ephemeris databases and formulas are from the
** calculation routines in the program "Placalc" and are programmed and
** Copyright (C) 1989,1991,1993 by Astrodienst AG and Alois Treindl
** (alois@azur.ch). The use of that source code is subject to
** regulations made by Astrodienst Zurich, and the code is not in the
** public domain. This copyright notice must not be changed or removed
** by any user of this program.
**
** Initial programming 8/28,30, 9/10,13,16,20,23, 10/3,6,7, 11/7,10,21/1991.
** X Window graphics initially programmed 10/23-29/1991.
** PostScript graphics initially programmed 11/29-30/1992.
** Last code change made 1/29/1995.
*/

/* $VER: $Id: general.c,v 1.2 1995/07/02 22:21:37 tf Exp $ */

#include "astrolog.h"


/*
******************************************************************************
** General Procedures.
******************************************************************************
*/

/* Swap two floating point values. */

void SwapR(d1, d2)
real *d1, *d2;
{
  real temp;

  temp = *d1; *d1 = *d2; *d2 = temp;
}


/* Return the length of a string (not counting the null terminator). */

int CchSz(sz)
CONST char *sz;
{
  int i;

  for (i = 0; *sz++; i++)
    ;
  return i;
}


/* Compare two strings. Return 0 if they are equal, a positive value if  */
/* the first string is greater, and a negative if the second is greater. */

int NCompareSz(s1, s2)
CONST char *s1, *s2;
{
  while (*s1 && *s1 == *s2)
    s1++, s2++;
  return *s1 - *s2;
}


/* Set a given number of bytes to zero given a starting pointer. */

void ClearB(pb, cb)
lpbyte pb;
int cb;
{
  while (cb-- > 0)
    *pb++ = 0;
}


/* Copy a given number of bytes from one location to another. */

void CopyRgb(pbSrc, pbDst, cb)
byte *pbSrc, *pbDst;
int cb;
{
  while (cb-- > 0)
    *pbDst++ = *pbSrc++;
}


/* Determine the sign of a number: -1 if value negative, +1 if value */
/* positive, and 0 if it's zero.                                     */

real RSgn(r)
real r;
{
  return r == 0.0 ? 0.0 : RSgn2(r);
}


/* Given an x and y coordinate, return the angle formed by a line from the */
/* origin to this coordinate. This is just converting from rectangular to  */
/* polar coordinates; however, we don't determine the radius here.         */

real Angle(x, y)
real x, y;
{
  real a;

  if (x != 0.0) 
  {
    if (y != 0.0)
      a = RAtn(y/x);
    else
      a = x < 0.0 ? rPi : 0.0;
  } 
  else a = y < 0.0 ? -rPiHalf : rPiHalf;

  if (a < 0.0)
    a += rPi;

  if (y < 0.0)
    a += rPi;

  return a;
}


/* Modulus function for floating point values, where we bring the given */
/* parameter to within the range of 0 to 360.                           */

real Mod(d)
real d;
{
  if (d >= rDegMax)        /* In most cases, our value is only slightly */
    d -= rDegMax;          /* out of range, so we can test for it and   */

  else if (d < 0.0)        /* avoid the more complicated arithmetic.    */
    d += rDegMax;

  if (d >= 0 && d < rDegMax)
    return d;

  return (d - RFloor(d/rDegMax)*rDegMax);
}


/* Another modulus function, this time for the range of 0 to 2 Pi. */

real ModRad(r)
real r;
{
  while (r >= rPi2)    /* We assume our value is only slightly out of       */
    r -= rPi2;         /* range, so test and never do any complicated math. */
  while (r < 0.0)
    r += rPi2;
  return r;
}


/* Integer division - like the "/" operator but always rounds result down. */

long Dvd(x, y)
long x, y;
{
  long z;

  if (y == 0)
    return x;

  z = x / y;

  if (((x >= 0) == (y >= 0)) || x-z*y == 0)
    return z;

  return z - 1;
}


/*
******************************************************************************
** General Astrology Procedures.
******************************************************************************
*/

/* A similar modulus function: convert an integer to value from 1..12. */

int Mod12(i)
int i;
{
  while (i > cSign)
    i -= cSign;

  while (i < 1)
    i += cSign;

  return i;
}


/* Convert an inputed fractional degrees/minutes value to a true decimal   */
/* degree quantity. For example, the user enters the decimal value "10.30" */
/* to mean 10 degrees and 30 minutes; this will return 10.5, i.e. 10       */
/* degrees and 30 minutes expressed as a floating point degree value.      */

real DecToDeg(d)
real d;
{
  return RSgn(d)*(RFloor(RAbs(d))+RFract(RAbs(d))*100.0/60.0);
}


/* This is the inverse of the above function. Given a true decimal value */
/* for a zodiac degree, adjust it so the degrees are in the integer part */
/* and the minute expressed as hundredths, e.g. 10.5 degrees -> 10.30    */

real DegToDec(d)
real d;
{
  return RSgn(d)*(RFloor(RAbs(d))+RFract(RAbs(d))*60.0/100.0);
}


/* Return the shortest distance between two degrees in the zodiac. This is  */
/* normally their difference, but we have to check if near the Aries point. */

real MinDistance(deg1, deg2)
real deg1, deg2;
{
  real i;

  i = RAbs(deg1-deg2);
  return i < rDegHalf ? i : rDegMax - i;
}


/* This is just like the above routine, except the min distance value  */
/* returned will either be positive or negative based on whether the   */
/* second value is ahead or behind the first one in a circular zodiac. */

real MinDifference(deg1, deg2)
real deg1, deg2;
{
  real i;

  i = deg2 - deg1;

  if (RAbs(i) < rDegHalf)
    return i;

  return RSgn(i)*(RAbs(i) - rDegMax);
}


/* Return the degree of the midpoint between two zodiac positions, making */
/* sure we return the true midpoint closest to the positions in question. */

real Midpoint(deg1, deg2)
real deg1, deg2;
{
  real mid;

  mid = (deg1+deg2)/2.0;
  return MinDistance(deg1, mid) < rDegQuad ? mid : Mod(mid+rDegHalf);
}


/* Given a planet and sign, determine whether: The planet rules the sign, */
/* the planet has its fall in the sign, the planet exalts in the sign, or */
/* is debilitated in the sign; and return an appropriate character.       */

char Dignify(obj, sign)
int obj, sign;
{
  if (obj > oNorm)
    return ' ';

  if (ruler1[obj] == sign || ruler2[obj] == sign)
    return 'R';

  if (ruler1[obj] == Mod12(sign+6) || ruler2[obj] == Mod12(sign+6))
    return 'F';

  if (exalt[obj] == sign)
    return 'e';

  if (exalt[obj] == Mod12(sign+6))
    return 'd';

  return '-';
}


/* Determine the number of days in a particular month. The year is needed, */
/* too, because we have to check for leap years in the case of February.   */

int DayInMonth(month, year)
int month, year;
{
  int d;

  if (month == mSep || month == mApr || month == mJun || month == mNov)
    d = 30;

  else if (month != mFeb)
    d = 31;

  else 
  {
    d = 28;
    if( ((year % 4) == 0) && ((year % 100) != 0 || (year % 400) == 0 || year <= yeaJ2G))
      d++;
  }
  return d;
}


/* Return the actual number of days in a particular month. Normally, this  */
/* is the same as the above routine which determines the index of the last */
/* day of the month, but the values can differ when changing between       */
/* calendar systems (Julian to Gregorian) in which one can jump over days. */

int DaysInMonth(month, year)
int month, year;
{
  int d;

  d= DayInMonth(month, year);

  if( (year == yeaJ2G) && (month == monJ2G) )
    d -= (dayJ2G2 - dayJ2G1 - 1);

  return d;
}


/* Return the day of the week (Sunday is 0) of the specified given date. */

int DayOfWeek(month, day, year)
int month, day, year;
{
  int d;

  d = (int)((MdyToJulian(month, day, year) + 1) % 7);
  return d < 0 ? d+7 : d;
}


/* Given a day, and the month and year it falls in, add a number of days    */
/* to it and return the new day index. As month changes are not checked for */
/* here, this is mostly just adding the offset to the day; however we need  */
/* to check for calendar changes for when days in a month may be skipped.   */

int AddDay(month, day, year, delta)
int month, day, year, delta;
{
  int d;

  d = day + delta;
  if( (year == yeaJ2G) && (month == monJ2G) )  /* Check for Julian to  */
  {
    if(d > dayJ2G1 && d < dayJ2G2)             /* Gregorian crossover. */
      d += NSgn(delta)*(dayJ2G2-dayJ2G1-1);
  }
  return d;
}


/* Given an aspect and two objects making that aspect with each other,   */
/* return the maximum orb allowed for such an aspect. Normally this only */
/* depends on the aspect itself, but some objects require narrow orbs,   */
/* and some allow wider orbs, so check for these cases.                  */

real GetOrb(obj1, obj2, asp)
int obj1, obj2, asp;
{
  real orb, i;

  orb = aspectorb[asp];
  i = (obj1 > oNorm) ? 2.0 : planetorb[obj1];

  orb = Min(orb, i);
  i = (obj2 > oNorm) ? 2.0 : planetorb[obj2];

  orb = Min(orb, i);

  if (obj1 <= oNorm)
    orb += planetadd[obj1];

  if (obj2 <= oNorm)
    orb += planetadd[obj2];

  return orb;
}


/*
******************************************************************************
** String Procedures.
******************************************************************************
*/

/* Exit the program, and do any cleanup necessary. Note that if we had     */
/* a non-fatal error, and we are in the -Q loop mode, then we won't        */
/* actually terminate the program, but drop back to the command line loop. */

void Terminate(tc)
int tc;
{
  char sz[cchSzDef];

  if (tc == tcForce)
  {
    S = stdout;
    AnsiColor(kWhite);
    sprintf(sz, "\n%s %s exited.\n", szAppName, szVersionCore);
    PrintSz(sz);
  }

  if (tc == tcError && us.fLoop)
    return;

  if (us.fAnsi) 
  {
    sprintf(sz, "%c[0m", chEscape);    /* Get out of any Ansi color mode. */
    PrintSz(sz);
  }

#ifdef AMIGA
  /* Unlike on other systems we must take care here that all graphics are    */
  /* disposed since we allow altering the switches in graphics mode as well, */
  /* and Terminate() might be called with an EndX() pending.                 */

  if(gi.win)
    EndX();
#endif /* AMIGA */

  exit(abs(tc));
}


/* Print a string on the screen. A seemingly simple operation, however we */
/* keep track of what column we are printing at after each newline so we  */
/* can automatically clip at the appropriate point, and we keep track of  */
/* the row we are printing at, so we may prompt before screen scrolling.  */

void PrintSz(sz)
CONST char *sz;
{
  char szInput[cchSzDef], *pch;
  int nT;

#ifdef AMIGA
  if(gi.win) { xPutString(sz); return; }
#endif /* AMIGA */

  for (pch = (char *)sz; *pch; pch++)
  {
    if (*pch != '\n') 
    {
      is.cchCol++;
      if (us.fClip80 && is.cchCol >= us.nScreenWidth)  /* Clip if need be. */
        continue;
    }
    else 
    {
      is.cchRow++;
      is.cchCol = 0;
    }
    putc(*pch, S);

    if (*pch == '\n' && us.nScrollRow > 0 && is.cchRow >= us.nScrollRow && S == stdout) 
    {
      /* If we've printed 'n' rows, stop and wait for a line to be entered. */

      nT = us.fAnsi;
      us.fAnsi = 0;

      fflush(S);  /* added by tf */
      InputString("Press return to continue scrolling", szInput);
      us.fAnsi = nT;
      is.cchRow = 0;

      /* One can actually give a few simple commands before hitting return. */

      if (szInput[0] == '.' || szInput[0] == 'q')
        Terminate(tcForce);
      else if (szInput[0] == '8')
        not(us.fClip80);
      else if (szInput[0] == 'Q')
        us.nScrollRow = 0;
      else if (szInput[0] == 'k') 
      {
        if (us.fAnsi)
          AnsiColor(kDefault);
        not(us.fAnsi);
      }
    }
  }
  fflush(S); /* added by tf */
}


/* Print a single character on the screen. */

void PrintCh(ch)
char ch;
{
  char sz[2];

  sz[0] = ch; sz[1] = chNull;    /* Treat char as a string of length one. */
  PrintSz(sz);                   /* Then call above to print the string.  */
}


/* Print a string on the screen. Unlike the normal PrintSz(), here we still */
/* go to the standard output even if text is being sent to a file with -os. */

void PrintSzScreen(sz)
char *sz;
{
  FILE *fileT;

  fileT = S;
  S = stdout;
  PrintSz(sz);
  S = fileT;
}


/* Print a general user message given a string. This is just like the */
/* warning displayer below just that we print in a different color.   */

void PrintNotice(sz)
char *sz;
{
  AnsiColor(kYellow);

#ifdef AMIGA
  if(gi.win) xPrint("%s\n",sz);
  else
#endif /* AMIGA */

  fprintf(stderr, "%s\n", sz);
  AnsiColor(kDefault); 
}


/* Print a warning message given a string. This is called in non-fatal  */
/* cases where we return to normal execution after printing the string. */

void PrintWarning(sz)
char *sz;
{
  AnsiColor(kRed);
#ifdef AMIGA
  if(gi.win) xPrint("%s\n",sz);
  else
#endif /* AMIGA */
  fprintf(stderr, "%s\n", sz);
  AnsiColor(kDefault); 
}


/* Print an error message. This is called in more serious cases which halt */
/* running of the current chart sequence, which can terminate the program  */
/* but isn't a fatal error in that we can still fall back to the -Q loop.  */

void PrintError(sz)
char *sz;
{
  AnsiColor(kRed);
#ifdef AMIGA
  if(gi.win) xPrint("%s: %s\n", szAppName, sz);
  else
#endif /* AMIGA */
  fprintf(stderr, "%s: %s\n", szAppName, sz);
  Terminate(tcError);
  AnsiColor(kDefault); 
}


/* Simplification for a commonly printed error message. */

void ErrorArgc(szOpt)
char *szOpt;
{
  char sz[cchSzDef];

  sprintf(sz, "Too few options to switch %c%s", chSwitch, szOpt);
  PrintError(sz);
}


/* Another simplification for a commonly printed error message. */

void ErrorValN(szOpt, nVal)
char *szOpt;
int nVal;
{
  char sz[cchSzDef];

  sprintf(sz, "Value %d passed to switch %c%s out of range.\n", nVal, chSwitch, szOpt);
  PrintError(sz);
}


/* Yet another place to print a type of error message. */

void ErrorArgv(szOpt)
char *szOpt;
{
  char sz[cchSzDef];

  sprintf(sz, "The switch %c%s is not allowed now.\n", chSwitch, szOpt);
  PrintError(sz);
}


/* Still another place to print a type of error message. */

void ErrorSwitch(szOpt)
char *szOpt;
{
  char sz[cchSzDef];

  sprintf(sz, "Unknown switch '%s'", szOpt);
  PrintError(sz);
}


/* A simple procedure used throughout Astrolog: Print a particular */
/* character on the screen 'n' times.                              */

void PrintTab(ch, cch)
char ch;
int cch;
{
  int i;

  for (i = 0; i < cch; i++)
    PrintCh(ch);
}


/* Set an Ansi text color. */

void AnsiColor(k)
int k;
{
  char sz[cchSzDef];
  int cchSav;

#ifdef AMIGA
  if(gi.win) { DrawColor(k); return; }
#endif /* AMIGA */

  /* Special case: If we are passed the value Reverse, and ansi is not    */
  /* only on but set to a value > 1, then we'll enter reverse video mode. */

  if (!us.fAnsi || (k == kReverse && us.fAnsi < 2))
    return;

  cchSav = is.cchCol;
  is.cchCol = 0;
  sprintf(sz, "%c[", chEscape);
  PrintSz(sz);

  if (k == kDefault)
    PrintCh('0');

  else if (k == kReverse) 
  {
    PrintCh('7');
  } 
  else
  {
    sprintf(sz, "%c;%d", k > 7 ? '1' : '0', 30 + (k & 7));
    PrintSz(sz);
  }
  PrintCh('m');
  is.cchCol = cchSav;
}


/* Print a zodiac position on the screen. This basically just prints the */
/* string returned from SzZodiac() below, except we take care of color.  */

void PrintZodiac(deg)
real deg;
{
  AnsiColor(kElemA[(int)(deg / 30.0) & 3]);
  PrintSz(SzZodiac(deg));
  AnsiColor(kDefault);
}


/* Given a zodiac position, return a string containing it as it's */
/* formatted for display to the user.                             */

char *SzZodiac(deg)
real deg;
{
  static char zod[11];
  int sign, d, m;
  real s;

  switch (us.nDegForm) {

  case 0:
    /* Normally, we format the position in degrees/sign/minutes format: */

    deg = Mod(deg + (is.fSeconds ? rRound/60.0/60.0 : rRound/60.0));
    sign = (int)floor(deg / 30.0);
    d = (int)deg - sign*30;
    m = (int)(RFract(deg)*60.0);
    sprintf(zod, "%2d%c%c%c%02d", d, chSig3(sign + 1), m);
    if (is.fSeconds) {
      s = RFract(deg)*60.0; s = RFract(s)*60.0;
      sprintf(&zod[7], "'%02d\"", (int)s);
    }
    break;

  case 1:
    /* However, if -sh switch in effect, get position in hours/minutes: */

    deg = Mod(deg + (is.fSeconds ? rRound/4.0/60.0 : rRound/4.0));
    d = (int)(deg / 15.0);
    m = (int)((deg - (real)d*15.0)*4.0);
    sprintf(zod, "%2dh,%02dm", d, m);
    if (is.fSeconds) {
      s = RFract(deg)*4.0; s = RFract(s)*60.0;
      sprintf(&zod[7], ",%02ds", (int)s);
    }
    break;

  default:
    /* Otherwise, if -sd in effect, format position as a simple degree: */

    sprintf(zod, is.fSeconds ? "%11.7f" : "%7.3f", deg);
    break;
  }
  return zod;
}


/* This is similar to formatting a zodiac degree, but here we return a */
/* string of a (signed) declination value in degrees and minutes.      */

char *SzAltitude(deg)
real deg;
{
  static char alt[10];
  int d, m, f;
  real s;
  char ch;

  f = deg < 0.0;
  deg = RAbs(deg) + (is.fSeconds ? rRound/60.0/60.0 : rRound/60.0);
  d = (int)deg;
  m = (int)(RFract(deg)*60.0);
  ch = us.fAnsi == -1 ? 128 : chDeg1;
  sprintf(alt, "%c%2d%c%02d'", f ? '-' : '+', d, ch, m);

  if (is.fSeconds)
  {
    s = RFract(deg)*60.0; s = RFract(s)*60.0;
    sprintf(&alt[7], "%02d\"", (int)s);
  }
  return alt;
}


/* Here we return a string simply expressing the given value as degrees */
/* and minutes (and sometimes seconds) in the 0 to 360 degree circle.   */

char *SzDegree(deg)
real deg;
{
  static char pos[11];
  int d, m;
  real s;

  deg = RAbs(deg) + (is.fSeconds ? rRound/60.0/60.0 : rRound/60.0);
  d = (int)deg;
  m = (int)(RFract(deg)*60.0);
  sprintf(pos, "%3d%c%02d'", d, chDeg1, m);
  if (is.fSeconds) 
  {
    s = RFract(deg)*60.0; s = RFract(s)*60.0;
    sprintf(&pos[7], "%02d\"", (int)s);
  }
  return pos;
}


/* Another string formatter, here we return a date string given a month,    */
/* day, and year. We format with the day or month first based on whether    */
/* the "European" date variable is set or not. The routine also takes a     */
/* parameter to indicate how much the string should be abbreviated, if any. */

char *SzDate(mon, day, yea, nFormat)
int mon, day, yea, nFormat;
{
  static char szDate[20];

  if (us.fEuroDate)
  {
    switch (nFormat) {
    case  2: sprintf(szDate, "%2d %c%c%c%5d", day, chMon3(mon), yea); break;
    case  1: sprintf(szDate, "%d %s %d", day, szMonth[mon], yea);     break;
    case -1: sprintf(szDate, "%2d-%2d-%2d", day, mon, abs(yea)%100);  break;
    default: sprintf(szDate, "%2d-%2d-%4d", day, mon, yea);           break;
    }
  } 

  else 
  {
    switch (nFormat) {
    case  3: sprintf(szDate, "%c%c%c %2d, %d", chMon3(mon), day, yea); break;
    case  2: sprintf(szDate, "%c%c%c %2d%5d", chMon3(mon), day, yea);  break;
    case  1: sprintf(szDate, "%s %d, %d", szMonth[mon], day, yea);     break;
    case -1: sprintf(szDate, "%2d/%2d/%2d", mon, day, abs(yea)%100);   break;
    default: sprintf(szDate, "%2d/%2d/%4d", mon, day, yea);            break;
    }
  }
  return szDate;
}


/* Return a string containing the given time expressed as an hour and */
/* minute quantity. This is formatted in 24 hour or am/pm time based  */
/* on whether the "European" time format flag is set or not.          */

char *SzTime(hr, min)
int hr, min;
{
  static char tim[8];

  if (us.fEuroTime)
    sprintf(tim, "%2d:%02d", hr, min);

  else
    sprintf(tim, "%2d:%02d%cm", Mod12(hr), min, hr < 12 ? 'a' : 'p');

  return tim;
}


/* This just determines the correct hour and minute and calls the above. */

char *SzTim(tim)
real tim;
{
  return SzTime(NFloor(tim), (int)(RFract(RAbs(Tim))*100.0+rRound/60.0));
}


/* Return a string containing the given time zone, given as a real value     */
/* having the hours before GMT in the integer part and minutes fractionally. */

char *SzZone(zon)
real zon;
{
  static char tim[7];

  sprintf(tim, "%c%d:%02d", zon > 0.0 ? '-' : '+', (int)RAbs(zon), (int)(RFract(RAbs(zon))*100.0+rRound/60.0));
  return tim;
}


/* Nicely format the given longitude and latitude locations and return    */
/* them in a string. Various parts of the program display a chart header, */
/* and this allows the similar computations to be coded only once.        */

char *SzLocation(lon, lat)
real lon, lat;
{
  static char loc[15];
  int i, j;
  char ch;

  i = (int)(RFract(RAbs(lon))*100.0+rRound);
  j = (int)(RFract(RAbs(lat))*100.0+rRound);
  ch = us.fAnsi == -1 ? 128 : chDeg1;

  sprintf(loc, "%3.0f%c%02d%c%3.0f%c%02d%c",
    RFloor(RAbs(lon)), ch, i, lon < 0.0 ? 'E' : 'W',
    RFloor(RAbs(lat)), ch, j, lat < 0.0 ? 'S' : 'N');

  return loc;
}


#ifdef TIME

#ifdef AMIGA
/*
 *  The Amiga system clock knows local mean time (LMT) only.  Time zone settings
 *  are managed via the locale.library and are configurable by the Locale prefs.
 *
 *  Astrolog's GetTimeNow() function expects the time() function to return the
 *  #of seconds since 0 hours, 0 minutes, 0 seconds, January 1, 1970, Coordinated
 *  Universal Time.  My C compilers however all return LMT instead of CUT and so
 *  we need to compute GMT from LMT here.
 */

/*global*/ struct Library *LocaleBase;

time_t gmtoff(void)
{
  time_t t= 0;  /* the result will hold the #of seconds from GMT */

  char *s= getenv("GMTOFF");

  /*
   *  We first check the existence of the environment variable `GMTOFF' which can
   *  be used to override the Locale settings.
   */

  if(s && *s)
  {
    t= (time_t)atol(s);
    /*printf("GMT Offset (from environment variable `GMTOFF') = %ld second(s)\n", t);*/
  }

  else
  {
    /* We try to open locale.library (V38+) in order to get the loc_GMTOffset from there. */

    LocaleBase= OpenLibrary("locale.library", 38L);

    if(LocaleBase)
    {
      /* We now try to obtain the current locale settings as saved via the Locale prefs. */

      struct Locale *loc= OpenLocale(NULL);

      if(loc)
      {
        /*
         *  The loc_GMTOffset field contains the offset in minutes of the current location from GMT.
         *  Positive indicates a Westerly direction from GMT, negative Easterly.
         */

        t= -60 * loc->loc_GMTOffset;
        CloseLocale(loc);

        /*printf("GMT Offset (from `locale.library') = %ld second(s)\n", t);*/
      }

      CloseLibrary(LocaleBase);
    }
  }

  return t;
}
#endif /* AMIGA */

#if 0
void print_time(time_t *t)
{
  struct tm *tp= localtime(t);

  printf("tm_sec    = %d;"     "\n", tp->tm_sec);        /* seconds [0,59]                                 */
  printf("tm_min    = %d;"     "\n", tp->tm_min);        /* minutes [0,59]                                 */
  printf("tm_hour   = %d;"     "\n", tp->tm_hour);       /* hours [0,23]                                   */
  printf("tm_mday   = %d;"     "\n", tp->tm_mday);       /* day of month [1,31]                            */
  printf("tm_mon    = %d;"     "\n", tp->tm_mon);        /* month of the year [0,11]                       */
  printf("tm_year   = %d;"     "\n", tp->tm_year);       /* year [0,99] (-1900)                            */
  printf("tm_wday   = %d;"     "\n", tp->tm_wday);       /* days since sunday [0,6]                        */
  printf("tm_yday   = %d;"     "\n", tp->tm_yday);       /* days since jan 1 [0,365]                       */
  printf("tm_isdst  = %d;"     "\n", tp->tm_isdst);      /* nonzero if daylight savings time is in effect  */
  printf("tm_gmtoff = %ld;"    "\n", tp->tm_gmtoff);     /* offset from GMT in seconds                     */
                                                         /* positive values indicating East of Greenwich   */
  printf("tm_zone   = \"%s\";" "\n", tp->tm_zone);       /* abbreviation of timezone name                  */

  printf("--> getenv(TZ) = \"%s\"\n", getenv("TZ"));
}
#endif /*0*/

/* Compute the date and time it is right now as the program is running      */
/* using the computer's internal clock. We do this by getting the number    */
/* of seconds which have passed since January 1, 1970 and going from there. */
/* The time return value filled is expressed in the given zone parameter.   */

void GetTimeNow(mon, day, yea, tim, zon)
int *mon, *day, *yea;
real *tim, zon;
{
  dword curtimer;  /* time_t */
  int min, sec;
  real hr;

  time(&curtimer);

#ifdef AMIGA
  /* The internal clock on Amiga systems knows local mean time only! */
  curtimer -= gmtoff();  /* LMT -> GMT */
#endif

  sec = (int)(curtimer % 60);
  curtimer /= 60;

  min = (int)(curtimer % 60);
  curtimer /= 60;

#ifdef MAC
  curtimer += 8;
#endif

  hr = (real)(curtimer % 24) - zon;

  curtimer /= 24;

  while (hr <   0.0) { curtimer--;  hr += 24.0; }
  while (hr >= 24.0) { curtimer++;  hr -= 24.0; }

  curtimer += ldTime;  /* Number of days between 1/1/1970 and 1/1/4713 BC. */

  JulianToMdy((real)curtimer, mon, day, yea);

  *tim = hr + (real)min / 100.0 + (real)sec / 6000.0;

  /*printf("--> GetTimeNow()  mon=%d, day=%d, yea=%d, tim=%f, zon=%f\n", *mon, *day, *yea, *tim, zon);*/
}
#endif /* TIME */


#ifdef PCG
/* Map one character value to another. This is used in processing special  */
/* keys and alt key combinations, which are read in from the keyboard as a */
/* zero immediately followed by some value. This converts that value into  */
/* something more useful to process and deal with.                         */

int NFromAltN(nAlt)
int nAlt;
{
  /* Map number pad keys to the numbers characters they correspond to. */

  if (nAlt == 82)
    return '0';

  else if (FBetween(nAlt, 79, 81))
    return '1' + nAlt - 79;

  else if (FBetween(nAlt, 75, 77))
    return '4' + nAlt - 75;

  else if (FBetween(nAlt, 71, 73))
    return '7' + nAlt - 71;

  /* Map F1 through F12 function keys to the values 201-212. */

  else if (FBetween(nAlt, 59, 68))
    return 201 + nAlt - 59;

  else if (FBetween(nAlt, 133, 134))
    return 211 + nAlt - 133;

  /* Map Shift+F1 through Shift+F12 keys to the values 213-224. */

  else if (FBetween(nAlt, 84, 93))
    return 213 + nAlt - 84;

  else if (FBetween(nAlt, 135, 136))
    return 223 + nAlt - 135;

  /* Map Control+F1 through Control+F12 keys to the values 225-236. */

  else if (FBetween(nAlt, 94, 103))
    return 225 + nAlt - 94;

  else if (FBetween(nAlt, 137, 138))
    return 235 + nAlt - 137;

  /* Map Alt+F1 through Alt+F12 keys to the values 237-248. */

  else if (FBetween(nAlt, 104, 113))
    return 237 + nAlt - 104;

  else if (FBetween(nAlt, 139, 140))
    return 247 + nAlt - 139;

  return chNull;
}
#endif


/* Given a string representing the complete pathname to a file, strip off    */
/* all the path information leaving just the filename itself. This is called */
/* by the main program to determine the name of the Astrolog executable.     */

char *ProcessProgname(szPath)
char *szPath;
{
  char *b, *c, *e;

  b = c = szPath;

  while (*c)
  {
#ifdef PC
    *c = ChUncap(*c);    /* Because DOS filenames are case insensitive. */
#endif
    c++;
  }
  e = c;

  while (c > b && *c != '.')
    c--;

  if (c > b)
    *c = 0;

  else
    c = e;

  while (c > b && *c != chDirSep)
    c--;

  if (c > b)
    szPath = c+1;

  return szPath;
}


/* Given a string, return a pointer to a persistent version of it, where  */
/* 'persistent' means its contents won't be invalidated when the stack    */
/* frame changes. Strings such as macros, et al, need to be in their own  */
/* space and can't just be local variables in a function reading them in. */

char *SzPersist(szSrc)
char *szSrc;
{
  char szT[cchSzDef], *szNew;
  int cb;

  /* Some strings such as outer level command line parameter arguments */
  /* already persist, so we can just return the same string passed in. */ 

  if (is.fSzPersist)
    return szSrc;

  /* Otherwise we make a copy of the string in the local heap and use it. */

  cb = CchSz(szSrc)+1;
  AllocateNear(szNew, cb);

  if (szNew == NULL) 
  {
    sprintf(szT, "%s: Not enough near memory for string (%d bytes).", szAppName, cb);
    PrintWarning(szT);
  } 
  else
    CopyRgb((byte *)szSrc, (byte *)szNew, cb);

  return szNew;
}


/* This is Astrolog's memory allocation routine, returning a pointer given */
/* a size, a flag for if it is a more than 64K huge allocation, and a      */
/* string to use when printing an error if the allocation fails.           */

lpbyte PAllocate(lcb, fHuge, szType)
long lcb;
bool fHuge;
char *szType;
{
  char szT[cchSzDef];
  lpbyte lp;

  if (fHuge)
    AllocateHuge(lp, lcb);
  else
    AllocateFar(lp, (int)lcb);

#ifdef PC
  /* For PC's the array better not cross a segment boundary. */

  if (lp && !fHuge && WHi(WLo(lp) + lcb) > 0)
    lp = NULL;
#endif

  if (lp == NULL && szType) 
  {
    sprintf(szT, "%s: Not enough memory for %s (%ld bytes).",
      szAppName, szType, lcb);
    PrintWarning(szT);
  }
  return lp;
}

/* general.c */
