背景介绍
====
进来因为移植万年历, 需要比较和计算1970之前和之后的时间,比如1900-2100。javascript 中Date.UTC() 对于小于
1970 的日期返回负值,对大的日期也支持,很好用。于是想如何在C中做。
我试图改造过 ADOdb Date Library, part of the ADOdb abstraction library
Download: http://phplens.com/phpeverywhere/,把PHP翻译为C, 但是它写的啰嗦, 很是麻烦, 还有性能问题。
最后想到可以参考标准库, 设计决策, 不要使用负值time_t, 处理起来麻烦;规定1900为0起点,-1表示出错。
全文如下,FYI:
/*time64.c -- 64 bit version of time.c
author: ludi
ref P.J. PLAUCER "the standard C library.pdf"
doc:
time_t -- unsigned int -- [1900,2036]
unix -- 1970.01.01 00:00:00 -- (70*365+17)*86400 seconds passed since 1900.
_I64_MAX = 9223372036854775807 -- enough for big bang time--15 billion years = 47304 * 10^13 seconds.
*/
#include
#include
#include
typedef struct datetime{
int second; /*seconds after the minute [0,61]*/
int minute; /*minutes after the hour [0,59]*/
int hour; /*hours since midnight [0,23]*/
int mday; /*day of the month [1,31]*/
int mon; /*months since January [0,11]*/
int year; /*years since _1900_*/
int wday; /*days since Sunday [0,6]*/
int yday; /*days since January 1 [0,365]*/
}datetime_t;
typedef int64_t date_t;
#define _TBIAS ((date_t)(70*365+17)*86400)
#define WDAY 1
static const short lmos[] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335};
static const short mos[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
#define MONTAB(y) (y & 03 || y == 0 ? mos : lmos)
int isLeapYear(int year)
{/*四年一闰,百年不闰,四百年再闰*/
return (year%4==0&&year%100!=0)||year%400==0;
}
void test_leap()
{
int start = 1901, end = 50050;
int i, sum = 0, day;
for(i = start; i <= end; ++i){
if(isLeapYear(i))++sum;
}
end = end - start;
day = end/4 - end/100 + (1900+end)/400 - 1900/400;
printf("real %d, esti %d\n", sum, day);
}
int Daysto(int year, int mon)
{/*compute extra days to start of month*/
int days, end;
/*correct for leap year: 1801-*/
if(0 < year){
end = year - 1;
days = end/4 - end/100 + (1900+end)/400 - 1900/400;
}else if(year <= -4){
days = 1 + (4-year)/4;
}else{
days = 0;
}
return days + MONTAB(year)[mon];
}
datetime_t Ttotm( date_t secarg)
{/*convert scalar time to datetime*/
int year, mon;
date_t days, i;
date_t secs;
datetime_t dt;
const short *pm;
secs = secarg + _TBIAS;
days = secs/86400;
dt.wday = (days + WDAY)%7;
for(year = days/365; days < (i = Daysto(year, 0) + 365*year);)
--year;
days -= i;
dt.year = year;
dt.yday = days;
pm = MONTAB(year);
for(mon = 12; days < pm[--mon]; )
;
dt.mon = mon;
dt.mday = days - pm[mon] + 1;
secs %= 86400;
dt.hour = secs/3600;
secs %= 3600;
dt.minute = secs/60;
dt.second = secs%60;
return dt;
}
date_t tmtoT(datetime_t *t)
{/*convert time structure to scalar time*/
long double dsecs;
int mon, year, ymon;
date_t secs;
ymon = t->mon/12;
mon = t->mon - ymon*12;
if(mon < 0)
mon += 12, --ymon;
if(ymon < 0 && t->year < INT_MIN - ymon
|| 0 < ymon && INT_MAX - ymon < t->year)
return (date_t)-1;
year = t->year + ymon;
dsecs = 86400.0 * (Daysto(year, mon) - 1)
+ 31536000.0 * year + 86400.0 * t->mday;
dsecs += 3600.0 * t->hour + 60.0 * t->minute + (double)t->second;
if(dsecs < 0.0)
return (date_t)(-1);
secs = (date_t)dsecs - _TBIAS;
return secs;
}
int main()
{
test_leap();
datetime_t dt = {
.year = 1900 - 1900,
.mon = 6 - 1,
.mday = 18,
.hour = 18,
.minute = 48,
.second = 0,
};
datetime_t d2 = {0};
date_t t = tmtoT(&dt);
d2 = Ttotm(t);
/*you can compare with:
var t = Date.UTC(3013,5,18, 18, 48,0)/1000;
document.write(" t " + t);
*/
printf("t %I64d \n", t);
printf("%d-%d-%d %02d:%02d:%02d\n", d2.year + 1900, d2.mon + 1, d2.mday, d2.hour, d2.minute, d2.second);
}