Linux下系统时间函数、DST等相关问题总结

1. 内核中时间的基本类型:

           在Linux内核中,常见的时间类型有以下两种:系统时间(system time)和实时时间(real time),其实,方便理解,可以将二者分别认为是相对时间和绝对时间,同时它们分别对应于内核中的两个全局变量值:jiffies和xtime。

           xtime: xtime值是cmos电路中取得的时间,一般是从某个历史时刻(1970年1月1日0时0分)开始到现在的时间,其实也就是我们操作系统上面所显示的时间,它的精度是微秒。

           jiffies:jiffies是记录从电脑开机到现在总共的时钟中断次数(拍数),它的值取决于系统的频率,单位是HZ,其倒数即表示一秒钟中断所产生的次数,在Linux 2.5内核版本之后将HZ从100提高到1000MHZ,它的精度也就是10毫秒。

           根据对上面两个全局变量值的介绍,大提升应该了解到Linux系统中系统时间与实时时间之间的区别,前者表示的是从电脑开机到现在的时间,可以通过全局变量jiffies值换算而来;而实时时间则是指我们日常生活中的日期时间,它跟UTC有着密切关系,这些将在后面章节做介绍。

 

2. Linux time API中常见的时间结构:

(1)time_t:它是一个长整型数据,用来表示从1970年之后到现在的秒数。一般通过time函数获取。

(2)timeval结构:通过gettimeofday函数获取。

            struct timeval

            {

              long tv_sec; /* seconds */

               long tv_usec; /* microseconds */

             };

(3)timezone结构:通过gettimeofday函数获取。

            struct timezone

            {

                      int tz_minuteswest; /* 和Greewich时间差了多少分钟*/

                      int tz_dsttime; /*DST types*/

            };

           【引申】常见的DST类型如下:

            #define   DST_NONE      0    /* not on dst */

            #define   DST_USA        1    /* USA style dst */

            #define   DST_AUST       2   /* Australian style dst */

            #define   DST_WET        3    /* Western European dst */

            #define   DST_MET        4    /* Middle European dst */

            #define   DST_EET         5    /* Eastern European dst */

            #define   DST_CAN        6    /* Canada */

(4)timespec结构:通过clock_gettime函数获取。

            struct timespec {

            time_t tv_sec;         /* seconds */

             long   tv_nsec;        /* nanoseconds */

            };

(5)tm结构:通常由gmtime, localtime, mktime等函数返回。

           struct tm {

                  /*

               * the number of seconds after the minute,normally in the range

               * 0 to 59, but can be up to 60 to allow forleap seconds

               */

                int tm_sec;

                /* the number of minutes after the hour, in the range 0 to 59*/

                int tm_min;

                 /* the number of hours past midnight, in the range 0 to 23 */

                 int tm_hour;

                 /* the day of the month, in the range 1 to 31*/

                 int tm_mday;

                 /* the number of months since January, in the range 0 to 11 */

                 int tm_mon;

                 /* thenumber of years since 1900 */

                long tm_year;

                /* the number of days since Sunday, in the range 0 to 6 */

               int tm_wday;

               /* the number of days since January 1, in the range 0 to 365 */

              int tm_yday;

           };

 

3. 常见的时间系统函数:

(1) time:   #include

      time_t time(time_t *t)

      若函数的参数为NULL,则返回从1970年1月1日0时0分0秒到现在(系统时间)所经过的秒;若参数非空,则将返回的值存在由指针t所指代的内存中。

(2) gettimeofday: #include

      int gettimeofday(structtimeval *tv ,struct timezone *tz )

      此函数可以获取两方面的时间信息,一个是可以获取到从1970年1月1日0时0分0秒到现在(系统时间)所经过的微秒,精度相比time函数精度有所提升;另外还可以获取到系统的时区信息。

【说明】

◆     gettimeofday函数成功返回0;否则返回-1,错误存储在errno中。

◆     tz_minuteswest值的确定问题:它表示的是与GTM之间相差的分钟数,其值应该为GMT(GMT +0)减去本地

         时区对应的时间所得到的值,以EDT(GMT -4)为例,其值为240分钟。

◆     在实际开发中,gettimeofday中的tz参数实际很少使用,因为各种原因,一直未能实现(所获取出来的值恒为

         0),因此,通常将此处直接写成NULL。

◆     对于gettimeofday函数的效率以及内部实现(系统调用实现),可参考

           http://blog.csdn.net/russell_tao/article/details/7185588中的阐述。

