/*	
 *	cliptime.c
 *
 *	S1C33 Family Cコンパイラパッケージの時刻関数を置き換えます。
 *	S1C33 Family Cコンパイラパッケージの時刻関数はほとんどダミー実装です。
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2011 Naoyuki Sawa
 *
 *	* Tue May 11 21:10:00 JST 2004 Naoyuki Sawa
 *	- 1stリリース。
 *	* Wed Feb 22 00:00:00 JST 2006 Naoyuki Sawa
 *	- include/time.hにてANSI形式の関数宣言を行ったので、このモジュール内でのANSI形式関数宣言は不要となりました。
 *	* Wed Jan 05 01:40:25 JST 2011 Naoyuki Sawa
 *	- clock()の実装を追加、および、strftime()のダミー実装を追加しました。
 *	  上記の追加理由は、「Lua 5.1.4」をコンパイルするために、必要になったためです。
 *	- 今回追加したstrftime()は、リンカを通すためだけのダミー実装であり、実際に呼び出された場合エラー終了します。
 *	  今後、本当にstrftime()の処理が必要になった際に、実装する予定です。
 *	* Tue Jan 11 02:21:51 JST 2011 Naoyuki Sawa
 *	- strftime()を実装しました。
 *	- 既存のasctime()の実装を、strftime()を利用するように変更しました。
 */
#include "clip.h"

/*****************************************************************************
 *	
 *****************************************************************************/

/*{{2006/02/22 include/time.hにてANSI形式の関数宣言を行ったので、このモジュール内でのANSI形式関数宣言は不要となりました。*/
//clock_t clock() { DIE(); }                     // 呼び出し側プロセスが使ったCPU時間を返します。※P/ECEでは意味がないので未実装です。→{{2011/01/05 実装しました}}
//time_t time(time_t* timer);                    // GMTの1970/1/1 00:00:00からの経過秒数を返します。
//time_t mktime(struct tm* timeptr);             // tm構造体をGMTの1970/1/1 00:00:00からの経過秒数に変換します。GMT<=>JST変換は行いません。
//struct tm* gmtime(const time_t* timer);        // GMTの1970/1/1 00:00:00からの経過秒数をtm構造体に変換します。GMT<=>JST変換は行いません。
//struct tm* localtime(const time_t* timer);     // GMTの1970/1/1 00:00:00からの経過秒数をJSTに変換し、さらにtm構造体に変換します。
//char* asctime(const struct tm* timeptr);       // tm構造体を文字列に変換し、改行コードを付加します。GMT<=>JST変換は行いません。
//char* ctime(const time_t* timer);              // GMTの1970/1/1 00:00:00からの経過秒数をJSTに変換し、さらに文字列に変換します。
//double difftime(time_t timer1, time_t timer0); // 二つの時間の差を秒単位で返します。
/*}}2006/02/22 include/time.hにてANSI形式の関数宣言を行ったので、このモジュール内でのANSI形式関数宣言は不要となりました。*/

/*****************************************************************************
 *	
 *****************************************************************************/

#define TZ_BIAS (9 * 60 * 60)	/* グリニッジ標準時(GMT)と明石標準時(JST)の時間差[分] JST=GMT+TZ_BIAS */

static const short mtbl[2/*0:通常/1:うるう年*/][12] = {{
	/*1/1〜 1/1*/0,
	/*1/1〜 2/1*/31,
	/*1/1〜 3/1*/31+28,
	/*1/1〜 4/1*/31+28+31,
	/*1/1〜 5/1*/31+28+31+30,
	/*1/1〜 6/1*/31+28+31+30+31,
	/*1/1〜 7/1*/31+28+31+30+31+30,
	/*1/1〜 8/1*/31+28+31+30+31+30+31,
	/*1/1〜 9/1*/31+28+31+30+31+30+31+31,
	/*1/1〜10/1*/31+28+31+30+31+30+31+31+30,
	/*1/1〜11/1*/31+28+31+30+31+30+31+31+30+31,
	/*1/1〜12/1*/31+28+31+30+31+30+31+31+30+31+30,
},{
	/*1/1〜 1/1*/0,
	/*1/1〜 2/1*/31,
	/*1/1〜 3/1*/31+29,
	/*1/1〜 4/1*/31+29+31,
	/*1/1〜 5/1*/31+29+31+30,
	/*1/1〜 6/1*/31+29+31+30+31,
	/*1/1〜 7/1*/31+29+31+30+31+30,
	/*1/1〜 8/1*/31+29+31+30+31+30+31,
	/*1/1〜 9/1*/31+29+31+30+31+30+31+31,
	/*1/1〜10/1*/31+29+31+30+31+30+31+31+30,
	/*1/1〜11/1*/31+29+31+30+31+30+31+31+30+31,
	/*1/1〜12/1*/31+29+31+30+31+30+31+31+30+31+30,
}};

