/*! \file dates.c
    Date Calculation Routines.
    A collection of date calculation routines that are Year 2000
    compliant for MMDDYYYY or MMDDYY string dates.
 \author Jon A. Lambert
 \version 1.96
 \date 06/27/2002
 \remarks
    This code copyright (C) 2000-2002 by Jon A. Lambert<BR>
    <antilochos@ix.netcom.com> -  All rights reserved.<BR>
    No part of this code may be used without my written consent.<BR>
 This code released to the PUBLIC DOMAIN on 4/2/2006
*/

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

static int CurrentCentury ();
static bool ValidDayMonthRange (int month, int day);

long  DaysSince1800(const char * date);
int   DaysInMonth(int month, int year);
bool  IsLeapYear(int year);
int   Month(const char * date);
int   Day(const char * date);
int   Year(const char * date);
void  MakeDateString(char * date, struct tm * dt);
bool  ValidateDate(char* dt);

/*!
DaysSince1800(const char *): long

Returns the # of days from 1/1/1800 to date.

\param      - date in MMDDYYYY  or MMDDYY format.
\return         - long integer value of days since 1800.
\remarks -  Calculates the number of days from January 1st,
                            1800 to the passed date.
                            Used for date arithmetic.
 */
long DaysSince1800 (const char *date)
{
  int curr_year = 1800, curr_month = 1, curr_day = 1;
  long total_days = 0;

  if ((Year (date) >= 1800) && (Month (date) > 0)
    && (Day (date) > 0)) {
    while (curr_year < Year (date))
      total_days += IsLeapYear (curr_year++) ? 366 : 365;

    while (curr_month < Month (date))
      total_days += DaysInMonth (curr_month++, curr_year);

    while (curr_day++ < Day (date))
      ++total_days;
  }

  return total_days;
}


/*!
DaysInMonth(int, int): int

Returns the # of days in the month.

\param      - Integer month and integer year.
\return         - Integer value of # of days in the month.
\remarks -  Will return the # of days in the passed month.
 */
int DaysInMonth (int month, int year)
{
  // thirty days hath september, april, june and november
  if (month == 9 || month == 4 || month == 6 || month == 11)
    return 30;

  if (month == 2)
    if (IsLeapYear (year))
      return 29;
    else
      return 28;
  else
    return 31;
}


/*!
IsLeapYear(year: positive integer): boolean

Check whether a given year is a leap year.

\param      - year   Year to check.
\return         - true when `year' is a leap year, false if it is not.
\remarks    -   In the Gregorian calendar a year is a leap year when it
                            is divisible by 4 and is not a turn of the century
                            exept when the turn of the century is in a year being
                            a multiple of 400. The Gregorian calendar was
                            introduced by pope Gregorius XIII in 1582.

Rule 1: If the year is less than 1582 it IS NOT a leap year.
Rule 2: If the year is divisible by 400, it IS a leap year.
Rule 3: If the year is divisible by 100, it IS NOT a leap year.
Rule 4: If the year is divisible by 4, it IS a leap year.
 */
bool IsLeapYear (int year)
{
  return ((year > 1581) && ((year % 400 == 0) || ((year % 100)
        && (year % 4 == 0))));
}


/*!
Month(const char *): int

Returns the month from date.

\param      - Date in MMDDYYYY or MMDDYY format.
\return         - integer value of month or 0
\remarks    -   Integer value of month will be returned.
 */
int Month (const char *date)
{
  char month_str[3];

  strncpy (month_str, date, 2);
  month_str[2] = '\0';
  return (atoi (month_str));
}


/*!
Day(const char *): int

Returns the day from date.

\param      - Date in MMDDYYYY or MMDDYY format.
\return       - integer value of day or 0
\remarks    -   integer value of day will be returned.
 */
int Day (const char *date)
{
  char day_str[3];

  strncpy (day_str, date + 2, 2);
  day_str[2] = '\0';
  return (atoi (day_str));
}


/*!
Year(const char *): int

Returns the year from date.

\param      - Date in MMDDYYYY or MMDDYY format.
\return         - integer value of year or zero
\remarks    -   Converts 2 digit years to 4 digits by assuming
                            current dates century.
 */
int Year (const char *date)
{
  char year_str[5];
  int year_val;

  strncpy (year_str, date + 4, 4);
  year_str[4] = '\0';

  year_val = atoi (year_str);
  // Check if only two digits entered
  if (year_val < 100)
    year_val += CurrentCentury ();      // add century information

  return (year_val);
}


/*!
CurrentCentury(): int

Retrieves the current century

\return         - integer.
\remarks    -   The current system time is retrieved and the century is
            returned from the current year.

Ex: if the year is 1998 then 1900 is returned
        if the year is 2019 then 2000 is returned
 */
static int CurrentCentury ()
{
  time_t curr_time;
  struct tm *local_time;

  time (&curr_time);
  local_time = localtime (&curr_time);

  // Integer math to truncate century to hundreds
  return (((local_time->tm_year + 1900) / 100) * 100);
}


/*!
MakeDateString(char * date, struct tm * dt): void

Sets the month in the destination date field.

\param      - pointer to string to destination date must have allocated
                            a mininum of 9 characters.
                            pointer to a tm structure.
\return         - void.
\remarks    -   Date field will be set.
 */
void MakeDateString (char *date, struct tm *dt)
{
  char temp_str[MAX_INPUT_LENGTH];

  sprintf (temp_str, "%02d%02d%02d",
    dt->tm_mon + 1, dt->tm_mday, dt->tm_year - 100);
  temp_str[6] = '\0';
  strcpy (date, temp_str);
}


/*!
ValidDayMonthRange(int month, int day): boolean

Check whether a day falls within legal date range for month

\param      - Integer month and integer year.
\return         - true when day, false day is out of range.
\remarks    - Checks table of months to see if day is within the
                            legal range.
 */
static bool ValidDayMonthRange (int month, int day)
{
  int day_table[12] = { 31, 29, 31, 30, 31, 30,
    31, 31, 30, 31, 30, 31
  };

  if ((month < 1) || (month > 12))
    return FALSE;

  if ((day > day_table[month - 1]) || (day < 1))
    return FALSE;
  else
    return TRUE;
}


/*!
ValidateDate(char*): boolean

Check whether a date is valid

\param      - date in MMDDYYYY  or MMDDYY format.
\return         - true when date is valid, false when date invalid.
\remarks    -   In the Gregorian calendar a year is a leap year when it
            is divisible by 4 and is not a turn of the century
            except when the turn of the century is in a year being
            a multiple of 400. The Gregorian calendar was
            introduced by pope Gregorius XIII in 1582.
 */
bool ValidateDate (char *dt)
{
  int month, day, year;

  month = Month (dt);
  if ((month < 1) || (month > 12))
    return FALSE;

  year = Year (dt);
  if ((year < 100) || (year > 9999))
    return FALSE;

  day = Day (dt);
  // Check that day is within month's allowed range
  if (!ValidDayMonthRange (month, day))
    return FALSE;

  // Check for valid leapyear conditions
  if ((month == 2) && (day == 29)) {
    if (!(IsLeapYear (year)))
      return FALSE;
  }

  return TRUE;
}