当前位置: 首页 > news >正文

【C语言】localtime和localtime_r;strftime和strftime_l

文章目录

  • `localtime` 和 `localtime_r`
      • 一、函数原型与核心差异
      • 二、关键区别详解
        • 1. 存储方式:全局缓冲区 vs 用户缓冲区
        • 2. 线程安全性:非线程安全 vs 线程安全
        • 3. 可移植性
      • 三、struct tm 结构体说明
      • 四、使用场景与最佳实践
        • 1. 何时用 `localtime`?
        • 2. 何时用 `localtime_r`?
        • 3. Windows 平台替代方案
      • 五、常见错误提醒
      • 总结
  • `strftime` 和 `strftime_l`
      • 一、函数原型与核心差异
        • 关键参数说明:
      • 二、核心区别详解
        • 1. 区域(locale)依赖:全局 vs 自定义
        • 2. 线程安全性:非线程安全 vs 线程安全
        • 3. 可移植性
      • 三、格式化字符串(format)常用说明符
      • 四、返回值说明(两者完全一致)
      • 五、使用场景与最佳实践
        • 1. 何时用 `strftime`?
        • 2. 何时用 `strftime_l`?
        • 3. Windows 平台本地化替代方案
      • 六、常见错误提醒
      • 总结

localtimelocaltime_r

localtimelocaltime_r 都是 C 标准库中用于将时间戳(time_t 类型,从 1970-01-01 00:00:00 UTC 起的秒数) 转换为本地时间(struct tm 结构体) 的函数,但核心区别在于 线程安全性返回值存储方式,这也是实际开发中选择的关键依据。

一、函数原型与核心差异

先明确两个函数的标准原型(头文件:<time.h>):

函数原型核心特点
localtimestruct tm *localtime(const time_t *timer);非线程安全,返回全局静态缓冲区的指针
localtime_rstruct tm *localtime_r(const time_t *timer, struct tm *result);线程安全(_r 代表 reentrant 可重入),结果存储在用户提供的缓冲区

二、关键区别详解

1. 存储方式:全局缓冲区 vs 用户缓冲区
  • localtime
    函数内部维护一个 全局静态的 struct tm 缓冲区,每次调用都会覆盖该缓冲区的数据,然后返回缓冲区的指针。
    示例:

    #include <time.h>
    #include <stdio.h>int main() {time_t now = time(NULL);// 第一次调用:返回全局缓冲区指针,存储当前时间struct tm *t1 = localtime(&now);printf("t1: %02d:%02d\n", t1->tm_hour, t1->tm_min);// 模拟延迟(或另一个线程调用)sleep(2);time_t later = time(NULL);// 第二次调用:覆盖全局缓冲区,t1 指向的数据也会被修改!struct tm *t2 = localtime(&later);printf("t2: %02d:%02d\n", t2->tm_hour, t2->tm_min);printf("t1(被覆盖后): %02d:%02d\n", t1->tm_hour, t1->tm_min); // 与 t2 相同return 0;
    }
    

    输出会发现 t1 的值被第二次调用覆盖——因为 t1t2 指向同一个全局缓冲区。

  • localtime_r
    不使用全局缓冲区,而是要求用户提前分配 struct tm 变量(作为第二个参数 result),函数将转换结果写入该变量,并返回 result 的指针(方便链式调用)。
    示例:

    #include <time.h>
    #include <stdio.h>int main() {time_t now = time(NULL);struct tm t1; // 用户分配缓冲区localtime_r(&now, &t1); // 结果写入 t1printf("t1: %02d:%02d\n", t1.tm_hour, t1.tm_min);sleep(2);time_t later = time(NULL);struct tm t2; // 独立缓冲区localtime_r(&later, &t2); // 结果写入 t2,不影响 t1printf("t2: %02d:%02d\n", t2.tm_hour, t2.tm_min);printf("t1(未被覆盖): %02d:%02d\n", t1.tm_hour, t1.tm_min); // 保持原值return 0;
    }
    

    输出中 t1t2 各自保持独立——因为使用了不同的用户缓冲区。

