/**
 * Timestamp, time and date UTC conversion routines. The following date
 * formats are supported:
 * 
 * - Unix timestamp as number of seconds elapsed since 1970-01-01 00:00:00 UTC;
 *   negative values indicate dates before that epoch. Here we use "int".
 *   On compilers with 32-bits "int" (like gcc on any know platform including
 *   64-bits processors) a timestamp covers dates in the range between
 *   1901-12-13 20:45:52 (fractional year 1901.9503155758), and up to
 *   2038-01-19 03:14:07 (fractional year 2038.0496843924).
 * 
 * - Broken-down Gregorian date and time as year, month, day, hour, minutes and
 *   seconds UTC time. The minimal supported year is 1583.
 * 
 * - Year with fractional part. For example, 2017.5 means 2017-07-02 12:00 UTC.
 *   The minimal supported value is 1583.0.
 * 
 * We assume that each year has 365 days (366 for leap years); each day has
 * 86400 seconds. Timestamps does not account for the leap seconds, so the
 * difference between two timestamps may not corresponds to the real elapsed
 * time, but it is very close to it. Anyway, the calculations performed by this
 * library exactly match those performed by the GNU C standard library I have
 * compared with.
 * 
 * The local time zone has no effect on this library; the standard C library
 * already covers local time and local dates, but apparently there are not
 * portable and easy-to-use conversion functions for UTC.
 * 
 * References:
 * - Eric S. Raymond, "Time, Clock, and Calendar Programming In C"
 *   http://www.catb.org/esr/time-programming/
 *   ...or, why the C standard (and non standard) library is too much complex
 *   and confusing to be used safely, and then the reason why I wrote this
 *   replacement module.
 * 
 * @file
 * @author Umberto Salsi <salsi@icosaedro.it>
 * @version $Date: 2017/10/22 15:01:18 $
 */

#ifndef ZULU_H
#define ZULU_H

#ifdef zulu_IMPORT
	#define EXTERN
#else
	#define EXTERN extern
#endif

/**
 * The Gregorian calendar was fully established and enter at regime in 1583.
 * 11 days were removed the year before to re-align the Julian calendar with the
 * new one. This library does not support years before this and may return wrong
 * results attempting to enter or calculate dates and timestamps before this
 * limit.
 */
#define zulu_YEAR_MIN (1583)

/**
 * Broken-down Gregorian date and time. Time always refers to the UTC or "Zulu"
 * time.
 */
typedef struct {
	/** Year equal or greater than zulu_YEAR_MIN. */
	int year;
	/** Month in the range [1,12]. */
	int month;
	/** Day of the month in the range [1,31]; the exact maximum value depends
	 on the specific month; for February it also depends on the year. */
	int day;
	/** Hour in the range [0,23]. */
	int hour;
	/** Minutes in the range [0,59]. */
	int minutes;
	/** Seconds in the range [0,59]. */
	int seconds;
} zulu_Date;


/**
 * Returns true if the year is a leap year according to the Gregorian calendar,
 * that is when February has 29 days. That rule can be stated like this:
 * "Years divisible by 100 are leap year only if they are also divisible by 400;
 * any other year is a leap year if it is divisible by 4."
 * The result is correct starting from year 1501, when the Julian calendar
 * was still in use; year 1500 was a leap year according to the Julian calendar,
 * but it was not according to this function that implements the Gregorian rule
 * only.
 * @param year No range check on this value.
 * @return True if the year is a leap year according to the Gregorian calendar.
 */
EXTERN int zulu_isLeapYear(int year);

/**
 * Converts timestamp into date.
 * @param ts Timestamp to convert. No range check is performed: on 32-bits int
 * timestamps, the whole range returns a valid date; for 64-bits int timestamps,
 * the client should avoid unreasonable values for performances reasons.
 * @param d Here stores the resulting date.
 */
EXTERN void zulu_timestampToDate(int ts, zulu_Date *d);

