C實現Unix時間戳和本地時間轉化

目錄(?)[+]

我們平常說時間都說的幾點幾分幾秒,星期幾,但是在計算機裏面並不是直接使用我們所說的時間,而是使用Unix時間戳,這樣不管是哪個平臺,哪個系統,都可以根據自己對時間的定義進行轉換,像JavaPHP等都提供了接口來進行轉化,C庫裏面也有這樣的函數,那具體是怎麼實現的呢?要了解這個問題首先我們就必須要清楚什麼是Unix時間戳,什麼是我們平常使用的時間。

1. Unix時間戳

UNIX時間戳:Unix時間戳(英文爲Unix epoch, Unix time, POSIX time 或 Unix timestamp) 
是從1970年1月1日(UTC/GMT的午夜)開始所經過的秒數,不考慮閏秒。 
UNIX時間戳的0按照ISO 8601規範爲 :1970-01-01T00:00:00Z. 
一個小時表示爲UNIX時間戳格式爲:3600秒;一天表示爲UNIX時間戳爲86400秒,閏秒不計算。 
在大多數的UNIX系統中UNIX時間戳存儲爲32位,這樣會引發2038年問題或Y2038。

對應的時間和秒數如下

時間
1 分鐘 60 秒
1 小時 3600 秒
1 天 86400 秒
1 周 604800 秒
1 月 (30.44 天) 2629743 秒
1 年 (365.24 天) 31556926 秒

2. 本地時間

平常我們最常聽見的是陰曆和陽曆之分,陰曆是按照月亮的運行規律來計算日期的,那我們所說的陽曆又是什麼呢?其實陽曆或者說叫西曆最早是叫做儒略曆,是由羅馬共和國獨裁官儒略·愷撒(即蓋烏斯·尤里烏斯·凱撒)採納數學家兼天文學家索西琴尼的計算後,於公元前45年1月1日起執行的取代舊羅馬曆法的一種曆法,但令人遺憾的是,當時那些頒發曆書的祭司們,卻不瞭解改歷的實質。結果,可笑的是,當時羅馬執掌頒佈曆書的祭司竟把原來歷法上規定的“每隔三年置閏”誤解爲“每三年置一閏”。從公元前45年起,到公元前9年爲止,這之間本應設置10個閏年,他們卻設置了13個閏年。公元前9年,人們終於發現這一差錯,這時愷撒的外甥奧古斯都執掌政權,他糾正了這個錯誤,才停止了“三年一閏”。奧古斯都下令改正過來,改到次年(公元前8年)才置閏年。當改正這種閏年的錯誤時已經多閏了3年,爲了去掉着多閏的3年,奧古斯都又下令停閏3年,即以公元前5年、公元前1年、公元前4年仍爲平年,以後恢復了每4年一閏的規定了。奧古斯都爲了宣揚這一功勞,仿效儒略·愷撒的做法,下令把自己出生的儒略曆中的8月改稱爲奧古斯都月(這一名稱在西方沿用到今天)。8月後的大,小月份都翻轉過來了,9月爲30天,10月爲31天,11月爲30天,12月爲31天,這種置月方式一直沿用至今。如此一來,一年多出了一天,於是也從二月份29天裏再減去一天,二月份只剩下28天了 1582年羅馬教皇格里高利對”儒略曆”又進行修改,規定被4整除的年爲閏年,但逢百之年只有能被400除盡才能是閏年。這就是使用至今的“格里曆”。這樣做是爲了使歷年與迴歸年相接近。迴歸年的週期是365.2425天。儒略曆一年的平均長度爲365.25日,比迴歸年(365.2425天)長11分14秒,自公元325年(該年採用儒略曆作爲宗教日曆)積累到十六世紀末,春分日由3月21日提早到3月11日。於是羅馬教皇格里高利十三世(Gregorius XⅢ)於1582年10月4日還下令將次日(即原10月5日)定爲10月15日,把春分日又恢復爲3月21日。這樣,1582年的10月5日-14日這十天就成了“不存在”的日子,變爲歷史的空白。1949年9月27日,經過中國人民政治協商會議第一屆全體會議通過,中華人民共和國使用國際社會多數國家通用的西曆和西元作爲曆法和紀年。

3. 蔡勒公式

清楚了Unix時間戳和公曆的意義,怎麼講秒數轉化成本地時間大家心裏應該有個大概了,既然本地時間知道了,那我們可不可以根據這個日期直接知道那一天是星期幾呢?數學家蔡勒(Zeller)推算出了這個公式,大家稱爲蔡勒公式,使用這個公式隨便給出一個日期,就可以計算出是星期幾。

公式具體是這樣的 
W = [C / 4] – 2C + y + [y / 4] + [13 * (M + 1) / 5] + d – 1 
或者是:w = y + [y / 4] + [c / 4] – 2c + [26(m + 1) / 10] + d – 1

