Unix时间戳:Unix 时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。此值也和时区无关。一个Unix 时间戳,在不同时区对应不同时区日期时间。
1、获取Unix时间戳
在linux系统中使用time(NULL)获取到的就是Unix时间戳(单位:秒,与时区无关,与夏令时无关)。
time(NULL); // 获取unix时间戳,与时区无关
同样gettimeofday获取的也是Unix时间戳(单位:微妙,与时区无关,与夏令时无关)。
uint64_t intervals = 0;
timeval tv;
gettimeofday(&tv, NULL);
intervals = tv.tv_sec * 1000000 + tv.tv_usec;
注意C/C++与golang不同,golang中使用如下方式获取Unix时间戳(单位:秒),不受夏令时影响
time.Now().Unix()
2、Unix时间戳与日期
在编程中全部使用unix时间戳一般不会造成混乱(注意夏令时影响),容易出问题的地方在于与日期的转换。
Unix时间戳与时区无关,定义为从格林威治时间1970年01月01日00时00分00秒起至现在的总秒数。因此,严格来说,不管你处在地球上的哪个地方,任意时间点的时间戳都是相同的。Unix时间戳对应的日期就是格林威治所在时区的日期,即0时区的日期。
1)Unix时间戳转格林尼治标准时间(GMT):
time_t time = time(NULL);
struct tm utc_tm;
gmtime_r(&time, &utc_tm); // 生成的日期为0时区日期。时区为0
2)Unix时间戳转本地日期:
time_t time = time(NULL);
struct tm local_tm;
localtime_r(&time, &local_tm); // 转换为本地日期时间,携带本地时区,受夏令时影响
struct tm结构体定义如下,
struct tm {
int tm_sec; /* Seconds [0, 60] */
int tm_min; /* Minutes [0, 59] */
int tm_hour; /* Hour [0, 23] */
int tm_mday; /* Day of the month [1, 31] */
int tm_mon; /* Month [0, 11] (January = 0) */
int tm_year; /* Year minus 1900 */
int tm_wday; /* Day of the week [0, 6] (Sunday = 0) */
int tm_yday; /* Day of the year [0, 365] (Jan/01 = 0) */
int tm_isdst; /* Daylight savings flag */
long tm_gmtoff; /* Seconds East of UTC */
const char *tm_zone; /* Timezone abbreviation */
};
tm_gmtoff, tm_zone:
Since glibc 2.20:
_DEFAULT_SOURCE
glibc 2.20 and earlier:
_BSD_SOURCE
3)本地日期转Unix时间戳:
输入日期不变,在不同时区中,mktime返回值不同,受夏令时影响。另外与tm_gmtoff设置无关。
mktime返回值的意义:当前时区的日期时间为xxxx-xx-xx xx:xx:xx时,返回对应的unix时间戳(注意unix时间戳和时区无关,可以这样理解:unix时间戳和0时区日期时间一一对应,0时区日期时间在不同时区对应的日期时间不同)。
例如输入为:2024-04-18 12:30:20
东八区(北京时间)结果为:1713414620
西五区(纽约时间)结果为:1713457820
两者差距为12小时(本来应该差13小时,但是由于夏令时的影响,西五区(纽约时间)实为西四区时间)。
void data_time_string_to_int(const char* date, int* year, int* month, int* day, int* hour, int* minute, int* second)
{
if (14 == strlen(date)) { //20200707213148
if(sscanf(date, "%4d%2d%2d%2d%2d%2d",
year, month, day, hour, minute, second));
} else if (19 == strlen(date)) { //2020-07-07 21:33:15
if(sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d",
year, month, day, hour, minute, second));
}
}
time_t date_2_time(const char* date)
{
int year = 1900, month = 1, day = 0, hour = 0, minute = 0, second = 0;
data_time_string_to_int(date, &year, &month, &day, &hour, &minute, &second);
struct tm tmDate = { 0 };
//开始时间
tmDate.tm_year = year - 1900;
tmDate.tm_mon = month - 1;
tmDate.tm_mday = day;
tmDate.tm_hour = hour;
tmDate.tm_min = minute;
tmDate.tm_sec = second;
// -1:自动判断系统是否处于夏令时状态,如果是受系统夏令时影响。如果设置为0,则不受夏令时影响
tmDate.tm_isdst = -1;
// mktime的结果为系统时区对应的UTC时间,受夏令时影响
return mktime(&tmDate);
}
4)RFC3339格式(2018-06-19T14:28:00+08:00)转Unix时间戳
void data_time_string_to_int_rfc3339(const char* date, int* year, int* month, int* day,
int* hour, int* minute, int* second, int* msec, float* timeZone)
{
if (strlen(date) == 14) {
if (sscanf(date, "%4d%2d%2d%2d%2d%2d",
year, month, day, hour, minute, second));
*msec = 0;
*timeZone = get_time_zone();
} else if (strlen(date) == 19) {
if (sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d",
year, month, day, hour, minute, second));
*msec = 0;
*timeZone = get_time_zone();
} else {
if (sscanf(date, "%4d-%2d-%2dT%2d:%2d:%2d",
year, month, day, hour, minute, second));
*msec = 0;
//尝试解析2018-06-19T14:28:00+08:00
const char* dot = strstr(date + 12, ".");
const char* plus = strstr(date + 12, "+");
const char* minus = strstr(date + 12, "-");
const char* zoreB = strstr(date + 12, "Z");
const char* zoreb = strstr(date + 12, "z");
if (dot) {
char ms[8] = { 0 };
if (strncpy_s(ms, sizeof(ms), dot + 1, 3));
*msec = atoi(ms);
}
// +08:00/Z
if (zoreB || zoreb) {
*timeZone = 0;
return;
}
const char* zone_opt = (NULL != plus ? plus : minus);
if (NULL == zone_opt || strlen(zone_opt) < 6) {
printf("not support, %s\n", date);
return;
}
const char* zone_colon = strstr(zone_opt + 1, ":");
if (NULL == zone_colon || strlen(zone_colon) < 3) {
printf("not support, %s\n", date);
return;
}
char zone[8] = { 0 };
strncpy_s(zone, sizeof(zone), zone_opt + 1, 2);
*timeZone = atof(zone);
strncpy_s(zone, sizeof(zone), zone_colon + 1, 2);
*timeZone += (atof(zone) / 60);
*timeZone *= ((NULL == minus) ? 1.0 : -1.0);
}
}
// 获取真实时区,不受夏令时干扰
float get_time_zone()
{
time_t timeUtc = 0;
struct tm dateTmTime;
float timeZone = 0.0;
// utc时间为0,对应1970年1月1日 0时0分0秒,北半球为冬天不需要考虑夏令时,但是南半球需要考虑夏令时。
dateTmTime.tm_isdst = 0;
localtime_r(&timeUtc, &dateTmTime);
timeZone = (dateTmTime.tm_hour > 12) ? (dateTmTime.tm_hour -= 24) : dateTmTime.tm_hour;
if (dateTmTime.tm_min > 0) {
// 世界上部分国家使用非整时区,例如:伊朗用东3.5时区,阿富汗用东4.5时区,印度、尼泊尔用东5.5时区,新西兰的查塔姆群岛用西12.75时区(-12:45),等等
timeZone += 1.0 * dateTmTime.tm_min / 60;
}
// 如果存在夏令时需要减去夏令时偏移
if (dateTmTime.tm_isdst) {
timeZone -= 1;
}
return timeZone;
}
time_t rfc3339_2_utc_timestamp(const char* date)
{
int year = 1900, month = 1, day = 0, hour = 0, minute = 0, second = 0, msec = 0;
float time_zone = 0;
data_time_string_to_int_rfc3339(date, &year, &month, &day, &hour, &minute, &second, &msec, &time_zone);
struct tm tmDate = { 0 };
// 获取本地真实时区
float local_zone = get_time_zone();
// 1.将给定时间作为本时区时刻,真实的时间增加了offset
int offset = (time_zone - local_zone) * 3600;
tmDate.tm_year = year - 1900;
tmDate.tm_mon = month - 1;
tmDate.tm_mday = day;
tmDate.tm_hour = hour;
tmDate.tm_min = minute;
tmDate.tm_sec = second;
tmDate.tm_isdst = 0; // 0:tm_isdst不受夏令时干扰
// 2.mktime的结果为真实时区对应的UTC时间
uint64_t timeStamp = mktime(&tmDate);
// 3.减去多出的offset,获得真实时刻对应的UTC时间
timeStamp = timeStamp - offset;
return timeStamp;
}
注意Unix时间戳本身不会造成混乱,造成混乱的是不带时区的日期时间。
3、linux下夏令时的实现
将linux时区设置为美国西五区,夏令时生效时显示位于西四区。此时mktime(tm_isdst=-1)返回的时间戳意义为:当西四区的日期是xxxx-xx-xx xx:xx:xx时,对应的unix时间戳
参照:
时区、时间戳、 时区、格林威治(GMT)、协调世界时(UTC)的关系 - 简书 (jianshu.com)