Crane
Table_bottom

Search
Loading
Table_bottom

分类
Table_bottom

随机文章
Table_bottom

标签云
Table_bottom

最新评论
Table_bottom

链接
Table_bottom

功能
Table_bottom

消失的11天

最近做一个万年历的作业,粗粗了解了下历法,做农历的时候发现农历没有固定的算法,只能查表来计算,中华人民共和国提供了1800-2100三百年间的标准农历供使用,于是一下子限制了我的程序的查询范围。所以想着公历的准确,计算的容易,可是正想着公历算法的好呢,发现了一个问题,那就是以前公历不大完善的时候,也有问题。

比如这个,在linux下打入这个命令(windows限制时间范围是1980-2099,所以看不到这个现象)

crane@debian:~$ cal 9 1752
   September 1752
Su Mo Tu We Th Fr Sa
       1  2 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

cal 9 1752就是查询1752年9月的日历,于是很跌眼镜的发现,2号后面就是14号,少了11天,怎么回事呢?

查了下资料,发现是因为历法调整的问题。

1582年2月,罗马教廷要求从1582 年10月中减去10天,因此1852 年10月4日后面紧跟着就是15日。在意大利、西班牙等国家都这样处理了。其他天主教国家也很快跟着这么做了,但是新教国家不愿意修改,而且希腊等东正教 国家直到20世纪初才修改,所以这个改革在英国及其殖民地(包括美国)在1752年9月才被执行。这样 1752 年9月2日后面跟着的就是1752 年9月14日。 这就是为什么cal会生成上面输出的原因了。

这里有个问题,上面说教廷说的是减去10天,但是刚才发现1752年9月减了11天,这是为什么呢?

这是历法转换的问题,现行公历叫格里历(Gregorian calendar),这是十六世纪的罗马教皇Gregorian XIII (格里十三世)针对当时使用的儒略历 (Julian calendar)进行修订后,于1582年10月开始实行的。所以就出现了上面的1582年10月调整10天的情况。但是由于写cal的是美国人,cal是从AT&T的Unix中出来的,前面说到过,美国跟从英国的历法是从1752年才开始改的,所以不太一样,所以还牵涉了1600-1800年的一些问题。

根本原因是因为1800年以前的闰年计算的问题,我们知道闰年是4年一闰,百年不闰,400年再闰,但是1800年以前(所以不包括1800年)没百年不闰,所以就出现了偏差,比如我们可以看一下

crane@debian:~$ cal 2 1700
   February 1700
Su Mo Tu We Th Fr Sa
             1  2  3
 4  5  6  7  8  9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29

crane@debian:~$ cal 2 1600
   February 1600
Su Mo Tu We Th Fr Sa
                1  2
 3  4  5  6  7  8  9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29

可以清楚地看到,1600和1700年2月都是29天,一眼看去,想当然的认为多算了17天,其实实际上多算了13天,因为400,800,1200,1600是闰年,2月应该有29天。但是为什么调整的时候只少了11天呢,有个很纠结的原因,由于儒略历 (Julian calendar)公元前闰年的不规则,少算了2个闰年,从13天中去掉2天,所以在cal中看到的是少了11天。

话说回来,做万年历的时候这可是陷阱啊,得小心才是。

二进制LED时钟--程序员的装备

二进制时钟

今天在网上看到这样一张图片,挺有意思的,一眼看去就知道这是个时钟,但是却不一能看出时间来。程序员每天和0,1打交道,虽然没有像黑客帝国里面那样可以看懂大串的01串流,但是简短的二进制数据还是没有问题的。不过我还是没有看出中间那一串是怎么表示的时间,但是却可以通过下面的那个Calender time知道时间啊,呵呵,当然要用工具啦,不然这计算量可要命:

crane@debian:~$ date -d "@1230766664"
Wed Dec 31 18:37:44 EST 2008

呵呵,看到了吧,日期那个时钟上也写得很清楚了,不过却不知道那个小时和分钟是怎么表示的。

不过另外的一种二进制LED时钟却很容易看懂。

8421clock

就像图示的那样,六列LED分成三组,分别表示时分秒,每列的四个灯从上到下的权值是8,4,2,1,把亮着的灯的权值加起来就是这列的数值了。

C/C++中的日期和时间

以前看过一个笑话,有人问一geek:Can you tell me the time now?被这样回答:Of course,it's 1229883309 seconds since 1970/1/1。

笑话归笑话,但是程序员往往能从中看出点有趣的东西出来,比如说这样的时间怎么得到,怎么用程序得到?如何将这样的时间还原成看得懂的时间?这样的计时方法有没有什么优点或者不足的地方?

其实这样的时间有个名字叫日历时间(Calendar time),要得到这样的时间很容易,C标准库就有函数可以做到,在time.h中定义了一个这样的函数:

time_t time(time_t * timer);

其中的time_t是这样定义的:

#ifndef _TIME_T_DEFINED
typedef long time_t;         /* 时间值 */
#define _TIME_T_DEFINED      /* 避免重复定义 time_t */
#endif

于是通过这样的函数调用 time(NULL) 就可以得到我们需要的东西,从上面的定义中可以看到,这个值是保存在一个长整型数中的,但是我们都知道长整数是有限制的,当这个数达到这个限制的时候会发生什么事呢?这可以算是Unix/Linux系统的千年虫问题了,现在一般都是32位系统,我们知道这个最大的数是2147483647,那么这会在什么时候发生呢,其实不用太担心,在2038年才会出现,准确的说是2038年1月19日03时14分07秒,不过在这么长的时间内,硬件的发展肯定可以补上这个漏洞,所以我们大可不必担心。