static const char* const wday_table[7] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
static const char* const mon_table[12] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };

static void
tm_test(const struct tm* timeptr)
{
	if(timeptr->tm_year < 1970 - 1900 || timeptr->tm_year > 2038 - 1900) DIE(); /* 1970〜2038年、格納する値は70〜138 */
	if(timeptr->tm_mon  <    1 -    1 || timeptr->tm_mon  >   12 -    1) DIE(); /*    1〜  12月、格納する値は 0〜 11 */
	if(timeptr->tm_mday <    1        || timeptr->tm_mday >   31       ) DIE(); /*    1〜  31日、格納する値は 1〜 31 */
	if(timeptr->tm_hour <    0        || timeptr->tm_hour >   23       ) DIE(); /*    0〜  23時、格納する値は 0〜 23 */
	if(timeptr->tm_min  <    0        || timeptr->tm_min  >   59       ) DIE(); /*    0〜  59分、格納する値は 0〜 59 */
	if(timeptr->tm_sec  <    0        || timeptr->tm_sec  >   59       ) DIE(); /*    0〜  59秒、格納する値は 0〜 59 */
}

/* 週の通し番号を数えます。
 * - 当年の最初の日曜日(または月曜日)から始まる週を、第1週と数えます。
 *   当年のそれ以前の日は、第0週と見なします。
 * - 1月1日が日曜日(または月曜日)の場合、その年には第0週が有りません。
 *   直観と異なりますが、strftime()の%U(および%W)の仕様です。
 */
static int
tm_wnum(const struct tm* timeptr, int wstart/*週の最初の曜日:0=日曜日,1=月曜日*/)
{
	/* 週の通し番号を、第0週としておきます。 */
	int wnum = 0;

	/* 当日の曜日と通算日数を取得します。 */
	int wday = timeptr->tm_wday;
	int yday = timeptr->tm_yday;
	if((unsigned)wday >   6) DIE(); /* 不正な曜日 */
	if((unsigned)yday > 365) DIE(); /* 不正な通算日数 */

	/* 当年の各通算日数について繰り返し… */
	do {
		/* 週の最初の曜日だったら… */
		if(wday == wstart) {
			/* 週の通し番号を増やします。 */
			wnum++;
		}
		/* 前日の曜日へ移動します。 */
		if(--wday < 0) {
			wday += 7;
		}
	/* 通算日数を減らし、前年になったら抜けます。 */
	} while(--yday >= 0);

	return wnum;
}

/*****************************************************************************
 *	
 *****************************************************************************/

clock_t
clock()
{
	/* * Wed Jan 05 01:34:52 JST 2011 Naoyuki Sawa
	 * - 一般的には、clock()は、呼び出し側プロセスが使ったCPU時間を計算します。
	 *   しかしそのためには、現在のP/ECEアプリケーションが起動した時刻を記憶しておく必要があります。
	 *   現在のCLiPライブラリには、P/ECEアプリケーションが起動した時刻を記憶しておく変数が有りません。
	 *   本関数だけのために、P/ECEアプリケーションが起動した時刻を記憶する変数を追加するのも、冗長です。
	 * - 調べてみると、clock()は必ずしも、アプリケーション起動時からの時間を返さなくても良いようです。
	 *   │Linux Programmer's Manual (3) - CLOCK より引用
	 *   │(http://archive.linux.or.jp/JM/html/LDP_man-pages/man3/clock.3.html)
	 *   │Cの標準規格ではプログラムの開始の時点ではどんな値が返ってきてもかまわない。
	 *   │移植性を最大限確保するためには、プログラムの開始時にclock()を呼び出してその値を差し引くこと。
	 *   そこで、本実装は、1ms単位の内部カウント値を、CLOCKS_PER_SEC単位に変換して返すことにしました。
	 * - 上記のページには、以下のような説明もあります。
	 *   │使用したプロセッサ時間が得られない場合や、その値を表現できない場合、この関数は(clock_t)-1を返す。
	 *   (clock_t)-1でも良いかと思いましたが、意味のある値を返した方が便利と考え、前述のように実装しました。
	 */
	return pceTimerGetCount() * 1000;
}

