日志1--时间戳类型设计
本文章先进行讲解c++实现日志系统的第一部分实现时间戳类型
先进行讲解涉及的相应知识点
1.获取当前时间到gettimeofday()精确到微秒
gettimeofday()
是 Unix/Linux 系统中用于获取当前时间(精确到微秒)的系统调用,定义在 <sys/time.h>
头文件中,常用于计时、日志时间戳等场景。
#include <sys/time.h>int gettimeofday(struct timeval *tv, struct timezone *tz);
参数讲解:
struct timeval tv;结构体存储秒和微秒
struct timezone通常使用nullptr指针默认系统时间
// 存储时间(秒和微秒)
struct timeval {time_t tv_sec; // 秒数(自 1970-01-01 00:00:00 UTC 起)suseconds_t tv_usec; // 微秒数(0 ~ 999999)
};// 存储时区信息(通常不使用)
struct timezone {int tz_minuteswest; // 与 UTC 的分钟差(西为正)int tz_dsttime; // 夏令时相关(已废弃)
};
- 精度:提供微秒级精度(1 秒 = 1,000,000 微秒),比
time()
函数(秒级)更高。 - 时间基准:
tv_sec
基于 Unix 时间戳(从 1970-01-01 00:00:00 UTC 开始计算的秒数)。 - 时区转换:需配合
localtime()
或gmtime()
将 UTC 时间转换为本地时间或格林威治时间。
2.将秒转为年月日特定格式
C 语言中,localtime_r
是一个用于将时间戳(从 1970 年 1 月 1 日 00:00:00 UTC 起的秒数)转换为本地时间的函数。
#include <time.h>struct tm *localtime_r(const time_t *timep, struct tm *result);
参数说明:
timep
:指向要转换的时间戳(time_t
类型,本质是表示秒数的整数)的指针。result
:指向用于存储转换后本地时间结果的struct tm
结构体的指针,需要预先分配好内存。- 返回值:成功时返回指向
result
的指针,失败时返回NULL
(不过通常情况下不会失败,除非传入的指针无效)。
struct tm
结构体
struct tm {int tm_sec; // 秒(0 - 59)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(例如2024年,这里的值是124)int tm_wday; // 一周中的第几天(0 - 6,0 代表星期日)int tm_yday; // 一年中的第几天(0 - 365)int tm_isdst; // 是否为夏令时(正数表示夏令时,0 表示不是,负数表示未知)
};
从 1970 年 1 月 1 日 00:00:00 UTC(协调世界时)开始到当前时间的秒数(有时也精确到毫秒或微秒),数据类型通常是 time_t
(本质是一个整数类型,在 32 位系统上通常是 int
,在 64 位系统上通常是 long
) 或表示更精确时间的扩展类型(如 long long
表示毫秒级时间戳,struct timeval
包含秒和微秒字段)。
3.格式化写入字符串
printf
是 C 语言中用于格式化字符串并写入字符数组的标准库函数,定义在 <stdio.h>
头文件中。它与 printf
功能类似,但 printf
将结果输出到标准输出(如控制台),而 sprintf
将结果写入指定的字符数组,常用于动态构建字符串。
#include <stdio.h>int sprintf(char *str, const char *format, ...);
参数说明:
str
:指向目标字符数组的指针,用于存储格式化后的字符串。format
:格式化字符串(与printf
的格式字符串规则相同),包含普通字符和格式说明符(如%d
、%s
等)。...
:可变参数列表,对应格式字符串中的格式说明符。
返回值:
- 成功:返回写入字符数组的字符总数(不包括结尾的
\0
)。 - 失败:返回负数(通常为
-1
),并可能设置errno
。
具体时间戳类型类型设计讲解
1.首先我们定义一个时间戳的类Timestamp.hpp,设置私有成员微秒成员为无符号64位整形 uint64_t micr_(需包含头文件<stdint.h>,设置静态成员 static const uint32_t KMicPerSec=1000*1000便于进行秒到微秒的转换。
2.设置公有静态函数 static Timestamp Now();获取当前时间,返回类成员变量,并用成员函数Timestamp::now()在内部用this指针接受。
3.类型转换设计,我们定义了三种类型转换的成员函数
- string toString()const;直接返回秒+微秒
- string toFormattedString(bool showmicto=true)const;showmicto判断微妙是否为0。返回格式为:年/月/日 时:分:秒.微妙
- string toFormattedFile()cosnt;返回格式:年月日时分秒.微妙
4.diffsecond函数返回秒差值,diffMicro返回微妙差值。
string toString()const;//"123.32z"string toFormattedString(bool showmicto=true)const;//"2025/10/11 15:29:23"//"2025/10/11 15:29:23.32z"string toFormattedFile()const;//20251011152923.32
时间戳类型头文件设计如下:
#ifndef TIMESTAMP_HPP
#define TIMESTAMP_HPP
#include<stdint.h>//uint64_t
#include<string>
#include<sys/time.h>
#include<ctime>
using namespace std;
namespace wangt{class Timestamp{private:uint64_t micr_;//微秒public:Timestamp();Timestamp(uint64_t ms);~Timestamp();void swap(Timestamp &other);string toString()const;//"123.32z"string toFormattedString(bool showmicto=true)const;//"2025/10/11 15:29:23"//"2025/10/11 15:29:23.32z"string toFormattedFile()const;//20251011152923.32bool valid()const;//确保微秒有效uint64_t getmicro()const;uint64_t getsecond()const;const Timestamp&now();operator uint64_t()const;static Timestamp Now();//现在时间static Timestamp Invalid();static const uint32_t KMicPerSec=1000*1000;};inline time_t diffSecond(const Timestamp &a,const Timestamp &b){return a.getsecond()-b.getsecond();}inline time_t diffMicro(const Timestamp &a,const Timestamp &b){return a.getmicro()-b.getmicro();}
}
#endif
成员类型 | 具体成员 | 作用说明 |
---|---|---|
私有成员 | uint64_t micr_ | 存储时间的核心变量(单位:微秒),用 uint64_t 避免溢出 |
静态常量 | static const uint32_t KMicPerSec | 秒与微秒的转换系数(1000*1000 ),简化计算 |
构造函数 | Timestamp() Timestamp(uint64_t ms) | - 默认构造:初始化 micr_=0 (无效时间)- 带参构造:用指定微秒数初始化 |
静态成员函数 | static Timestamp Now() static Timestamp Invalid() | - Now() :获取当前系统时间(核心函数)- Invalid() :返回无效时间戳(micr_=0 ) |
成员函数 | toString() toFormattedString() toFormattedFile() | 三种时间格式转换:- 秒。微秒(如 1728678000.123456Z )- 标准格式(如 2024/10/12 15:00:00.123456Z )- 文件名格式(如 20241012150000.12 ) |
工具函数 | valid() getmicro() getsecond() | - valid() :判断时间戳是否有效(micr_>0 )- getmicro() /getsecond() :获取微秒 / 秒级时间 |
友元 / 全局函数 | diffSecond() diffMicro() | 计算两个时间戳的 “秒差” 和 “微秒差”(内联函数,提升效率) |
具体时间戳类型类型具体实现讲解
1.设置公有静态函数 static Timestamp Now();获取当前时间,返回类成员变量,并用成员函数Timestamp::now()在内部用this指针接受。
如下:
我们利用::gettimeofday(tv,nullptr)获取当前时间存储到struct timeval结构体tv中,并用成员函数Timestamp::now()在内部用this指针接受。
2.类型转换设计,我们定义了三种类型转换的成员函数。
- string toString()const;直接返回秒+微秒
slen定义为128,永內部私有成员micr_进行秒和微秒的转换,后用sprintf进行写入到buff中
string wangt::Timestamp::toString() const
{char buff[slen]={};time_t sec=micr_/KMicPerSec;time_t mic=micr_-KMicPerSec*sec;sprintf(buff,"%lu.%luZ",sec,mic);return string(buff);
}
- string toFormattedString(bool showmicto=true)const;showmicto判断微妙是否为0。返回格式为:年/月/日 时:分:秒.微妙
slen定义为128,用內部私有成员micr_进行秒和微秒的转换,用localtime_r函数对sec进行格式转换存储到struct tm 结构体中,后用sprintf按格式进行写入到buff中。
如果showmicto不为0将其写入buff_pos后面。
string wangt::Timestamp::toFormattedString(bool showmicto) const
{char buff[slen]={};time_t sec=micr_/KMicPerSec;time_t mic=micr_-KMicPerSec*sec;struct tm tm_time;localtime_r(&sec,&tm_time);
// tm_year 是“年份-1900”,需加回
// tm_mon 是 0-11,需加 1int pos=sprintf(buff,"%04d/%02d/%02d/%02d:%02d:%02d",tm_time.tm_year+1900,tm_time.tm_mon+1,tm_time.tm_mday,tm_time.tm_hour,tm_time.tm_min,tm_time.tm_sec);if(showmicto){sprintf(buff+pos,".%04luZ",mic);}return string(buff);
}
- string toFormattedFile()cosnt;返回格式:年月日时分秒.微妙
同上思想一样。
wangt::Timestamp wangt::Timestamp::Now()
{struct timeval tv;::gettimeofday(&tv,nullptr);return Timestamp(tv.tv_sec*KMicPerSec+tv.tv_usec);
}
const wangt::Timestamp &wangt::Timestamp::now()
{*this=wangt::Timestamp::Now();return *this;
}
时间戳类型类型源文件如下:
#include "Timestamp.hpp"
static uint32_t slen=128;
wangt::Timestamp::Timestamp():micr_(0)
{//构造函数并初始化micr_为0
}wangt::Timestamp::Timestamp(uint64_t ms):micr_(ms)
{//构造函数并初始化micr_为ms
}wangt::Timestamp::~Timestamp()
{//析构函数
}void wangt::Timestamp::swap(Timestamp &other)
{//交换函数Timestamp a=*this;*this=other;other=a;
}string wangt::Timestamp::toString() const
{char buff[slen]={};time_t sec=micr_/KMicPerSec;time_t mic=micr_-KMicPerSec*sec;sprintf(buff,"%lu.%luZ",sec,mic);return string(buff);
}string wangt::Timestamp::toFormattedString(bool showmicto) const
{char buff[slen]={};time_t sec=micr_/KMicPerSec;time_t mic=micr_-KMicPerSec*sec;struct tm tm_time;localtime_r(&sec,&tm_time);int pos=sprintf(buff,"%04d/%02d/%02d/%02d:%02d:%02d",tm_time.tm_year+1900,tm_time.tm_mon+1,tm_time.tm_mday,tm_time.tm_hour,tm_time.tm_min,tm_time.tm_sec);if(showmicto){sprintf(buff+pos,".%04luZ",mic);}return string(buff);
}string wangt::Timestamp::toFormattedFile() const
{//20251011152923.32char buff[slen]={};time_t sec=micr_/KMicPerSec;time_t mic=micr_-KMicPerSec*sec;struct tm tm_time;localtime_r(&sec,&tm_time);int pos=sprintf(buff,"%04d%02d%02d%02d%02d%02d.%02lu",tm_time.tm_year+1900,tm_time.tm_mon+1,tm_time.tm_mday,tm_time.tm_hour,tm_time.tm_min,tm_time.tm_sec,mic);return string(buff);
}bool wangt::Timestamp::valid() const
{return micr_>0;
}uint64_t wangt::Timestamp::getmicro() const
{//获取当前微妙时间return micr_;
}uint64_t wangt::Timestamp::getsecond() const
{//获取当前妙时间return micr_/KMicPerSec;
}const wangt::Timestamp &wangt::Timestamp::now()
{*this=wangt::Timestamp::Now();return *this;
}wangt::Timestamp::operator uint64_t() const
{//将参数转为uint64_t类型return micr_;
}wangt::Timestamp wangt::Timestamp::Now()
{struct timeval tv;::gettimeofday(&tv,nullptr);return Timestamp(tv.tv_sec*KMicPerSec+tv.tv_usec);
}wangt::Timestamp wangt::Timestamp::Invalid()
{return Timestamp(0);
}
运行代码如下:
注意事项与优化点
- 缓冲区大小:代码中
slen=128
是保守值,实际时间字符串最长约 30 字符(如2024/10/12 15:00:00.123456Z
),无需过大,但需避免溢出。 - 跨平台兼容性:
gettimeofday()
是 Unix/Linux 特有函数,Windows 需替换为QueryPerformanceCounter()
或GetSystemTimePreciseAsFileTime()
,需做跨平台适配。 - 微秒显示精度:
toFormattedFile()
中取微秒前 2 位(mic/10000
),可根据需求调整(如取前 3 位则改为mic/1000
)。 - 线程安全:
localtime_r()
已确保线程安全,但需注意sprintf()
的缓冲区是栈上局部变量,无线程安全问题。
总结
Timestamp
类是日志系统的 “时间基石”,通过封装 gettimeofday()
等底层函数,实现了 “高精度时间获取” 和 “多格式转换” 的核心能力。其设计遵循 “底层存储统一(微秒)、上层格式多样” 的原则,既保证了时间精度,又适配了日志内容、文件名等不同场景的需求。后续日志系统可直接依赖此类,为每条日志添加精确的时间戳,提升日志的可追溯性。