Unix时间戳转化为UTC日期格式(Unix 50周年快乐)

前言

1970年为Unix元年,今年(2020年)恰好是Unix诞生50周年。
Unix 50

Unix系统时间

Unix系统内记录的时间是一个时间戳,所谓时间戳,就是当前时间距离1970年1月1日0时0分0秒经过的总秒数。
现今Linux系统中所有时区的时间,都是由Unix时间戳转化而来的。以上就是Unix系统时间的含义,本篇博客主要讲述Unix时间戳转化为UTC时间格式的方法,并给出相应的C源码。

转化原理

我们规定,转化而成的UTC日期时间格式为“yyyy-mm-dd HH:MM:SS”,实现此功能的函数形式为:

char *getUTCtimeString(unsigned long date_sec);

为了代码的可读性,引出下面的宏:

#define UNIX_YEAR       1970
#define YEAR            365
#define DAY             (24*60*60)
#define HOUR            (60*60)
#define MINUTE          60

以上的宏给出了Unix元年的时间,平年一年的总天数,以及日时分包含的秒数。
由时间戳的定义,我们不难计算出时间戳中包含的总天数和对应的时分秒等相关的信息,计算的方法如下:

int day, hour, minute, second;
day = date_sec/DAY;
hour = date_sec%DAY/HOUR;
minute = date_sec%DAY%HOUR/MINUTE;
second = date_sec%DAY%HOUR%MINUTE;

计算原理很容易理解,就是依照各个时间单位之间的余数和商的关系,来求得结果。此时求得的时分秒的数值,就是最终对应的计算结果了。

接下来就是在已知总天数的基础上,求得时间格式中年的值了。我们知道,公元纪年的年份中,年有平年与闰年之分,其中能被4整除但是无法被100整除的年份是闰年,其他的年份为平年,这在计算中是要慎重考虑的。
由总天数求当前所在年份的方法如下:

int curr_year = UNIX_YEAR;
while(day >= YEAR){
	day-=YEAR;
	if(curr_year%4==0 && curr_year%100!=0)
		day--;
	curr_year++;
}

计算的方法就是,从Unix元年开始算起,每过一年day的值先减去一个YEAR值(365),然后curr_year的值加一。通过if条件判端得出curr_year的值是否是闰年值,若是,则此时day应多减去一天。最后循环余下的day值是此时间戳在一年中偏移的天数,大小在0~364,通过这时的day值,可以求得对应的月份和日份的值。

最后就是通过day值求月份和日份的值了,这也是时间戳转化中最难也是最繁琐的一步,其对应的计算方法如下:

int month, day_temp;
for(month=1; ;month++){
	day_temp = day;
	switch(month){
		case 1:
			day-=31;break;
		case 2:{
			day-=28;
			if(curr_year%4==0 && curr_year%100!=0)
				day--;
			break;
		}
		case 3:
			day-=31;break;
		case 4:
			day-=30;break;
		case 5:
			day-=31;break;
		case 6:
			day-=30;break;
		case 7:
			day-=31;break;
		case 8:
			day-=31;break;
		case 9:
			day-=30;break;
		case 10:
			day-=31;break;
		case 11:
			day-=30;break;
		case 12:
			day-=31;break;
	}
    if(day <= 0){
    	if(day == 0)
    		day++;
    	else
        	day = day_temp;
        break;
    }
}

思想其实很简单,就是一个月一个月地减,直到所剩的day值相对于下一个月来说不能减为止。由于每个月的天数是无规律可寻的,所以这种情形,使用switch-case语句再合适不过了。怎样才能使循环终止呢?方法很简单,我们可以试探性地在已知month值的情形下在switch中先减去一定的天数,若减去之后day的值是非正数,则代表循环应该结束,否则,将day的值赋给day_temp暂存,开启另一次switch。若减过之后day值为0,表示一个月已经恰好过完了,此时day应该赋值为1。
上面计算得到的就是最终的month值和day值了。

至此,curr_year,month,day,hour,minute,second值计算完毕,直接通过sprintf格式化生成对应的字符串即可:

char					*str_date = (char *)malloc(24);
sprintf(str_date, "%4d-%02d-%02d %02d:%02d:%02d", curr_year, month, day, hour, minute, second);

代码整合

getUTCtimeString.h

#ifndef _GETUTCTIMESTRING_H
#define _GETUTCTIMESTRING_H

#include <stdio.h>
#include <stdlib.h>

#define UNIX_YEAR       1970
#define YEAR            365
#define DAY             (24*60*60)
#define HOUR            (60*60)
#define MINUTE          60

extern char *getUTCtimeString(unsigned long date_sec);

#endif

getUTCtimeString.c

#include "getUTCtimeString.h"

char *getUTCtimeString(unsigned long date_sec){
	
	char					*str_date = (char *)malloc(24);

	int						curr_year = UNIX_YEAR, day_temp;
	int						day, month, hour, minute, second;
	
	day = date_sec/DAY;
	hour = date_sec%DAY/HOUR;
	minute = date_sec%DAY%HOUR/MINUTE;
	second = date_sec%DAY%HOUR%MINUTE;
	
	while(day >= YEAR){
		day-=YEAR;
		if(curr_year%4==0 && curr_year%100!=0)
			day--;
		curr_year++;
	}

	for(month=1; ;month++){
		day_temp = day;
		switch(month){
			case 1:
				day-=31;break;
			case 2:{
				day-=28;
				if(curr_year%4==0 && curr_year%100!=0)
					day--;
				break;
			}
			case 3:
				day-=31;break;
			case 4:
				day-=30;break;
			case 5:
				day-=31;break;
			case 6:
				day-=30;break;
			case 7:
				day-=31;break;
			case 8:
				day-=31;break;
			case 9:
				day-=30;break;
			case 10:
				day-=31;break;
			case 11:
				day-=30;break;
			case 12:
				day-=31;break;
		}
        if(day <= 0){
            day = day_temp;
            break;
        }
	}

    if(day == 0)
        day++;
	
	sprintf(str_date, "%4d-%02d-%02d %02d:%02d:%02d", curr_year, month, day, hour, minute, second);
	
	return str_date;
}

后记

50年,沧海桑田;50年,斗转星移。时间一次次更迭过往,还在一步步实现愿望。从百变多用的Linux,到引领智能手机潮流的IOS,再到国人为止骄傲的HarmonyOS… 时代在变,创新永不止境,向半世纪以来推动IT发展的英雄们致敬!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值