PHP获取时间戳慢了8个小时

511
@Ta 09-07 03:33发布,09-07 03:33修改 6832点击
XDM,我php获取时间戳慢8个小时,我php.ini已经改成
data.timezone=PRC

,还有在php开头用
data_default_timezone_set('PRC');

,结果都一样,还是慢了8个小时,唯一解决办法就是在获取出来的时间戳+28800才可以准确
回复列表(30)
  • @Ta / 09-07 04:30 / /

    UNIX时间戳是指从绝对时间点(UTC时间1970年1月1日午夜)起经过的秒数,不考虑闰秒。

    它是时区无关的。

    时区的作用体现在把unix时间戳转换为时间字符串的时候。

    date_default_timezone_set('UTC');
    var_dump(time());
    var_dump(date('Y-m-d H:i:s', 0));
    var_dump(date('Y-m-d H:i:s', time()));
    var_dump(date('Y-m-d H:i:s'));
    
    date_default_timezone_set('PRC');
    var_dump(time());
    var_dump(date('Y-m-d H:i:s', 0));
    var_dump(date('Y-m-d H:i:s', time()));
    var_dump(date('Y-m-d H:i:s'));
    

    你从函数名称也能看出来,既然是date_default_timezone_set,它影响的肯定是date()函数,不会是time()函数。

    Screenshot_20220907_042925.jpg(325.08 KB)

  • @Ta / 09-07 04:37 / /

    以下函数受到时区的影响:

    • date
    • mktime
    • strftime

    以下函数不受时区的影响:

    • time
    • gmdate
    • gmmktime
    • gmstrftime
  • @Ta / 09-07 05:17 / /

    为什么UNIX时间戳必须与时区无关呢?因为时区记录的不止是简单的时间偏移,还包括:

    • 时区的变迁:某些国家曾经调整自己的法定时区。
    • 夏令时的开始和结束时间,每个国家都不同。
    • 夏令时的实施和取消时间。某些国家(比如中国)曾经实施过夏令时,但现已取消。

    所以本地时间不是连续的,它在国家立法调整时区,以及每年夏令时开始和结束的那一刻断开。

    这样一来,让时间戳与时区有关就只会带来坏处,不会带来好处,比如:

    • 两个时间戳相减无法保证能得到正确的时间差,因为它们可能一个位于夏令时中一个位于夏令时外,两者本身相差一小时。
    • 在夏令时结束的那一刻,时间戳的数值会变小3600(一小时)。不能保证时间戳是永增的。
    • 在国家颁布法律调整时区的那一刻,时间戳必须进行偏移,所有程序都需要修改才能保证一致性。

    此外,无论当地是否有夏令时和时区调整,时间戳和时区相关都会带来以下问题:

    • 在用户调整时区后,一些刚刚修改的文件可能会被认为在未来修改(比如从UTC+8调整到UTC+7,本地时间戳数值变小了3600)。这会让依赖修改时间判断是否需要重新编译的程序(比如make)无法正确重新编译,因为目标文件的修改时间在未来,总是大于源代码的修改时间。

    • 时间戳将不可在不同系统间交换,因为不同系统的时区设置可能不同,交换时必须附带时区。

    上述坏处只需要让时间戳与时区无关就能全部解决。目前,时间戳只代表协调世界时的字面值(不考虑润秒),所以:

    • 它是单调递增的,数值更大则时间一定更新。
    • 无论国家如何折腾时区和夏令时,它的数值都不会改变。
    • 它可以在多个系统间自由交换,因为时区设置不会影响其数值。
    • 两个时间戳相减得到的结果一定是它们相差的秒数,不会有例外。
    • 有些网站允许用户自行设置时区。只要时间戳与时区无关,那么无论用户怎么调整自己的时区,存储在数据库里的时间戳都不需要改变。时区只在显示(把时间戳变成时间字符串)时起作用。
  • @Ta / 09-07 05:30 / /

    关于夏令时的影响,举个例子:协调世界时1990年6月1日的午夜,并不是北京时间8点,而是北京时间——9点

    date_default_timezone_set('PRC');
    $t = gmmktime(0, 0, 0, 6, 1, 1990);
    var_dump($t);
    var_dump(gmdate('Y-m-d H:i:s', $t));
    var_dump(date('Y-m-d H:i:s', $t));
    

    Screenshot_20220907_052434.jpg(153.17 KB)

    就算在中国,如果时间戳和时区相关,你也会在历史问题上引入难以解决的复杂性,更不用说那些正在实施夏令时的国家了。

  • @Ta / 09-07 05:44 / /

    关于数据库、时间戳和时区

    以下是我认为良好的设计

    • 不要依靠数据库处理时区问题。
    • 数据库只存储时间戳,因为它是时区无关的。
    • 把按日期/时间查找转换为按时间戳范围查找(date = xxx -> time >= xxx and time <= xxx)。
    • 不使用数据库的时间戳转日期/日期转时间戳函数(因为数据库时区处理方式可能和网站程序不同),在数据库里只进行单纯的时间戳处理,日期和时间戳互转的操作总是在网站程序内进行。
    • 可以使用数据库的时间戳函数(比如select unix_timestamp()),因为时间戳是时区无关的,即使数据库和网站程序的时区不同,结果也一致。
  • @Ta / 09-07 05:49 / /

    你可能会问,set time_zone = '+8:00';这样不行吗,这不就设为北京时间了吗?

    1990年6月1日午夜问题就能看出来,这样不行,你会在历史问题上犯错误。

    中国曾在1986年到1991年期间实施夏令时,所以UTC+8并非总是北京时间。

  • @Ta / 09-07 09:56 / /

    以前手撸程序的时候都习惯性的在第一句加上date_default_timezone_set('Asia/Shanghai');

  • 511
    @Ta / 09-07 12:01 / /
    @echo醉老仙@老虎会游泳,那么我需要怎么做才能让time时间也+8呢
  • @Ta / 09-07 12:20 / /

    @511,你不应该这么做,这不是unix时间戳的正确用法。

    如果你为了兼容性目的不得不这么做,那么就用time() + 3600 * 8,这种方法没有缺点,因为time()是时区无关的,time() + 3600 * 8一定是unix时间戳偏移8小时后的秒数,无论时区设置是什么。

    如果不是为了兼容目的,不要这么做。我已经用3到6楼陈述了时间戳附带时区的缺点。

  • @Ta / 09-07 12:21 / /

    注意我说time() + 3600 * 8是“unix时间戳偏移8小时后的秒数”,它已经不再是unix时间戳。

    只有未偏移的秒数(当前时刻与UTC时间1970年1月1日午夜的秒数之差)才叫unix时间戳。

    unix时间戳是一个绝对概念,同一时刻世界上有且只有一个unix时间戳,无论你在哪个时区。或者换种说法,unix时间戳是协调世界时的整数表示法,而协调世界时显然是与时区无关的。同一时刻世界上只有一个协调世界时,所以只有一个unix时间戳。

    至于时区,应该在显示时进行处理,也就是在把unix时间戳从整数转换为时间字符串时进行处理。不应该对协调世界时(unix时间戳整数)本身添加偏移。

    这就是为什么,date_default_timezone_set只对date函数有效,对time函数无效。

  • @Ta / 09-07 12:17 / /

    time()始终返回协调世界时,不应该对其添加时区偏移。只有当你把它转换为时间字符串(date($format, $time))时,才会自动添加时区偏移。

    如果你对time()的返回值添加了时区偏移,那么当你使用date($format, $time)把它转换为时间字符串的时候,结果就会是错误的,因为date总是会自动为时间添加时区偏移,如果你已经偏移过一次,那最终结果就偏移了两次。

  • 511
    @Ta / 09-07 17:58 / /
    @老虎会游泳,time() + 3600 * 8的话是不是跟我+28800一样了呀
  • @Ta / 09-07 18:00 / /

    @511,是啊,如果你确实希望如此,这就是正确的解决方法。time()始终是协调世界时(因为unix时间戳一定是协调世界时),想偏移就得自己加。

  • @Ta / 09-07 18:00 / /

    @511,但问题是,你为什么要偏移它?unix时间戳就应该是协调世界时,不应该偏移它。

  • 511
    @Ta / 09-07 18:04 / /
    @老虎会游泳,因为我经常用time()获取时间戳,没有用date,所以比如现在用户签到,18:00,我直接记录会变成10:00的
  • @Ta / 09-07 18:07 / /

    @老虎会游泳,估计楼主没搞清楚 开发者、用户、服务器、数据库 间的时区关系。。

  • @Ta / 09-07 19:28 / /

    @511,你在哪里看到是10:00,你是怎么看的

  • @Ta / 09-07 19:31 / /

    @511time()的返回结果是个整数,存到数据库里也会是个整数,怎么会变成10:00呢???10:00又不是一个整数。

  • 511
    @Ta / 09-07 19:32 / /
    @老虎会游泳,是这样的,我直接把时间戳放数据库,输出的话直接拿数据库的时间戳出来date解析成时间格式
添加新回复
回复需要登录