#if ! defined(_DATE_H_)				/* 避免重複 include */
#define _DATE_H_

typedef struct {				/* 日期型別 */
    int year;					/* 年 */
    short month, day;				/* 月, 日 */
} date;

int weekday(date d);
date date_add(date d, int n);
int date_sub(date d, date e);
int is_leap_year(int year);

#endif

/*
名稱: date.c
作者: 洪朝貴 http://www.cyut.edu.tw/~ckhung/
功能: 日期模組 (簡單版)
技巧: 為避免在許多副程式中重複撰寫麻煩的閏年運算, 設計兩個
      僅供內部使用的轉換程式 date_to_int() 及 int_to_date().
      欲作任何需要考慮閏年的運算時, 先將日期轉換為流水日期
      (以 1753 年 1 月 1 日為基準), 作簡單的算術運算, 最後
      再轉換為西歷.
*/
/* #include "date.h" */
#include <assert.h>

static int days_in_month[] = { 0,		/* 每個月有多少天 */
    31, 28, 31, 30, 31, 30,
    31, 31, 30, 31, 30, 31
};

static int days_upto_year(int year)
/* 從基準日 1753 年一月一日起到 year 年一月一日止, 共經過了多少天? */
{
    return (year-1753) * 365 + (year-1753) / 4 -
	(year-1701) / 100 + (year-1601) / 400;
}

int is_leap_year(int year)
/* year 這一年是否為閏年? */
{
    if (year % 4) return 0;
    if (year % 100) return 1;
    return ! (year % 400);
}

static int date_to_int(date d)
/* 以基準日 1753 年一月一日為第一天, 則 d 這一天是第幾天? */
{
    int n, i;

    n = days_upto_year(d.year);			/* 今年之前的日數 */
    for (i=1; i<d.month; ++i)			/* 這個月之前的日數 */
	n += days_in_month[i];
    n += d.day;					/* 這個月到今天為止的日數 */
    if (is_leap_year(d.year) && 3 <= d.month) ++n;
	/* 如果今年是閏年, 而且 d 在三月以後, 則總日數要加一天 */
    return n;
}

static date int_to_date(int n)
/* 以基準日 1753 年一月一日為第一天, 則第 n 天究竟是何年何月何日? */
{
    int k;
    date result;

    result.year  = (int) (n / 365.2425) + 1753;	/* 大約估計是那一年 */
    k = days_upto_year(result.year);		/* 希望估計到去年... */
    if (k >= n) {				/* 過頭了, 估計到去年就好 */
	--result.year;
	k = days_upto_year(result.year);
    }
    n -= k;					/* 今年已過了多少天? */
    assert(0 < n && 366 >= n);
    for (k=1; 0<n; ++k)				/* n 這麼多天究竟到幾月了? */
	n -= days_in_month[k];
    n += days_in_month[--k];
    /* 現在 k 大約就是月份, n 大約就是日期了 */

    /* 但是如果是閏年而且是二月以後 ... */
    if (is_leap_year(result.year) && 2<k) {
	--n;					/* 要把二月廿九日算進去 */
	if (0 >= n) {				/* 如果正好是月初變月底 ... */
	    n = days_in_month[--k];		/* 改成上個月的最後一天 */
	    if (2==k) ++n;			/* 當然要記得二月是廿九天 */
	}
    }
    result.month = k;
    result.day = n;
    return result;
}

int weekday(date d)
/* d 這一天是星期幾? (週日以 0 表示) */
{
    return date_to_int(d) % 7;
}

date date_add(date d, int n)
/* 從 d 這一天開始, 再過 n 天是那一天? */
{
    return int_to_date(date_to_int(d) + n);
}

int date_sub(date d, date e)
/* d 這一天在 e 這一天之後多少天? */
{
    return date_to_int(d) - date_to_int(e);
}

#if defined(TEST)

/*  測試程式: 以 "gcc -Wall -DTEST date.c" 編譯, 以 "a.out 1999 12 30" 測試. */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
    date x;
    int n;
    char const * progname;

    if (4 != argc) {
	progname = strrchr(argv[0], '/');
	if (progname == NULL) progname = argv[0];
	fprintf(stderr, "Usage: %s year month day\n", progname);
	exit(1);
    }
    x.year = atoi(argv[1]);
    x.month = atoi(argv[2]);
    x.day = atoi(argv[3]);
    n = date_to_int(x);
    printf("%d (%d)\n", n, weekday(x));
    x = int_to_date(n);
    printf("%d %d %d\n", x.year, x.month, x.day);

    x = date_add(x, 3);
    printf("\n3 days later...\n");
    n = date_to_int(x);
    printf("%d (%d)\n", n, weekday(x));
    printf("%d %d %d\n", x.year, x.month, x.day);
    return 0;
}

#endif

