
#define IDENTIFICATION "tm 1.22, made on 2008-01-30"

//
// Purpose:
//
//    Exercise unix/c timevalues.
//
// Usage:
//
//    See help().
//
// Notes:
//
//    MS C:
//
//       mktime()       on failure, returns -1
//       localtime()    on failure, returns NULL (prior to 1980)
//       gmtime()       on failure, returns NULL (prior to 1980)
//
//    HPUX C: Remember, -1 *is* a valid timeval for this guy...
//
//       mktime()       on failure, returns -1 and sets errno to ERANGE
//       localtime()    doesn't fail
//       gmtime()       doesn't fail
//
//    Also, the ranges are different. See help().
//
// Authors:
//
//    William W. Patterson
//
// History:
//
//    2000-01-27  1.00  WWP   Creation.
//    2000-02-02  1.10  WWP   Heavily modified for greater utility!
//    2000-02-02  1.11  WWP   Renamed getopt() to mygetopt() for hpux.
//    2000-02-02  1.12  WWP   Improved error messages.
//    2000-02-02  1.20  WWP   Earlier than 1980-01-01 is a problem for DOS.
//    2008-01-30  1.21  WWP   Use '-' instead of '.' in dates, per ISO.
//    2008-01-30  1.22  WWP   Use __GNUC__ predefined macro.
//

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#ifdef _HPUX_SOURCE
#include <sys/types.h>
#endif

#if !defined(_MSC_VER) && !defined(_HPUX_SOURCE) && !defined(__GNUC__)
#error SOURCE MODIFICATION MAY BE NECESSARY!
#endif

int debugging = 0;

char *daynames[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };

#define CONVERSION 1
#define TABLE 2

unsigned long ujs[] =
   {
   0x00000000L,
   0x10000000L,
   0x20000000L,
   0x30000000L,
   0x40000000L,
   0x50000000L,
   0x60000000L,
   0x70000000L,
   0x7fffffffL,
   0x80000000L,
   0x80000001L,
   0x90000000L,
   0xa0000000L,
   0xb0000000L,
   0xc0000000L,
   0xd0000000L,
   0xe0000000L,
   0xf0000000L,
   0xffffffffL
   };

//*****************************************************************************

#define GETOPTERR(s,c) \
   if (opterr) \
      { \
      char errbuf[2]; \
      errbuf[0] = c; \
      errbuf[1] = '\n'; \
      (void) write(2,argv[0],(unsigned)strlen(argv[0])); \
      (void) write(2,s,(unsigned)strlen(s)); \
      (void) write(2,errbuf,2); \
      }

int opterr = 1;
int optind = 1;
int optopt;
char *optarg;

int mygetopt( int argc, char **argv, char *opts )

   {

   static int sp = 1;
   register int c;
   register char *cp;

   if ( sp == 1 )
      {
      if ( optind>=argc || argv[optind][0]!='-' || argv[optind][1]=='\0' )
         {
         return(EOF);
         }
      else
         {
         if ( strcmp(argv[optind],"--") == (int)NULL ) /* 002 */
            {
            ++optind;
            return(EOF);
            }
         }
      }

   optopt = c = argv[optind][sp];

   if ( c==':' || (cp=strchr(opts,c))==NULL )
      {
      GETOPTERR(": illegal option -- ", c);
      if ( argv[optind][++sp] == '\0' )
         {
         ++optind;
         sp = 1;
         }
      return('?');
      }

   if ( *++cp == ':' )
      {
      if ( argv[optind][sp+1] != '\0' )
         {
         optarg = &argv[optind++][sp+1];
         }
      else
         {
         if ( ++optind >= argc )
            {
            GETOPTERR(": option requires an argument -- ",c);
            sp = 1;
            return('?');
            }
         else
            {
            optarg = argv[optind++];
            }
         }
      sp = 1;
      }
   else
      {
      if ( argv[optind][++sp] == '\0' )
         {
         sp = 1;
         ++optind;
         }
      optarg = NULL;
      }

   return(c);

   }

//*****************************************************************************

void help( char *errortext )
   {
   printf("\n%s\n\n",IDENTIFICATION);
   puts("Quirks:\n");
   puts("1. In HPUX, the usual, except that all timevals are legal and");
   puts("   wrap back around to 1901 and then back to the base date.");
   puts("2. DOS version can't handle dates prior to 1980, but afterwards");
   puts("   timeval is treated as unsigned, running until 2106.");
   puts("3. First Linux version is 1.22; behaves like the HPUX version.\n");
   puts("usage: tm [options] [args]");
   puts("   -h -? ?              help");
   puts("   -n                   show info for current time");
   puts("   -d yyyymmddhhmmss    show info for this local date/time");
   puts("   -t n                 show info for this timeval");
   puts("   -T                   show time table for this version");
   puts("   -D                   show debug info\n");
   if (*errortext) puts(errortext);
   exit(0);
   }

//*****************************************************************************

