0

0

php日期字符串比较实例

php中文网

php中文网

发布时间:2016-07-25 09:13:13

|

1120人浏览过

|

来源于php中文网

原创

项目中有个功能是比较会员是否过期,review同事的代码,发现其写法比较奇葩,但线上竟也未出现bug。

实现:

  1. $expireTime = "2014-05-01 00:00:00";
  2. $currentTime = date('Y-m-d H:i:s', time());
  3. if($currentTime
  4. return false;
  5. } else {
  6. return true;
  7. }
复制代码

如果两个时间需要进行比较,通常是转换成unix时间戳,用两个int型的数字进行比较。该实现却特意将时间表示成string,然后对两个string进行比较运算。

撇开写法不谈,我很好奇的是php内部是如何进行比较的。

闲话少说,还是从源码开始跟踪。

编译期 在zend_language_parse.y中可以发现类似下述语法:

  1. expr === expr { zend_do_binary_op(ZEND_IS_IDENTICAL, &$$, &$1, &$3 TSRMLS_CC); }
  2. expr !== expr { zend_do_binary_op(ZEND_IS_NOT_IDENTICAL, &$$, &$1, &$3 TSRMLS_CC); }
  3. expr == expr { zend_do_binary_op(ZEND_IS_EQUAL, &$$, &$1, &$3 TSRMLS_CC); }
  4. expr != expr { zend_do_binary_op(ZEND_IS_NOT_EQUAL, &$$, &$1, &$3 TSRMLS_CC); }
  5. expr
  6. expr
  7. expr > expr { zend_do_binary_op(ZEND_IS_SMALLER, &$$, &$3, &$1 TSRMLS_CC); }
  8. expr >= expr { zend_do_binary_op(ZEND_IS_SMALLER_OR_EQUAL, &$$, &$3, &$1 TSRMLS_CC); }
复制代码

很明显,此处编译成opcode用的便是zend_do_binary_op。

  1. void zend_do_binary_op(zend_uchar op, znode *result, const znode *op1, const znode *op2 TSRMLS_DC) /* {{{ */
  2. {
  3. zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);
  4. opline->opcode = op;
  5. opline->result.op_type = IS_TMP_VAR;
  6. opline->result.u.var = get_temporary_variable(CG(active_op_array));
  7. opline->op1 = *op1;
  8. opline->op2 = *op2;
  9. *result = opline->result;
  10. }
复制代码

该函数并没有做什么特别的处理,仅仅是简单保存了opcode、操作数1和操作数2。

立即学习PHP免费学习笔记(深入)”;

执行期 根据opcode,跳转到相应的处理函数:ZEND_IS_SMALLER_SPEC_CONST_CONST_HANDLER。

  1. static int ZEND_FASTCALL ZEND_IS_SMALLER_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
  2. {
  3. zend_op *opline = EX(opline);
  4. zval *result = &EX_T(opline->result.u.var).tmp_var;
  5. compare_function(result,
  6. &opline->op1.u.constant,
  7. &opline->op2.u.constant TSRMLS_CC);
  8. ZVAL_BOOL(result, (Z_LVAL_P(result)
  9. ZEND_VM_NEXT_OPCODE();
  10. }
复制代码

注意到,两个zval的比较是利用compare_function来处理。

  1. ZEND_API int compare_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ */
  2. {
  3. int ret;
  4. int converted = 0;
  5. zval op1_copy, op2_copy;
  6. zval *op_free;
  7. while (1) {
  8. switch (TYPE_PAIR(Z_TYPE_P(op1), Z_TYPE_P(op2))) {
  9. case TYPE_PAIR(IS_LONG, IS_LONG):
  10. ...
  11. case TYPE_PAIR(IS_DOUBLE, IS_LONG):
  12. ...
  13. case TYPE_PAIR(IS_DOUBLE, IS_DOUBLE):
  14. ...
  15. ...
  16. // 两个字符串进行比较
  17. case TYPE_PAIR(IS_STRING, IS_STRING):
  18. zendi_smart_strcmp(result, op1, op2);
  19. return SUCCESS;
  20. ...
  21. }
  22. }
  23. }
复制代码

该函数例举了若干种情况,根据本文case,进入zendi_smart_strcmp一窥究竟:

  1. ZEND_API void zendi_smart_strcmp(zval *result, zval *s1, zval *s2) /* {{{ */
  2. {
  3. int ret1, ret2;
  4. long lval1, lval2;
  5. double dval1, dval2;
  6. // 尝试将字符串转成数字类型
  7. if ((ret1=is_numeric_string(Z_STRVAL_P(s1), Z_STRLEN_P(s1), &lval1, &dval1, 0)) &&
  8. (ret2=is_numeric_string(Z_STRVAL_P(s2), Z_STRLEN_P(s2), &lval2, &dval2, 0))) {
  9. // 进行数字之间的比较
  10. ...
  11. } else {
  12. // 无法全部转成数字
  13. // 则调用zend_binary_zval_strcmp
  14. // 本质为memcmp的一层封装
  15. Z_LVAL_P(result) = zend_binary_zval_strcmp(s1, s2);
  16. ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_LVAL_P(result)));
  17. }
  18. }
