首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C語言實作 Unix 時間戳與日期時間雙向轉換:含閏年、時區處理

     編輯:關於C語言

C語言實作 Unix 時間戳與日期時間雙向轉換:含閏年、時區處理

在嵌入式系統或日誌記錄中,常需在 Unix Timestamp(自 1970-01-01 00:00:00 UTC 起的秒數)與人類可讀的日期時間結構互轉。標準庫 time.h 雖提供 localtime/gmtime,但若需自訂基準時間(如 2000-01-01)或去除標準庫依賴,自行實作是很好的練習。

資料結構定義

/* ctime.h */
#ifndef CTIME_H_
#define CTIME_H_

#include "common/micro_type.h"  /* 假設定義 uint8, uint16, uint32 等 */

#define OFFSET_SECOND   946684800UL   /* 1970-01-01 到 2000-01-01 的秒數差 */
#define SECOND_OF_DAY   86400UL       /* 一天秒數 */

typedef struct {
    uint16 iYear;   /* 年 */
    uint16 iMon;    /* 月 1-12 */
    uint16 iDay;    /* 日 1-31 */
    uint16 iHour;   /* 時 0-23 */
    uint16 iMin;    /* 分 0-59 */
    uint16 iSec;    /* 秒 0-59 */
    uint16 iMsec;   /* 毫秒 0-999 */
} DATE_TIME;

/* 將秒數轉為日期時間結構 */
void GetDateTimeFromSecond(unsigned long lSec, DATE_TIME *tTime);

#endif /* CTIME_H_ */

核心演算法:日期轉秒數 GetSecondTime

/* ctime.c */
#include "common/micro_type.h"
#include "ctime.h"

static const uint8 DayOfMon[12] = {31,28,31,30,31,30,31,31,30,31,30,31};

unsigned long GetSecondTime(DATE_TIME *date_time) {
    uint16 iYear = date_time->iYear;
    uint16 iMon  = date_time->iMon;
    uint16 iDay  = date_time->iDay;
    uint16 iHour = date_time->iHour;
    uint16 iMin  = date_time->iMin;
    uint16 iSec  = date_time->iSec;

    /* 1. 計算 1970 年至目標年份前一年的總天數(含閏年) */
    uint16 i, leapYears = 0;
    for (i = 1970; i < iYear; i++) {
        if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0))
            leapYears++;
    }
    unsigned long totalDays = leapYears * 366UL + (iYear - 1970 - leapYears) * 365UL;

    /* 2. 累加當年度各月天數 */
    for (i = 1; i < iMon; i++) {
        if (i == 2 && ((iYear % 4 == 0 && iYear % 100 != 0) || (iYear % 400 == 0)))
            totalDays += 29;               /* 閏年二月 */
        else
            totalDays += DayOfMon[i - 1];
    }

    /* 3. 加上當月已過天數(iDay 從 1 起算) */
    totalDays += (iDay - 1);

    /* 4. 換算為總秒數 */
    return totalDays * SECOND_OF_DAY + iHour * 3600UL + iMin * 60UL + iSec;
}

核心演算法:秒數轉日期 GetDateTimeFromSecond


void GetDateTimeFromSecond(unsigned long lSec, DATE_TIME *tTime) {
    uint16 i, j, dayOfMonth;
    unsigned long days = lSec / SECOND_OF_DAY;
    unsigned long secOfDay = lSec % SECOND_OF_DAY;

    /* 1. 從 1970 年往前推算年份 */
    i = 1970;
    while (days > 365) {
        if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0)) {
            if (days < 366) break;
            days -= 366;
        } else {
            days -= 365;
        }
        i++;
    }
    /* 處理平年恰好 365 天的邊界 */
    if (days == 365 && !((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0))) {
        days -= 365;
        i++;
    }
    tTime->iYear = i;

    /* 2. 推算月份 */
    for (j = 0; j < 12; j++) {
        if (j == 1 && ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0)))
            dayOfMonth = 29;
        else
            dayOfMonth = DayOfMon[j];

        if (days >= dayOfMonth)
            days -= dayOfMonth;
        else
            break;
    }
    tTime->iMon  = j + 1;
    tTime->iDay  = days + 1;

    /* 3. 秒數拆解時分秒,並加上東八區偏移 (+8 小時) */
    tTime->iHour = ((secOfDay / 3600) + 8) % 24;
    tTime->iMin  = (secOfDay % 3600) / 60;
    tTime->iSec  = (secOfDay % 3600) % 60;
    tTime->iMsec = 0;
}

關鍵細節說明

  1. 閏年判斷:(year%4==0 && year%100!=0) || (year%400==0),符合格里曆規則。
  2. 基準時間偏移:OFFSET_SECOND 可切換 1970 或 2000 為起始點,視專案需求定義。
  3. 時區處理:範例在轉換時直接 +8 小時轉為北京時間(UTC+8),實際專案建議分離 UTC 與本地時間轉換邏輯。
  4. 無符號長整數:使用 unsigned long 避免 2038 年問題(32 位元有符號整數溢位),若系統為 64 位元則更安全。

測試建議

  • 邊界值:1970-01-01 00:00:00、2000-01-01 00:00:00、閏年 2 月 29 日、年底 12 月 31 日。
  • 雙向驗證:GetSecondTime(GetDateTimeFromSecond(t)) == t。
  • 與標準庫 localtime/mktime 對照測試。

此實作無平台依賴,適合嵌入式裝置、Bootloader 或需精確控制時間基準的場合使用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved