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

我們以localtime函數獲取到本地系統時(shí)間為例,演示如何將其轉換成UTC時(shí)間,前面已經(jīng)說(shuō)過(guò),localtime所獲取到的時(shí)間已經(jīng)包含了時(shí)區信息,但是之前我們必須要確認目前的這個(gè)時(shí)間是否處于夏令時(shí)區域之內,若是,則還需要經(jīng)過(guò)A階段(去掉DST偏移量,通常是一個(gè)小時(shí)),若不是,只需要經(jīng)歷第二個(gè)階段B,即去時(shí)區,最后轉化成UTC,當然這兩個(gè)階段并沒(méi)有嚴格的先后順序。反過(guò)來(lái),在具體實(shí)現中,還經(jīng)常出現將UTC時(shí)間轉化成本地時(shí)間的情況,比如NTP就是基于這樣的原理,它從NTP server端獲取統一的UTC時(shí)間,然后需要經(jīng)過(guò)C(加時(shí)區)和D(加DST,如果存在或正好處于夏令時(shí)區域范圍之內的話(huà))兩個(gè)階段將其轉化成本地系統時(shí)間。
下面主要闡述第一種情況(本地系統時(shí)間——>UTC)是如何具體實(shí)現的。當然前提是我們要知道目前所在的時(shí)區,這是一切的根本。在此之前,值得說(shuō)明的是,一般來(lái)講,時(shí)區是一個(gè)固定的信息,難以想象一個(gè)國家或地區去改變時(shí)區所帶來(lái)的后果,但是DST因為是人為規定的,因此可能存在著(zhù)修改的情況,基于這個(gè)事實(shí),在具體實(shí)現中,時(shí)區信息可以存儲在本地,而DST信息既可以靜態(tài)存儲在本地,也可以通過(guò)相關(guān)的server動(dòng)態(tài)獲取到。我們以靜態(tài)存儲的方式為例來(lái)講解具體是如何實(shí)現去時(shí)區,去DST。
下面這個(gè)結構體存儲了跟時(shí)區相關(guān)的位移量(offset)以及是否存在DST等信息,根據所在的時(shí)區信息,很容易找到系統時(shí)間與UTC時(shí)間之間的時(shí)區偏移,另外根據rule是否為-1來(lái)確定此時(shí)區是否實(shí)施了夏令時(shí),若為-1,表明這個(gè)時(shí)區地已經(jīng)實(shí)現了夏令時(shí),則還需要經(jīng)過(guò)去DST階段,否則只需要經(jīng)過(guò)去時(shí)區就可以得到UTC時(shí)間。
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,即找到系統時(shí)間與標準時(shí)間之間的DST偏移量呢?在此之前需要了解到DST的規則問(wèn)題,如規則格式、規則數據等等。
DST規則規定了實(shí)施夏令時(shí)的起始時(shí)間以及結束時(shí)間,如澳大利亞的是:從4月的第一個(gè)星期天的凌晨3點(diǎn)到10月的第一個(gè)星期天的凌晨2點(diǎn),全世界DST可參考www.worldtimezone.com/daylight.html。下面主要闡述如何判斷目前的時(shí)間是否包含有夏令時(shí)。
rpytime(rule1,year) < (gm_time + zone->z_gmtoff))< rpytime(rule2,year)
上面的式子中g(shù)m_time是本地系統時(shí)間(注意是通過(guò)localtime獲取,沒(méi)有加入時(shí)區,單位為秒),z_gmtoff是指制定時(shí)區的偏移量,這樣式子中間代表就是標準時(shí)間;式子中rule1,rule2分別對應于DST規則中的兩個(gè)界點(diǎn),并利用rpytime函數計算出從1970年以來(lái)的時(shí)間總長(cháng)(以秒為單位),若上面的式子成立,表明存在DST,那是因為DST使得在標準時(shí)間之上提前了1小時(shí)。
聯(lián)系客服