轉載請說明出處:http://blog.csdn.net/cywosp/article/details/25839551
在程式中時間處理往往是一個麻煩的事,Linux系統提供了很多關於時間處理的函數,我們可以用這些函數來完成我們所需要的功能。那麼在程式中一般會關心哪些時間問題呢?
- 真即時間:程式啟動並執行時間,即程式啟動到程式消亡所用時間或程式運行到現在所經過的時間
- 進程時間:一個進程所使用的CPU時間總量,適用於對程式、演算法效能的檢查或最佳化
本文只關注真即時間的處理與轉換
一、Epoch無論地理位置如何,Linux系統內部對時間的表示方式均是以自Epoch以來的秒數來度量的,Epoch即通用協調時間(UTC,以前也稱為格林威治標準時間,或GMT)的1970年1月1日零點零分零秒。這大致也是UNIX系統問世的時間。該時間可儲存於time_t類型的變數中。
在32位Linux系統中,time_t是一個有符號整數,可以表示的日期範圍從1901年12月13日20時45分52秒至2038年1月19日03:14:07。因此,32位系統的機器都將面臨2038年的問題,當然這我們不必擔心,到了2038年,我相信這些系統早已升級為64位或者更多位的系統了。不過有些壽命更長的嵌入式裝置,可能還會受到此問題的影響。
二、時間轉換函式如所示,儲存於time_t變數的數值可以和其他時間格式相互轉換,其中還包含列印輸出。這些函數屏蔽了因時區、夏令時(DST)制和本地化等問題給轉換所帶來的種種複雜
三、函數詳解 1. 系統調用gettimeofday(),用於tv指向的緩衝中返回日曆時間,其聲明如下
#include <sys/time.h>
// Return 0 on success, or -1 on errorint gettimeofday (struct timeval* tv, struct timezone* tz);
tv所指向的結構體如下struct timeval { time_t tv_sec; /* Seconds since 00:00:00, 1 Jan 1970 UTC*/ susecond_t tv_usec; /* Additional microseconds (long int)*/};gettimeofday()函數提供到微秒級的精度並儲存於tv_usec中,這樣的精度對大多數程式來說已經能夠滿足。在x86-64平台上,gettimeofday()不是系統調用,而是在使用者態實現的,沒有環境切換和陷入核心的開銷,因此其速度是比較快的[1]。參數tz是一個曆史產物,在早期的UNIX系統實現中使用其來擷取系統的時區資訊,目前已被廢棄,因此在調用時始終置為NULL。
2. 系統調用time(),其返回自Epoch以來的秒數,聲明如下
#include <time.h>
// Returns number of seconds since the Epoch, or (time_t)-1 on errortime_t time (time_t* timep);
如果timep參數不為NULL,那麼還會將自Epoch以來的的秒數置於timep所指向的位置中。但是在日常使用中我們都採用簡單的調用方式t = time (NULL)。從函數說明上可以得知gettimeofday()的精確度要比time()要高。
3. time_t可用於儲存Epoch到現在的秒數,這個數值非常不便於我們直接解釋,因為我們更容易接受的是MM-DD-YY HH:MIN:SS的形式。Linux中提供了簡單的ctime()函數來處理time_t的轉換
#include <time.h>
// Returns pointer to statically allocated string terminated by newline and \0 on success, or NULL on errorchar* ctime (const time_t* timep);
把一個指向time_t的指標作為timep參數傳入函數,將返回一個長達26個位元組的字串,內含標準格式的日期和時間,如下: Wed May 14 15:22:34 2014該字串包含分行符號合終止空位元組各一。ctime()函數在進行行轉換時會自動對本地時區和DST設定加以考慮。返回的字串經由靜態分配的,因此無需調用free函數,正因如此,該函數是不可重新進入的(非安全執行緒的),安全執行緒的版為ctime_r()。
4. time_t與struct tm之間的轉換
struct tm { int tm_sec; /*Seconds (0-60)*/ int tm_min; /*Minites (0-59)*/ int tm_hour; /*Hours (0-23)*/ int tm_mday; /*Day of the month (1-31)*/ int tm_mon; /*Month (1-12)*/ int tm_year; /*Year since 1900*/ int tm_wday; /*Day of the week (Sunday = 0)*/ int tm_yday; /*Day in the year (0-365; 1 Jan = 0)*/ int tm_isdst; /*Daylight saving time flag > 0: DST is in effect; = 0: DST is not effect; < 0: DST information not available*/
};結構體tm將日期和時間分解成多個獨立的欄位,這樣能方便程式擷取不同的欄位值來處理。欄位tm_sec的上限為60而不是59,這樣的設計主要是考慮閏秒,偶爾用其將人類日曆調整至精確的天文年(所謂的迴歸年)。如果程式中定義了_BSD_SOURCE測試宏,那麼有glibc定義的tm結構還會包括兩個欄位,一個為long int tm_gmtoff,用於表示時間超出UTC以東的秒數,一個為const char* tm_zone,用於表示時區的縮寫(例如:CEST為歐洲中部夏令時間)。
gmtime()和localtime()兩個函數可將time_t轉換成struct tm。gmtime()直接將time_t分解成UTC時間的tm,localtime()需要考慮時區和夏令時的設定,具體聲明如下:#include <time.h>
// Both return a pointer to a statically allocated broker-down time structure on success, or NULL on errorstruct tm* gmtime (const time_t *timep);struct tm* localtime (const time_t *timep);以上兩個函數都是非安全執行緒的,安全執行緒版本為gmtime_r()和localtime_r()
mktime()函數可以將struct tm轉換成time_t,其聲明如下:#include <time.h>
// Returns seconds since the Epoch corresponding to timeptr on success, or (time_t)-1 on errortime_t mktime (struct tm *timeptr);該函數可能會修改timeptr對應的值,至少會確保對tm_wday和tm_yday欄位的設定,確保這些欄位與其他欄位能夠相互對應起來。同時,mktime()在進行轉換時會對時區進行設定。此外,DST設定的使用與否取決於輸入欄位tm_isdst的值。
- 若tm_isdst為0,則將這一時間視為標準時間(即,忽略夏令時)
- 若tm_isdst大於0,則將這一時間視為夏令時
- 若tm_isdst小於0,則試圖判定DST在每年的這一時間是否生效。這往往是眾望所歸的設定
5. 將struct tm轉化易於理解的字串
#include <time.h>
// Returns pointer to statically allocated string terminated by newline and \0 on success, or NULL on errorchar* asctime (const struct tm *timeptr);
與ctime()相比,本地時區的設定對asctime()沒有影響。其返回的指標所指向的是由靜態分配的字串,因此其不是安全執行緒的,安全執行緒的版本為asctime_r(),輸出結果大概如下: Wed May 14 16:43:21 CET 2014
asctime()函數返回的是一個固定形式的字串,有時為了更易於理解,程式不想僅局限於這樣的字串,於是Linux中提供了strftime()函數,聲明如下:#include <time.h>
// Returns number of bytes placed in outstr (excluding terminating null bytes) on success, or 0 on errorsize_t strftime (char *outstr, size_t maxsize, const char *format, const struct tm *timeptr);
outstr中返回的字串按照format參數定義的格式做了格式化,maxsize則是ourstr的最大長度,如果成功則將格式化後的內容寫入outstr所指向的緩衝區中,然後返回字串的真實長度,含終止空位元組,如果真實長度超過了maxsize參數的大小,那麼返回0以表示出錯,且無法確定outstr的內容。參數format是一個字串,類似於printf()參數。具體值在此不做詳解,可以google。
strptime()函數與strftime()函數正好相反,其可以將包含日期和時間的字串轉換成struct tm,聲明如下:#define _XOPEN_SOURCE#include <time.h>
// Returns pointer to next unprocessed character in str on success, or NULL on errorchar* strptime (const char *str, const char *format, struct tm *timeptr);
函數strptime()按照format參數內容對由日期和時間組成的字串str加以解析,並將轉換後的數值儲存於timeptr所指的緩衝中,如果成功,返回指向str中下一個還未解析過的字元,如果所給的str無法和format相對應,則會解析失敗,返回NULL,以示錯誤。最後還需注意,strptime()函數不會設定tm中的tm_isdst欄位。
四、時區不同的國家使用不同的時區和夏時制,對於要輸入輸出時間的程式來說,必須對時區進行考慮,尤其是全球的分布式系統。出於時區資訊太多,Linux系統沒有直接將其編碼於程式或函數庫中,而是以標準格式儲存於檔案。這些檔案位於/usr/share/zoneinfo目錄裡。系統的本地時區則由檔案/etc/localtime定義,其通常是一個連結到/usr/share/zoneinfo下的檔案。
時區檔案格式記敘於tzfile(5)手冊頁,其建立可通過zic(8)(時區資訊編譯器,zone information compiler)工具來完成。zdump(8)命令可根據指定時區檔案中的時區來顯示目前時間
為啟動並執行程式指定一個時區,需要將TZ環境變數設定為一個冒號(:)和時區名稱組成的字串,其中時區名稱定義於/usr/share/zoneinfo中。時區的設定會影響到ctime()、localtime()、mktime()、strftime()等函數,為了擷取時區設定,這些函數都會調用tzset(3)對如下全域變數進行設定:
char *tzname[2]; /*Name of timezone and alternate (DST) timezone*/int daylight; /*Nonzero if there is an alternate (DST) timezone*/long timezone; /*Seconds difference between UTC and local standard time*/
五、總結多種系統調用允許我們擷取和設定系統的時間,以及一系列的庫函數能夠使我們完成各種時間標記法之間的轉換。Linux是非即時的多任務作業系統,如果我們在程式中想完全精確的計時和定時時無法做到的,因為當前任務可能會被CPU隨時切換出去,但是在日常的時間處理中,以上這些函數已經足夠滿足需求了。本文沒有涉及到所講述的函數用例,如有在程式中使用到這些函數,可以百度或者google,再者你可以查看https://github.com/ApusApp/Swift/tree/master/swift/base中關於時間處理的實現。
參考[1] http://lwn.net/Articles/446528