2. 线程安全性:非线程安全 vs 线程安全
  • localtime 非线程安全
    全局缓冲区是所有线程共享的。如果多个线程同时调用 localtime,会导致缓冲区数据被交叉覆盖,最终返回错误的时间(竞态条件)。

  • localtime_r 线程安全
    每个线程可以传入自己的 struct tm 变量作为缓冲区,线程间的缓冲区相互独立,不会出现数据竞争,因此适合多线程环境(如服务器、多线程程序)。

3. 可移植性
  • localtime:是 C89 标准函数,所有符合 C 标准的编译器都支持(跨平台兼容性强)。
  • localtime_r:是 POSIX 标准(如 Linux、Unix、macOS)定义的可重入函数,Windows 平台不支持(Windows 对应的线程安全函数是 localtime_s,属于 C11 标准的边界检查函数,原型不同:errno_t localtime_s(struct tm *result, const time_t *timer);)。

三、struct tm 结构体说明

两个函数的转换结果都存储在 struct tm 中,字段含义如下(注意月份和星期的起始值):

struct tm {int tm_sec;   // 秒(0-60,允许闰秒)int tm_min;   // 分(0-59)int tm_hour;  // 时(0-23)int tm_mday;  // 日(1-31)int tm_mon;   // 月(0-11,0=1月,11=12月)int tm_year;  // 年(从 1900 开始,如 2024 对应 tm_year=124)int tm_wday;  // 星期(0-6,0=周日,6=周六)int tm_yday;  // 年内天数(0-365)int tm_isdst; // 夏令时标记(正数=启用,0=禁用,负数=未确定)
};

四、使用场景与最佳实践

1. 何时用 localtime
  • 单线程程序(无线程安全问题);
  • 对可移植性要求极高(需兼容非 POSIX 平台,且无需线程安全);
  • 简单工具类程序(无需长期保存 struct tm 数据,调用后立即使用)。
2. 何时用 localtime_r
  • 多线程程序(如 Linux 服务器、多线程服务);
  • 需要保存多次转换的结果(避免数据被覆盖);
  • POSIX 平台(Linux、Unix、macOS)开发。
3. Windows 平台替代方案

Windows 不支持 localtime_r,需使用 localtime_s(C11 标准,需开启 /std:c11 编译选项):

#include <time.h>
#include <stdio.h>int main() {time_t now = time(NULL);struct tm t;// localtime_s 返回错误码(0=成功),而非指针if (localtime_s(&t, &now) == 0) {printf("当前时间:%04d-%02d-%02d %02d:%02d:%02d\n",t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,t.tm_hour, t.tm_min, t.tm_sec);}return 0;
}

五、常见错误提醒

  1. 忘记 tm_montm_year 的偏移:
    月份需 +1(0=1月),年份需 +1900(tm_year=124 对应 2024),否则会输出错误日期(如 2024 显示为 124 年)。

  2. 复用 localtime 返回的指针:
    不要长期保存 localtime 的返回值,后续调用(或其他线程调用)会覆盖该指针指向的全局缓冲区。

  3. 多线程中误用 localtime
    多线程环境下必须使用线程安全版本(localtime_r/localtime_s),否则会出现随机的时间错误。

总结

特性localtimelocaltime_r
线程安全❌ 非线程安全(全局缓冲区)✅ 线程安全(用户缓冲区)
存储方式全局静态缓冲区用户提供的缓冲区
可移植性跨平台(C89 标准)仅 POSIX 平台(Linux/Unix/macOS)
适用场景单线程、简单程序多线程、POSIX 平台、需保存多份结果

最佳实践:在 Linux/Unix 多线程程序中优先使用 localtime_r;单线程或需兼容非 POSIX 平台时用 localtime;Windows 平台用 localtime_s

strftimestrftime_l

strftimestrftime_l 都是 C 标准库中用于将 struct tm 类型的时间数据 格式化为 字符串 的函数,核心区别在于 时区/区域(locale)的指定方式线程安全性,适用于不同的本地化与多线程场景。