公式中的符號含義如下: 
w:星期; w對7取模得:0-星期日,1-星期一,2-星期二,3-星期三,4-星期四,5-星期五,6-星期六 
c:世紀-1(前兩位數) 
y:年(後兩位數) 
m:月(m大於等於3,小於等於14,即在蔡勒公式中,某年的1、2月要看作上一年的13、14月來計算,比如2003年1月1日要看作2002年的13月1日來計算) 
d:日 [ ]代表取整,即只要整數部分。

下面以中華人民共和國成立100週年紀念日那天(2049年10月1日)來計算是星期幾,過程如下: 
w = y + [y / 4] + [c / 4] – 2c + [26(m + 1) / 10] + d – 1 
= 49 + [49 / 4] + [20 / 4] – 2 × 20 + [26 × (10 + 1) / 10] + 1 – 1 
= 49 + [12.25] + 5 – 40 + [28.6] 
= 49 + 12 + 5 – 40 + 28 
= 54 (除以7餘5) 
即2049年10月1日(100週年國慶)是星期五。

再比如計算2006年4月4日,過程如下: 
w = y + [y / 4] + [c / 4] – 2c + [26(m + 1) / 10] + d – 1 
= 6 + [6 / 4] + [20 / 4] – 2 * 20 + [26 * (4 + 1) / 10] + 4 – 1 
= -12 (除以7餘5,注意對負數的取模運算!實際上應該是星期二而不是星期五)

不過要注意的是,蔡勒公式只適合於1582年(明朝萬曆十年)10月15日之後的情形。

4. 具體實現

清楚了上面這些內容,我們來看一下用C怎麼實現Unix時間戳和本地時間的相互轉化,並根據指定的時間算出對應的日期

#include <stdio.h>
#include <stdbool.h>

#define UTC_BASE_YEAR 1970
#define MONTH_PER_YEAR 12
#define DAY_PER_YEAR 365
#define SEC_PER_DAY 86400
#define SEC_PER_HOUR 3600
#define SEC_PER_MIN 60

/* 每個月的天數 */
const unsigned char g_day_per_mon[MONTH_PER_YEAR] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

/* 自定義的時間結構體 */
typedef struct
{
    unsigned short nYear;
    unsigned char nMonth;
    unsigned char nDay;
    unsigned char nHour;
    unsigned char nMin;
    unsigned char nSec;
    unsigned char DayIndex; /* 0 = Sunday */
} mytime_struct;

/* * 功能: * 判斷是否是閏年 * 參數: * year:需要判斷的年份數 * * 返回值: * 閏年返回1,否則返回0 */
unsigned char applib_dt_is_leap_year(unsigned short year)
{
    /*----------------------------------------------------------------*/
    /* Local Variables */
    /*----------------------------------------------------------------*/

    /*----------------------------------------------------------------*/
    /* Code Body */
    /*----------------------------------------------------------------*/
    if ((year % 400) == 0) {
        return 1;
    } else if ((year % 100) == 0) {
        return 0;
    } else if ((year % 4) == 0) {
        return 1;
    } else {
        return 0;
    }
}

/* * 功能: * 得到每個月有多少天 * 參數: * month:需要得到天數的月份數 * year:該月所對應的年份數 * * 返回值: * 該月有多少天 * */
unsigned char applib_dt_last_day_of_mon(unsigned char month, unsigned short year)
{
    /*----------------------------------------------------------------*/
    /* Local Variables */
    /*----------------------------------------------------------------*/

    /*----------------------------------------------------------------*/
    /* Code Body */
    /*----------------------------------------------------------------*/
    if ((month == 0) || (month > 12)) {
        return g_day_per_mon[1] + applib_dt_is_leap_year(year);
    }

    if (month != 2) {
        return g_day_per_mon[month - 1];
    } else {
        return g_day_per_mon[1] + applib_dt_is_leap_year(year);
    }
}

/* * 功能: * 根據給定的日期得到對應的星期 * 參數: * year:給定的年份 * month:給定的月份 * day:給定的天數 * * 返回值: * 對應的星期數,0 - 星期天 ... 6 - 星期六 */
unsigned char applib_dt_dayindex(unsigned short year, unsigned char month, unsigned char day)
{
    char century_code, year_code, month_code, day_code;
    int week = 0;

    century_code = year_code = month_code = day_code = 0;

    if (month == 1 || month == 2) {
        century_code = (year - 1) / 100;
        year_code = (year - 1) % 100;
        month_code = month + 12;
        day_code = day;
    } else {
        century_code = year / 100;
        year_code = year % 100;
        month_code = month;
        day_code = day;
    }

    /* 根據蔡勒公式計算星期 */
    week = year_code + year_code / 4 + century_code / 4 - 2 * century_code + 26 * ( month_code + 1 ) / 10 + day_code - 1;
    week = week > 0 ? (week % 7) : ((week % 7) + 7);

    return week;
}

