mznote mznote
首页
基础知识
编程语言
实用技术
更多
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

sbwcwso

Just do it.
首页
基础知识
编程语言
实用技术
更多
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • C
    • float有效数字位的问题
    • 2038 年问题的进一步思考
      • 2038 问题简介
      • 用代码解释 2038 问题
      • 64 位 time_t 的时间界限在哪里
        • 使得 gmtime(input) 不出错的 input 值的范围
        • 使得 asctime(gmtime(input)) 不出错的 input 的范围
        • 当前 C 标准库下的时间界限
  • 编程语言
  • C
  • 2038 年问题的进一步思考
木子识时务
2022-03-06

2038 年问题的进一步思考

# 2038 问题简介

2038 问题

  • Wiki: Year 2038 problem (opens new window)

  • 简要说就是 Unix 用距离 00:00:00 UTC on 1 January 1970 的秒数来储存时间, C 中储存到 time_t 中

    • 之前采用的是 32 位的 int 储存,至 2038 年便会产生溢出问题

# 用代码解释 2038 问题

查看 C 中 time_t 的数据格式

  • what-is-time-t-ultimately-a-typedef-to (opens new window)
  • 当前主流操作系统和 C 编译器,time_t 均采用 64 位的 long 储存

用代码计算 32 位的 time_t 所能表示的时间范围

  • 代码
    #include <limits.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    
    int main()
    {
      time_t int_max, int_min;
      int_max = INT_MAX;
      int_min = INT_MIN;
    
      fputs(asctime(gmtime(&int_max)), stdout);
      fputs(asctime(gmtime(&int_min)), stdout);
    
      return EXIT_SUCCESS;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
  • 输出为
    Tue Jan 19 03:14:07 2038
    Fri Dec 13 20:45:52 1901
    
    1
    2

# 64 位 time_t 的时间界限在哪里

直接套用 32 位下表示时间的代码,会出错

  • 代码
    #include <limits.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    
    int main()
    {
      time_t long_max, long_min;
      long_max = LONG_MAX;
      long_min = LONG_MIN;
    
      fputs(asctime(gmtime(&long_max)), stdout);
      fputs(asctime(gmtime(&long_min)), stdout);
    
      return EXIT_SUCCESS;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
  • 错误如下
    [1]    641381 segmentation fault (core dumped)  ./a.out
    
    1

错误分析

  • 如果 time_t 用 long ,即 64 位表示,则理论上,其可表示的年限至少为

      • 假设一年有 365 天
      • 大约能表示至 2900 亿年后
  • gmtime 的返回值为 struct *tm,其在 /usr/include/bits/types/struct_tm.h 中的定义如下

    /* ISO C `broken-down time' structure.  */
    struct tm
    {
      int tm_sec;			/* Seconds.	[0-60] (1 leap second) */
      int tm_min;			/* Minutes.	[0-59] */
      int tm_hour;			/* Hours.	[0-23] */
      int tm_mday;			/* Day.		[1-31] */
      int tm_mon;			/* Month.	[0-11] */
      int tm_year;			/* Year	- 1900.  */
      int tm_wday;			/* Day of week.	[0-6] */
      int tm_yday;			/* Days in year.[0-365]	*/
      int tm_isdst;			/* DST.		[-1/0/1]*/
    
    # ifdef	__USE_MISC
      long int tm_gmtoff;		/* Seconds east of UTC.  */
      const char *tm_zone;		/* Timezone abbreviation.  */
    # else
      long int __tm_gmtoff;		/* Seconds east of UTC.  */
      const char *__tm_zone;	/* Timezone abbreviation.  */
    # endif
    };
    
    #endif
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    • 可以看到 tm_year 是用 int 储存的,其最大值 2147483647 要明显小于 LONG_MAX 所表示的年限,这个即是程序出错的原因

# 使得 gmtime(input) 不出错的 input 值的范围

分析

  • 从 struct tm 构造实际的日期时,需要将 tm_year 加上 1900,为了保证正确性,要确保加上 1900 后不会溢出
  • 所以猜测对于 gmtime 来说,其合法输入 input 转换为 struct tm 后 所对应的 tm_year 在 INT_MIN ~ INT_MAX 之间
    • 由此可借助代码来计算其输入的取值范围

代码验证

  • 代码
    #include <limits.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    
    int main()
    {
      time_t long_max, long_min;
      struct tm t;
      struct tm *tm;
    
      // Set the time zone to UTC
      tzname[0] = tzname[1] = "GMT";
      timezone = 0;
      daylight = 0;
    
      setenv("TZ", "UTC", 1);
    
      t.tm_mon = 11; // Dec
      t.tm_mday = 31;
      t.tm_hour = 23;
      t.tm_min = 59;
      t.tm_sec = 59;
      t.tm_year = INT_MAX;
      t.tm_wday = t.tm_yday = t.tm_isdst = 0;
    
      long_max = mktime(&t);
      for (; long_max < LONG_MAX; long_max++)
      {
        tm = gmtime(&long_max);
        if (tm == NULL)
        {
          printf("max_input = %ld\n", --long_max);
          tm = gmtime(&long_max);
          printf("The maximum tm_year is %d\n", tm->tm_year);
          break;
        }
      }
    
      t.tm_mon = 0; // Jan
      t.tm_mday = 1;
      t.tm_hour = 0;
      t.tm_min = 0;
      t.tm_sec = 0;
      t.tm_year = INT_MIN;
      t.tm_wday = t.tm_yday = t.tm_isdst = 0;
    
      long_min = mktime(&t);
      for (; long_min > LONG_MIN; long_min--)
      {
        tm = gmtime(&long_min);
        if (tm == NULL)
        {
          printf("min_input = %ld\n", ++long_min);
          tm = gmtime(&long_min);
          printf("The minimum tm_year is %d\n", tm->tm_year);
          break;
        }
      }
    }
    
    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
  • 输出为
    max_input = 67768036191676799
    The maximum tm_year is 2147483647
    min_input = -67768040609740800
    The minimum tm_year is -2147483648
    
    1
    2
    3
    4
  • 年份为负数表示为公元前

# 使得 asctime(gmtime(input)) 不出错的 input 的范围

分析

  • asctime 输出字符串时,需要根据将 gmtime 中返回的 struct *tm 中的 tm_year 来计算实际的年份
  • 预估 asctime 中也是用 int 来表示最终输出的年份的,这就要求 tm_year + 1900 不能溢出
  • 再结合使用 gmtime 产生正确输出的条件,此时 input 的数值对应的年份应在 在 INT_MIN ~ INT_MAX -1900 之间

代码验证

  • 代码
    #include <limits.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    
    int main()
    {
      time_t long_max, long_min;
      struct tm t;
      struct tm *tm;
      char *buf;
    
      // Set the time zone to UTC
      tzname[0] = tzname[1] = "GMT";
      timezone = 0;
      daylight = 0;
    
      setenv("TZ", "UTC", 1);
    
      t.tm_mon = 11; // Dec
      t.tm_mday = 31;
      t.tm_hour = 23;
      t.tm_min = 59;
      t.tm_sec = 59;
      t.tm_year = INT_MAX - 1900;
      t.tm_wday = t.tm_yday = t.tm_isdst = 0;
    
      long_max = mktime(&t);
      for (; long_max < LONG_MAX; long_max++)
      {
        tm = gmtime(&long_max);
        if (tm != NULL)
          if ((asctime(tm)) != NULL)
            continue;
        printf("max_input = %ld\n", --long_max);
        tm = gmtime(&long_max);
        buf = asctime(tm);
        printf("The maximum date is %s\n", buf);
        break;
      }
    
      t.tm_mon = 0; // Jan
      t.tm_mday = 1;
      t.tm_hour = 0;
      t.tm_min = 0;
      t.tm_sec = 0;
      t.tm_year = INT_MIN;
      t.tm_wday = t.tm_yday = t.tm_isdst = 0;
    
      long_min = mktime(&t);
      for (; long_min > LONG_MIN; long_min--)
      {
        tm = gmtime(&long_min);
        if (tm != NULL)
          if ((asctime(tm)) != NULL)
            continue;
        printf("min_input = %ld\n", ++long_min);
        tm = gmtime(&long_min);
        buf = asctime(tm);
        printf("The minimum date is %s\n", buf);
        break;
      }
    }
    
    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
  • 输出为

    max_input = 67767976233532799
    The maximum date is Tue Dec 31 23:59:59 2147483647
    
    min_input = -67768040609740800
    The minimum date is Thu Jan  1 00:00:00 -2147481748
    
    
    1
    2
    3
    4
    5
    6
    • 可以看出最小值跟 gmtime 一致,最大值在比 gmtime 的最大值小
  • 提示

    • 可以看出在当前的 C 标准库下 asctime(gmtime(input)) 能够处理正确处理的日期范围是
      • Jan 1 00:00:00 -2147481748 ~ Dec 31 23:59:59 2147483647

# 当前 C 标准库下的时间界限

结论

  • 如果以 asctime(gmtime(input)) 能输出的日期作为界限,则当前的时间界限为
    • Jan 1 00:00:00 -2147481748 ~ Dec 31 23:59:59 2147483647
  • 可以看出时间结束在 21 亿之后,开始于公元前 21 亿年之前
    • 基本也能满足正常使用了,谁知道人类文明能不能存活到 21 亿年之后呢 🐶
    • 当然,要是表示地球 🌏 的起源(大约 46 亿年前),则显然就不够用了
      • 当然,研究地球起源的人可能用不着写代码
  • 感觉 C 标准库这里可以修改完善一下 🤔,毕竟用 long 来表示 time_t,却用 int 来表示 tm_year,有点 Dance in Chains
编辑 (opens new window)
#C
上次更新: 2024/06/09, 23:06:00
float有效数字位的问题

← float有效数字位的问题

最近更新
01
float有效数字位的问题
08-14
02
目录
07-23
03
技术
07-23
更多文章>
Theme by Vdoing | Copyright © 2021-2024 lijunjie9502| MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式