在 C 語言編程中,獲取、轉換、格式化時間是基礎且高頻的需求。本文系統梳理 time()、ctime()、localtime()/gmtime()、strftime() 的用法差異、線程安全陷阱、高精度替代方案(clock_gettime),並提供可直接落地的代碼模板。
基礎時間類型
| 類型 | 定義 | 含義 | 範圍/精度 |
time_t | 算術類型 (通常 long/int64_t) | **日曆時間**:自 Unix 紀元 (1970-01-01 00:00:00 UTC) 經過的**秒數** | 秒級,32 位 2038 年溢出,64 位安全 |
struct tm | 分解時間結構體 | 年、月、日、時、分、秒、星期、儒略日、夏令時標誌 | 組件化,便於格式化/運算 |
struct timespec | time_t tv_sec; long tv_nsec; | **高精度時間**:秒 + 納秒 | 納秒級,clock_gettime 返回 |
struct timeval | time_t tv_sec; suseconds_t tv_usec; | 微秒級時間 (POSIX.1-2001 標記過時) | 微秒級,gettimeofday 返回 |
核心函數族
1. time(time_t *tloc) — 獲取日曆時間
time_t now = time(NULL); // 推薦用法,忽略參數
// 或
time_t now;
time(&now);
- 返回:當前 UTC 秒數 (
time_t),出錯返回 (time_t)-1。
- 精度:秒級。
2. ctime(const time_t *timer) / ctime_r — 轉為固定格式字符串
char *ctime(const time_t *timer); // 非線程安全,返回靜態緩衝區
char *ctime_r(const time_t *timer, char *buf); // 線程安全,需提供 26+ 字節緩衝區
- 輸出格式固定:
"Wed Jun 30 21:49:08 1993\n\0" (26 字符)。
- 不可自定義格式,僅用於快速調試日志。
3. localtime / gmtime / localtime_r / gmtime_r — 分解時間
struct tm *localtime(const time_t *timer); // 非線程安全,返回靜態 struct tm
struct tm *localtime_r(const time_t *timer, struct tm *result); // 線程安全
struct tm *gmtime(const time_t *timer); // UTC 時間,非線程安全
struct tm *gmtime_r(const time_t *timer, struct tm *result); // 線程安全
localtime*:按**本地時區** (環境變量 TZ、/etc/localtime) 轉換。
gmtime*:按 **UTC** 轉換。
- 多線程程序必須使用
_r 後綴版本,避免靜態緩衝區競爭導致數據錯亂。
4. strftime — 靈活格式化 (核心推薦)
size_t strftime(char *buf, size_t maxsize, const char *format, const struct tm *tm);
- 線程安全:僅操作用戶提供的緩衝區。
- 格式化指令豐富 (常用):
| 指令 | 含義 | 示例 |
%Y | 四位數年份 | 2024 |
%m | 兩位數月份 (01-12) | 06 |
%d | 兩位數日期 (01-31) | 30 |
%H | 24制小時 (00-23) | 21 |
%M | 分鐘 (00-59) | 49 |
%S | 秒 (00-60, 含閏秒) | 08 |
%F | 等價 %Y-%m-%d | 2024-06-30 |
%T | 等價 %H:%M:%S | 21:49:08 |
%z | 時區偏移 (+HHMM/-HHMM) | +0800 |
%Z | 時區名稱 | CST |
%s | Unix 時間戳 (秒) (GNU 擴展) | 1719755348 |
- 返回:寫入字符數 (不含結束符),0 表示緩衝區不足。
5. 高精度:clock_gettime (推薦替代 gettimeofday)
#include <time.h>
int clock_gettime(clockid_t clk_id, struct timespec *tp);
| 時鐘類型 | 含義 | 精度/用途 |
CLOCK_REALTIME | 系統實時時間 (可被 NTP/用戶調整) | 納秒級,壁鐘時間,受系統時間調整影響 |
CLOCK_MONOTONIC | 單調時間 (自啟動以來,不可調整) | **測量間隔、超時、性能分析首選**,不受系統時間回撥影響 |
CLOCK_MONOTONIC_RAW | 原始單調時間 (無 NTP 頻率校準) | 硬件時鐘原始計數,最高精度 |
CLOCK_PROCESS_CPUTIME_ID | 進程 CPU 時間 | 性能分析 |
CLOCK_THREAD_CPUTIME_ID | 線程 CPU 時間 | 性能分析 |
實戰代碼模板
模板 1:線程安全獲取本地時間字符串 (strftime)
#include <time.h>
#include <stdio.h>
void get_local_time_str(char *buf, size_t len, const char *fmt) {
time_t now = time(NULL);
struct tm tm;
localtime_r(&now, &tm); // 線程安全
strftime(buf, len, fmt, &tm);
}
int main() {
char buf[64];
get_local_time_str(buf, sizeof(buf), "%F %T %z"); // 2024-06-30 21:49:08 +0800
puts(buf);
return 0;
}
模板 2:解析時間組件 (年月日時分秒)
time_t now = time(NULL);
struct tm tm;
localtime_r(&now, &tm);
int year = tm.tm_year + 1900; // tm_year: years since 1900
int month = tm.tm_mon + 1; // tm_mon: 0-11
int day = tm.tm_mday; // 1-31
int hour = tm.tm_hour; // 0-23
int min = tm.tm_min; // 0-59
int sec = tm.tm_sec; // 0-60 (含閏秒)
int wday = tm.tm_wday; // 0=Sun, 6=Sat
int yday = tm.tm_yday; // 0-365
int isdst = tm.tm_isdst; // >0 夏令時, 0 非, <0 未知
模板 3:高精度測量代碼執行耗時
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
// ... 待測代碼 ...
clock_gettime(CLOCK_MONOTONIC, &end);
long long elapsed_ns = (end.tv_sec - start.tv_sec) * 1000000000LL + (end.tv_nsec - start.tv_nsec);
printf("Elapsed: %.3f ms\n", elapsed_ns / 1e6);
模板 4:解析字符串時間 → time_t (strptime, GNU 擴展)
#define _GNU_SOURCE
#include <time.h>
struct tm tm = {0};
char *res = strptime("2024-06-30 21:49:08", "%F %T", &tm);
if (res) {
time_t t = mktime(&tm); // 本地時區解析
// time_t t = timegm(&tm); // UTC 解析 (BSD/GNU 擴展)
}
常見陷阱與避坑
- 多線程調用非
_r 函數:localtime/gmtime/ctime/asctime 返回靜態緩衝區,**極易導致數據錯亂**。**全項目強制使用 _r 版本**。
tm_year 為距 1900 年數,**必須 +1900**;tm_mon 為 0-11,**必須 +1**。
mktime() 會修正 struct tm 越界字段(如月份 13 → 翌年 1 月),並按**本地時區**轉換;timegm() 按 UTC 轉換(非標準,但廣泛可用)。
time_t 在 32 位系統 2038 年溢出 (Y2038 問題)。**新代碼務必編譯為 64 位**,或使用 int64_t 顯式存儲時間戳。
strftime 緩衝區不足返回 0,**務必檢查返回值**,避免截斷。預留 64-128 字節通常足夠。
- 測量耗時**嚴禁用
CLOCK_REALTIME**,NTP 時間同步導致時間回撥會產生負耗時。**必用 CLOCK_MONOTONIC**。
總結選型表
| 需求 | 推薦方案 | 關鍵點 |
| 獲取當前秒級時間戳 | time(NULL) | 簡單、可移植 |
| 格式化本地時間字符串 | time() + localtime_r() + strftime() | 線程安全、格式靈活 |
| 格式化 UTC 時間字符串 | time() + gmtime_r() + strftime() | 日誌系統、分布式追蹤統一用 UTC |
| 解析時間組件運算 | localtime_r() 取 struct tm | 注意 tm_year/tm_mon 偏移 |
| 高精度間隔測量/基準測試 | clock_gettime(CLOCK_MONOTONIC, ...) | 納秒級、單調不回撥 |
| 解析字符串時間 | strptime() + mktime()/timegm() | GNU 擴展,注意時區 |
掌握這套時間處理工具鏈,配合線程安全習慣與單調時鐘原則,能讓你在日誌系統、定時任務、協議時間戳、性能剖析等場景中編寫出精準、健壯、可移植的 C 代碼。