int main( int argc, char **argv )
   {

   int j, n, opt;

   int jyear, jmonth, jday, jhour, jminute, jsecond;

   time_t jtime = -1;

   struct tm s_gmtime;
   struct tm s_localtime;

   struct tm *ptm;

   int mode = 0;

   while ( optind < argc )
      {
      while ( ( opt = mygetopt(argc,argv,"?Dd:hnTt:") ) != EOF )
         {
         switch(opt)
            {
            case 'D':
               debugging = 1;
               break;
            case 'T':
               mode = TABLE;
               break;
            case 'd':
               if ( 6 != sscanf(optarg,"%04d%02d%02d%02d%02d%02d",&jyear,&jmonth,&jday,&jhour,&jminute,&jsecond) ) help("INVALID DATE/TIME ARGUMENT");
               s_localtime.tm_year = jyear - 1900;
               s_localtime.tm_mon = jmonth - 1;
               s_localtime.tm_mday = jday;
               s_localtime.tm_hour = jhour;
               s_localtime.tm_min = jminute;
               s_localtime.tm_sec = jsecond;
               s_localtime.tm_isdst = -1;
#ifdef _HPUX_SOURCE
               errno = 0;
#endif
               jtime = mktime(&s_localtime);
#ifdef _HPUX_SOURCE
               if ( ERANGE == errno ) help("HPUXC INVALID DATE/TIME ARGUMENT");
#endif
#if defined(_MSC_VER) || defined(__GNUC__)
               if ( -1 == jtime ) help("MSC INVALID DATE/TIME ARGUMENT");
#endif
#if !defined(_MSC_VER) && !defined(_HPUX_SOURCE) && !defined(__GNUC__)
#error ERROR CONDITION NOT HANDLED!
#endif
               if (debugging) printf("user specified time value: %ld\n",jtime);
               mode = CONVERSION;
               break;
            case 'h':
            case '?':
               help("");
               break;
            case 'n':
               time(&jtime);
               if (debugging) printf("using current time value: %ld\n",jtime);
               mode = CONVERSION;
               break;
            case 't':
               jtime = atol(optarg);
               if (debugging) printf("user specified time value: %ld\n",jtime);
               mode = CONVERSION;
               break;
            default:
               break;
            }
         }
      if ( optind < argc )
         {
         switch(*argv[optind])
            {
            case '?':
               help("");
               break;
            default:
               printf("unrecognized argument: %s\n",argv[optind]);
               break;
            }
         ++optind;
         }
      }

   switch(mode)
      {

      case CONVERSION:

         ptm = localtime(&jtime);

         // The following error cannot happen in hpux...
         if ( (struct tm *)NULL == ptm ) help("MISSING OR INVALID ARGUMENTS");

         memcpy(&s_localtime,ptm,sizeof(struct tm));

         ptm = gmtime(&jtime);

         // The following error cannot happen in hpux...
         if ( (struct tm *)NULL == ptm ) help("MISSING OR INVALID ARGUMENTS");

         memcpy(&s_gmtime,ptm,sizeof(struct tm));

         printf("timevalue: %ld\n",jtime);

         printf("local%s %04d-%02d-%02d %02d:%02d:%02d %s",
            s_localtime.tm_isdst ? " dst:" : ":    ",
            s_localtime.tm_year + 1900,
            s_localtime.tm_mon + 1,
            s_localtime.tm_mday,
            s_localtime.tm_hour,
            s_localtime.tm_min,
            s_localtime.tm_sec,
            daynames[s_localtime.tm_wday]
            );
         puts("");

         printf("gmt%s   %04d-%02d-%02d %02d:%02d:%02d %s",
            s_gmtime.tm_isdst ? " dst:" : ":    ",
            s_gmtime.tm_year + 1900,
            s_gmtime.tm_mon + 1,
            s_gmtime.tm_mday,
            s_gmtime.tm_hour,
            s_gmtime.tm_min,
            s_gmtime.tm_sec,
            daynames[s_gmtime.tm_wday]
            );
         puts("");

         break;

      case TABLE:

         if ( 4 != sizeof(long) )
            {
            puts("this program was designed for 32-bit machines");
            exit(0);
            }

         n = sizeof(ujs)/sizeof(ujs[0]);

         for ( j=0; j<n; ++j )
            {

            jtime = (long)ujs[j];

            printf("%08lX%15ld",ujs[j],jtime);

            printf("    ");

            ptm = localtime(&jtime);

            if ( (struct tm *)NULL == ptm ) // cannot happen in hpux
               {
               printf("-------ERROR-------");
               }
            else
               {
               memcpy(&s_localtime,ptm,sizeof(struct tm));
               printf("%04d-%02d-%02d %02d:%02d:%02d",
                  s_localtime.tm_year + 1900,
                  s_localtime.tm_mon + 1,
                  s_localtime.tm_mday,
                  s_localtime.tm_hour,
                  s_localtime.tm_min,
                  s_localtime.tm_sec
                  );
               }

            printf("    ");

            ptm = gmtime(&jtime);

            if ( (struct tm *)NULL == ptm ) // cannot happen in hpux
               {
               printf("-------ERROR-------");
               }
            else
               {
               memcpy(&s_gmtime,ptm,sizeof(struct tm));
               printf("%04d-%02d-%02d %02d:%02d:%02d",
                  s_gmtime.tm_year + 1900,
                  s_gmtime.tm_mon + 1,
                  s_gmtime.tm_mday,
                  s_gmtime.tm_hour,
                  s_gmtime.tm_min,
                  s_gmtime.tm_sec
                  );
               }

            puts("");

            }

         break;

      default:

         help("MISSING OR INVALID ARGUMENTS");

         break;

      }

   }