time_t
time(time_t* timer)
{
	PCETIME ptime;
	struct tm tm;
	time_t t;

	/* システム時刻(JST)を取得します。 */
	pceTimeGet(&ptime);

	/* 1970/1/1 00:00:00からの経過秒数を取得します。 */
	memset(&tm, 0, sizeof tm);
	tm.tm_year = ptime.yy - 1900;
	tm.tm_mon  = ptime.mm -    1;
	tm.tm_mday = ptime.dd;
	tm.tm_hour = ptime.hh;
	tm.tm_min  = ptime.mi;
	tm.tm_sec  = ptime.ss;
	t = mktime(&tm);
	if(t < 0) return t; /* エラー */

	/* JSTからGMTに変換します。 */
	t -= TZ_BIAS;

	/* 必要に応じて結果を格納します。 */
	if(timer) *timer = t;

	return t;
}

time_t
mktime(struct tm* timeptr)
{
	int days;
	time_t t;

	/* 時刻を検査します。 */
	tm_test(timeptr);

	/* 1970/1/1からyear/mon/mdayまでの日数。 */
	days  = (timeptr->tm_year - 70) * 365;			/* 1970/1/1からyear/1/1までの日数 (暫定) */
	days += (timeptr->tm_year - 1) / 4;			/* 1900/1/1からyear/1/1までのうるう年日数 (暫定) ※1901〜2099年の外では間違った結果になります */
	days -= 17;						/* 1900/1/1から1970/1/1までのうるう年日数 {1904,08,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68} */
	days += mtbl[!(timeptr->tm_year % 4)][timeptr->tm_mon];	/* year/1/1からyear/mon/1までの日数 ※1901〜2099年の外では間違った結果になります */
	days += timeptr->tm_mday - 1;				/* year/mon/1からyear/mon/mdayまでの日数 */

	/* 1970/1/1からyear/mon/mday hour:min:secまでの秒数。 */
	t  = (((days * 24) + timeptr->tm_hour) * 60 + timeptr->tm_min) * 60 + timeptr->tm_sec;

	return t;
}

struct tm*
gmtime(const time_t* timer)
{
	static struct tm tm;
	//
	time_t t;
	int leap;
	int days;

	/* まずクリア。 */
	memset(&tm, 0, sizeof tm);

	/* 時、分、秒を求めます。 */
	t = *timer;		/* t = 1970/1/1からの経過秒数 */
	tm.tm_sec  = t % 60;	/* 秒確定 */
	t /= 60;		/* t = 1970/1/1からの経過分数 */
	tm.tm_min  = t % 60;	/* 分確定 */
	t /= 60;		/* t = 1970/1/1からの経過時間 */
	tm.tm_hour = t % 24;	/* 時確定 */
	t /= 24;		/* t = 1970/1/1からの経過日数 */

	/* 年、月、日、曜日、年内の経過日数を求めます。 */
	tm.tm_wday = (t + 4/*1970/1/1は木曜日*/) % 7;
	for(tm.tm_year = 1970 - 1900; tm.tm_year < 2038 - 1900; tm.tm_year++) {
		leap = !(tm.tm_year % 4); /* うるう年判定 ※1901〜2099年の外では間違った結果になります */
		days = 365 + leap;
		if(t < days) break;
		t -= days;
	}
	tm.tm_yday = t; /* year/1/1からの経過日数 */
	for(tm.tm_mon = 1 - 1; tm.tm_mon < 12 - 1; tm.tm_mon++) {
		if(t < mtbl[leap][tm.tm_mon + 1]) break;
	}
	t -= mtbl[leap][tm.tm_mon];
	tm.tm_mday = t + 1;

	return &tm;
}

struct tm*
localtime(const time_t* timer)
{
	time_t t = *timer + TZ_BIAS; /* GMTからJSTに変換します */
	return gmtime(&t);
}

char*
asctime(const struct tm* timeptr)
{
	/*  000000000011111111112222２２  */
	/*  012345678901234567890123４５  */
	/* "Wed Jan 02 02:03:55 1980\n\0" */
	static char buf[26];

	/* 時刻を検査します。 */
	tm_test(timeptr);

	/* 時刻を文字列に変換します。 */
	strftime(buf, sizeof buf, "%a %b %d %X %Y\n", timeptr);

	return buf;
}

char*
ctime(const time_t* timer)
{
	return asctime(localtime(timer));
}

double
difftime(time_t timer1, time_t timer0)
{
	return timer1 - timer0;
}

