C基础 time.h 简单思路扩展
前言 - time 简单需求
时间业务相关代码. 基本属于框架的最底层. 涉及的变动都很小. 以前参与游戏研发时候,
这方面需求不少, 各种被策划花式吊打. 转行开发互联网服务之后很少遇到这方面需求.
但是封装基础库的时候, 这方面好用api 功能是需要的. 在这就抛砖引玉简单包一层.
首先看 time 库的封装有那些基础需求.
0. 线程安全
1. 时间串 和 时间戳互换
2. 时间比较 ... 例如是否在同一天
3. 毫秒精度时间串支持
一切从简单出发, 通过上面需求, 带着大家展开作者的思路.
times.h
#ifndef _H_TIMES #define _H_TIMES #include <time.h> #include <stdbool.h> // // 1s = 1000ms = 1000000us = 1000000000ns // 1秒 1000毫秒 1000000微秒 1000000000纳秒 // ~ 力求最小时间业务单元 ~ // #ifdef __GUNC__ #include <unistd.h> #include <sys/time.h> // // msleep - 睡眠函数, 颗粒度是毫秒. // m : 待睡眠的毫秒数 // return : void // inline void msleep(int ms) { usleep(ms * 1000); } #endif #ifdef _MSC_VER #include <windows.h> inline void msleep(int ms) { Sleep(ms); } // // localtime_r - 安全的得到当前时间结构体 // timep : 输入的时间戳指针 // result : 返回输出时间结构 // return : 失败 NULL, 正常返回 result // inline struct tm * localtime_r(const time_t * timep, struct tm * result) { return localtime_s(result, timep) ? NULL : result; } #endif // times_t - 时间串类型 #define INT_TIMES (64) typedef char times_t[INT_TIMES]; #endif//_H_TIMES
我们这里通过采用 localtime_r 解决 time_t 获取线程安全问题.
稍微扯一点 其实这里 winds localtime_s 设计思路好于 localtime_r.
_Check_return_wat_ static __inline errno_t __CRTDECL localtime_s( _Out_ struct tm* const _Tm, _In_ time_t const* const _Time ) { return _localtime64_s(_Tm, _Time); }
errno_t 显然比 struct tm * 判断语义判断更加精确. 但从已经确定 time_t 实现角度出发. 更好接口设计应该是这样的
errno_t localtime_s(time_t t, struct tm * result);
毕竟 time_t 传入结构就足够. 第 0 个需求 线程安全 就搞定了.
哈哈 看着 __GUNC__ _MSC_VER 突然想笑, 未来也许引进 __clang__. 程序员 = 同类玩同类的生物
正文 - time 简单思路
1. 时间串 和 时间戳 互换. 不妨直接从函数实现角度出发. 可以分为两部
i) 时间串 parse -> struct tm
ii) struct tm -> time_t
先看第 i 步思路
// times_tm - 从时间串中提取出来年月日时分秒 bool times_tm(times_t tsr, struct tm * pm) { int c, num, * es, * py; if ((!tsr) || !(c = *tsr) || c < '0' || c > '9') return false; num = 0; es = &pm->tm_sec; py = &pm->tm_year; do { if (c >= '0' && c <= '9') { num = 10 * num + c - '0'; c = *++tsr; continue; } *py-- = num; if (py < es) break; // 去掉特殊字符, 重新开始 for (;;) { if ((c = *++tsr) == '\0') return false; if (c >= '0' && c <= '9') break; } num = 0; } while (c); // true : py < es || c == '\0' && py == es if (py < es) return true; if (py == es) { *es = num; return true; } return false; }
这个函数为了能够处理下面这些类型的时间串, 并返回简单结果
"2018年3月11日 19点35分59秒"
"2018-03-11 19:35:58"
"2018年03月 :) 19点35分59秒"
... ... ...
后面随意些了, 第 ii 步 struct tm -> time_t
// // time_get - 解析时间串, 返回时间戳 // tsr : 时间串内容 // return : < 0 is error // inline time_t time_get(times_t tsr) { struct tm m; // 先高效解析出年月日时分秒 if (!times_tm(tsr, &m)) return -1; // 得到时间戳, 失败返回false m.tm_mon -= 1; m.tm_year -= 1900; return mktime(&m); }
不知道有没有人好奇为什么会出现魔法数字, 1 1900. (注 0, -1 是 api 设计中默认潜规则的魔法数字)
这个主要是遵从编译器 c runtime 底层设计的风格, 看下面
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // Types // //-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ struct tm { int tm_sec; // seconds after the minute - [0, 60] including leap second int tm_min; // minutes after the hour - [0, 59] int tm_hour; // hours since midnight - [0, 23] int tm_mday; // day of the month - [1, 31] int tm_mon; // months since January - [0, 11] int tm_year; // years since 1900 int tm_wday; // days since Sunday - [0, 6] int tm_yday; // days since January 1 - [0, 365] int tm_isdst; // daylight savings time flag };
这么多数字. 说白了和数学有点关系的就直接数字吧. 来个宏有可能更不懂. 这里写代码有个原则
最底层代码怎么写, 咱们就怎么写.
应用到社会生活中, 老大怎么弄, 咱们就怎么弄. 他打那我们射那 ... .... (ps: sb 哲学)
2. 时间比较 ... 例如是否在同一天
这个实现思路, 涉及到时区问题. 我在这里只实现了 China 部分.
// // time_day - 判断时间戳是否是同一天 // n : 第一个时间戳 // t : 第二个时间戳 // return : true 表示同一天 // inline bool time_day(time_t n, time_t t) { // China local 适用, 得到当前天数 // GMT [World] + 8 * 3600 = CST [China] n = (n + 8UL * 3600) / (24 * 3600); t = (t + 8UL * 3600) / (24 * 3600); return n == t; }
在国际化思路中. 可以通过 Configure 处理.
对上面函数包装一个语法糖函数如下
// // time_now - 判断时间戳是否是今天 // t : 待判断的时间戳 // return : 返回当前时间戳, -1 is error // inline time_t time_now(time_t t) { time_t n = time(NULL); return time_day(n, t) ? n : -1; }
或者直接通过时间串比较
// // times_day - 判断时间串是否是同一天 // ns : 第一个时间串 // ts : 第二个时间串 // return : true 表示同一天 // bool times_day(times_t ns, times_t ts) { time_t t, n = time_get(ns); // 解析失败直接返回结果 if ( (n < 0) || ((t = time_get(ts)) < 0)) return false; return time_day(n, t); }
更多相关判断业务, 也就是拼接积木了. 可以自己玩玩.
3. 毫秒精度时间串支持
这里我是这么封装的, 首先看声明
// // times_fmt - 通过 fmt 格式最终拼接一个字符串 // fmt : 必须包含 %04d %02d %02d %02d %02d %02d %03ld // buf : 最终保存的内容 // sz : buf 长度 // return : 返回生成串长度 // int times_fmt(const char * fmt, char buf[], size_t sz); // // times_str - 得到毫秒串 [2016-07-10 22:38:34 500] // osr : 返回生成串 // return : 返回生成串长度 // #define STR_TIMES "%04d-%02d-%02d %02d:%02d:%02d %03ld" inline int times_str(times_t osr) { return times_fmt(STR_TIMES, osr, sizeof(times_t)); }
随后是实现部分
int times_fmt(const char * fmt, char buf[], size_t sz) { struct tm m; struct timespec s; timespec_get(&s, TIME_UTC); localtime_r(&s.tv_sec, &m); return snprintf(buf, sz, fmt, m.tm_year + 1900, m.tm_mon + 1, m.tm_mday, m.tm_hour, m.tm_min, m.tm_sec, s.tv_nsec / 1000000); }
思路直白. 通过 struct tm 中各个字段意义填充. 使用 timespec_get 得到纳秒级别时间精度.
times_str 在 log 日志库封装中很常用
很简单是不是. 更多的封装, 可以在链接接口设计中扩展出去
times.h https://github.com/wangzhione/structc/blob/master/structc/system/times.h
开始起源于时光和星空 ~ 也许那些是真随机 : )
后记 - time 纯属扯淡
BUG 一定存在, 希望都在修改中提高. 心无旁贷 ~