/*
Copyright (c) 2000                      RIPE NCC


All Rights Reserved

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of the author not be
used in advertising or publicity pertaining to distribution of the
software without specific, written prior permission.

THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

/*
-------------------------------------------------------------------------------
Module Header
Filename          : iso8601.C
Author            : Rene Wilhelm
Date              : 13-OCT-2000
Description       : Functions for decoding ISO8601 date/period strings
Language Version  : C++
OSs Tested        : Solaris 2.6
$Id: iso8601.C,v 1.1 2003/05/28 13:02:39 ruben Exp $
-------------------------------------------------------------------------------
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#include "utils.h"
#include "iso8601.h"

static char const rcsid[] = "$Id: iso8601.C,v 1.1 2003/05/28 13:02:39 ruben Exp $";

/*
 *  Subroutine header
 *
 *  Error handler
 *
 *  Input: 	  none
 *  Output: 	  error message
 *  Side effects: terminate program
 */

void invaliddate() {
        fprintf (stderr, "%s: invalid date/time specification\n", progname);
        exit(1);
}

/*
 *  Subroutine header
 *
 *  decode ascii ISO8601 time period string into an ending time in time_t
 *  format (seconds since 1970) and a duration
 *
 *  Input:   char   *datestring  string to be decoded
 *  Output:  time_t& endtime     system timestamp of end time
 *           time_t& duration    duration in seconds
 *
 */

void decodePeriod (char* datestring, time_t& endtime, time_t& duration) {

	char *part1, *part2;
	char flag1, flag2;
	time_t time1, time2;

	/* datestring =  ISO8601 time period string */
	/* two parts seperated by /  */


	part1 = datestring;
	if ((part2 = strchr(part1, '/')) == NULL) {
		invaliddate();
        }

	*part2 = '\0';
	part2++;

	parseISO8601 (part1, time1, flag1);
	parseISO8601 (part2, time2, flag2);

        if ((flag1 == 'T') && (flag2 == 'D')) {
		/* format: starttime/duration */
		duration = time2;
		endtime = time1 + duration;
	}
	else if ((flag1 == 'D') && (flag2 == 'T')) {
		/* format: duration/endtime */
		duration = time1;
		endtime = time2;
	}
	else if ((flag1 == 'T') && (flag2 == 'T')) {
		/* format: starttime/endtime */
		if (time1 >= time2) {
			/* negative or zero time interval */
			invaliddate();
		}
		else {
			endtime = time2;
			duration = time2 - time1;
		}
	}
	else {
		invaliddate();
        }
}

/*
 *  Subroutine header
 *
 *  decode ascii time string into duration of time or fixed point in time
 *  the string should be in the formats described by ISO8601 standard
 *
 *  note that to not complicate the routine too much, points in time
 *  MUST be complete representations of either a date (CCYYMMDD) or a
 *  date+time (CCYYMMDDTHHMMSS). - and : are allowed as separators.
 *
 *  similarly, duration are only supported when specified in days,
 *  hours, minutes and seconds. Lenght of months and years are
 *  content dependant.
 *  
 *
 *  Input:   char*   text  string to be decoded
 *  Output:  time_t& time  system timestamp or duration  (seconds)
 *           char&   flag  indicates if returned time is timestamp or duration
 */

#define DAYFLAG    0x001000
#define DHMSFLAG   0x001111
#define HOURFLAG   0x000100
#define HMSFLAG    0x000111
#define MINFLAG    0x000010
#define MSFLAG     0x000011
#define SECFLAG    0x000001

void parseISO8601 (char *text, time_t& isotime, char& flag) {
	char *c;
	int num;

	struct tm  tmstruct;
 
	int year     = 0;
	int month    = 0;
	int seconds  = 0;
	int minutes  = 0;
	int hours    = 0;
	int days     = 0;

	int dateflags = 0;   /* flag which date component we've seen */

	c = text;
	isotime = 0;

	if (*c++ == 'P') {
		/* duration */
		flag = 'D';
		while (*c != '\0') {
			num = 0;
			while (*c >= '0' && *c <= '9') {
				/* assumes ASCII sequence! */
				num = 10 * num + *c++ - '0';
			}
		
			switch (*c++) {
			case 'D':
				if (dateflags & DHMSFLAG) {
					/* day, hour, min or sec already set */
					invaliddate();
				}
				else {	
					dateflags |= DAYFLAG;
					days = num;
				}
				break;
			case 'H':
				if (dateflags & HMSFLAG) {
					/* hour, min or sec already set */
					invaliddate();
				}
				else {	
					dateflags |= DAYFLAG;
					hours = num;
				}
				break;
			case 'M':
			  	if (dateflags & MSFLAG) {
					/* min or sec already set */
					invaliddate();
				}
				else {
					dateflags |= MINFLAG;
					minutes = num;
			  	}
				break;
			case 'S':
				if (dateflags & SECFLAG) {
					/* sec already set */
					invaliddate();
				}
				else {	
					dateflags |= SECFLAG;
					seconds = num;
				}
				break;
			default:
				invaliddate();
			}
		}
		isotime = seconds + 60 * minutes + 3600 * hours +
			   SECONDSPERDAY * days;
	}
	else {
		/* point in time, must be one of 
			CCYYMMDD
			CCYY-MM-DD
			CCYYMMDDTHHMM
			CCYY-MM-DDTHH:MM
			CCYYMMDDTHHMMSS
			CCYY-MM-DDTHH:MM:SS
		*/
		c = text;
		flag = 'T';

		/* NOTE: we have to check for the extended format first, 
		   because otherwise the separting '-' will be interpreted
		   by sscanf as signs of a 1 digit integer .... :-(  */

		if (sscanf(text, "%4u-%2u-%2u", &year, &month, &days) == 3) {
			c += 10;
		}
		else if (sscanf(text, "%4u%2u%2u", &year, &month, &days) == 3) {
			c += 8;
		}
		else {
			invaliddate();
		}

		tmstruct.tm_year = year - 1900;
		tmstruct.tm_mon  = month - 1;
		tmstruct.tm_mday = days;

		if (*c == '\0') {
			tmstruct.tm_hour = 0;
			tmstruct.tm_sec = 0;
			tmstruct.tm_min = 0;
			isotime = mktime (&tmstruct);
		}
		else if (*c == 'T') {
			/* time of day part */
			c++;
			if (sscanf(c, "%2d%2d", &hours, &minutes)  == 2) {
				c += 4;
			}
			else if (sscanf(c, "%2d:%2d", &hours, &minutes) == 2) {
				c += 5;
			}
			else {
				invaliddate();
			}

			if (*c == ':') {
				c++;
			}

			if (*c != '\0') {
			   	if (sscanf(c, "%2d", &seconds)  == 1) {
					c += 2;
			   	}
			   	else {
					invaliddate();
			   	}
				if (*c != '\0') {      /* something left? */
					invaliddate();
				}
			}
			tmstruct.tm_hour = hours;
			tmstruct.tm_min = minutes;
			tmstruct.tm_sec = seconds;
			isotime = mktime (&tmstruct);
		}

		else {
			invaliddate();
		}
	}

}
