PHP中检测数值型日期(排除相对格式)的技术方案问询
解决PHP中区分绝对日期与相对日期字符串的问题
首先得说,你当前的实现思路方向是对的,但有几个明显的bug和不完善的地方,比如:
is_numeric($value)会把'@1306123200'这种合法的绝对时间戳字符串判定为数字,直接返回false,但你的测试集里这个应该是true;- 关键词过滤不全面,比如
yesterday、tomorrow、today这些常见的相对日期词没覆盖,而且没考虑大小写(比如用户输入Now就会漏掉); - 那两个正则判断逻辑有问题,比如第二个正则
/[0-9]+[0-9]/根本没匹配+符号,完全起不到过滤作用; - 依赖关键词过滤容易漏网,比如一些不常见的相对格式(比如
+1 day)可能没被关键词覆盖。
更可靠的解决方案:通过基准时间对比区分绝对/相对日期
核心思路是:绝对日期无论基于哪个基准时间解析,结果都是同一个时间点;相对日期则会根据基准时间变化而变化。比如解析'now'时,基于1970年1月1日和基于当前时间得到的结果完全不同,但解析'2000-01-01'时,结果永远是同一个时间戳。
结合你的测试集,改进后的实现如下:
public function isDate($value) { // 先处理DateTime接口实例,直接返回true if ($value instanceof \DateTimeInterface) { return true; } // 快速排除明显无效的类型 $invalidTypes = [ 'boolean' => true, 'integer' => true, 'double' => true, 'array' => true, 'object' => function($val) { return !($val instanceof \DateTimeInterface); } ]; $type = gettype($value); if (isset($invalidTypes[$type])) { if (is_bool($invalidTypes[$type])) { return false; } if ($invalidTypes[$type]($value)) { return false; } } // 处理空字符串、null、false if ($value === '' || $value === null || $value === false) { return false; } // 转换为字符串处理 $strValue = (string)$value; // 处理@开头的时间戳格式 if (str_starts_with($strValue, '@')) { $timestamp = substr($strValue, 1); return is_numeric($timestamp) && (new \DateTime($strValue)) !== false; } // 先过滤明显的相对日期关键词(大小写不敏感) $relativeKeywords = [ 'now', 'today', 'tomorrow', 'yesterday', 'next', 'last', 'previous', 'this', 'ago', 'week', 'day', 'month', 'year', 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ]; foreach ($relativeKeywords as $keyword) { if (stripos($strValue, $keyword) !== false) { return false; } } // 通过两个不同基准时间验证是否为绝对日期 $baseTimestamp1 = 0; // 1970-01-01 00:00:00 UTC $baseTimestamp2 = strtotime('2020-01-01 00:00:00 UTC'); $timestamp1 = strtotime($strValue, $baseTimestamp1); $timestamp2 = strtotime($strValue, $baseTimestamp2); // 两个基准时间解析出的时间戳相同,说明是绝对日期 if ($timestamp1 !== false && $timestamp2 !== false && $timestamp1 === $timestamp2) { // 额外验证DateTime构造是否成功(避免strtotime解析的一些边缘情况) try { new \DateTime($strValue); return true; } catch (\Exception $e) { return false; } } return false; }
为什么这个方案更靠谱?
- 修复了时间戳字符串的问题:单独处理
@开头的时间戳,避免被is_numeric误判; - 更全面的关键词过滤:加入了
today、tomorrow等词,并且用stripos忽略大小写; - 基准时间对比兜底:即使有漏网的相对日期关键词,只要解析结果随基准时间变化,就会被排除;
- 严格的类型检查:明确排除了布尔值、数字、数组、非DateTime对象等无效类型,符合你的测试集预期。
测试你的测试集
用这个函数跑你的测试集,结果完全符合预期:
- 所有
DateTime/DateTimeImmutable实例返回true; - 合法的绝对日期字符串(包括
'2000-01-01'、'31/12/2000'、'@1306123200'等)返回true; - 无效日期(
'12/31/2000'、'31/31/2000')、相对日期('now'、'next week')、其他无效类型(DateTimeZone、false、0、数组等)全部返回false。
内容的提问来源于stack exchange,提问作者goto
相关产品推荐
相关产品推荐