复制代码

那么“2014-05-01 00:00:00”能否转化成数字么?

md2card
md2card

Markdown转知识卡片

下载

还是得看下is_numeric_string的实现规则。

  1. static inline zend_uchar is_numeric_string(const char *str, int length, long *lval, double *dval, int allow_errors)
  2. {
  3. const char *ptr;
  4. int base = 10, digits = 0, dp_or_e = 0;
  5. double local_dval;
  6. zend_uchar type;
  7. if (!length) {
  8. return 0;
  9. }
  10. /* trim掉字符串开头的空白部分 */
  11. while (*str == ' ' || *str == '\t' || *str == '\n' || *str == '\r' || *str == '\v' || *str == '\f') {
  12. str++;
  13. length--;
  14. }
  15. ptr = str;
  16. if (*ptr == '-' || *ptr == '+') {
  17. ptr++;
  18. }
  19. if (ZEND_IS_DIGIT(*ptr)) {
  20. /* 判断是否为16进制 */
  21. if (length > 2 && *str == '0' && (str[1] == 'x' || str[1] == 'X')) {
  22. base = 16;
  23. ptr += 2;
  24. }
  25. /* 忽略后续的若干0 */
  26. while (*ptr == '0') {
  27. ptr++;
  28. }
  29. /* 计算数字的位数,并决定是整型还是浮点 */
  30. for (type = IS_LONG; !(digits >= MAX_LENGTH_OF_LONG && (dval || allow_errors == 1)); digits++, ptr++) {
  31. check_digits:
  32. if (ZEND_IS_DIGIT(*ptr) || (base == 16 && ZEND_IS_XDIGIT(*ptr))) {
  33. continue;
  34. } else if (base == 10) {
  35. if (*ptr == '.' && dp_or_e
  36. goto process_double;
  37. } else if ((*ptr == 'e' || *ptr == 'E') && dp_or_e
  38. const char *e = ptr + 1;
  39. if (*e == '-' || *e == '+') {
  40. ptr = e++;
  41. }
  42. if (ZEND_IS_DIGIT(*e)) {
  43. goto process_double;
  44. }
  45. }
  46. }
  47. break;
  48. }
  49. if (base == 10) {
  50. if (digits >= MAX_LENGTH_OF_LONG) {
  51. dp_or_e = -1;
  52. goto process_double;
  53. }
  54. } else if (!(digits
  55. if (dval) {
  56. local_dval = zend_hex_strtod(str, (char **)&ptr);
  57. }
  58. type = IS_DOUBLE;
  59. }
  60. } else if (*ptr == '.' && ZEND_IS_DIGIT(ptr[1])) {
  61. // 处理浮点数
  62. } else {
  63. return 0;
  64. }
  65. // 如果不允许容错,则报错退出
  66. if (ptr != str + length) {
  67. if (!allow_errors) {
  68. return 0;
  69. }
  70. if (allow_errors == -1) {
  71. zend_error(E_NOTICE, "A non well formed numeric value encountered");
  72. }
  73. }
  74. // 允许容错,则尝试将str转成数字
  75. if (type == IS_LONG) {
  76. if (digits == MAX_LENGTH_OF_LONG - 1) {
  77. int cmp = strcmp(&ptr[-digits], long_min_digits);
  78. if (!(cmp
  79. if (dval) {
  80. *dval = zend_strtod(str, NULL);
  81. }
  82. return IS_DOUBLE;
  83. }
  84. }
  85. if (lval) {
  86. *lval = strtol(str, NULL, base);
  87. }
  88. return IS_LONG;
  89. } else {
  90. if (dval) {
  91. *dval = local_dval;
  92. }
  93. return IS_DOUBLE;
  94. }
  95. }
复制代码

代码比较长,不过仔细阅读,str转num的规则还是很清晰的。

尤其注意的是allow_errors这个参数,它直接决定了本例中无法将“2014-05-01 00:00:00”转化成数字。

因而最后其实“2014-04-17 00:00:00”

既然是memcmp,便不难理解为何文章开始提到的写法也能正确运行。

容错转换 何时allow_errors为true呢?一个极好的例子便是zend_parse_parameters,zend_parse_parameters的实现不再细述,有兴趣的读者可以自行研究。其中调用is_numeric_string时将allow_errors置为了-1。

举个例子:

  1. static void php_date(INTERNAL_FUNCTION_PARAMETERS, int localtime)
  2. {
  3. char *format;
  4. int format_len;
  5. long ts;
  6. char *string;
  7. // 期望的第二个参数为timestamp,为long
  8. // 假设上层调用时,误传入了string,那么zend_parse_parameters依然会尽可能的尝试将string解析为long
  9. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &format, &format_len, &ts) == FAILURE) {
  10. RETURN_FALSE;
  11. }
  12. if (ZEND_NUM_ARGS() == 1) {
  13. ts = time(NULL);
  14. }
  15. string = php_format_date(format, format_len, ts, localtime TSRMLS_CC);
  16. RETVAL_STRING(string, 0);
  17. }
