先说重点,strtotime函数的返回值,在官方文档上的描述
strtotime
(PHP 4, PHP 5, PHP 7)
strtotime — 将任何字符串的日期时间描述解析为 Unix 时间戳
说明
int strtotime ( string$time
[, int$now
= time() ] )本函数预期接受一个包含美国英语日期格式的字符串并尝试将其解析为 Unix 时间戳(自 January 1 1970 00:00:00 GMT 起的秒数),其值相对于
now
参数给出的时间,如果没有提供此参数则用系统当前时间。本函数将使用 TZ 环境变量(如果有的话)来计算时间戳。自 PHP 5.1.0 起有更容易的方法来定义时区用于所有的日期/时间函数。此过程在 date_default_timezone_get() 函数页面中有说明。
参数
time日期/时间字符串。正确格式的说明详见 日期与时间格式。now用来计算返回值的时间戳。
返回值
成功则返回时间戳,否则返回
FALSE
。在 PHP 5.1.0 之前本函数在失败时返回 -1。错误/异常
在每 次调用日期/时间函数时,如果时区无效则会引发
E_NOTICE
错误,如果使用系统设定值或 TZ 环境变量,则会引发E_STRICT
或E_WARNING
消息。参见 date_default_timezone_set()。
实际上这个函数的返回值并非跟官方文档一致。
在文档下面的评论有一些说明:
http://php.net/manual/zh/function.strtotime.php#91801
The following code will produce difference output in 32 and 64 bit environments.
var_dump(strtotime(‘1000-01-30’));
32-bit PHP: bool(false)
64-bit PHP: int(-30607689600)
注意,有人提到了64位下返回值是是一个负值。
这个问题是本次踩坑的关键。
一段比较老的代码在下午四点半左右突然大面积报错异常,但是查了半天没有程序上线,没有代码调整,没有配置调整。
查了半天以后临时下线造成报错的安全限制代码,让人百思不得其解的是,正常逻辑都不太可能走进安全限制的代码的判断条件里面。
$intBeginTime = strtotime($input['begin_date']); $intEndTime = strtotime($input['end_date']) + 86400; if($intBeginTime < $split_time){ if($intEndTime > 0 && $intEndTime>$split_time){ Yii::log('It is not allow to call getJournalList with time cross 2017-01-01','warning'); return false; } }
上述代码中预计的输入值是“2017-07-01”类似这样的字符串,
按照预计的逻辑,经过strtotime处理过的数据如果格式有问题应该返回false,在执行时其他代码逻辑里面会被拦截,所以正常情况下不会有逻辑问题。
但是在2017-07-18 16:36:40这个时间左右,几乎所有调用该接口的功能全部报错。
跟踪日志请求内容如下:
[uri: /proxy?api=AccountingApi.getJournalList&logid=1032235382] [time: 7ms] [peak_mem: 1MB] [time: 7ms][peak_mem: 1MB]process sucess. input: {“api”:”AccountingApi.getJournalList”,”data”:{“banker_id”:”360891″,”transaction_type”:”10,11,12″,”bidding_type”:”3″,”division”:”1″,”begin_date”:”1499702400″,”end_date”:”1500367000″,”order_by”:”ASC”}}
传入的参数是时间戳。
按照原有的开发意图,时间戳的数据经过strtotime返回结果应该是false
$intBeginTime = strtotime(1499702400); //预期值是false,实际值是false $intEndTime = strtotime(1500367000) + 86400; //预期值是false+86400 = 86400 //实际服务器运行结果返回的是int(158748793236) //strtotime("1500366999")这个的返回值则是false
1500367000这个时间戳的时间是2017-07-18 16:36:40
因此从这个时间点开始请求大面积报错。
后面错误突然降低,是因为某些时间戳经过strtotime处理返回的时间信息是负值。
var_dump(strtotime("1500371247")); //int(-22798429163)
因此一些时刻前面的逻辑判断又不会出现问题。
测试环境下不到这个时间程序不会有任何异常,唯一的异常就是日志上传入时间是
“begin_date”:”1499702400″,”end_date”:”1500367000″
线下测试的时候如果时间并不是特定时间范围,得到的结果可能也是正确的,比如上面的1500371247这个时间点。
简单说,因为strtotime的返回结果不可预期,会出现无法预计的问题。
上述问题的发生和测试都是在64位的服务器上进行的,目前没有找到32位的环境,所以无法去验证这个问题是strtotime共有的问题还是只在64位下才存在的问题。
这个案例告诉我们,对参数的合规校验有多重要,因为你永远不能预期不合规的参数可能导致什么问题……
参数的合规校验才是王道,通过strtotime来判断输入的值是不是时间,在64位的机器上确实有可能得到一个不正确的值,跟大家分享一下。