[资料分享] 单片机的UTC时间时区转换

fish001   2019-10-11 14:44 楼主

一、创作背景

        之前做了个关于STM32低功耗信号采集的项目,使用STM32L031单片机,项目要求是这样的:

设备使用电池供电,检测传感器的信号,并将这个信号无线传出来。设备每次采集信号到传输出去的时间就几十mS,其他时间进入深度休眠,以节省电量。

        这个项目最主要的是,设备每天工作时间不确定,客户可能要求每个小时采集一次,也可能每天采集一次,或者只工作日才采集。

        因为工作的间隔不确定,而且通过无线网络能够轻易的得到UTC时间。所以,我决定做一个万年历,使用单片机的RTC外设和闹钟功能,每次设备采集完成之后,进入休眠之前,根据客户设置的工作机制,将下一次的RTC闹钟设置好,通过RTC的闹钟中断事件将程序唤醒。

 

二、UTC的调查

        经过调研UTC存在一个Y2038的一个BUG,即在2038119日(星期二)031407amGMT)正式发。因为32位电脑系统都用带符号32位整型来存储time_t的值,也就是说t_time只能用31位二进制数来表示(第一位用来表示正负号),而其最大值转换为十进制是2147483647,换算成日期和时间刚好是2038119031407amGMT),而这一秒过后,t_time的值将变成-2147483647这样32位软硬件系统的日期时间显示就都乱套了。

        现在离2038年也不是太远,所以这个隐患不能在这里埋下(必进这次写的代码下次我还想在用,总不能下次在研究)

 

三、方案设计

        由于STM32RTC肯定是32位的,摆在我面前的有两个方法:

        方法一:在通用的规则中,UTC=0 表示1970/1/1 0:0:0 我在项目中将这个时间进行平移,比如移到2010/1/1 0:0:0,这样Y2038就变成了2078

        方法二:C语言的time.h文件中,用的是 int计秒。我用准备用uint进行计秒,这样,就可以将千年虫的BUG推迟到北京时间2106/2/7 14:28:15

        所以,最终,我还是毫不犹豫的选择了方案二,因为这样不用去改动大家都遵循的标准。

 

四、程序设计

定义程序结构提类型

 

typedef struct
{
	uint8_t 	tm_sec;  	
	uint8_t 	tm_min;   
	uint8_t 	tm_hour;  
	uint8_t 	tm_mday;  
	uint8_t 	tm_mon;   
  uint8_t 	tm_wday;  	
	uint16_t 	tm_year;  
  //uint16_t 	tm_yday;  
}TimeType;
创建变量
 
//平年累积月分天数表 static const uint16_t NonleapYearMonth[12] = { 31,//1 31 + 28, //2 31 + 28 + 31, //3 31 + 28 + 31 + 30, //4 31 + 28 + 31 + 30 + 31, //5 31 + 28 + 31 + 30 + 31 + 30, //6 31 + 28 + 31 + 30 + 31 + 30 + 31, //7 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, //8 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, //9 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, //10 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, //11 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, //12 }; //闰年累积月分天数表 static const uint16_t LeapYearMonth[12] = { 31,//1 31 + 29, //2 31 + 29 + 31, //3 31 + 29 + 31 + 30, //4 31 + 29 + 31 + 30 + 31, //5 31 + 29 + 31 + 30 + 31 + 30, //6 31 + 29 + 31 + 30 + 31 + 30 + 31, //7 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31, //8 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30, //9 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, //10 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, //11 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, //12 };
uint8_t alg_IsLeapYear(uint32_t year)
{
	if((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))) //能被4整除,不能被百整除,能被400整除。
	{
		return 1;				//闰年
	}
	else
	{
		return 0;				//平年
	}
}