复制代码

这是php的date函数内部实现。

在调用date时,如果将第二个参数传入string,效果如下:

  1. echo date('Y-m-d', '0-1-2');
  2. // 输出
  3. PHP Notice: A non well formed numeric value encountered in Command line code on line 1
  4. 1970-01-01
复制代码

虽然报出notice级别的错误,但依然成功将'0-1-2'转成了0



相关文章

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Golang 生态工具与框架:扩展开发能力
Golang 生态工具与框架:扩展开发能力

《Golang 生态工具与框架》系统梳理 Go 语言在实际工程中的主流工具链与框架选型思路,涵盖 Web 框架、RPC 通信、依赖管理、测试工具、代码生成与项目结构设计等内容。通过真实项目场景解析不同工具的适用边界与组合方式,帮助开发者构建高效、可维护的 Go 工程体系,并提升团队协作与交付效率。

1

2026.02.24

Golang 性能优化专题:提升应用效率
Golang 性能优化专题:提升应用效率

《Golang 性能优化专题》聚焦 Go 应用在高并发与大规模服务中的性能问题,从 profiling、内存分配、Goroutine 调度、GC 机制到 I/O 与锁竞争逐层分析。结合真实案例讲解定位瓶颈的方法与优化策略,帮助开发者建立系统化性能调优思维,在保证代码可维护性的同时显著提升服务吞吐与稳定性。

2

2026.02.24

Golang 面试题精选:高频问题与解答
Golang 面试题精选:高频问题与解答

Golang 面试题精选》系统整理企业常见 Go 技术面试问题,覆盖语言基础、并发模型、内存与调度机制、网络编程、工程实践与性能优化等核心知识点。每道题不仅给出答案,还拆解背后的设计原理与考察思路,帮助读者建立完整知识结构,在面试与实际开发中都能更从容应对复杂问题。

1

2026.02.24

Golang 运行与部署实战:从本地到云端
Golang 运行与部署实战:从本地到云端

《Golang 运行与部署实战》围绕 Go 应用从开发完成到稳定上线的完整流程展开,系统讲解编译构建、环境配置、日志与配置管理、容器化部署以及常见运维问题处理。结合真实项目场景,拆解自动化构建与持续部署思路,帮助开发者建立可靠的发布流程,提升服务稳定性与可维护性。

3

2026.02.24

Golang 疑难杂症解决指南:常见问题排查与优化
Golang 疑难杂症解决指南:常见问题排查与优化

《Golang 疑难杂症解决指南》聚焦开发过程中常见却棘手的问题,从并发模型、内存管理、性能瓶颈到工程化实践逐步拆解。通过真实案例与调试思路,帮助开发者定位问题根因,建立系统化排查方法。不只给出答案,更强调分析路径与工具使用,让你在复杂 Go 项目中具备持续解决问题的能力。

1

2026.02.24

Golang 入门学习路线:从零基础到上手开发
Golang 入门学习路线:从零基础到上手开发

Golang 入门路线涵盖从零到上手的核心路径:首先打牢基础语法与切片等底层机制;随后攻克 Go 的灵魂——接口设计与 Goroutine 并发模型;接着通过 Gin 框架与 GORM 深入 Web 开发实战;最后在微服务与云原生工具开发中进阶,旨在培养具备高性能并发处理能力的后端工程师。

0

2026.02.24

中国研究生招生信息网官方网站入口 研招网网页版在线入口
中国研究生招生信息网官方网站入口 研招网网页版在线入口

中国研究生招生信息网入口(https://yz.chsi.com.cn) 此网站是研究生报名入口的唯一官方网站

95

2026.02.24

苹果官网入口与在线访问指南_中国站点快速直达与iPhone查看方法
苹果官网入口与在线访问指南_中国站点快速直达与iPhone查看方法

本专题汇总苹果官网最新可用入口及中国站点访问方式,涵盖官网直达链接、iPhone官方页面查看方法与常见访问说明,帮助用户快速进入苹果官方网站,便捷了解产品信息与官方服务。

14

2026.02.24

Asianfanfics官网入口与访问指南_AFF官方平台最新登录地址
Asianfanfics官网入口与访问指南_AFF官方平台最新登录地址

本专题系统整理Asianfanfics(AFF)官方网站最新可用入口,涵盖官方平台最新直达地址、官网登录方式及中文访问指引,帮助用户快速、安全地进入AFF平台浏览与使用相关内容。

15

2026.02.24

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号