下来又有一个问题,如果我们得到了一个日历时间,怎么知道真实的时间呢?

同样的在time.h中有相应的函数:

char * ctime(const time_t *timer);

这个函数可以把日历时间格式化输出,像这样的样子

Tue Jan 19 11:14:07 2038

这就是上面说到的那个时间,有8小时的时差,是因为中国和UTC时间差了8个小时的原因。

其实在C标准中还有一个表示日期和时间的数据结构:

#ifndef _TM_DEFINED
struct tm {
        int tm_sec;     /* 秒 – 取值区间为[0,59] */
        int tm_min;     /* 分 - 取值区间为[0,59] */
        int tm_hour;    /* 时 - 取值区间为[0,23] */
        int tm_mday;    /* 一个月中的日期 - 取值区间为[1,31] */
        int tm_mon;     /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */
        int tm_year;    /* 年份,其值等于实际年份减去1900 */
        int tm_wday;    /* 星期 – 取值区间为[0,6],其中0代表星期天,1代表星期一,以此类推 */
        int tm_yday;    /* 从每年的1月1日开始的天数 – 取值区间为[0,365],其中0代表1月1日,1代表1月2日,以此类推 */
        int tm_isdst;   /* 夏令时标识符,实行夏令时的时候,tm_isdst为正。不实行夏令时的进候,tm_isdst为0;不了解情况时,tm_isdst()为负。*/
        };
#define _TM_DEFINED
#endif

time.h还提供了两种不同的函数将日历时间(一个用time_t表示的整数)转换为我们平时看到的把年月日时分秒分开显示的时间格式tm:

struct tm * gmtime(const time_t *timer);                                          
struct tm * localtime(const time_t * timer);

还有个函数像上面说到的ctime一样,格式输出tm结构中的日期和时间。

char * asctime(const struct tm * timeptr);

看名字就知道了,asctime嘛!

来看个程序,实战:

 

  1. #include "time.h"
  2. #include "stdio.h"
  3. int main(void)
  4. {
  5.   time_t lt;
  6.   struct tm st,*pt;
  7.   lt=time(NULL);
  8.   printf("The Calendar time now is %ld\n",lt)
  9.   lt =2147483647;
  10.   printf("The bug time(local) is %s\n",ctime(&lt));
  11.   pt=gmtime(&lt);
  12.   printf("The bug time(UTC) is %s",asctime(pt));
  13.   pt=localtime(&lt);
  14.   printf("The bug time(local) is %s\n",time(pt));
  15.   system("pause");
  16.   return 0;
  17. }

会输出:

The Calendar time now is 1239709783
The bug time(local) is Tue Jan 19 11:14:07 2038

The bug time(UTC) is Tue Jan 19 03:14:07 2038
The bug time(local) is Tue Jan 19 11:14:07 2038

不过,如果我们不喜欢像Tue Jan 19 11:14:07 2038的形式,想按我们自己的想法输出,该怎么办呢,time.h还有个函数strftime,观名知义,格式化时间,原型如下:

size_t strftime(
   char *strDest,
   size_t maxsize,
   const char *format,
   const struct tm *timeptr
);

我们可以根据format指向字符串中格式命令把timeptr中保存的时间信息放在strDest指向的字符串中,最多向strDest中存放maxsize个字符。该函数返回向strDest指向的字符串中放置的字符数。

函数strftime()的操作有些类似于sprintf():识别以百分号(%)开始的格式命令集合,格式化输出结果放在一个字符串中。格式化命令说明 串strDest中各种日期和时间信息的确切表示方法。格式串中的其他字符原样放进串中。格式命令列在下面,它们是区分大小写的。

%a 星期几的简写
%A 星期几的全称
%b 月分的简写
%B 月份的全称
%c 标准的日期的时间串
%C 年份的后两位数字
%d 十进制表示的每月的第几天
%D 月/天/年
%e 在两字符域中,十进制表示的每月的第几天
%F 年-月-日
%g 年份的后两位数字,使用基于周的年
%G 年分,使用基于周的年
%h 简写的月份名
%H 24小时制的小时
%I 12小时制的小时
%j 十进制表示的每年的第几天
%m 十进制表示的月份
%M 十时制表示的分钟数
%n 新行符
%p 本地的AM或PM的等价显示
%r 12小时的时间
%R 显示小时和分钟:hh:mm
%S 十进制的秒数
%t 水平制表符
%T 显示时分秒:hh:mm:ss
%u 每周的第几天,星期一为第一天 (值从0到6,星期一为0)
%U 第年的第几周,把星期日做为第一天(值从0到53)
%V 每年的第几周,使用基于周的年
%w 十进制表示的星期几(值从0到6,星期天为0)
%W 每年的第几周,把星期一做为第一天(值从0到53)
%x 标准的日期串
%X 标准的时间串
%y 不带世纪的十进制年份(值从0到99)
%Y 带世纪部分的十进制年份
%z,%Z 时区名称,如果不能得到时区名称则返回空字符。
%% 百分号

还是来个例子:

 

  1. #include <stdio.h>
  2. #include <time.h>
  3.  
  4. main( void )
  5. {
  6.   struct tm *newtime;
  7.   char tmpbuf[128];
  8.   time_t lt1;
  9.   time( &lt1 );
  10.   newtime=localtime(&lt1);
  11.   strftime( tmpbuf, 128, "Today is %A, day %d of %B in the year %Y.\n", newtime);
  12.   printf(tmpbuf);
  13.   system("pause");
  14. }
  15.  

程序输出:

Today is Tuesday, day 14 of April in the year 2009.

呵呵,按我们自己的意愿了。