C/C++ 時間處理經歷了從 C 風格 time.h 到 C++11 <chrono> 的演進。本文系統梳理核心概念、資料結構、標準庫函式與格式化輸出,並對比新舊 API 優劣。
核心概念釐清
| 術語 | 定義 | 關鍵特性 |
| UTC (Collaborated Universal Time) | 協調世界時 / 格林威治標準時間 | 無時區偏移基準,中國為 UTC+8 |
| Calendar Time (日曆時間) | 從 Epoch 起經過的秒數 | 型別 time_t,全球同一時刻數值相同 |
| Epoch (時間基點) | 標準庫定義的起始時間點 | 多為 1970-01-01 00:00:00 UTC (Unix 時間) |
| Clock Tick (時鐘計時單元) | CPU 計時最小單位 | CLOCKS_PER_SEC 定義每秒計數,通常 1ms 或 1μs |
| Broken-down Time (分解時間) | 年月日時分秒分離的結構 | 結構體 tm,便於顯示與運算 |
C 風格 API (time.h/ctime) 完整速查
資料結構
struct tm {
int tm_sec; // 0-59 (允許 60 閏秒)
int tm_min; // 0-59
int tm_hour; // 0-23
int tm_mday; // 1-31
int tm_mon; // 0-11 (0=一月)
int tm_year; // 年份-1900
int tm_wday; // 0-6 (0=星期日)
int tm_yday; // 0-365 (0=1月1日)
int tm_isdst; // 夏令時標誌: >0 生效, 0 不生效, <0 未知
};
typedef long time_t; // 32 位系統 2038 年溢位,64 位已解決
核心函式分類
| 分類 | 函式 | 用途 | 備註 |
| 取得時間 | time(time_t*) | 取得當前日曆時間 | 回傳值與參數同值 |
clock() | 取得程序 CPU 時間 | 精度 CLOCKS_PER_SEC,非牆鐘時間 |
_time64() | 64 位版 time (MSVC) | 支援至 3001 年 |
| 轉換 | localtime(time_t*) | 日曆時間 → 本地分解時間 | 回傳靜態緩衝區,執行緒不安全 |
gmtime(time_t*) | 日曆時間 → UTC 分解時間 | 同上,需自行複製 |
mktime(tm*) | 分解時間 → 日曆時間 | 自動修正欄位溢位 (如月份 13→翌年1月) |
| 格式化 | asctime(tm*) | 分解時間 → 固定格式字串 | "Wed Jan 02 02:03:55 1980\n\0" |
ctime(time_t*) | 日曆時間 → 固定格式字串 | 等同 asctime(localtime(t)) |
strftime(char*,size_t,const char*,tm*) | 自訂格式輸出 | 支援 %Y-%m-%d %H:%M:%S 等豐富格式 |
wcsftime | 寬字元版 strftime | Unicode 環境適用 |
| 計算差值 | difftime(time_t,time_t) | 兩時間點秒數差 (double) | 僅秒級精度 |
strftime 格式化符號精選
%Y 四位數年份 (2024) %y 兩位數年份 (24)
%m 月份 01-12 %b 月份縮寫 (Jan) %B 月份全名 (January)
%d 日期 01-31 %e 日期 1-31 (空白填充)
%H 24制時 00-23 %I 12制時 01-12 %p AM/PM
%M 分鐘 00-59 %S 秒數 00-59
%F 等同 %Y-%m-%d %T 等同 %H:%M:%S
%z 時區偏移 (+0800) %Z 時區名稱 (CST)
%c 標準日期時間 %x 標準日期 %X 標準時間
%% 字面 % 字元
實戰範例:多種時間操作
#include <ctime>
#include <iostream>
#include <iomanip>
void demo() {
// 1. 當前時間
time_t now = time(nullptr);
std::cout << "Calendar time: " << now << '\n';
// 2. 本地時間分解
tm local = *localtime(&now); // 複製避免靜態緩衝區覆蓋
std::cout << "Local: " << std::put_time(&local, "%F %T") << '\n';
// 3. UTC 時間
tm utc = *gmtime(&now);
std::cout << "UTC: " << std::put_time(&utc, "%F %T") << '\n';
// 4. 自訂格式
char buf[64];
strftime(buf, sizeof(buf), "Today is %A, %d %B %Y", &local);
std::cout << buf << '\n';
// 5. 指定時間點構造 (1997-07-01)
tm target = {}; target.tm_year = 97; target.tm_mon = 6; target.tm_mday = 1;
time_t t = mktime(&target);
std::cout << "1997-07-01 是 " << std::put_time(localtime(&t), "%A") << '\n';
// 6. 計算程式碼執行時間 (clock)
clock_t start = clock();
// ... 執行待測程式碼 ...
clock_t end = clock();
std::cout << "Elapsed: " << double(end-start)/CLOCKS_PER_SEC << "s\n";
}
C++11 <chrono> 現代時間庫
解決 C API 的執行緒不安全、精度限制、型別不安全、時區處理缺失等痛點。
核心型別體系
// 時間長度
using namespace std::chrono;
nanoseconds ns(100);
milliseconds ms = duration_cast<milliseconds>(ns); // 0ms (截斷)
microseconds us = ms; // 隱含轉換 (無精度損失)
// 時間點
system_clock::time_point now = system_clock::now();
time_t tt = system_clock::to_time_t(now); // 互轉 C API
steady_clock::time_point start = steady_clock::now();
// ... 程式碼 ...
auto elapsed = steady_clock::now() - start; // 高精度持續時間
std::cout << duration_cast<milliseconds>(elapsed).count() << "ms\n";
// 字面值 (C++14)
using namespace std::chrono_literals;
auto timeout = 30s + 500ms; // 30.5 秒
格式化輸出 (C++20 <format> / std::chrono::format)
// C++20
#include <format>
#include <chrono>
auto now = std::chrono::system_clock::now();
std::cout << std::format("{:%Y-%m-%d %H:%M:%S}", now) << '\n';
// 輸出: 2024-01-15 14:30:45
// 時區支援 (C++20 tzdb)
auto zt = std::chrono::zoned_time{"Asia/Taipei", now};
std::cout << std::format("{:%Y-%m-%d %H:%M:%S %Z}", zt) << '\n';
新舊 API 選擇建議
| 場景 | 推薦 API | 理由 |
| 簡單顯示當前時間 | time + strftime | 相容性佳,無依賴 |
| 高精度效能計時 | steady_clock | 單調、納秒級、不受系統時間調整影響 |
| 跨平台時區處理 | C++20 zoned_time / Howard Hinnant's date.h | C API 無時區資料庫 |
| 時間運算 (加減天/月) | C++20 year_month_day / date.h | mktime 僅支援秒級運算 |
| 舊代碼維護 | time.h + localtime_r/gmtime_r | 執行緒安全版本 (POSIX) |
2038 年問題與 64 位遷移
- 32 位
time_t 最大值 0x7FFFFFFF 對應 2038-01-19 03:14:07 UTC。
- 現代 64 位編譯器預設
time_t 為 64 位 (MSVC 需定義 _USE_32BIT_TIME_T=0)。
- 跨平台專案建議統一使用
int64_t 或 std::chrono::system_clock::time_point 存儲時間點。