C 中時(shí)間少欺、時(shí)區(qū)喳瓣、時(shí)令

問題場景

發(fā)送業(yè)務(wù)時(shí)間戳?xí)r,調(diào)用 localtime 時(shí)消耗太大赞别,改用 gmtime + 8小時(shí)的時(shí)區(qū)偏移來計(jì)算畏陕,是否合理?

localtime 的流程

Code Debugging with GDB - part 4: Basic Debug Glibc Source Code | Ethanol's blog (ethanol1310.github.io)

localtime()
  __tz_convert()
    tzset_internal() # 解析 TZ 的時(shí)區(qū)設(shè)置仿滔,只處理一次
      if tz == NULL, tz = /etc/localtime # 使用系統(tǒng)設(shè)置時(shí)區(qū)
      #  /etc/localtime -> /usr/share/zoneinfo/America/Los_Angeles
      __tzfile_read() #解析時(shí)區(qū)文件惠毁,https://www.man7.org/linux/man-pages/man5/tzfile.5.html
        fopen()
          #tzh_magic = "TZif"
          #tzh_version = "2" 8b 轉(zhuǎn)換時(shí)間
          #tzh_reserved = '\000' <repeats 14 times>
          #num_isgmt = 6 tzh_ttisutcnt = "\000\000\000\006",
          #num_isstd = 6 tzh_ttisstdcnt = "\000\000\000\006",
          #num_leaps = 0 tzh_leapcnt = "\000\000\000",
          #num_transitions = 186 tzh_timecnt = "\000\000\000\272",
          #num_types = 6 tzh_typecnt = "\000\000\000\006",
          #chars = 20 tzh_charcnt = "\000\000\000\024"}
          # tzh_timecnt 個(gè) U32 計(jì)算日期的轉(zhuǎn)變點(diǎn)
          # tzh_timecnt 個(gè)上述轉(zhuǎn)變點(diǎn)之前時(shí)間段內(nèi)的類型
          # tzh_typecnt 個(gè) ttinfo 信息
          #  struct ttinfo
          #    int32_t       tt_utoff     UT 時(shí)間上增加的秒數(shù),[-89999, 93599], -25h -> 6h
          #    unsigned char tt_isdst     是否設(shè)置 tm.tm_isdst, 夏令時(shí)標(biāo)志
          #    unsigned char tt_desigidx  指向 ttinfo 之后的時(shí)區(qū)簡寫結(jié)構(gòu)的索引崎页,相當(dāng)于該區(qū)段的名稱
          # tzh_leapcnt 對4b數(shù)值對鞠绰,閏秒發(fā)生時(shí)間,閏秒改變的秒數(shù)
          # tzh_ttisstdcnt 個(gè)轉(zhuǎn)變時(shí)間是標(biāo)準(zhǔn)時(shí)間飒焦,還是當(dāng)?shù)貢r(shí)間蜈膨,與 type 對應(yīng)
          # tzh_ttisutcnt  轉(zhuǎn)變時(shí)間是不是 UT 時(shí)間,與 type 對應(yīng)
          # 時(shí)區(qū)名稱 "PST8PDT,M3.2.0,M11.1.0"
        rule_stdoff = -28800s
        rule_dstoff = -25200s
    __tzfile_compute()
      # 時(shí)區(qū)偏移荒给, -28800
      # 時(shí)令偏移, 0
      # 閏秒偏移丈挟,0
      __offtime(t, off, tp)
          d1 = (t + off) / (24 * 60 * 60)
          h = (t + off) % (24 * 60 * 60) / (60 * 60)
          m = (t + off) % (24 * 60 * 60) % (60 * 60) / 60
          s = (t + off) % (24 * 60 * 60) % (60 * 60) % 60
          wd = (4 + d1) % 7 # 1970-1-1 周四
          # 從 1970 的閏年開始計(jì)算,年志电,月曙咽,日
          # 加上閏秒
gmtime()
  __tz_convert()
    tzset_internal() # 解析 TZ 的時(shí)區(qū)設(shè)置,只處理一次
      if tz == NULL, tz = /etc/localtime # 使用系統(tǒng)設(shè)置時(shí)區(qū)
      #  /etc/localtime -> /usr/share/zoneinfo/America/Los_Angeles
      __tzfile_read() #解析時(shí)區(qū)文件挑辆,https://www.man7.org/linux/man-pages/man5/tzfile.5.html
        fopen()
      __tzfile_compute()
        # 計(jì)算閏秒的偏移量
        __offtime()