一、函数原型与核心差异

先明确两个函数的标准原型(头文件:<time.h>),以及关键参数的含义:

函数原型核心特点
strftimesize_t strftime(char *restrict s, size_t maxsize, const char *restrict format, const struct tm *restrict tm);使用 当前进程的全局区域(locale),非线程安全(全局 locale 可能被其他线程修改)
strftime_lsize_t strftime_l(char *restrict s, size_t maxsize, const char *restrict format, const struct tm *restrict tm, locale_t loc);显式传入 自定义区域(locale_t 类型),线程安全(不依赖全局 locale)
关键参数说明:
  • s:存储格式化结果的字符串缓冲区(用户需提前分配内存);
  • maxsize:缓冲区的最大大小(包括字符串结束符 \0);
  • format:格式化字符串(与 printf 类似,支持时间相关的转换说明符,如 %Y 表示4位年、%m 表示2位月);
  • tm:待格式化的 struct tm 时间数据(可由 localtime_r/localtime 等函数生成);
  • loc(仅 strftime_l):显式指定的区域对象(locale_t 类型),用于控制日期、时间的本地化显示(如星期名称、月份名称的语言)。

二、核心区别详解

1. 区域(locale)依赖:全局 vs 自定义

区域(locale)决定了时间的本地化表现,例如:

  • 英文 locale(如 en_US.UTF-8):月份显示为 Jan/Feb,星期显示为 Mon/Tue

  • 中文 locale(如 zh_CN.UTF-8):月份显示为 一月/二月,星期显示为 星期一/星期二

  • strftime
    依赖进程的 全局 locale(通过 setlocale(LC_TIME, "...") 设置)。如果多个线程修改全局 locale,会导致 strftime 的格式化结果不可预期(非线程安全)。
    示例:

    #include <time.h>
    #include <stdio.h>int main() {// 设置全局 locale 为中文(Linux/macOS 示例,Windows 需用 "Chinese_China.936")setlocale(LC_TIME, "zh_CN.UTF-8");time_t now = time(NULL);struct tm t;localtime_r(&now, &t);char buf[64];// 使用全局 locale 格式化:结果为中文(如 "2024年10月01日 星期二")strftime(buf, sizeof(buf), "%Y年%m月%d日 %A", &t);printf("中文格式:%s\n", buf);// 切换全局 locale 为英文setlocale(LC_TIME, "en_US.UTF-8");// 再次格式化:结果为英文(如 "2024-10-01 Tuesday")strftime(buf, sizeof(buf), "%Y-%m-%d %A", &t);printf("英文格式:%s\n", buf);return 0;
    }
    
  • strftime_l
    不依赖全局 locale,而是通过第5个参数 loc 显式指定区域。即使其他线程修改全局 locale,也不会影响当前调用的结果,因此支持 多线程同时使用不同 locale 格式化时间(线程安全)。
    示例:

    #include <time.h>
    #include <stdio.h>
    #include <locale.h> // 需包含 locale.h 以使用 newlocale/duplocaleint main() {time_t now = time(NULL);struct tm t;localtime_r(&now, &t);char buf[64];// 1. 创建中文 locale 对象(POSIX 平台写法)locale_t zh_loc = newlocale(LC_TIME_MASK, "zh_CN.UTF-8", (locale_t)0);// 2. 用中文 locale 格式化strftime_l(buf, sizeof(buf), "%Y年%m月%d日 %A", &t, zh_loc);printf("中文格式:%s\n", buf);freelocale(zh_loc); // 释放 locale 对象(避免内存泄漏)// 3. 创建英文 locale 对象locale_t en_loc = newlocale(LC_TIME_MASK, "en_US.UTF-8", (locale_t)0);// 4. 用英文 locale 格式化strftime_l(buf, sizeof(buf), "%Y-%m-%d %A", &t, en_loc);printf("英文格式:%s\n", buf);freelocale(en_loc); // 释放 locale 对象return 0;
    }
    
2. 线程安全性:非线程安全 vs 线程安全
  • strftime 非线程安全
    全局 locale 是进程级共享资源。如果多个线程同时调用 strftime,且其中一个线程通过 setlocale 修改了全局 locale,会导致其他线程的格式化结果错乱(竞态条件)。

  • strftime_l 线程安全
    每个调用独立传入 locale_t 对象,线程间的 locale 互不干扰。只要每个线程使用自己的 locale_t(或确保 locale_t 不被并发修改),即可安全并发调用。

3. 可移植性
  • strftime:是 C90 标准函数,所有符合 C 标准的编译器都支持(跨平台兼容性强,Windows、Linux、macOS 均支持)。
  • strftime_l:是 POSIX 标准(如 Linux、Unix、macOS)定义的函数,Windows 平台不支持(Windows 无 locale_t 类型,需通过其他方式实现本地化,如 SetThreadLocale 结合 strftime)。

三、格式化字符串(format)常用说明符

strftimestrftime_lformat 参数格式完全一致,支持以下常用转换说明符(完整列表见 C 标准或 POSIX 标准):

说明符含义示例(中文 locale)示例(英文 locale)
%Y4位年份(完整年份)20242024
%y2位年份(00-99)2424
%m2位月份(01-12)1010
%b缩写月份名称(本地化)十月Oct
%B完整月份名称(本地化)十月October
%d2位日期(01-31)0101
%A完整星期名称(本地化)星期二Tuesday
%a缩写星期名称(本地化)周二Tue
%H24小时制(00-23)1414
%I12小时制(01-12)0202
%M2位分钟(00-59)3030
%S2位秒(00-60,支持闰秒)4545
%p上/下午标记(本地化)下午PM
%c完整日期时间(本地化默认格式)2024年10月01日 14:30:45Tue Oct 1 14:30:45 2024

示例:格式化完整的本地化时间字符串

// 中文 locale 下输出:2024年10月01日 星期二 14:30:45
strftime(buf, sizeof(buf), "%Y年%m月%d日 %A %H:%M:%S", &t);
// 英文 locale 下输出:Tuesday, October 01, 2024 14:30:45
strftime(buf, sizeof(buf), "%A, %B %d, %Y %H:%M:%S", &t);

四、返回值说明(两者完全一致)

两个函数的返回值规则相同,核心用于判断格式化是否成功:

  • 成功:返回写入缓冲区的字符数(不包括结束符 \0);
  • 失败:返回 0(常见原因:缓冲区大小 maxsize 不足,或 format 包含非法说明符)。

注意:返回 0 时,缓冲区可能会被写入部分数据,但未包含完整的格式化结果(且不一定以 \0 结尾),因此失败后不应使用缓冲区内容。

示例:判断格式化是否成功

char buf[16]; // 假设缓冲区过小
size_t ret = strftime(buf, sizeof(buf), "%Y年%m月%d日 %H:%M:%S", &t);
if (ret == 0) {printf("格式化失败:缓冲区不足!\n");
} else {printf("格式化结果:%s(长度:%zu)\n", buf, ret);
}

五、使用场景与最佳实践

1. 何时用 strftime
  • 单线程程序(无线程安全问题);
  • 不需要多语言/多区域切换(全局 locale 固定);
  • 跨平台兼容性要求高(需支持 Windows);
  • 简单场景(无需复杂本地化配置)。
2. 何时用 strftime_l
  • 多线程程序(需并发使用不同 locale 格式化时间);
  • 需动态切换区域(如同一进程同时输出中文、英文时间);
  • POSIX 平台(Linux、Unix、macOS)开发;
  • 对线程安全和本地化灵活性有要求(如多语言服务器)。
3. Windows 平台本地化替代方案

Windows 不支持 strftime_l,需通过 SetThreadLocale 设置线程级 locale,再调用 strftime(线程安全,因为 SetThreadLocale 是线程局部的):

#include <time.h>
#include <stdio.h>
#include <windows.h> // 需包含 Windows 头文件int main() {time_t now = time(NULL);struct tm t;localtime_s(&t, &now); // Windows 线程安全的 localtime 替代char buf[64];// 1. 设置线程 locale 为中文(Windows locale ID:0x0804 对应 zh-CN)SetThreadLocale(MAKELCID(MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED), SORT_DEFAULT));strftime(buf, sizeof(buf), "%Y年%m月%d日 %A", &t);printf("中文格式:%s\n", buf);// 2. 设置线程 locale 为英文(locale ID:0x0409 对应 en-US)SetThreadLocale(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT));strftime(buf, sizeof(buf), "%Y-%m-%d %A", &t);printf("英文格式:%s\n", buf);return 0;
}

六、常见错误提醒

  1. 缓冲区大小不足:
    格式化后的字符串长度(包括 \0)不能超过 maxsize,否则返回 0。建议根据格式化字符串预估长度(如包含完整日期时间需至少 32 字节)。

  2. 忽略 locale 初始化:
    使用 strftime_l 时,需通过 newlocale 创建 locale_t 对象(不能直接传 NULL),且使用后需调用 freelocale 释放(避免内存泄漏)。

  3. 混淆 %M%m
    %M 是分钟(Minute),%m 是月份(Month),误用会导致日期时间错误(如 %m 写成 %M 会显示分钟作为月份)。

  4. 多线程中误用 strftime
    多线程环境下,若使用 strftime 且修改全局 locale,会导致结果错乱,需改用 strftime_l(POSIX)或 SetThreadLocale + strftime(Windows)。

总结

特性strftimestrftime_l
区域依赖全局 locale显式传入的 locale_t 对象
线程安全❌ 非线程安全(全局 locale 共享)✅ 线程安全(独立 locale 对象)
可移植性跨平台(C90 标准)仅 POSIX 平台(Linux/Unix/macOS)
本地化灵活性低(仅支持全局 locale)高(支持动态切换多 locale)
返回值规则成功返回字符数,失败返回 0strftime 完全一致

最佳实践

  • 单线程、跨平台场景用 strftime
  • POSIX 多线程、多语言场景用 strftime_l(注意 locale_t 的创建与释放);
  • Windows 多语言场景用 SetThreadLocale + strftime
http://www.dtcms.com/a/574505.html

相关文章:

  • 扁平化设计网站代码打开网站后直接做跳转
  • Go 语言依赖注入实战指南:从基础到高级实践
  • 全场景自动化 Replay 技术:金仓 KReplay 如何攻克数据库迁移 “难验证“ 难题
  • 阳新县建设局网站win2008系统asp网站建设
  • 网站域名分几种新东方雅思培训机构官网
  • 网站怎么样做不违规学科基地网站建设
  • MySQL-4-视图和索引
  • 电脑被捆绑软件缠上?3 步根治卡顿弹窗~
  • Linux时间处理与系统时间管理详解
  • 上饶建设局网站开封到濮阳
  • 织梦网站动态华为云自助建站
  • RocketMQ集群核心概念 生产者端的负载均衡
  • 做恒生指数看什么网站贵州网站优化
  • 百度搜索引擎平台seo全称英文怎么说
  • 黑马点评学习笔记07(缓存工具封装)
  • BLDC电流采样的四种方式
  • 物流行业网站建设市场分析品牌策划方案案例
  • 高校对网站建设的重视郑州建设电商网站
  • 网站后台管理代码凡科h5在线制作
  • 做网站外包多少钱网站建设 工作计划
  • 自己做的网站很卡深圳建立网站公司
  • Trae 大模型选型对比
  • IO多路复用之epoll
  • 模拟一个机械手指:从数学模型到高保真仿真的全平台指南
  • 响应式网站导航栏内容矿区网站建设
  • 网站建设哪家最好wordpress安装到跟目录
  • FFNN(前馈神经网络)层
  • 建设隔离变压器移动网站营销课程培训视频
  • 安阳企业建网站建设用地规划查询网站
  • 【Kernel】Linux CFS(完全公平调度器)实现原理与机制