在嵌入式系統或日誌記錄中,常需在 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;
}
(year%4==0 && year%100!=0) || (year%400==0),符合格里曆規則。OFFSET_SECOND 可切換 1970 或 2000 為起始點,視專案需求定義。+8 小時轉為北京時間(UTC+8),實際專案建議分離 UTC 與本地時間轉換邏輯。unsigned long 避免 2038 年問題(32 位元有符號整數溢位),若系統為 64 位元則更安全。GetSecondTime(GetDateTimeFromSecond(t)) == t。localtime/mktime 對照測試。此實作無平台依賴,適合嵌入式裝置、Bootloader 或需精確控制時間基準的場合使用。