目录
概述
本文主要介绍C 标准库 <time.h> 函数,<time.h>
是 C 语言中处理时间和日期的标准库,提供了一系列函数和数据类型用于时间获取、转换和格式化操作提供了一套完整的时间处理工具链,尽管在精度和时区处理上有一定限制,但对于大多数应用场景已经足够。在开发跨平台应用时,应注意不同系统对线程安全和时区支持的差异。
1 核心数据类型
1.1 time_t
描述:表示日历时间的算术类型(通常为整数)
特性:
存储从 "纪元"(通常为 1970-01-01 00:00:00 UTC)起经过的秒数
在 32 位系统上最大值为 2038-01-19 03:14:07(Y2038 问题)
64 位系统可表示约 2920 亿年的时间范围
1.2 clock_t
描述:表示处理器时间的算术类型
用途:测量程序执行时间(CPU 时间)
1.3 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=一月)
int tm_year; // 年份(从1900开始的年数)
int tm_wday; // 星期几 [0-6](0=周日)
int tm_yday; // 年中的日 [0-365]
int tm_isdst; // 夏令时标志:>0(生效)、0(不生效)、<0(未知)
};
1.4 size_t
用于
strftime
函数表示缓冲区大小
2 核心函数
2.1 时间获取函数
1) time_t time(time_t *timer)
功能:获取当前日历时间
参数:
timer
:存储结果的指针(可为 NULL)返回:
成功:当前时间(从纪元开始的秒数)
失败:
(time_t)(-1)
time_t now;
now = time(NULL); // 获取当前时间
2) clock_t clock(void)
功能:获取程序使用的处理器时间
返回:
成功:从程序启动开始的 CPU 时间(时钟滴答数)
失败:
(clock_t)(-1)
注意:需除以
CLOCKS_PER_SEC
转换为秒
clock_t start = clock();
// 执行代码...
clock_t end = clock();
double cpu_time = (double)(end - start) / CLOCKS_PER_SEC;
2.2 时间转换函数
1) struct tm *gmtime(const time_t *timer);
功能:将日历时间转换为 UTC 时间
参数:
timer
- 指向日历时间的指针返回:指向静态
tm
结构的指针(非线程安全)
time_t now = time(NULL);
struct tm *utc_time = gmtime(&now);
2) struct tm *localtime(const time_t *timer);
功能:将日历时间转换为本地时间(考虑时区和夏令时)
参数:
timer
- 指向日历时间的指针返回:指向静态
tm
结构的指针(非线程安全)
time_t now = time(NULL);
struct tm *local_time = localtime(&now);
3) time_t mktime(struct tm *timeptr);
功能:将本地时间转换为日历时间
特性:
自动规范化超出范围的字段
计算并填充
tm_wday
和tm_yday
正确处理夏令时
struct tm new_year = {0};
new_year.tm_year = 124; // 2024年(2024-1900)
new_year.tm_mon = 0; // 一月
new_year.tm_mday = 1; // 1号
time_t timestamp = mktime(&new_year);
2.3 时间差计算
double difftime(time_t time1, time_t time0);
功能:计算两个日历时间的差值(秒)
返回:
time1 - time0
(以秒为单位的双精度值)
time_t start = time(NULL);
// 执行操作...
time_t end = time(NULL);
double elapsed = difftime(end, start);
2.4 时间格式化函数
1) char *asctime(const struct tm *timeptr);
功能:将
tm
结构转换为固定格式字符串格式:
"Day Mon dd hh:mm:ss yyyy\n"
返回:指向静态缓冲区的指针(非线程安全)
struct tm *t = localtime(&now);
printf("Current: %s", asctime(t));
// 输出示例:Tue Jun 18 14:30:45 2024
2) char *ctime(const time_t *timer);
功能:将日历时间转换为本地时间字符串
等效:
asctime(localtime(timer))
返回:指向静态缓冲区的指针(非线程安全)
time_t now = time(NULL);
printf("Current: %s", ctime(&now));
3) size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr);
功能:自定义格式化时间输出
参数:
str
:输出缓冲区
maxsize
:缓冲区最大容量
format
:格式字符串
timeptr
:指向tm
结构的指针返回:写入字符数(不包括终止空字符),失败返回0
常用格式说明符:
说明符 | 含义 | 示例 |
---|---|---|
%Y |
四位年份 | 2024 |
%y |
两位年份 | 24 |
%m |
月份(01-12) | 06 |
%d |
月中的日(01-31) | 18 |
%H |
24小时制小时(00-23) | 14 |
%I |
12小时制小时(01-12) | 02 |
%M |
分钟(00-59) | 30 |
%S |
秒(00-60) | 45 |
%A |
完整星期名 | Tuesday |
%a |
缩写星期名 | Tue |
%B |
完整月份名 | June |
%b |
缩写月份名 | Jun |
%p |
AM/PM | PM |
%Z |
时区名 | CST |
%z |
UTC偏移(+HHMM) | +0800 |
%F |
ISO 8601日期格式 | 2024-06-18 |
%T |
ISO 8601时间格式 | 14:30:45 |
char buffer[80];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S %Z", localtime(&now));
printf("Formatted: %s\n", buffer); // 2024-06-18 14:30:45 CST
3 线程安全版本(POSIX 扩展)
3.1 函数列表
标准函数 | 线程安全版本 |
---|---|
gmtime |
gmtime_r |
localtime |
localtime_r |
asctime |
asctime_r |
ctime |
ctime_r |
使用案例:
// 线程安全用法示例
struct tm result;
localtime_r(&now, &result);
char buffer[80];
asctime_r(&result, buffer);
3.2 时间处理完整示例
#include <stdio.h>
#include <time.h>
int main() {
// 获取当前时间
time_t now = time(NULL);
// 转换为本地时间
struct tm *local = localtime(&now);
printf("当前时间: %s", asctime(local));
// 自定义格式化
char time_str[100];
strftime(time_str, sizeof(time_str), "今天是 %Y年%m月%d日 %A", local);
printf("%s\n", time_str);
// 计算未来时间
struct tm future = *local;
future.tm_mday += 30; // 30天后
mktime(&future); // 规范化时间
strftime(time_str, sizeof(time_str), "30天后: %Y-%m-%d %H:%M:%S", &future);
printf("%s\n", time_str);
// 测量CPU时间
clock_t start = clock();
for (volatile long i = 0; i < 1000000000; i++); // 耗时循环
clock_t end = clock();
printf("CPU时间: %.2f秒\n", (double)(end - start) / CLOCKS_PER_SEC);
// 计算时间差
time_t end_time = time(NULL);
printf("实际时间: %.2f秒\n", difftime(end_time, now));
return 0;
}
4 重要注意事项
非线程安全性:
gmtime
,localtime
,asctime
,ctime
使用静态缓冲区多线程环境中应使用
_r
后缀的线程安全版本时区处理:
时区信息通常来自系统环境变量
TZ
可使用
tzset()
函数(POSIX)初始化时区设置Y2038 问题:
32 位系统的
time_t
在 2038 年会溢出解决方案:使用 64 位系统或专门的时间库
夏令时处理:
localtime
和mktime
自动处理夏令时设置
tm_isdst = -1
让系统自动确定精度限制:
标准时间函数精度通常为秒级
更高精度需求需使用平台特定 API(如 POSIX
clock_gettime()
)
5 实际应用场景
5.1 日志记录
void log_message(const char *msg) {
time_t now = time(NULL);
char time_str[30];
strftime(time_str, sizeof(time_str), "[%Y-%m-%d %H:%M:%S]", localtime(&now));
printf("%s %s\n", time_str, msg);
}
5.2 定时任务
void daily_task() {
time_t now = time(NULL);
struct tm *t = localtime(&now);
// 每天凌晨2点执行
if (t->tm_hour == 2 && t->tm_min == 0) {
perform_maintenance();
}
}
5.3 性能分析
void profile_function() {
clock_t start = clock();
// 被分析的函数
expensive_operation();
clock_t end = clock();
printf("CPU时间: %.4f秒\n",
(double)(end - start) / CLOCKS_PER_SEC);
}
5.4 时间戳转换
time_t parse_iso8601(const char *str) {
struct tm tm = {0};
sscanf(str, "%d-%d-%dT%d:%d:%d",
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
&tm.tm_hour, &tm.tm_min, &tm.tm_sec);
tm.tm_year -= 1900;
tm.tm_mon -= 1;
tm.tm_isdst = -1;
return mktime(&tm);
}
6 Nordic MCU上验证
源代码:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* File Name : app_rtc.c
* Description : define the macro parameters
******************************************************************************
* @attention
*
* COPYRIGHT: Copyright (c) 2025 mingfei_tang
* DATE: MAR 27th, 2025
******************************************************************************
*/
/* USER CODE END Header */
#include "app_rtc.h"
static time_t g_rtc_cur_stamp = 0;
static bool rtc2tm(et_rtc_t *p_rtc, struct tm *p_tm, bool r2t)
{
if (r2t)
{
p_tm->tm_year = 2000 + p_rtc->year - 1900;
p_tm->tm_mon = p_rtc->month - 1;
p_tm->tm_mday = p_rtc->day;
p_tm->tm_hour = p_rtc->hour;
p_tm->tm_min = p_rtc->minute;
p_tm->tm_sec = p_rtc->second;
}
else
{
p_rtc->year = p_tm->tm_year + 1900 - 2000;
p_rtc->month = p_tm->tm_mon + 1;
p_rtc->day = p_tm->tm_mday;
p_rtc->hour = p_tm->tm_hour;
p_rtc->minute = p_tm->tm_min;
p_rtc->second = p_tm->tm_sec;
}
return (p_tm->tm_year >= 2024 - 1900) &&
(p_tm->tm_mon >= 0) && (p_tm->tm_mon <= 11) &&
(p_tm->tm_mday >= 1) && (p_tm->tm_mday <= 31) &&
(p_tm->tm_hour >= 0) && (p_tm->tm_hour <= 23) &&
(p_tm->tm_min >= 0) && (p_tm->tm_min <= 59) &&
(p_tm->tm_sec >= 0) && (p_tm->tm_sec <= 59);
}
void rtc_expiry_function_callback(void )
{
g_rtc_cur_stamp += 1;
app_rtc_test();
}
void app_rtc_set( et_rtc_t *prtc)
{
struct tm t;
rtc2tm(prtc, &t, true);
drv_grtc_stop();
g_rtc_cur_stamp = mktime(&t);
drv_grtc_start();
}
void app_rtc_get( et_rtc_t *p_rtc, time_t *stamp)
{
struct tm t;
*stamp = g_rtc_cur_stamp;
t = *localtime(stamp);
rtc2tm(p_rtc, &t, false);
}
void app_init_rtc( void )
{
et_rtc_t rtc;
build_rtc( &rtc );
drv_grtc_init();
app_rtc_set( &rtc );
}
void app_rtc_test( void )
{
time_t stamp;
et_rtc_t rtc;
app_rtc_get((et_rtc_t *)&rtc, &stamp);
printk("rtc(%02d/%02d/%02d %02d:%02d:%02d)",
rtc.year, rtc.month, rtc.day,
rtc.hour, rtc.minute, rtc.second);
}
void app_rtc_first_set( void )
{
et_rtc_t _rtcObj;
build_rtc( &_rtcObj );
app_rtc_set( &_rtcObj );
}
/** End of this file */
验证数据: