当前位置:首页 > 娱乐

struct tm

写C代码处理时间,绕不开struct tm。这东西在<time.h>里躺着,看起来人畜无害,九个int字段排排坐,真用起来坑不少。

先看它的样子:

struct tm {  int tm_sec; // 秒,0-60(60用于闰秒) int tm_min; // 分,0-59 int tm_hour; // 时,0-23 int tm_mday; // 日,1-31 int tm_mon; // 月,0-11 int tm_year; // 年,从1900起算 int tm_wday; // 星期,0-6,0是周日 int tm_yday; // 一年中的第几天,0-365 int tm_isdst; // 夏令时标志 };

第一次见tm_year的人多半会愣一下。想表示2024年,得填124。这个设计来自早期Unix,那会儿没想过代码能活五十年。更别扭的是tm_mon,一月是0,十二月是11。写月份循环的时候,for (int m = 0; m < 12; m++)倒是挺顺,可一旦要跟人类可读的月份对上,脑子里就得做一次加减法。

我见过有人写printf("%d年%d月\n", t.tm_year, t.tm_mon),输出“124年0月”,然后对着屏幕发呆。这种错误不致命,但足够让人抓狂,尤其是调试到凌晨两点的时候。

把时间戳转成struct tm,用localtime()或gmtime()。前者给本地时区,后者给UTC。两个函数都返回指向静态内存的指针——这意味着下一次调用会覆盖上一次的结果。多线程环境下这就是个雷。POSIX提供了localtime_r()和gmtime_r(),让你自己传缓冲区进去,但Windows上叫localtime_s(),参数顺序还不一样。跨平台代码写到这儿,基本得靠宏来抹平差异。

反向操作是mktime(),把struct tm转回time_t。这个函数有个隐藏技能:它会自动规范化字段。比如你把tm_mday设成35,mktime()不会报错,而是帮你算到下个月的某一天。利用这个特性做日期加减挺方便——加30天?直接tm_mday += 30,扔给mktime()就行。但前提是tm_isdst得设对,否则夏令时切换那几天可能差一个小时。我因为这事在项目里排查过整整一个下午,最后发现是tm_isdst没初始化。

格式化输出用strftime(),比sprintf手动拼字段靠谱得多。%Y给四位年份,%m给两位月份,%d给两位日期。注意%Y已经帮你处理了1900偏移,直接输出2024,不用自己加。但输入解析就没这么幸运了——strptime()不是标准C,Linux有,Windows没有。想在Windows上解析日期字符串,要么用sscanf自己拆,要么上第三方库。

还有一个容易忽略的点:struct tm里的tm_wday和tm_yday是“派生字段”。localtime()会帮你填好,但你自己构造struct tm传给mktime()时,这两个字段会被忽略并重新计算。所以别费劲去设它们,没用。

夏令时字段tm_isdst有三种取值:正数表示夏令时生效,0表示不生效,负数表示让系统自己判断。最后这个“让系统判断”听着省心,实际上在时区边界附近经常出幺蛾子。我的习惯是明确设成0或1,除非确实需要系统自动处理。

说到底,struct tm是个历史包袱和实用工具的结合体。它的字段命名和取值约定带着七十年代的烙印,但至今仍是C标准库中唯一能让你拆解日历时间的结构。用的时候多留个心眼:月份从0开始,年份减1900,localtime()不线程安全,mktime()会改你的结构体。记住这几条,能省下不少调试时间。

分享到: