前言 - 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 __GNUC__ #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 一定存在, 希望都在修改中提高. 心無旁貸 ~