Wrong %z in strftime() on Visual C++?
(這是一篇積存的舊文,原始發想時間大約在 2007 年,但精確的日子已不可考。)
這是怎樣,平均每個月可以遇到一個 VC6 的問題,真苦。XD
這次的問題出在 strftime() 這個標準 C 的函式。依據 C99,若傳進 strftime() 的 format specifier 有 %Z 或 %z 的話,分別應該被代換為:
%Zis replaced by the locale’s time zone name or abbreviation, or by no characters if no time zone is determinable. [tm_isdst]
%zis replaced by the offset from UTC in the ISO8601 format ‘‘-0430’’ (meaning 4 hours 30 minutes behind UTC, west of Greenwich), or by no characters if no time zone is determinable. [tm_isdst]
也就是說,%Z 會被代換成,目前 locale 所規定的,各個 timezone 的名稱或縮寫,而 %z 則會被代換成,ISO8601 裡規定的格式:±NNNN,如果無法確定目前的 timezone,%Z 與 %z 兩者皆會被代換成空字串。
可是,MSDN 裡卻這麼說 (VS.60/VS.71/VS.80,斜體標示的是 VS.60 沒有的部份):
%z,%ZEither the time-zone name or time zone abbreviation, depending on registry settings; no characters if time zone is unknown
我手邊找不到 C89 Standard 全文,所以不確定 C89 的規定如何。但 K&R 的《C Programming Language 2nd Ed.》這本聖經裡只有提到(pp.234):
%Ztime zone name, if any.
並沒有小寫的 %z 這種 format specifier。
瞭解了各方說法之後,我寫了以下的測試程式,分別用 VC6 與 VC8[1] 來測試:
#include <stdio.h>
#include <time.h>
int main()
{
time_t now;
char buf[128];
struct tm* ptm = NULL;
time(&now);
ptm = localtime(&now);
strftime(buf, sizeof(buf), "%Z", ptm);
printf("%%Z: %s\n", buf);
strftime(buf, sizeof(buf), "%z", ptm);
printf("%%z: %s\n", buf);
return 0;
}
VC6 和 VC8 的執行結果都是:
%Z: 台北標準時間 %z: 台北標準時間
因為 C89[2] 只有規定大寫 %Z 要代換成 timezone name,也沒有規定 timezone name 的格式,「台北標準時間」可以是一個 timezone name,故 1998 年出的 VC6,符合標準。然而,在 C99 裡已經明確規定了,大寫 %Z 代換成 timezone name,但小寫 %z 要代換成 ISO8601 的 ±NNNN 格式,因此,在 2005 年出的 VC8,其小寫 %z 的處理方式,是不符合標準的。
所以,結論是,如果我要做出符合 ISO8601 格式的 time-stamp,不能光用 strftime(),還必須自己從 struct tm.tm_gmtoff 轉出來。
2008-10-08 補充:如何模擬 %z 的 ±NNNN?
要模擬出 ±NNNN,必須取得 local time 對 gmt time 的 offset 來。在 Visual C++ 下可以用 _timezone 這個全域變數,或是 _get_timezone() 這個函式,後者在新一點的 Visual C++ 版本,才有提供。如下:
std::string get_timestamp()
{
// Get local time
time_t now;
time(&now);
struct tm* ptm = localtime(&now);
// Get offset from local time to GMT time
long gmtoff = 0;
#if defined(_MSC_VER)
# if (_MSC_VER > 1200) // VC6 above, not including VC6
_get_timezone(&gmtoff);
# else
gmtoff = _timezone;
# endif
#elif defined(__GNUC__)
gmtoff = ptm->tm_gmtoff;
#endif
// Compose the format string, we simulate %z here.
char fmt[20]; // "%Y%m%dT%H%M%S+NNNN" (18 chars)
memset(fmt, 0, sizeof(fmt));
snprintf(
fmt, (sizeof(fmt) - sizeof(fmt[0])),
"%%Y%%m%%dT%%H%%M%%S%+03d%02d"
// ^^^^^^^^^ = %z
, -(gmtoff / (60 * 60))
, (gmtoff % (60 * 60))
);
// Format the string
char str[21]; // YYYYMMDDTHHMMSS+NNNN (20 chars)
strftime(str, sizeof(str), fmt, ptm);
return string(str);
}
關鍵就在註解裡 =%z 的那邊,要注意的是,必須要用 %+03d 而不是 %+02d,因為正、負號與兩位數字,加起來寬度應該是 3。
相關資料:
- MSDN > Visual Studio 6.0 > C Language Reference > Implementation-Defined Behaviro > Library Functions > The Time Zone - "The local time zone is Pacific Standard Time. Microsoft C supports Daylight Saving Time."
- 只寫這一行到底是什麼意思?
- MSDN > [kb826556] BUG: Date formats that contain 'gg' for Emperor Era year are not formatted correctly with the COleDateTime class when you set the locale to Japanese on a computer that is running Windows 2000
- MSDN > [kb193509] FIX: Exception When Calling strftime() Function
- MSDN > [kb320742] FIX: STRFTIME Returns the Wrong Strings
- Time Zone Disaster--All Outlook Appointments Bite the Dust



Post a Comment