/**
 * Converts timestamp into year.
 * @param ts Timestamp to convert. No range check is performed: on 32-bits int
 * timestamps, the whole range returns a valid year; for 64-bits int timestamps,
 * the client should avoid unreasonable values for performances reasons.
 * @return Year.
 */
EXTERN double zulu_timestampToYear(int ts);

/**
 * Converts date to timestamp.
 * @param d Date to convert. If any field is out of the allowed range, a fatal
 * error occurs; dates before the year 1583 when the Gregorian calendar was
 * established may return wrong timestamps; on 32-bits int compilers the range
 * of dates is much more narrow. If the result overflows or underflows the "int"
 * capacity of a timestamp, a fatal error occurs.
 * @return Timestamp.
 */
EXTERN int zulu_dateToTimestamp(zulu_Date *d);

/**
 * Converts date to fractional year.
 * @param d Date to convert. If any field is out of the allowed range, a fatal
 * error occurs; dates before year the 1583 when the Gregorian calendar was
 * established may return wrong results.
 * @return Year.
 */
EXTERN double zulu_dateToYear(zulu_Date *d);

/**
 * Converts year to timestamp.
 * @param year If the resulting timestamp overflows the "int" capacity of a
 * timestamp, a fatal error occurs. For performances reasons, the client should
 * definitely check for unreasonable values before calling this function.
 * @return Timestamp.
 */
EXTERN int zulu_yearToTimestamp(double year);

/**
 * Converts fractional year to date.
 * @param year Year to convert. No range check is performed; years before
 * 1583 when the Gregorian calendar was established may return wrong results.
 * @param d Here stores the resulting UTC date.
 */
EXTERN void zulu_yearToDate(double year, zulu_Date *d);

/**
 * Parse a zulu date and time string. Basically this function parses the
 * ISO 8601 extended format "yyyy-mm-ddThh:ii:ss" ("ii" are the minutes)
 * where the time zone part is missing as here we are dealing with zulu only.
 * Each component of the date and time counts as a "field"; the total number
 * of fields parsed is returned after successful parsing and validation of the
 * string passed, so allowing the client to know which pars has been omitted.
 * The full format then returns 6 fields; the following abbreviated formats are
 * also recognized:
 * - "yyyy-mm", assumes day 1, time 00:00:00 and returns 2.
 * - "yyyy-mm-dd", assumes time 00:00:00 and returns 3.
 * - "yyyy-mm-ddThh:ii", assumes seconds 00 and returns 5.
 * Year field must have 4 digits; the allowed year range is [1583,9999].
 * Month must have two digits; the allowed month range is [01,12].
 * Day must have two digits and its allowed range depends on the specific month
 * and year. Seconds must have two digits; decimals not allowed.
 * @param s String to parse. Null, empty or white spaces are not allowed.
 * @param z Parsed date and time.
 * @return True if parsing succeeded; false if null, empty, cannot parse or
 * some field out of the ranges. When parsing succeeds, the value returned is
 * the number of fields parsed.
 */
EXTERN int zulu_dateParse(char *s, zulu_Date *z);

/**
 * Compares dates.
 * @param a
 * @param b
 * @return Negative, zero or positive if the first date is comes before,
 * is equal or comes past the second date.
 */
EXTERN int zulu_dateCompare(zulu_Date *a, zulu_Date *b);

/**
 * Format a zulu date. The resulting format depends on the capacity of the
 * destination string; the following cases are examined in the order:
 * - At least 20 bytes: "yyyy-mm-ddThh:ii:ss" where "ii" are minutes.
 * - At least 17 bytes: "yyyy-mm-ddThh:ii".
 * - At least 11 bytes: "yyyy-mm-dd".
 * - At least 8  bytes: "yyyy-mm" where "ii" are minutes.
 * Smaller capacity causes fatal error.
 * @param d Date to format.
 * @param s Destination string. The format depends on the capacity of the
 * destination string buffer as explained above.
 * @param s_capacity Capacity of the destination buffer.
 */
EXTERN void zulu_dateFormat(zulu_Date *d, char *s, int s_capacity);

#undef EXTERN
#endif