Asia/Shanghai 時(shí)區(qū)文件

1986年4月例朱,中國中央有關(guān)部門發(fā)出“在全國范圍內(nèi)實(shí)行夏時(shí)制的通知”,具體做法是:每年從四月中旬第一個(gè)星期日的凌晨2時(shí)整(北京時(shí)間)鱼蝉,將時(shí)鐘撥快一小時(shí)洒嗤,即將表針由2時(shí)撥至3時(shí),夏令時(shí)開始魁亦;到九月中旬第一個(gè)星期日的凌晨2時(shí)整(北京夏令時(shí))渔隶,再將時(shí)鐘撥回一小時(shí),即將表針由2時(shí)撥至1時(shí)洁奈,夏令時(shí)結(jié)束间唉。從1986年到1991年的六個(gè)年度,除1986年因是實(shí)行夏時(shí)制的第一年利术,從5月4日開始到9月14日結(jié)束外呈野,其它年份均按規(guī)定的時(shí)段施行。在夏令時(shí)開始和結(jié)束前幾天印叁,新聞媒體均刊登有關(guān)部門的通告被冒。1992年起军掂,夏令時(shí)暫停實(shí)行。

(gdb) p transitions[0]@29
$18 = {-2177481943, -1600675200, -1585904400, -933667200, -922093200, -908870400, -888829200, -881049600, -767869200, -745833600,
  -733827600, -716889600, -699613200, -683884800, -670669200, -652348800, -650019600, 515527200, 527014800, 545162400, 558464400,
  577216800, 589914000, 608666400, 621968400, 640116000, 653418000, 671565600, 684867600}
1900-12-31 23:54:17, isdst = 0.
1919-04-13 01:00:00, isdst = 1.
1919-09-30 23:00:00, isdst = 0.
1940-06-01 01:00:00, isdst = 1.
1940-10-12 23:00:00, isdst = 0.
1941-03-15 01:00:00, isdst = 1.
1941-11-01 23:00:00, isdst = 0.
1942-01-31 01:00:00, isdst = 1.
1945-09-01 23:00:00, isdst = 0.
1946-05-15 01:00:00, isdst = 1.
1946-09-30 23:00:00, isdst = 0.
1947-04-15 01:00:00, isdst = 1.
1947-10-31 23:00:00, isdst = 0.
1948-05-01 01:00:00, isdst = 1.
1948-09-30 23:00:00, isdst = 0.
1949-05-01 01:00:00, isdst = 1.
1949-05-27 23:00:00, isdst = 0.
1986-05-04 03:00:00, isdst = 1.
1986-09-14 01:00:00, isdst = 0.
1987-04-12 03:00:00, isdst = 1.
1987-09-13 01:00:00, isdst = 0.
1988-04-17 03:00:00, isdst = 1.
1988-09-11 01:00:00, isdst = 0.
1989-04-16 03:00:00, isdst = 1.
1989-09-17 01:00:00, isdst = 0.
1990-04-15 03:00:00, isdst = 1.
1990-09-16 01:00:00, isdst = 0.
1991-04-14 03:00:00, isdst = 1.
1991-09-15 01:00:00, isdst = 0.
(gdb) n
(gdb) p types[0].offset
$19 = 29143
(gdb) p types[1].offset
$20 = 32400
(gdb) p types[2].offset
$21 = 28800

中國無冬令時(shí)昨悼、夏令時(shí)區(qū)分蝗锥,gmtime 同樣會(huì)將閏秒的修正計(jì)算在內(nèi),盡管當(dāng)面并沒看到時(shí)區(qū)文件中有閏秒修正率触。

閏秒的另一個(gè)小問題

struct tm {
    int tm_sec;         /* seconds */
    int tm_min;         /* minutes */
    int tm_hour;        /* hours */
    int tm_mday;        /* day of the month */
    int tm_mon;         /* month */
    int tm_year;        /* year */
    int tm_wday;        /* day of the week */
    int tm_yday;        /* day in the year */
    int tm_isdst;       /* daylight saving time */
};
The members of the tm structure are:
tm_sec
The number of seconds after the minute, normally in the range 0 to 59, but can be up to 60 to allow for leap seconds.

測試