TimeType alg_Utc2LocalTime(uint32_t UtcVal, int8_t TimeZone)
{
	uint32_t i = 0;
	TimeType LocalTime;
	uint32_t Hour,Days,Year;

	
	LocalTime.tm_sec	=	UtcVal%60;						//得到秒余数
	LocalTime.tm_min	=	(UtcVal/60)%60;				//得到整数分钟数
	
	Hour	=	(UtcVal/60)/60;										//得到整数小时数
	LocalTime.tm_hour	=	Hour%24+TimeZone;			//得到小时余数+时区
	if(LocalTime.tm_hour>23)
	{
		LocalTime.tm_hour-=24;
		Days=Hour/24+1;
	}
	else
	{
		Days=Hour/24;
	}
	LocalTime.tm_wday=(Days+4)%7;							//计算星期,0-表示星期天		注:1970-1-1 是星期4

													//注:400年=146097天,100年=36524天,4年=1461天
	Year = 1970;															//utc时间从1970开始
	Year += (Days/146097)*400;

	Days %= 146097;														//计算400年内的剩余天数
	Year += (Days/36525)*100;

	Days %= 36525; 
	Year += (Days/1461)*4;

	Days %= 1461;															//计算4年内剩余天数,1970平1972闰年
	while( Days > 365)
	{
		if(alg_IsLeapYear(Year))
		{
			Days--;
		}
		Days -= 365;
		Year++;
	}
	if (!alg_IsLeapYear(Year) && (Days == 365) )
	{
		Year++;
		LocalTime.tm_mday	=1;		
		LocalTime.tm_mon	=1;
		LocalTime.tm_year	=Year;
		return LocalTime;
	}
	LocalTime.tm_year	=Year;
	LocalTime.tm_mon=0;
	LocalTime.tm_mday=0;
	if (alg_IsLeapYear(Year))									//本年是闰年
	{
		for (i = 0; i < 12; i++)
		{
			if (Days < LeapYearMonth[i])
			{
				LocalTime.tm_mon = i + 1;
				if (i == 0)
				{
					LocalTime.tm_mday = Days;
				}
				else
				{
					LocalTime.tm_mday = Days - LeapYearMonth[i - 1];
				}
				LocalTime.tm_mday++;
				return LocalTime;
			}
		}
	}
	else																			//本年是平年
	{
		for (i = 0; i < 12; i++)
		{
			if (Days < NonleapYearMonth[i])
			{
				LocalTime.tm_mon  = i + 1;
				if (i == 0)
				{
					LocalTime.tm_mday = Days;
				}
				else
				{
					LocalTime.tm_mday = Days - NonleapYearMonth[i - 1];
				}
				LocalTime.tm_mday++;
				return LocalTime;
			}
		}
	}
	return LocalTime;
}


uint32_t  alg_LocalTime2Utc(TimeType LocalTime, int8_t TimeZone)
{
	uint32_t y = LocalTime.tm_year -1970;							//看一下有几个400年,几个100年,几个4年
	uint32_t dy = (y / 400);
	uint32_t days = dy *  (400 * 365 + 97);						//400年的天数
	
	dy = (y % 400) / 100;
	days += dy * (100 * 365 + 25);										//100年的天数
	
	dy = (y % 100) / 4;
	days += dy * (4 * 365 + 1);												//4年的天数
	
	dy = y % 4;																				//注意:这里1972是闰年,与1970只差2年
	days += dy * 365 ;

	if(dy == 3)																				//这个4年里,有没有闰年就差1天	
	{
		days++;	//只有这个是要手动加天数的,因为1973年计算时前面的天数按365天算,1972少算了一天
	}
	
	if (LocalTime.tm_mon != 1)
	{
		if(alg_IsLeapYear(LocalTime.tm_year))												//看看今年是闰年还是平年
		{
			days += LeapYearMonth[(LocalTime.tm_mon - 1) - 1];
		} 
		else 
		{
			days += NonleapYearMonth[(LocalTime.tm_mon  - 1) - 1]; 		//如果给定的月份数为x则,只有x-1个整数月
		}
	}
	days += LocalTime.tm_mday - 1;
	
	return (days * 24 * 3600 + ((uint32_t)LocalTime.tm_hour - TimeZone)* 3600 + (uint32_t)LocalTime.tm_min * 60 + (uint32_t)LocalTime.tm_sec);  
}

以上,程序的API基本就完成了。

调用的时候,只需要使用如下两个函数就能进行互转:
TimeType  alg_Utc2LocalTime(uint32_t UtcVal, int8_t TimeZone);
uint32_t  alg_LocalTime2Utc(TimeType LocalTime, int8_t TimeZone);

回复评论

暂无评论,赶紧抢沙发吧
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复