Linux下系統(tǒng)時間函數(shù)、DST等相關問題總結
1. 內(nèi)核中時間的基本類型:
在Linux內(nèi)核中,常見的時間類型有以下兩種:系統(tǒng)時間(system time)和實時時間(real time),其實,方便理解,可以將二者分別認為是相對時間和絕對時間,同時它們分別對應于內(nèi)核中的兩個全局變量值:jiffies和xtime。
xtime: xtime值是從cmos電路中取得的時間,一般是從某個歷史時刻(1970年1月1日0時0分)開始到現(xiàn)在的時間,其實也就是我們操作系統(tǒng)上面所顯示的時間,它的精度是微秒。
jiffies:jiffies是記錄從電腦開機到現(xiàn)在總共的時鐘中斷次數(shù)(拍數(shù)),它的值取決于系統(tǒng)的頻率,單位是HZ,其倒數(shù)即表示一秒鐘中斷所產(chǎn)生的次數(shù),在Linux 2.5內(nèi)核版本之后將HZ從100提高到1000MHZ,它的精度也就是10毫秒。
根據(jù)對上面兩個全局變量值的介紹,大提升應該了解到Linux系統(tǒng)中系統(tǒng)時間與實時時間之間的區(qū)別,前者表示的是從電腦開機到現(xiàn)在的時間,可以通過全局變量jiffies值換算而來;而實時時間則是指我們?nèi)粘I钪械娜掌跁r間,它跟UTC有著密切關系,這些將在后面章節(jié)做介紹。
2. Linux time API中常見的時間結構:
(1)time_t:它是一個長整型數(shù)據(jù),用來表示從1970年之后到現(xiàn)在的秒數(shù)。一般通過time函數(shù)獲取。
(2)timeval結構:通過gettimeofday函數(shù)獲取。
struct timeval
{
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
(3)timezone結構:通過gettimeofday函數(shù)獲取。
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函數(shù)獲取。
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
(5)tm結構:通常由gmtime, localtime, mktime等函數(shù)返回。
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. 常見的時間系統(tǒng)函數(shù):
(1) time: #include <time.h>
time_t time(time_t *t)
若函數(shù)的參數(shù)為NULL,則返回從1970年1月1日0時0分0秒到現(xiàn)在(系統(tǒng)時間)所經(jīng)過的秒;若參數(shù)非空,則將返回的值存在由指針t所指代的內(nèi)存中。
(2) gettimeofday: #include <sys/time.h>
int gettimeofday(structtimeval *tv ,struct timezone *tz )
此函數(shù)可以獲取兩方面的時間信息,一個是可以獲取到從1970年1月1日0時0分0秒到現(xiàn)在(系統(tǒng)時間)所經(jīng)過的微秒,精度相比time函數(shù)精度有所提升;另外還可以獲取到系統(tǒng)的時區(qū)信息。
【說明】
◆ gettimeofday函數(shù)成功返回0;否則返回-1,錯誤存儲在errno中。
◆ tz_minuteswest值的確定問題:它表示的是與GTM之間相差的分鐘數(shù),其值應該為GMT(GMT +0)減去本地
時區(qū)對應的時間所得到的值,以EDT(GMT -4)為例,其值為240分鐘。
◆ 在實際開發(fā)中,gettimeofday中的tz參數(shù)實際很少使用,因為各種原因,一直未能實現(xiàn)(所獲取出來的值恒為
0),因此,通常將此處直接寫成NULL。
◆ 對于gettimeofday函數(shù)的效率以及內(nèi)部實現(xiàn)(系統(tǒng)調(diào)用實現(xiàn)),可參考
http://blog.csdn.net/russell_tao/article/details/7185588中的闡述。
◆ 與gettimeofday函數(shù)相對應的是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)
以上三個函數(shù)實現(xiàn)了time_t與tm結構的互換。前兩者將time_t結構轉(zhuǎn)換成tm結構,mktime則正好相反。
【說明】gmtime與localtime之間的區(qū)別:
二者均可以將time_t結構的時間值轉(zhuǎn)化成真實世界所使用的日期時間表示方法(tm結構),但是,前者返回的時間值未作時區(qū)的轉(zhuǎn)換,即返回的是UTC時間;而localtime函數(shù)則返回的經(jīng)過了時區(qū)轉(zhuǎn)換的時間值,所獲取到的值才是本地的真實時間。例如,在Linux系統(tǒng)中運行date命令,它顯示的是經(jīng)過時區(qū)轉(zhuǎn)換之后的時間值(通過localtime獲?。?,而若運行“date-u”則能顯示未經(jīng)過時區(qū)轉(zhuǎn)換的UTC時間(通過gmtime獲?。?。
(3) strftime: #include <time.h>
size_tstrftime (char *s,sizetsize, const char *format,const struct tm *brokentime)
此函數(shù)的功能是將由brokentime指針所指的時間按照format指針所指的格式輸出到由s指針所指向的存儲空間中,其中size是指存儲空間的最大值。若返回0,則表明出現(xiàn)錯誤,所寫進存儲空間的結果是未定義的,若為真,則返回的是寫進存儲空間的字符數(shù)。
(4) clock_gettime: #include <time.h>
intclock_gettime(clockid_tclk_id,struct timespec *tp);
此函數(shù)的功能是用來獲取不同類型計時時鐘的時間,其類型由clockid_t指定,常見的有:
CLOCK_REALTIME(與實時時間對應)
CLOCK_MONOTONIC(與系統(tǒng)時間對應)
【說明】
◆ clock_gettime函數(shù)能將所獲得的時間值精確到納秒級別;
◆ 函數(shù)運行成功則返回0,否則返回-1,并將錯誤存在errno中;
◆ 除上面的兩個時鐘類型之外,還有以下兩種類型:
CLOCK_PROCESS_CPUTIME_ID和CLOCK_THREAD_CPUTIME_ID
但是這兩種時鐘類型一般出現(xiàn)在多處理機系統(tǒng)(SMP)中,值得注意的是,在老版本的Linux系統(tǒng)中可能會出現(xiàn)CPU時間之間不一致的現(xiàn)象,這是因為不同的CPU之間沒有保證時間一致性措施,導致CPU時間之間出現(xiàn)偏移量,在2.6.18版本之后解決了這方面的問題,使得系統(tǒng)啟動后不同的CPU之間具有相同的時間基準點。
◆CLOCK_REALTIME時間可以通過settime或者settimeofday函數(shù)進行修改,或者通過NTP周期性 地糾正,此時需要用到adjtimex函數(shù);CLOCK_MONOTONIC時間則不能通過settime或者settimeofday函數(shù)進行修改,但是同樣可以通過NTP進行調(diào)整,此時同樣需要用到adjtimex函數(shù)。
◆對比兩種時鐘類型,若要在實際開發(fā)中要統(tǒng)計某個事件的時間,則最好是使用CLOCK_MONOTONIC,因為CLOCK_REALTIME被影響的因素太多,如手動修改,時區(qū)變化等等。
4. DST以及相關的系統(tǒng)函數(shù):
(1)UTC、GMT與DST
目前世界上常見的計時方式主要有:太陽時(MT)和原子時。GMT(格林尼治時間)的正午是指當太陽橫穿格林尼治子午線時的時間,由于地球的自轉(zhuǎn)呈現(xiàn)不規(guī)則性,并且正在緩慢減速,因此格林威治時間目前已經(jīng)不再作為標準時間使用,取而代之的是協(xié)調(diào)時間時(UTC),它是由原子鐘提供,它是基于標準的GMT提供的準確時間,若在不需要精確到秒的前提下,通常也將GMT與UTC視作等同。
DST(daylight saving time)也稱為夏令時,它是以節(jié)約能源為目的而人為規(guī)定的一種制度,它規(guī)定某段時間作為夏令時間,并在標準時間的基礎上提前多長時間(通常是一個小時),同時DST還規(guī)定了規(guī)定生效的起始時間和末尾時間,詳細規(guī)則會在tzset函數(shù)中介紹,值得注意的是目前只是部分國家實施了夏令時制度。
標準時間是相對于UTC/GMT時間而言的,它在UTC/GMT之上增加了時區(qū)信息,比如中國標準時間是GMT+8,即在UTC時間上增加8個小時。
(2) 系統(tǒng)時間、標準時間以及UTC時間之間的關系:
這節(jié)主要探討在具體項目實現(xiàn)過程中,如何處理系統(tǒng)時間、標準時間以及UTC時間之間的關系,其中系統(tǒng)時間可以通過前面的系統(tǒng)函數(shù)獲取到,它可能正處于夏令時間區(qū)域,下面這個圖可以清晰地闡述三者之間的關系:
我們以localtime函數(shù)獲取到本地系統(tǒng)時間為例,演示如何將其轉(zhuǎn)換成UTC時間,前面已經(jīng)說過,localtime所獲取到的時間已經(jīng)包含了時區(qū)信息,但是之前我們必須要確認目前的這個時間是否處于夏令時區(qū)域之內(nèi),若是,則還需要經(jīng)過A階段(去掉DST偏移量,通常是一個小時),若不是,只需要經(jīng)歷第二個階段B,即去時區(qū),最后轉(zhuǎn)化成UTC,當然這兩個階段并沒有嚴格的先后順序。反過來,在具體實現(xiàn)中,還經(jīng)常出現(xiàn)將UTC時間轉(zhuǎn)化成本地時間的情況,比如NTP就是基于這樣的原理,它從NTP server端獲取統(tǒng)一的UTC時間,然后需要經(jīng)過C(加時區(qū))和D(加DST,如果存在或正好處于夏令時區(qū)域范圍之內(nèi)的話)兩個階段將其轉(zhuǎn)化成本地系統(tǒng)時間。
下面主要闡述第一種情況(本地系統(tǒng)時間——>UTC)是如何具體實現(xiàn)的。當然前提是我們要知道目前所在的時區(qū),這是一切的根本。在此之前,值得說明的是,一般來講,時區(qū)是一個固定的信息,難以想象一個國家或地區(qū)去改變時區(qū)所帶來的后果,但是DST因為是人為規(guī)定的,因此可能存在著修改的情況,基于這個事實,在具體實現(xiàn)中,時區(qū)信息可以存儲在本地,而DST信息既可以靜態(tài)存儲在本地,也可以通過相關的server動態(tài)獲取到。我們以靜態(tài)存儲的方式為例來講解具體是如何實現(xiàn)去時區(qū),去DST。
下面這個結構體存儲了跟時區(qū)相關的位移量(offset)以及是否存在DST等信息,根據(jù)所在的時區(qū)信息,很容易找到系統(tǒng)時間與UTC時間之間的時區(qū)偏移,另外根據(jù)rule是否為-1來確定此時區(qū)是否實施了夏令時,若為-1,表明這個時區(qū)地已經(jīng)實現(xiàn)了夏令時,則還需要經(jīng)過去DST階段,否則只需要經(jīng)過去時區(qū)就可以得到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,即找到系統(tǒng)時間與標準時間之間的DST偏移量呢?在此之前需要了解到DST的規(guī)則問題,如規(guī)則格式、規(guī)則數(shù)據(jù)等等。
DST規(guī)則規(guī)定了實施夏令時的起始時間以及結束時間,如澳大利亞的是:從4月的第一個星期天的凌晨3點到10月的第一個星期天的凌晨2點,全世界DST可參考www.worldtimezone.com/daylight.html。下面主要闡述如何判斷目前的時間是否包含有夏令時。
rpytime(rule1,year) < (gm_time + zone->z_gmtoff))< rpytime(rule2,year)
上面的式子中gm_time是本地系統(tǒng)時間(注意是通過localtime獲取,沒有加入時區(qū),單位為秒),z_gmtoff是指制定時區(qū)的偏移量,這樣式子中間代表就是標準時間;式子中rule1,rule2分別對應于DST規(guī)則中的兩個界點,并利用rpytime函數(shù)計算出從1970年以來的時間總長(以秒為單位),若上面的式子成立,表明存在DST,那是因為DST使得在標準時間之上提前了1小時。