◆     与gettimeofday函数相对应的是settimeofday,它可以设置实时时间RTC。但之前必须要具有root权限。

(3) gmtime,localtime and mktime:

           struct tm*gmtime(const time_t *timep)

           struct tm *localtime(const time_t *timep)

           time_tmktime(struct tm*tm)

           以上三个函数实现了time_t与tm结构的互换。前两者将time_t结构转换成tm结构,mktime则正好相反。

【说明】gmtime与localtime之间的区别:

           二者均可以将time_t结构的时间值转化成真实世界所使用的日期时间表示方法(tm结构),但是,前者返回的时间值未作时区的转换,即返回的是UTC时间;而localtime函数则返回的经过了时区转换的时间值,所获取到的值才是本地的真实时间。例如,在Linux系统中运行date命令,它显示的是经过时区转换之后的时间值(通过localtime获取),而若运行“date-u”则能显示未经过时区转换的UTC时间(通过gmtime获取)。

 

(3) strftime:  #include

     size_tstrftime (char *s,sizetsize, const char *format,const struct tm *brokentime)

     此函数的功能是将由brokentime指针所指的时间按照format指针所指的格式输出到由s指针所指向的存储空间中,其中size是指存储空间的最大值。若返回0,则表明出现错误,所写进存储空间的结果是未定义的,若为真,则返回的是写进存储空间的字符数。

 

(4) clock_gettime: #include

      intclock_gettime(clockid_tclk_id,struct timespec *tp);

      此函数的功能是用来获取不同类型计时时钟的时间,其类型由clockid_t指定,常见的有:

       CLOCK_REALTIME(与实时时间对应)

       CLOCK_MONOTONIC(与系统时间对应)

【说明】

◆     clock_gettime函数能将所获得的时间值精确到纳秒级别;

◆     函数运行成功则返回0,否则返回-1,并将错误存在errno中;

◆     除上面的两个时钟类型之外,还有以下两种类型:

        CLOCK_PROCESS_CPUTIME_ID和CLOCK_THREAD_CPUTIME_ID

        但是这两种时钟类型一般出现在多处理机系统(SMP)中,值得注意的是,在老版本的Linux系统中可能会出现CPU时间之间不一致的现象,这是因为不同的CPU之间没有保证时间一致性措施,导致CPU时间之间出现偏移量,在2.6.18版本之后解决了这方面的问题,使得系统启动后不同的CPU之间具有相同的时间基准点。

◆CLOCK_REALTIME时间可以通过settime或者settimeofday函数进行修改,或者通过NTP周期性 地纠正,此时需要用到adjtimex函数;CLOCK_MONOTONIC时间则不能通过settime或者settimeofday函数进行修改,但是同样可以通过NTP进行调整,此时同样需要用到adjtimex函数。

对比两种时钟类型,若要在实际开发中要统计某个事件的时间,则最好是使用CLOCK_MONOTONIC,因为CLOCK_REALTIME被影响的因素太多,如手动修改,时区变化等等。

 

4. DST以及相关的系统函数:

(1)UTC、GMT与DST

           目前世界上常见的计时方式主要有:太阳时(MT)和原子时。GMT(格林尼治时间)的正午是指当太阳横穿格林尼治子午线时的时间,由于地球的自转呈现不规则性,并且正在缓慢减速,因此格林威治时间目前已经不再作为标准时间使用,取而代之的是协调时间时(UTC),它是由原子钟提供,它是基于标准的GMT提供的准确时间,若在不需要精确到秒的前提下,通常也将GMTUTC视作等同

           DST(daylight saving time)也称为夏令时,它是以节约能源为目的而人为规定的一种制度,它规定某段时间作为夏令时间,并在标准时间的基础上提前多长时间(通常是一个小时),同时DST还规定了规定生效的起始时间和末尾时间,详细规则会在tzset函数中介绍,值得注意的是目前只是部分国家实施了夏令时制度。

           标准时间是相对于UTC/GMT时间而言的,它在UTC/GMT之上增加了时区信息,比如中国标准时间是GMT+8,即在UTC时间上增加8个小时。   

