/*
------------------------------------------------------------------------
History
------------------------------------------------------------------------
*/
/*
dcalc - date calculator
*/

/*
------------------------------------------------------------------------
Include Files
------------------------------------------------------------------------
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/*
------------------------------------------------------------------------
Defines
------------------------------------------------------------------------
*/
#define VERSION_STR "dcalc 0.9"
#define TIME_LEN 64


/*
------------------------------------------------------------------------
DEBUGGING MACROS
------------------------------------------------------------------------
*/
#define WRITEMSG \
   printf ("File %s line %d: ", __FILE__, __LINE__); \
   printf ("errmsg <%s>\n", strerror(errno)); fflush(stdout); 
#define WRITEVAR(VAL,FMT) \
   printf ("File %s line %d: ", __FILE__, __LINE__); \
   printf ("%s=",#VAL); printf (#FMT, VAL); printf ("\n"); \
   fflush(stdout); 


/*
------------------------------------------------------------------------
Macros
------------------------------------------------------------------------
*/
/*
------------------------------------------------------------------------
Typedefs
------------------------------------------------------------------------
*/
/*
------------------------------------------------------------------------
Global Variables
------------------------------------------------------------------------
*/

/*
------------------------------------------------------------------------
Module Wide Variables
------------------------------------------------------------------------
*/
/*
------------------------------------------------------------------------
Local Function Prototypes
------------------------------------------------------------------------
*/
void Usage(void);
long tomin (char *);
long date2nmin (int year, int month, int day, int hour, int min);
void nmin2date (int *, int *, int *, int *, int *, long); 



/*
------------------------------------------------------------------------
Main
------------------------------------------------------------------------
*/
int main (int argc, char *argv[]) {
   char optchar;
   int  nopt=0;
   int  nscan=0;
   char *time_increment = NULL;
   char *rounding_time  = NULL;
   char *initial_time   = NULL;
   int year, month, day, hour, min;
   long current_time  = 0;

   while (-1 != (optchar=getopt(argc,argv,"d:hi:r:t:v"))) {
      nopt++;
      switch (optchar) {
         case '?':
            return 1;
         case 'h':
            Usage();
            return 0;
         case 'v':
            printf ("%s (compiled %s)\n", VERSION_STR, __DATE__);
            return 0;
         case 'i':
            time_increment = strdup(optarg);
            break;
         case 'r':
            rounding_time  = strdup(optarg);
            break;
         default:
            return 1;
      }
   }

	/*  Check for required args  */
	if (optind>=argc) {
		Usage();
		return 1;
	}
	initial_time = strdup(argv[optind]);


   /*  Read input time  */
   if (initial_time) {
      nscan = sscanf (initial_time, "%d-%d-%d-%d:%d", &year, &month, &day, &hour, &min);
      if (nscan<3) {
         perror ("Initial time is in unrecognized format\n");
         exit (1);
      } else if (nscan<5)  {
      	nscan = sscanf (initial_time, "%d-%d-%d", &year, &month, &day);
			hour = min = 0;
      }
      current_time = date2nmin (year, month, day, hour, min);
   }

   /*  Round time  */
   if (rounding_time) 
      current_time -= current_time % tomin(rounding_time);

   /*  Increment time  */
   if (time_increment) 
      current_time += tomin(time_increment);

   /*  Print time  */
	nmin2date (&year, &month, &day, &hour, &min, current_time);
	printf ("%4d-%02d-%02d-%02d:%02d\n", year, month, day, hour, min);

   /*  All done  */
   return 0;
}

/*
------------------------------------------------------------------------
Functions
------------------------------------------------------------------------
*/

/* 
Convert strings of form 10s, 21m, 102h, 7d to seconds
where letters smhd denote seconds, minutes, hours, days
*/
long tomin (char *timestr) {
   int length = strlen(timestr);
   int unitchar   = tolower (timestr[length-1]);
   long unit;
   long time;

   switch (unitchar) {
      case 'd':
         unit = 60*24;
         break;
      case 'h':
         unit = 60;
         break;
      case 'm':
         unit = 1;
         break;
      default:
         perror ("Invalid time unit\n");
         exit(1);
   }
   return unit * atol(timestr);
}


void Usage (void) {
   printf("\n");
   printf(" dcalc [-hfiv] <date-time>\n");
   printf("\n");
   printf("   Increment and round date/times\n"); 
   printf("\n");
   printf("   -h               Print help.\n");
   printf("   -i  <increment>  Adds (negative) increment to time.\n");
   printf("                    Append letter s,m,h,d for to use units of\n");
   printf("                    seconds, minutes, hours or days.\n");
   printf("   -r  <interval>   Round time to previous interval using same\n");
   printf("                    units as above.  This is done before\n");
   printf("                    increment is added.  For example, if\n");
   printf("                    time is 12:41, the '-r 30min' option would\n");
   printf("                    round the time to 12:30\n");
   printf("   -v               Print version.\n");
	printf("\n");
	printf("  <date-time> is in format yyyy-mm-dd or yyyy-mm-dd-hh:mm\n");
   printf("\n");
   exit(0);
}




/*
Need this because  Linux time routines give screwed up time
when DST changes to Standard time.  GNU date does it right
but it is not ported to all systems.

..sigh...   :( 
*/

int dayofyear_for_month[12] = {0,31,59,90,120,151,181,212,243,273,304,334};

/*  Calculate days since Jan 1, 1900 (day 0)  */
long date2nmin (int year, int month, int day, int hour, int min) {
	long nday;
	int nyear, lyear, dayofyear;

	if (month<0 || month>12) {
		fprintf (stderr, "nday: month (%d) out of range (1-12)\n", month);
		exit(1);
	}
	nyear = year - 1900;
	/*  Previous leep years  */
	lyear = (year - 1904)/4;
	if (lyear<0) lyear=0;
	dayofyear = dayofyear_for_month[month-1];
	/*  This leap year  */
	if (year%4==0 && dayofyear > 59)  lyear++;
	nday = nyear*365 + lyear + dayofyear + day-1;
	return 24*60*nday + 60*hour + min;
}

/*  Given days since Jan 1, 1900, calculate date  */
void nmin2date (int *year, int *month, int *day, int *hour, int *min, long nmin) {
	int test_year, test_month, test_day;
	int year1_nmin, year2_nmin, month1_nmin, month2_nmin;
	long nday;

	nday = nmin / 60 / 24;

	/*  First guess at year  */
	test_year = 1900+nmin/60/24/365;
	/*  Find component of nmin for previous years  */
	do {
		year1_nmin = date2nmin (test_year,   1, 1, 0, 0);
		year2_nmin = date2nmin (test_year+1, 1, 1, 0, 0);
		if (year1_nmin>nmin) test_year--;
		else if (nmin>=year2_nmin) test_year++;
	} while (year1_nmin > nmin || nmin>=year2_nmin);
	/*  Find component of nmin for previous months in year  */
	test_month = (nmin-year1_nmin)/60/24/30 + 1;
	if (test_month<12) {
		do {
			month1_nmin = date2nmin (test_year, test_month,   1, 0, 0);
			month2_nmin = date2nmin (test_year, test_month+1, 1, 0, 0);
			if (month1_nmin > nmin) test_month--;
			else if (nmin >= month2_nmin) test_month++;
		} while ( (month1_nmin > nmin || nmin >= month2_nmin) && test_month>0 && test_month<12);
		test_day = (nmin-month1_nmin)/24/60+1;
	}
	*year  = test_year;
	*month = test_month;
	*day   = test_day;
	*hour  = nmin/60 - nday*24;
	*min   = nmin % 60;
}