size_t
strftime(char* string, size_t maxsize, const char* format, const struct tm* timeptr)
{
	char* string0 = string;
	int f;
	int n;

	/* 時刻を検査します。 */
	tm_test(timeptr);

	/* formatが空文字列だった場合のために、出力文字列の先頭に終端のnulを書き込んでおく。
	 * formatが空文字列でなければ、これ以降は、snprintf()が終端のnulを書き込んでくれる。
	 */
	if(maxsize) *string = '\0';

	while((f = *format++) != '\0') {
		if(f == '%') {
			switch(f = *format++) {
			case 'a': /* 曜日の省略名 */
				n = snprintf(string, maxsize, "%.3s", wday_table[timeptr->tm_wday]);
				break;
			case 'A': /* 曜日の正式名 */
				n = snprintf(string, maxsize, "%s", wday_table[timeptr->tm_wday]);
				break;
			case 'b': /* 月の省略名 */
				n = snprintf(string, maxsize, "%.3s", mon_table[timeptr->tm_mon]);
				break;
			case 'B': /* 月の正式名 */
				n = snprintf(string, maxsize, "%s", mon_table[timeptr->tm_mon]);
				break;
			case 'c': /* ロケールに応じた日付と時間の表現 */
				if(!(n = strftime(string, maxsize, "%x %X", timeptr))) n = -1;
				break;
			case 'd': /* 10進数で表す月の日付(01〜31) */
				n = snprintf(string, maxsize, "%02d", timeptr->tm_mday);
				break;
			case 'H': /* 24時間表記の時間(00〜23) */
				n = snprintf(string, maxsize, "%02d", timeptr->tm_hour);
				break;
			case 'I': /* 12時間表記の時間(01〜12) */
				n = snprintf(string, maxsize, "%02d", (timeptr->tm_hour + 11) % 12 + 1);
				break;
			case 'j': /* 10進数で表す年頭からの日数(001〜366) */
				n = snprintf(string, maxsize, "%03d", timeptr->tm_yday + 1);
				break;
			case 'm': /* 10進数で表す月(01〜12) */
				n = snprintf(string, maxsize, "%02d", timeptr->tm_mon + 1);
				break;
			case 'M': /* 10進数で表す分(00〜59) */
				n = snprintf(string, maxsize, "%02d", timeptr->tm_min);
				break;
			case 'p': /* 現在のロケールのAM/PM */
				n = snprintf(string, maxsize, "%s", (timeptr->tm_hour < 12) ? "AM" : "PM");
				break;
			case 'S': /* 10進数で表す秒(00〜59) */
				n = snprintf(string, maxsize, "%02d", timeptr->tm_sec);
				break;
			case 'U': /* 10進数で表す週の通し番号。日曜日を週の最初の日とする(00〜53)。 */
				n = snprintf(string, maxsize, "%02d", tm_wnum(timeptr, 0/*日曜日*/));
				break;
			case 'w': /* 10進数で表す曜日。日曜日を0とする(0〜6)。 */
				n = snprintf(string, maxsize, "%d", timeptr->tm_wday);
				break;
			case 'W': /* 10進数で表す週の通し番号。月曜日を週の最初の日とする(00〜53)。 */
				n = snprintf(string, maxsize, "%02d", tm_wnum(timeptr, 1/*月曜日*/));
				break;
			case 'x': /* 現在のロケールの日付表示 */
				if(!(n = strftime(string, maxsize, "%m/%d/%y", timeptr))) n = -1;
				break;
			case 'X': /* 現在のロケールの時刻表示 */
				if(!(n = strftime(string, maxsize, "%H:%M:%S", timeptr))) n = -1;
				break;
			case 'y': /* 10進数で表す西暦の下2桁(00〜99) */
				n = snprintf(string, maxsize, "%02d", timeptr->tm_year % 100);
				break;
			case 'Y': /* 10進数で表す4桁の西暦 */
				n = snprintf(string, maxsize, "%d", timeptr->tm_year + 1900);
				break;
			case 'z': /* 時間帯の名前またはその省略名。時間帯がわからない場合には文字を入れない。 */
			case 'Z': /* 時間帯の名前またはその省略名。時間帯がわからない場合には文字を入れない。 */
				n = snprintf(string, maxsize, "%s", "JST");
				break;
			case '%': /* パーセント記号 */
			default:
				if(f) {
					n = snprintf(string, maxsize, "%c", f);
				} else {
					n = 0, format--;
				}
				break;
			}
		} else {
			n = snprintf(string, maxsize, "%c", f);
		}
		if(n < 0) return 0;
		string  += n;
		maxsize -= n;
	}

	return string - string0;
}