// 以時(shí)區(qū)中的轉(zhuǎn)變點(diǎn)做邊界測試玛追,如下所示,91-09-15 之后闲延,gmtime +8 與 localtime 一致
localtime               1986-05-04 03:00:01, isdst = 1.
gmtime+8                1986-05-04 02:00:01, isdst = 0.
localtime               1986-09-14 01:00:01, isdst = 0.
gmtime+8                1986-09-14 01:00:01, isdst = 0.
localtime               1987-04-12 03:00:01, isdst = 1.
gmtime+8                1987-04-12 02:00:01, isdst = 0.
localtime               1987-09-13 01:00:01, isdst = 0.
gmtime+8                1987-09-13 01:00:01, isdst = 0.
localtime               1988-04-17 03:00:01, isdst = 1.
gmtime+8                1988-04-17 02:00:01, isdst = 0.
localtime               1988-09-11 01:00:01, isdst = 0.
gmtime+8                1988-09-11 01:00:01, isdst = 0.
localtime               1989-04-16 03:00:01, isdst = 1.
gmtime+8                1989-04-16 02:00:01, isdst = 0.
localtime               1989-09-17 01:00:01, isdst = 0.
gmtime+8                1989-09-17 01:00:01, isdst = 0.
localtime               1990-04-15 03:00:01, isdst = 1.
gmtime+8                1990-04-15 02:00:01, isdst = 0.
localtime               1990-09-16 01:00:01, isdst = 0.
gmtime+8                1990-09-16 01:00:01, isdst = 0.
localtime               1991-04-14 03:00:01, isdst = 1.
gmtime+8                1991-04-14 02:00:01, isdst = 0.
localtime               1991-09-15 01:00:01, isdst = 0.
gmtime+8                1991-09-15 01:00:01, isdst = 0.
localtime               2023-03-05 22:25:16, isdst = 0.
gmtime+8                2023-03-05 22:25:16, isdst = 0.

localtime 的性能瓶頸

在當(dāng)前機(jī)器上痊剖,localtime 耗時(shí) 142ns,gmtime 耗時(shí) 38ns垒玲。應(yīng)該是時(shí)區(qū)相關(guān)的處理耗時(shí)較多陆馁。
TODO 按照這個(gè)猜想,時(shí)區(qū)設(shè)置為夏令時(shí)的時(shí)區(qū)合愈,localtime 耗時(shí)更大叮贩,但是并沒有,有待繼續(xù)分析佛析。

附錄

muduo 中時(shí)區(qū)計(jì)算

class TimeZone : public muduo::copyable
{
 public:

  static TimeZone UTC();
  static TimeZone China();  // Fixed at GMT+8, no DST
  static TimeZone loadZoneFile(const char* zonefile);
  struct DateTime toLocalTime(int64_t secondsSinceEpoch, int* utcOffset = nullptr) const;
  int64_t fromLocalTime(const struct DateTime&, bool postTransition = false) const;
  // gmtime(3)
  static struct DateTime toUtcTime(int64_t secondsSinceEpoch);
  // timegm(3)
  static int64_t fromUtcTime(const struct DateTime&);
  struct Data;
 private:
  explicit TimeZone(std::unique_ptr<Data> data);
  std::shared_ptr<Data> data_;
  friend class TimeZoneTestPeer;
};

《linux 多線程服務(wù)器編程》chp5.2 日志庫中格式化日期操作

void Logger::Impl::formatTime()
{
  int64_t microSecondsSinceEpoch = time_.microSecondsSinceEpoch();
  time_t seconds = static_cast<time_t>(microSecondsSinceEpoch / Timestamp::kMicroSecondsPerSecond);
  int microseconds = static_cast<int>(microSecondsSinceEpoch % Timestamp::kMicroSecondsPerSecond);
  if (seconds != t_lastSecond) // 緩存秒部分益老,只在跨秒時(shí)更新字符串中的年月日時(shí)分秒部分
  {
    t_lastSecond = seconds;
    struct DateTime dt;
    dt = g_logTimeZone.toLocalTime(seconds);
    int len = snprintf(t_time, sizeof(t_time), "%4d%02d%02d %02d:%02d:%02d",
        dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second);
    assert(len == 17); (void)len;
  }
  Fmt us(".%06d ", microseconds);
  assert(us.length() == 8);
  stream_ << T(t_time, 17) << T(us.data(), 8);
}

《linux 多線程服務(wù)器編程》chp9.3 業(yè)務(wù)層心跳機(jī)制應(yīng)規(guī)避閏秒

考慮到閏秒的影響,Tc小于1秒是無意義的寸莫,因?yàn)殚c秒會(huì)讓兩臺機(jī)器的相對時(shí)間發(fā)生跳變捺萌,可能產(chǎn)生報(bào)警。

