Press "Enter" to skip to content

令人困惑的strtotime

经常会有人被strtotime结合-1 month, +1 month, next month的时候搞得很困惑, 然后就会觉得这个函数有点不那么靠谱, 动不动就出问题. 用的时候就会很慌...
这不, 刚刚就有人在微博上又问我:

鸟哥,今天是2018-07-31 执行代码:

date("Y-m-d",strtotime("-1 month"))

怎么输出是2018-07-01?

好的吧, 虽然这个问题看起来很迷惑, 但从内部逻辑上来说呢, 其实是"对"的, 你先别着急哈, 让我慢慢讲:
我们来模拟下date内部的对于这种事情的处理逻辑:

  • 1. 先做-1 month, 那么当前是07-31, 减去一以后就是06-31.
  • 2. 再做日期规范化, 因为6月没有31号, 所以就好像2点60等于3点一样, 6月31就等于了7月1

是不是逻辑很"清晰"呢? 我们也可以手动验证第二个步骤, 比如:

var_dump(date("Y-m-d", strtotime("2017-06-31")));
//输出2017-07-01

也就是说, 只要涉及到大小月的最后一天, 都可能会有这个迷惑, 我们也可以很轻松的验证类似的其他月份, 印证这个结论:

var_dump(date("Y-m-d", strtotime("-1 month", strtotime("2017-03-31"))));
//输出2017-03-03
var_dump(date("Y-m-d", strtotime("+1 month", strtotime("2017-08-31"))));
//输出2017-10-01
var_dump(date("Y-m-d", strtotime("next month", strtotime("2017-01-31"))));
//输出2017-03-03
var_dump(date("Y-m-d", strtotime("last month", strtotime("2017-03-31"))));
//输出2017-03-03

那怎么办呢?
从PHP5.3开始呢, date新增了一系列修正短语, 来明确这个问题, 那就是"first day of" 和 "last day of", 也就是你可以限定好不要让date自动"规范化":

var_dump(date("Y-m-d", strtotime("last day of -1 month", strtotime("2017-03-31"))));
//输出2017-02-28
var_dump(date("Y-m-d", strtotime("first day of +1 month", strtotime("2017-08-31"))));
////输出2017-09-01
var_dump(date("Y-m-d", strtotime("first day of next month", strtotime("2017-01-31"))));
////输出2017-02-01
var_dump(date("Y-m-d", strtotime("last day of last month", strtotime("2017-03-31"))));
////输出2017-02-28

那如果是5.3之前的版本(还有人用么?), 你可以使用mktime之类的, 把所有的日子忽略掉, 比如都限定为每月1号就可以了, 只不过就不如直接用first day来的更加优雅.
现在, 搞清楚了内部原理, 是不是就不慌了? 🙂

168 Comments

  1. v24
    v24 July 1, 2020

    这有点坑啊,以前都没发现,赶紧翻一翻代码

  2. smith smith
    smith smith December 31, 2019

    用carbon库就没这个问题了

    • w-viviv-vip
      w-viviv-vip January 8, 2020

      `Carbon::parse(‘2019-12-31’)->subMonth();` 还是`2019-12-01`

      • whitek
        whitek March 19, 2020

        可以使用 subMonthNoOverflow()

  3. Alan
    Alan November 19, 2019

    前几天,我走进大自然,没有来自城市的喧闹声,没有头痛,只有我和大自然的声音。

  4. seying
    seying October 27, 2019

    正好用到这个,学习了。

  5. xiong
    xiong October 17, 2019

    确实很容易出现坑

  6. zjc348
    zjc348 September 3, 2019

    学习了

  7. 好了睡
    好了睡 August 29, 2019

    请教一下,如果想获取上周的周一,这种时间,strtotime 有没有类似的修正短语呢

  8. Thinklong
    Thinklong July 29, 2019

    这个问题是我经常问初中级的面试题

    • abc
      abc October 31, 2019

      zi yi wei shi de sha bi mian shi guan

  9. cck
    cck July 29, 2019

    学习了

  10. xiaoming
    xiaoming July 26, 2019

    这个 +1 month ,可以理解成 +30 day,这样就可以理解了为啥2017-08-31 变成了 2017-10-01

    • xiaoming
      xiaoming July 26, 2019

      错了是 +31 day

    • LC
      LC November 26, 2019

      就是月份加1不是加30天,再校验日期,请再熟读一边
      2017-08-31加1月是 2017-09-31 进位了成2017-10-01
      没有错,
      相当于月位相加减,再格式化为合法的日期

  11. mx
    mx July 20, 2019

    good

  12. zhangbin
    zhangbin July 11, 2019

    $now = strtotime(“2018-03”);
    echo date(‘Y-m-d’,strtotime(‘-1 month’, $now));
    echo “\n”;
    var_dump(date(‘Y-m-d’,strtotime(“first day of -1 month”,strtotime(“2018-03-31”))));
    echo “\n”;

  13. 张鹏
    张鹏 July 6, 2019

    为什么要加first day , last day这种方式来手动规范结果而不是PHP语言自动将正确结果显示出来?我用PHP7的版本还是会有这种问题。

  14. lao lu
    lao lu July 3, 2019

    卧槽。。不敢用了。。这太坑了。。

  15. vegisau
    vegisau June 27, 2019

    emmm, 我一直都是01号开始+1 month,这种逻辑出现在金融上不堪设想

  16. 陈龙强
    陈龙强 March 12, 2019

    鸟哥,个人认为这个问题,不应该说是修正程序的问题吗?
    如果 -1 month 溢出了,应该要取上个月真实的最后一天吧?
    而不是没有这天,就直接把差值累加上去了。
    您在这里说清楚了,但这个坑相信会被踩无数次,哈哈~

  17. Vito
    Vito March 8, 2019

    鸟哥,这个last day of -1 month,只能解决想获得的是所求日期的上个月月末最后一天,而并不能得到所求日期的一个月前的时间,说白了,一个月多久不定,php无法定义,
    感觉如果真的想严谨的定义 -1 month 或者 +1month, 应该提供一个求上个月或者下个月天数n 的的函数,然后利用 -n days 或者 +n days 比较科学,因为一天24个小时,是固定不变的。。。
    也就是这个 -1 month 不如自己写个方法,自己定义好12个月对应的天数n,然后利用 -n days 或者 +n days

  18. ta_shuo
    ta_shuo March 4, 2019

    以前竟然都没考虑过这种极端问题,隐藏的好深的bug

  19. 云天
    云天 February 17, 2019

    我觉得这个问题应该这样理解,内部在实现的时候减去的为上个月所含有的天数。比如2月有29天 3月有31天
    var_dump(date(“Y-m-d”, strtotime(“-1 month”, strtotime(“2017-03-31”))));得到的结果为2019-03-31减去的29天之后的日期。这样算是科学的,因为应该是从上月1号对应本月1号开始往后一一对应正推,而不是从本月底对应上月底来倒推。所以应该减去上月的天数。

    • 云天
      云天 February 17, 2019

      笔误应该是var_dump(date(“Y-m-d”, strtotime(“-1 month”, strtotime(“2019-03-31”))));

      • 黄江宇
        黄江宇 March 16, 2019

        test1

    • Malu
      Malu May 23, 2019

      我测试过了好像的确是这样的,3月的30号减一个月就是03-02,29号就是03-01。

  20. 安德森
    安德森 February 13, 2019

    学到了

    • 安德森
      安德森 February 17, 2019

      学到了

  21. 夏
    February 13, 2019

    好,学到了

    • 安德森
      安德森 February 17, 2019

      学好了

      • 安德森
        安德森 February 17, 2019

        再学一次

        • 安德森
          安德森 February 17, 2019

          111

          • 安德森
            安德森 February 17, 2019

            11

          • 安德森
            安德森 February 17, 2019

            1

  22. 曹东超
    曹东超 February 13, 2019

    了解了。

    • Jason
      Jason February 2, 2019

      写的不错!

  23. 周
    January 29, 2019

    妙啊

  24. CVV shop
    CVV shop January 29, 2019

    很清楚 – what does it mean pls?

  25. guang
    guang January 22, 2019

    可是原本的需求还是没解决啊

  26. VPS234
    VPS234 January 9, 2019

    感觉这是WordPress的代码呢

  27. Hoe
    Hoe January 8, 2019

    恍然大悟啊

  28. boyer
    boyer December 31, 2018

    学习了

  29. tlanyan
    tlanyan December 29, 2018

    总结就是程序不会错,结果错说明用法不对

  30. php二次开发
    php二次开发 December 25, 2018

    经常这样用,一直不理解,偶尔还出错,总算弄明白了,感谢博主。

  31. ARM
    ARM December 19, 2018

    暂时还没用到,先Mark一下

  32. chenweijie
    chenweijie November 26, 2018

    稳!

  33. 安满
    安满 November 24, 2018

    鸟哥分析的很清晰易懂

  34. fatming
    fatming November 21, 2018

    还是很慌怎么办….
    既然正常人都觉得 10-31 的 *上一个月*不应该还是10月份,
    那为什么不把默认值就设置为9月30号呢. 就算有短语可以修正,真的一点都不优雅, 而且很拗口=.=

  35. noone
    noone November 20, 2018

    漂亮

  36. AlicFeng
    AlicFeng November 17, 2018

    nice??

  37. avrilko
    avrilko November 16, 2018

    学习了

  38. 三顿饭
    三顿饭 November 14, 2018

    鸟哥。正文的字体太细了,能否改粗点

  39. showbox
    showbox November 6, 2018

    经常会有人被strtotime结合

  40. van23qf
    van23qf November 5, 2018

    还有这种操作,平时都没注意到。。。

  41. yun
    yun October 29, 2018

    学习了

  42. qwqw
    qwqw October 22, 2018

    这个问题不应该用鸟哥最后讲的方法解决,还是应该在函数内部解决

  43. straiway
    straiway October 20, 2018

    之前遇到过这样的情况,也觉得strtotime不靠谱,但是没深究。感谢鸟哥,看来遇到问题最彻底的办法就是看源码?

  44. DusNoob
    DusNoob October 18, 2018

    涨姿势了,做项目一直都没注意到

  45. annon
    annon October 15, 2018

    鸟哥好棒

    • yang
      yang November 15, 2018

      nice

      • yang
        yang November 15, 2018

        1

        • 周
          January 30, 2019

          2

      • 周
        January 30, 2019

        00

    • 周
      January 30, 2019

      000

  46. Venus Zhu
    Venus Zhu September 30, 2018

    只有我觉得右侧新浪添加关注的插件会挡住昵称而别扭嘛?!逼死强迫症。谷歌浏览器 版本 69.0.3497.100(正式版本) (64 位)

  47. 2s
    2s September 28, 2018

    不慌不慌

  48. jian.liu
    jian.liu September 27, 2018

    简单的问题复杂化,复杂的问题简单化!

  49. zhebie
    zhebie September 26, 2018

    通俗易懂,厉害厉害,我是转行过来做的php,现在会点python和golang,一入编程深似海,感觉学不完,随便哪个语言。。。

  50. Liu zhi peng
    Liu zhi peng September 26, 2018

    我刚刚百度完鸟哥的个人信息,好厉害,以后我们就叫你做大哥,你就叫我们练功夫

  51. bunsen zhou
    bunsen zhou September 25, 2018

    还是挺好理解的,官网文档看仔细点就不会乱了

  52. Donne
    Donne September 14, 2018

    一般获得「上个月的今天,当这天不存在就取上月的最后一天」,是更「亲人」的描述方式吧,这种逻辑就没办法很简单的一个 strtotime 去处理了。

  53. haibao
    haibao September 13, 2018

    讲的很清楚了~ 666

  54. JackMa
    JackMa September 12, 2018

    涨姿势了

  55. Lanffy
    Lanffy September 5, 2018

    向大佬学习,这以前还真不知道。哈哈

  56. king
    king August 31, 2018

    这是大佬时间长没写过业务了么? 为什么我搞清楚了反倒是感到这个函数好不靠谱? strtotime一点都不php风格,我现在有需求要求查询用户上个月的今天到今天一共做了某某个事件,请问怎么用一句strtotime解决???

    • za
      za August 31, 2018

      取数据的时候,取这俩个时间的unix时间戳之间的对应的数据,count一下,不行嘛?

      • pp
        pp October 2, 2018

        取数据的时候,取这俩个时间的unix时间戳之间的对应的数据,count一下,不行嘛?

    • ********
      ******** October 17, 2018

      echo date(“Y-m-d H:i:s”, strtotime(date(‘Y-m’)));
      看下这个日期是不是你要的结果

  57. charlie
    charlie August 31, 2018

    慌而不乱

  58. L
    L August 30, 2018

    看完深覺 strtotime 的月份加減是雞肋了
    沒辦法精準加減到上、下個月對應的日子
    即使 first day of、last day of 也只能取得月初、月底
    任意日期,也得另外判斷每個月有幾天
    (正努力回想寫過的程式有沒用到功能過)

  59. yan
    yan August 27, 2018

    鸟哥终于换主题了! 特别像sublime的风格!

  60. LEWISS
    LEWISS August 27, 2018

    比如说我们是当前日期的上个月对应时间, 是个动态情况条件,那这个还是没有解决实际问题吧

    • niming
      niming August 27, 2018

      自己转换一下呗,比如每个月都有10号,你就只用10号做’-1 month’操作,得到月份后再拼接一下日期。

  61. cvv shop
    cvv shop August 26, 2018

    你是一位出色的作家。 今天很难找到一个能清楚理解的人。谢谢! 我是你的常客。

  62. 轮子
    轮子 August 20, 2018

    我司还在用5.2 #fml

    • Renling
      Renling August 23, 2018

      都是7的时代了,你竟然还在用5.2?

  63. Bill Gates
    Bill Gates August 19, 2018

    作为PHP开发组为数不多的华人成员,私觉得鸟哥适合稍微提高一下发文频次,让我们这些“二把刀”能够对PHP有更深的了解。比如,PHP未来的发展方向、最新主版本(比如现在的PHP 7.3 Beta)在性能上的表现以及为什么。

  64. breeze
    breeze August 18, 2018

    模板比以前好看

  65. Bruce
    Bruce August 16, 2018

    解释得清晰明了!

  66. 笨小孩
    笨小孩 August 15, 2018

    第一个参数为时间戳时,在windows环境下,返回值一直正常。但是在linux环境下,有些时间戳返回正常,有些时间戳返回与预期不一样。(正常应该返回false),例如strtotime(1500516291)

  67. 奋斗
    奋斗 August 15, 2018

    感觉这个博客模板很low啊

  68. cloedy
    cloedy August 13, 2018

    鸟儿哥, 你终于终于换了个blog模板了

  69. aaa
    aaa August 9, 2018

    鸟哥

  70. masker
    masker August 9, 2018

    仰望鸟哥,我这种PHP小菜鸟快完蛋了

  71. 裸奔の蜗牛
    裸奔の蜗牛 August 9, 2018

    更加慌了

  72. mengkang
    mengkang August 7, 2018

    恭喜鸟哥博客前端改版,看微博得知还是鸟哥亲自操刀。

  73. 落舞者
    落舞者 August 7, 2018

    哈哈哈,还是还以为谁博客呢。换主题了。差点没认出来。鸟哥加油永远支持php。

  74. 推广网
    推广网 August 7, 2018

    为什么不直接在php服务器里做判断 ,而是要用first day of 这个判断 ,每个月多少天是固定的

  75. Fast Light
    Fast Light August 6, 2018

    我在写js的时候也遇到过类似的问题,都是因为各个月份天数不同造成的。

  76. lcp0578
    lcp0578 August 6, 2018

    我也有点慌了,鸟哥

  77. 谢谢鸟哥
    谢谢鸟哥 August 6, 2018

    哇,更新了,谢谢鸟哥

  78. AIinjection
    AIinjection August 5, 2018

    PHP 的时间函数和时区结合起来,感觉有点乱。特别是当语言自身,框架,ini 都可以配置时区的时候,就更加措手不及了。

  79. Willove
    Willove August 5, 2018

    还是很慌。。。为啥 first day of next month 取的不是0点的时间戳,而还是对应了当前时间的时分秒,大多数能够用到这个参数的需求是想取0点的时间戳。

  80. dilex
    dilex August 4, 2018

    谢谢

  81. 龙笑天
    龙笑天 August 4, 2018

    time()、date()、strtotime() 这三个函数的时区问题很烦~~

  82. 西枫里
    西枫里 August 4, 2018

    哇哦,博客换主题了,点个卯~

  83. 沦陷今生
    沦陷今生 August 2, 2018

    我也遇到过,幸好可以用mysql填这个坑
    SELECT DATE_ADD( ‘2017-01-31’, INTERVAL 1 MONTH ),DATE_ADD( ‘2017-01-31’, INTERVAL 2 MONTH ),DATE_ADD( ‘2017-01-31’, INTERVAL 3 MONTH );
    就可以得到和预期一样的结果
    2017-02-28
    2017-03-31
    2017-04-30
    http://lxjsmdc.com/php/get-next-month.html

  84. jack
    jack August 1, 2018

    额 其实还是很方啊,我一直以为是这样的,2.28 +1 month = 3.31 结果是3.28
    统计的时候,要判断是大小月 还要判断上个月是大小月,然后还得今天是月末,还是月初。。。。
    之前在php手册上看到 这个
    [kumar AT swatantra.info Swatantra Kumar ]
    这位同学在5 years ago的说的
    var_dump(date(“Y-m-d”, strtotime(“-1 month”, strtotime(“2017-03-31”))));
    //输出2017-03-03
    只是感觉不对劲
    这能归结为一个bug么。鸟大大 。

  85. 余志斌
    余志斌 July 31, 2018

    写错日期了,修改写:实际我是想获取到前者(2018-09-12)

  86. Tonight
    Tonight July 31, 2018

    鸟哥,请问下first day of/last day of后面的关键词,在底层是如何解析的?

  87. 余志斌
    余志斌 July 31, 2018

    使用first day of +1 month或者last day of +1 month,是可以获取到当前日期下个月的第一天或最后一天。
    当如果我当前日期是2018-08-12,使用+1 month,会获取到2018-09-12,使用first day of +1 month,会得到2018-09-01。实际我是想获取到前者(2018-08-12),所以这个first day of还是有些情况不能兼容到

  88. dream-fei
    dream-fei July 31, 2018

    不慌了,原来如此

  89. 白菜
    白菜 July 31, 2018

    感觉还是mktime好用,这种情况还是比较特殊的。不够灵活。

  90. 侉帮子
    侉帮子 July 31, 2018

    鸟哥终于更新博客了。

  91. loadinger
    loadinger July 31, 2018

    鸟哥竟然更新了…
    最近是玩农药玩得少了呀,这可不行.再说dota2,马上ti了,训练不能少~!

  92. Charles
    Charles July 31, 2018

    谢谢鸟哥回复

  93. mengkang
    mengkang July 31, 2018

    匹配这么多种情况,还有这么长的字符串,我还是用mktime吧,啥时候给博客加上微博表情包就好了。

Leave a Reply to shirne Cancel reply

Your email address will not be published. Required fields are marked *