(2) 系统时间、标准时间以及UTC时间之间的关系:

           这节主要探讨在具体项目实现过程中,如何处理系统时间、标准时间以及UTC时间之间的关系,其中系统时间可以通过前面的系统函数获取到,它可能正处于夏令时间区域,下面这个图可以清晰地阐述三者之间的关系:

             我们以localtime函数获取到本地系统时间为例,演示如何将其转换成UTC时间,前面已经说过,localtime所获取到的时间已经包含了时区信息,但是之前我们必须要确认目前的这个时间是否处于夏令时区域之内,若是,则还需要经过A阶段(去掉DST偏移量,通常是一个小时),若不是,只需要经历第二个阶段B,即去时区,最后转化成UTC,当然这两个阶段并没有严格的先后顺序。反过来,在具体实现中,还经常出现将UTC时间转化成本地时间的情况,比如NTP就是基于这样的原理,它从NTP server端获取统一的UTC时间,然后需要经过C(加时区)和D(加DST,如果存在或正好处于夏令时区域范围之内的话)两个阶段将其转化成本地系统时间。

           下面主要阐述第一种情况(本地系统时间——>UTC)是如何具体实现的。当然前提是我们要知道目前所在的时区,这是一切的根本。在此之前,值得说明的是,一般来讲,时区是一个固定的信息,难以想象一个国家或地区去改变时区所带来的后果,但是DST因为是人为规定的,因此可能存在着修改的情况,基于这个事实,在具体实现中,时区信息可以存储在本地,而DST信息既可以静态存储在本地,也可以通过相关的server动态获取到。我们以静态存储的方式为例来讲解具体是如何实现去时区,去DST。

           下面这个结构体存储了跟时区相关的位移量(offset)以及是否存在DST等信息,根据所在的时区信息,很容易找到系统时间与UTC时间之间的时区偏移,另外根据rule是否为-1来确定此时区是否实施了夏令时,若为-1,表明这个时区地已经实现了夏令时,则还需要经过去DST阶段,否则只需要经过去时区就可以得到UTC时间。

        struct zone zones[N_ZONES] = {

        /* offset rules */

        { -43200, -1 }, /* (GMT-12:00)International Date Line West */

        { -39600, -1 }, /* (GMT-11:00) Midway Island,Samoa */

        { -36000, -1 }, /* (GMT-10:00) Hawaii */

        { -32400,  0 }, /* (GMT-09:00) Alaska */

        { -28800,  0 }, /* (GMT-08:00) Pacific Time, Tijuana */

        { -25200, -1 }, /* (GMT-07:00) Arizona, Mazatlan*/

        { -25200, 13 }, /* (GMT-07:00) Chihuahua, La Paz*/

        { -25200,  0 }, /* (GMT-07:00) Mountain Time */

        { -21600,  0 }, /* (GMT-06:00) Central America */

        { -21600,  0 }, /* (GMT-06:00) Central Time */

        { -21600, 13 }, /* (GMT-06:00) Guadalajara, MexicoCity, Monterrey*/

        { -21600, -1 }, /* (GMT-06:00) Saskatchewan */

        { -18000, -1 }, /* (GMT-05:00) Bogota, Lima, Quito */

        { -18000,  0 }, /* (GMT-05:00) Eastern Time */

        { -18000, -1 }, /* (GMT-05:00) Indiana */

        { -14400,  0 }, /* (GMT-04:00) Atlantic Time */

        {-14400, -1 }, /* (GMT-04:00) Caracas, La Paz */

        { -14400,  2 }, /* (GMT-04:00) Santiago */

        { -12600,  0 }, /* (GMT-03:30) Newfoundland */

        { -10800, 14 }, /* (GMT-03:00) Brasilia */

        { -10800, -1 }, /* (GMT-03:00) Buenos Aires, Georgetown*/

        { -10800, -1 }, /* (GMT-03:00) Greenland */

        { -7200, -1 }, /* (GMT-02:00) Mid-Atlantic */

        { -3600,  1 }, /* (GMT-01:00) Azores */

        { -3600, -1 }, /* (GMT-01:00) Cape Verde Is. */

        {     0, -1 }, /* (GMT) Casablanca, Monrovia */

        {     0,  1 }, /* (GMT) Greenwich MeanTime: Dublin, Edinburgh,Lisbon, London*/

        {  3600,  1 }, /* (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna

        {  3600,  1 }, /* (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague */

        {  3600,  1 }, /* (GMT+01:00) Brussels, Copenhagen, Madrid, Paris*/

        {  3600,  1 }, /* (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb*/

        {  3600, -1 }, /* (GMT+01:00) West Central Africa*/

        {  7200,  1 }, /* (GMT+02:00) Athens, Istanbul, Minsk */

        {  7200,  1 }, /* (GMT+02:00) Bucharest */

        {  7200,  4 }, /* (GMT+02:00) Cairo */

        {  7200, -1 }, /* (GMT+02:00) Harare, Pretoria */

        {  7200,  1 }, /* (GMT+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius */

        {  7200,  5 }, /* (GMT+02:00) Jerusalem */

        { 10800,  6 }, /* (GMT+03:00) Baghdad */

        { 10800, -1 }, /* (GMT+03:00) Kuwait,Riyadh */

        { 10800,  7 }, /* (GMT+03:00) Moscow, St. Petersburg, Volgograd */

        { 10800, -1 }, /* (GMT+03:00) Nairobi*/

        { 12600,  8 }, /* (GMT+03:30) Tehran */

        { 14400, -1 }, /* (GMT+04:00) Abu Dhabi, Muscat */

        { 14400,  9 }, /* (GMT+04:00) Baku, Tbilisi, Yerevan */

        { 16200, -1 }, /* (GMT+04:30) Kabul*/

        { 18000,  7 }, /* (GMT+05:00)Ekaterinburg */

        { 18000, -1 }, /* (GMT+05:00) Islamabad, Karachi, Tashkent*/

        { 19800, -1 }, /* (GMT+05:30) Chennai, Kolkata, Mumbai, New Delhi */

        { 20700, -1 }, /* (GMT+05:45) Kathmandu*/

        { 21600, 12 }, /* (GMT+06:00) Almaty, Novosibirsk */

        { 21600, -1 }, /* (GMT+06:00) Astana, Dhaka*/

        { 21600, -1 }, /* (GMT+06:00) Sri Jayawardenepura */

        {  23400, -1 }, /* (GMT+06:30) Rangoon */

        { 25200, -1 }, /* (GMT+07:00) Bangkok, Hanoi, Jakarta*/

        { 25200,  7 }, /* (GMT+07:00) Krasnoyarsk */

        { 28800, -1 }, /* (GMT+08:00) Beijing,Chongquing, Hong Kong, Urumqi*/

        { 28800, -1 }, /* (GMT+08:00) Irkutsk,Ulaan Bataar */

        { 28800, -1 }, /* (GMT+08:00) Kuala Lumpur, Singapore*/

        { 28800, -1 }, /* (GMT+08:00) Perth*/

        { 28800, -1 }, /* (GMT+08:00) Taipei*/

        { 32400, -1 }, /* (GMT+09:00) Osaka, Sapporo, Tokyo*/

        { 32400, -1 }, /* (GMT+09:00) Seoul*/

        { 32400,  7 }, /* (GMT+09:00) Yakutsk */

        { 34200,  3 }, /* (GMT+09:30) Adelaide */

        { 34200, -1 }, /* (GMT+09:30) Darwin*/

        { 36000, -1 }, /* (GMT+10:00) Brisbane*/

        { 36000,  3 }, /* (GMT+10:00) Canberra, Melbourne, Sydney*/

        { 36000, -1 }, /* (GMT+10:00) Guam, Port Moresby */

        { 36000, 10 }, /* (GMT+10:00) Hobart*/

        { 36000,  7 }, /* (GMT+10:00) Vladivostok */

        { 39600, -1 }, /* (GMT+11:00) Magadan */

        { 39600,  7 }, /* (GMT+11:00)Solomon Is., New Caledonia*/

        { 43200, 11 }, /* (GMT+12:00) Auckland, Wellington */

        { 43200, -1 }, /* (GMT+12:00) Fiji,Kamchatka, Marshall Is. */

        { 43200, -1 }, /* (GMT+12:00) NZ */

};        

           那么又如何去掉DST,即找到系统时间与标准时间之间的DST偏移量呢?在此之前需要了解到DST的规则问题,如规则格式、规则数据等等。

           DST规则规定了实施夏令时的起始时间以及结束时间,如澳大利亚的是:从4月的第一个星期天的凌晨3点到10月的第一个星期天的凌晨2点,全世界DST可参考www.worldtimezone.com/daylight.html。下面主要阐述如何判断目前的时间是否包含有夏令时。

rpytime(rule1, year) < (gm_time + zone->z_gmtoff))< rpytime(rule2, year)

      上面的式子中gm_time是本地系统时间(注意是通过localtime获取,没有加入时区,单位为秒),z_gmtoff是指制定时区的偏移量,这样式子中间代表就是标准时间;式子中rule1,rule2分别对应于DST规则中的两个界点,并利用rpytime函数计算出从1970年以来的时间总长(以秒为单位),若上面的式子成立,表明存在DST,那是因为DST使得在标准时间之上提前了1小时。



你可能感兴趣的:(Linux网络编程)