linux 如何處理閏秒

  • RF8536
  • UNIX Time: The time as returned by the time() function provided by the C programming language (see Section 3 of the "System Interfaces" volume of [POSIX]). This is an integer number of seconds since the POSIX epoch, not counting leap seconds. As an extension to POSIX, negative values represent times before the POSIX epoch, using UT.
  • UNIX Leap Time: UNIX time plus all preceding leap-second corrections. For example, if the first leap-second record in a TZif file occurs at 1972-06-30 23:59:60 UTC, the UNIX leap time for the timestamp 1972-07-01 00:00:00 UTC would be 78796801, one greater than the UNIX time for the same timestamp. Similarly, if the second leap-second record occurs at 1972-12-31 23:59:60 UTC, it accounts for the first leap second, so the UNIX leap time of 1972-12-31 23:59:60 UTC would be 94694401, and the UNIX leap time of 1973-01-01 00:00:00 UTC would be 94694402. If a TZif file specifies no leap-second records, UNIX leap time is equal to UNIX time.
    按照 POSIX 標(biāo)準(zhǔn) linux time() 函數(shù)返回的是 UT 時(shí)間膘茎,不計(jì)入閏秒
  • linux clock_gettime(CLOCK_TAI) (since Linux 3.10; Linux-specific) A nonsettable system-wide clock derived from wall-clock time but ignoring leap seconds. This clock does not experience discontinuities and backwards jumps caused by NTP inserting leap seconds as CLOCK_REALTIME does.
    而 clock_gettime(CLOCK_REALTIME) 是計(jì)入閏秒的桃纯,因此 linux 上的時(shí)區(qū)文件無閏秒的修正,但是假設(shè)以該時(shí)間處理心跳等披坏,會(huì)引起偶發(fā)的邏輯問題态坦。

2038 年問題

2038年問題_百度百科 (baidu.com)
localtime(INT_MAX) = "2038-1-19 11:14:07",因此 mktime("2038-1-19 11:14:08") 就會(huì)導(dǎo)致 32位的 time_t 移出棒拂,通常當(dāng)下的 64 位機(jī)器無影響伞梯,在特殊場合如 webassambly 編譯中遇到過。

mktime 的校驗(yàn)

The mktime() function modifies the fields of the tm structure as follows: tm_wday and tm_yday are set to values determined from the contents of the other fields; if structure members are outside their valid interval, they will be normalized (so that, for example, 40 October is changed into 9 November); tm_isdst is set (regardless of its initial value) to a positive value or to 0, respectively, to indicate whether DST is or is not in effect at the specified time. Calling mktime() also sets the external variable tzname with information about the current timezone. mktime 當(dāng)各字段超出有效范圍帚屉,mktime 會(huì)修改輸入?yún)?shù)谜诫,如10月40號,修改為11月9日處理涮阔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末猜绣,一起剝皮案震驚了整個(gè)濱河市灰殴,隨后出現(xiàn)的幾起案子敬特,更是在濱河造成了極大的恐慌掰邢,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伟阔,死亡現(xiàn)場離奇詭異辣之,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)皱炉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門怀估,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人合搅,你說我怎么就攤上這事多搀。” “怎么了灾部?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵康铭,是天一觀的道長。 經(jīng)常有香客問我赌髓,道長从藤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任锁蠕,我火速辦了婚禮夷野,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘荣倾。我一直安慰自己悯搔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布舌仍。 她就那樣靜靜地躺著鳖孤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抡笼。 梳的紋絲不亂的頭發(fā)上苏揣,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音推姻,去河邊找鬼平匈。 笑死,一個(gè)胖子當(dāng)著我的面吹牛藏古,可吹牛的內(nèi)容都是我干的增炭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拧晕,長吁一口氣:“原來是場噩夢啊……” “哼隙姿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起厂捞,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤输玷,失蹤者是張志新(化名)和其女友劉穎队丝,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體欲鹏,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡机久,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赔嚎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膘盖。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖尤误,靈堂內(nèi)的尸體忽然破棺而出侠畔,到底是詐尸還是另有隱情,我是刑警寧澤损晤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布践图,位于F島的核電站,受9級特大地震影響沉馆,放射性物質(zhì)發(fā)生泄漏码党。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一斥黑、第九天 我趴在偏房一處隱蔽的房頂上張望揖盘。 院中可真熱鬧,春花似錦锌奴、人聲如沸兽狭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽箕慧。三九已至,卻和暖如春茴恰,著一層夾襖步出監(jiān)牢的瞬間颠焦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工往枣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伐庭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓分冈,卻偏偏與公主長得像圾另,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子雕沉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容