1.现象描述

将本地系统时区设置为“(UTC)都柏林,爱丁堡,里斯本,伦敦”,并开启自动夏令时,将系统时间设置为处于当地的夏令时时间段内。代码中每次对时间进行转换,结果都会偏移一个小时。

2.现象分析

定位代码到C标准库time.h的函数mktime,struct tm结构的年月日的表示时间通过mktime转换为时间戳之后会偏移一个小时。原因是夏令时标志位tm_isdst没有设置,默认为0,表示不是夏令时。系统在调用mktime时会判断当前时区是否启用夏令时,如果启用夏令时,而传入的时间事实上处于夏令时,但是夏令时标志为0,系统就会加一个小时做默认的转换,并将夏令时标志置为1。

3.解决问题

首先明确什么是夏令时。维基百科的说明如下:

夏时制或夏令时间(英语:Summer time),又称日光节约时制、日光节约时间(英语:Daylight saving time),是一种为节约能源而人为规定地方时间的制度,在这一制度实行期间所采用的统一时间称为“夏令时间”。一般在天亮早的夏季人为将时间提前一小时,可以使人早起早睡,减少照明量,以充分利用光照资源,从而节约照明用电。各个采纳夏时制的国家具体规定不同。

简单的说,某些国家会将一年中的某一段时间调快一个小时。那么在本机系统开启夏令时的情况下,调用mktime就有以下几种情况:

  • 传入的时间根据当地的夏令时规定正处于实行夏令时的时间段,且标志位为1,调用mktime不进行偏移。
  • 传入的时间根据当地的夏令时规定正处于实行夏令时的时间段,但标志位为0,调用mktime加一个小时,并置标志位为1。
  • 传入的时间根据当地的夏令时规定正处于实行夏令时的时间段,但标志位为-1,调用mktime不进行偏移,并置标志位为1。
  • 传入的时间根据当地的夏令时规定不处于实行夏令时的时间段,但标志位为1,调用mktime减一个小时,并置标志位为0。
  • 传入的时间根据当地的夏令时规定不处于实行夏令时的时间段,但标志位为0,调用mktime不进行偏移,并置标志位为0。
  • 传入的时间根据当地的夏令时规定不处于实行夏令时的时间段,但标志位为-1,调用mktime不进行偏移,并置标志位为0。
  • 可以发现,如果置夏令时标志为-1,实质上是让系统自己判断传入的时间是否是夏令时。

    传入时间处于非夏令时时间段:

    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
    struct tm stTime={0};
    time_t tTime;
    stTime.tm_year = 2014;
    stTime.tm_mon = 12;
    stTime.tm_mday = 26;
    stTime.tm_hour = 1;
    stTime.tm_min = 30;
    stTime.tm_sec = 12;
    stTime.tm_mon -= 1;
    stTime.tm_year -= 1900;
    stTime.tm_isdst = -1; // 设置夏令时标志为-1,表示该时间是否是夏令时未知
    printf("\n++++++++++[标记stTime.tm_isdst = %d]++++++++++\n\n", stTime.tm_isdst);
    printf("stTime时间:%04d%02d%02dT%02d%02d%02dZ 夏令时标志:%d\n",
    stTime.tm_year+1900, stTime.tm_mon+1, stTime.tm_mday, stTime.tm_hour, stTime.tm_min, stTime.tm_sec, stTime.tm_isdst);
    tTime = mktime(&stTime);
    printf("\n运行tTime = mktime(&stTime);语句之后\n\n");
    printf("stTime时间:%04d%02d%02dT%02d%02d%02dZ 夏令时标志:%d\n",
    stTime.tm_year+1900, stTime.tm_mon+1, stTime.tm_mday, stTime.tm_hour, stTime.tm_min, stTime.tm_sec, stTime.tm_isdst);
    stTime.tm_isdst = 0; // 设置夏令时标志为0,表示该时间不是夏令时
    printf("\n++++++++++[标记stTime.tm_isdst = %d]++++++++++\n\n", stTime.tm_isdst);
    printf("stTime时间:%04d%02d%02dT%02d%02d%02dZ 夏令时标志:%d\n",
    stTime.tm_year+1900, stTime.tm_mon+1, stTime.tm_mday, stTime.tm_hour, stTime.tm_min, stTime.tm_sec, stTime.tm_isdst);
    tTime = mktime(&stTime);
    printf("\n运行tTime = mktime(&stTime);语句之后\n\n");
    printf("stTime时间:%04d%02d%02dT%02d%02d%02dZ 夏令时标志:%d\n",
    stTime.tm_year+1900, stTime.tm_mon+1, stTime.tm_mday, stTime.tm_hour, stTime.tm_min, stTime.tm_sec, stTime.tm_isdst);
    stTime.tm_isdst = 1; // 设置夏令时标志为1,表示该时间是夏令时
    printf("\n++++++++++[标记stTime.tm_isdst = %d]++++++++++\n\n", stTime.tm_isdst);
    printf("stTime时间:%04d%02d%02dT%02d%02d%02dZ 夏令时标志:%d\n",
    stTime.tm_year+1900, stTime.tm_mon+1, stTime.tm_mday, stTime.tm_hour, stTime.tm_min, stTime.tm_sec, stTime.tm_isdst);
    tTime = mktime(&stTime);
    printf("\n运行tTime = mktime(&stTime);语句之后\n\n");
    printf("stTime时间:%04d%02d%02dT%02d%02d%02dZ 夏令时标志:%d\n",
    stTime.tm_year+1900, stTime.tm_mon+1, stTime.tm_mday, stTime.tm_hour, stTime.tm_min, stTime.tm_sec, stTime.tm_isdst);

    运行结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ++++++++++[标记stTime.tm_isdst = -1]++++++++++
    stTime时间:20141226T013012Z 夏令时标志:-1
    运行tTime = mktime(&stTime);语句之后
    stTime时间:20141226T013012Z 夏令时标志:0
    ++++++++++[标记stTime.tm_isdst = 0]++++++++++
    stTime时间:20141226T013012Z 夏令时标志:0
    运行tTime = mktime(&stTime);语句之后
    stTime时间:20141226T013012Z 夏令时标志:0
    ++++++++++[标记stTime.tm_isdst = 1]++++++++++
    stTime时间:20141226T013012Z 夏令时标志:1
    运行tTime = mktime(&stTime);语句之后
    stTime时间:20141226T003012Z 夏令时标志: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
    struct tm stTime={0};
    time_t tTime;
    stTime.tm_year = 2014;
    stTime.tm_mon = 8;
    stTime.tm_mday = 26;
    stTime.tm_hour = 1;
    stTime.tm_min = 30;
    stTime.tm_sec = 12;
    stTime.tm_mon -= 1;
    stTime.tm_year -= 1900;
    stTime.tm_isdst = -1; // 设置夏令时标志为-1,表示该时间是否是夏令时未知
    printf("\n++++++++++[标记stTime.tm_isdst = %d]++++++++++\n\n", stTime.tm_isdst);
    printf("stTime时间:%04d%02d%02dT%02d%02d%02dZ 夏令时标志:%d\n",
    stTime.tm_year+1900, stTime.tm_mon+1, stTime.tm_mday, stTime.tm_hour, stTime.tm_min, stTime.tm_sec, stTime.tm_isdst);
    tTime = mktime(&stTime);
    printf("\n运行tTime = mktime(&stTime);语句之后\n\n");
    printf("stTime时间:%04d%02d%02dT%02d%02d%02dZ 夏令时标志:%d\n",
    stTime.tm_year+1900, stTime.tm_mon+1, stTime.tm_mday, stTime.tm_hour, stTime.tm_min, stTime.tm_sec, stTime.tm_isdst);
    stTime.tm_isdst = 1; // 设置夏令时标志为1,表示该时间是夏令时
    printf("\n++++++++++[标记stTime.tm_isdst = %d]++++++++++\n\n", stTime.tm_isdst);
    printf("stTime时间:%04d%02d%02dT%02d%02d%02dZ 夏令时标志:%d\n",
    stTime.tm_year+1900, stTime.tm_mon+1, stTime.tm_mday, stTime.tm_hour, stTime.tm_min, stTime.tm_sec, stTime.tm_isdst);
    tTime = mktime(&stTime);
    printf("\n运行tTime = mktime(&stTime);语句之后\n\n");
    printf("stTime时间:%04d%02d%02dT%02d%02d%02dZ 夏令时标志:%d\n",
    stTime.tm_year+1900, stTime.tm_mon+1, stTime.tm_mday, stTime.tm_hour, stTime.tm_min, stTime.tm_sec, stTime.tm_isdst);
    stTime.tm_isdst = 0; // 设置夏令时标志为0,表示该时间不是夏令时
    printf("\n++++++++++[标记stTime.tm_isdst = %d]++++++++++\n\n", stTime.tm_isdst);
    printf("stTime时间:%04d%02d%02dT%02d%02d%02dZ 夏令时标志:%d\n",
    stTime.tm_year+1900, stTime.tm_mon+1, stTime.tm_mday, stTime.tm_hour, stTime.tm_min, stTime.tm_sec, stTime.tm_isdst);
    tTime = mktime(&stTime);
    printf("\n运行tTime = mktime(&stTime);语句之后\n\n");
    printf("stTime时间:%04d%02d%02dT%02d%02d%02dZ 夏令时标志:%d\n",
    stTime.tm_year+1900, stTime.tm_mon+1, stTime.tm_mday, stTime.tm_hour, stTime.tm_min, stTime.tm_sec, stTime.tm_isdst);

    运行结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ++++++++++[标记stTime.tm_isdst = -1]++++++++++
    stTime时间:20140826T013012Z 夏令时标志:-1
    运行tTime = mktime(&stTime);语句之后
    stTime时间:20140826T013012Z 夏令时标志:1
    ++++++++++[标记stTime.tm_isdst = 1]++++++++++
    stTime时间:20140826T013012Z 夏令时标志:1
    运行tTime = mktime(&stTime);语句之后
    stTime时间:20140826T013012Z 夏令时标志:1
    ++++++++++[标记stTime.tm_isdst = 0]++++++++++
    stTime时间:20140826T013012Z 夏令时标志:0
    运行tTime = mktime(&stTime);语句之后
    stTime时间:20140826T023012Z 夏令时标志:1

    4.总结

    mktime对时间进行偏移的原理已经清楚了,那么代码事实上只是要保证无论在什么时区下,是否开启夏令时,时间的转换都不能出现偏移。那就置夏令时标志位为-1,系统会自动判断传入的时间是否是夏令时,并相应地标志夏令时标志位。