/* * 功能: * 根據UTC時間戳得到對應的日期 * 參數: * utc_sec:給定的UTC時間戳 * result:計算出的結果 * daylightSaving:是否是夏令時 * * 返回值: * 無 */
void utc_sec_2_mytime(unsigned int utc_sec, mytime_struct *result, bool daylightSaving)
{
    /*----------------------------------------------------------------*/
    /* Local Variables */
    /*----------------------------------------------------------------*/
    int sec, day;
    unsigned short y;
    unsigned char m;
    unsigned short d;
    unsigned char dst;

    /*----------------------------------------------------------------*/
    /* Code Body */
    /*----------------------------------------------------------------*/

    if (daylightSaving) {
        utc_sec += SEC_PER_HOUR;
    }

    /* hour, min, sec */
    /* hour */
    sec = utc_sec % SEC_PER_DAY;
    result->nHour = sec / SEC_PER_HOUR;

    /* min */
    sec %= SEC_PER_HOUR;
    result->nMin = sec / SEC_PER_MIN;

    /* sec */
    result->nSec = sec % SEC_PER_MIN;

    /* year, month, day */
    /* year */
    /* year */
    day = utc_sec / SEC_PER_DAY;
    for (y = UTC_BASE_YEAR; day > 0; y++) {
        d = (DAY_PER_YEAR + applib_dt_is_leap_year(y));
        if (day >= d)
        {
            day -= d;
        }
        else
        {
            break;
        }
    }

    result->nYear = y;

    for (m = 1; m < MONTH_PER_YEAR; m++) {
        d = applib_dt_last_day_of_mon(m, y);
        if (day >= d) {
            day -= d;
        } else {
            break;
        }
    }

    result->nMonth = m;
    result->nDay = (unsigned char) (day + 1);
    /* 根據給定的日期得到對應的星期 */
    result->DayIndex = applib_dt_dayindex(result->nYear, result->nMonth, result->nDay);
}

/* * 功能: * 根據時間計算出UTC時間戳 * 參數: * currTime:給定的時間 * daylightSaving:是否是夏令時 * * 返回值: * UTC時間戳 */
unsigned int mytime_2_utc_sec(mytime_struct *currTime, bool daylightSaving)
{
    /*----------------------------------------------------------------*/
    /* Local Variables */
    /*----------------------------------------------------------------*/
    unsigned short i;
    unsigned int no_of_days = 0;
    int utc_time;
    unsigned char dst;

    /*----------------------------------------------------------------*/
    /* Code Body */
    /*----------------------------------------------------------------*/
    if (currTime->nYear < UTC_BASE_YEAR) {
        return 0;
    }

    /* year */
    for (i = UTC_BASE_YEAR; i < currTime->nYear; i++) {
        no_of_days += (DAY_PER_YEAR + applib_dt_is_leap_year(i));
    }

    /* month */
    for (i = 1; i < currTime->nMonth; i++) {
        no_of_days += applib_dt_last_day_of_mon((unsigned char) i, currTime->nYear);
    }

    /* day */
    no_of_days += (currTime->nDay - 1);

    /* sec */
    utc_time = (unsigned int) no_of_days * SEC_PER_DAY + (unsigned int) (currTime->nHour * SEC_PER_HOUR +
                                                                currTime->nMin * SEC_PER_MIN + currTime->nSec);

    if (dst && daylightSaving) {
        utc_time -= SEC_PER_HOUR;
    }

    return utc_time;
}

int main(int argc, char *argv[])
{
    mytime_struct my_time;
    unsigned int sec;
    char *DayIndex[] = {"Sun.", "Mon.", "Tues.", "Wed.", "Thur.", "Fri.", "Sat."};

    /* 這裏根據UTC時間戳計算出來的時間是零時區的時間,所以如果要轉化成北京時間就需要多加8小時 */
    utc_sec_2_mytime(1484537668 + 8 * SEC_PER_HOUR, &my_time, false);


    printf("%d-%d-%d %d:%d:%d %s\n", my_time.nYear, my_time.nMonth, my_time.nDay,
            my_time.nHour, my_time.nMin, my_time.nSec, DayIndex[my_time.DayIndex]);

    sec = mytime_2_utc_sec(&my_time, false);
    printf("sec = %d\n", sec);

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266

基本上的內容就這些了,平常最好能夠直接使用系統提供的接口,如果不能使用的話就自己實現,根據上面的代碼修改成自己需要的,如果有不對的地方,希望能夠批評指正

点赞