Home  >  Article  >  Backend Development  >  PHP date string comparison example

PHP date string comparison example

WBOY
WBOYOriginal
2016-07-25 09:13:13899browse

There is a function in the project that compares whether members have expired. I reviewed my colleague’s code and found that the writing method was rather strange, but there were no bugs online.

Achievement:

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

If two times need to be compared, it is usually converted to a unix timestamp, using two int numbers. Compare. This implementation specifically expresses time as a string, and then performs a comparison operation on the two strings.

Writing aside, I am very curious about how comparison is performed internally in PHP.

Without further ado, let’s start tracking from the source code.

Compilation period Syntax similar to the following can be found in zend_language_parse.y: =expr === Expr {Zend_do_binary_op (ZEND_IS_IDENTICAL, & $ 1, & $ 3 TSRMLS_CC); Identical, & $ $, & $ 1, & $ 3 TSRMLS_CC ); }

expr == expr { zend_do_binary_op(ZEND_IS_EQUAL, &$$, &$1, &$3 TSRMLS_CC); }
expr != expr { zend_do_binary_op(ZEND_IS_NOT_EQUAL, &$$, &$1, &$3 TSRMLS_CC); }
expr < expr { zend_do_binary_op(ZEND_IS_SMALLER, &$$, &$1, &$3 TSRMLS_CC); }
    expr <= expr { zend_do_binary_op(ZEND_IS_SMALLER_OR_EQUAL, &$$, &$1, &$3 TSRMLS_CC); }
  1. expr > expr { zend_do_binary_op(ZEND_IS_SMALLER, &$$, &$3, &$1 TSRMLS_CC); }
  2. expr >= expr { zend_do_binary_op(ZEND_IS_SMALLER_OR_EQUAL, &$$, &$3, &$1 TSRMLS_CC); }
  3. Copy code
  4. Obviously, zend_do_binary_op is used to compile opcode here.
void zend_do_binary_op(zend_uchar op, znode *result, const znode *op1, const znode *op2 TSRMLS_DC) /* {{{ */

{

zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC );
opline->opcode = op;
    opline->result.op_type = IS_TMP_VAR;
  1. opline->result.u.var = get_temporary_variable(CG(active_op_array));
  2. opline->op1 = *op1;
  3. opline->op2 = *op2;
  4. *result = opline->result;
  5. }
  6. Copy code
  7. This function does not do any special processing, it just simply saves the opcode and operands 1 and operand 2.
Execution period According to the opcode, jump to the corresponding processing function: ZEND_IS_SMALLER_SPEC_CONST_CONST_HANDLER.

static int ZEND_FASTCALL ZEND_IS_SMALLER_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)

{

zend_op *opline = EX(opline);
zval *result = &EX_T(opline-> result.u.var).tmp_var;
  1. compare_function( result,
  2. &opline->op1.u.constant,
  3. &opline->op2.u.constant TSRMLS_CC);
  4. ZVAL_BOOL(result, (Z_LVAL_P(result) < 0));
  5. ZEND_VM_NEXT_OPCODE();
  6. }
  7. Copy the code
  8. Notice that the comparison of two zvals is handled by compare_function.
ZEND_API int compare_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ */

{

int ret;
int converted = 0;
zval op1_copy, op2_copy;
    zval *op_free;
  1. while (1) {
  2. switch (TYPE_PAIR(Z_TYPE_P(op1), Z_TYPE_P(op2))) {
  3. case TYPE_PAIR(IS_LONG, IS_LONG):
  4. ...
  5. case TYPE_PAIR(IS_DOUBLE, IS_LONG):
  6. ...
  7. case TYPE_PAIR(IS_DOUBLE, IS_DOUBLE):
  8. ...
  9. ...
  10. // Compare two strings
  11. case TYPE_PAIR(IS_STRING, IS_STRING):
  12. zendi_smart_strcmp(result, op1, op2);
  13. return SUCCESS;
  14. ...
  15. }
  16. }
  17. }
  18. Copy code
  19. This function exemplifies several situations. According to the case in this article, enter zendi_smart_strcmp to get a closer look:

    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. // Try to convert the string to a numeric type
    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. // Compare numbers
    10. ...
    11. } else {
    12. // All cannot be converted into numbers
    13. // Then call zend_binary_zval_strcmp
    14. // Essentially memcmp One layer of encapsulation
    15. Z_LVAL_P(result) = zend_binary_zval_strcmp(s1, s2);
    16. ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_LVAL_P(result)));
    17. }
    18. }
    Copy code

    Then "2014-05-01 Can 00:00:00" be converted into a number?

    You still have to look at the implementation rules of 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 out the blank part at the beginning of the string*/
    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. /* Determine whether it is hexadecimal */
    21. if (length > 2 && *str == '0' && (str[1] == 'x' || str[1] == 'X')) {
    22. base = 16;
    23. ptr += 2;
    24. }
    25. /* Ignore the following 0s */
    26. while (*ptr == '0') {
    27. ptr++;
    28. }
    29. /* Calculate the number of digits in the number, and Determine whether it is integer or floating point*/
    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 < 1) {
    36. goto process_double;
    37. } else if ((*ptr == 'e' || *ptr == 'E') && dp_or_e < 2) {
    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 < SIZEOF_LONG * 2 || (digits == SIZEOF_LONG * 2 && ptr[-digits] <= '7'))) {
    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. // Process floating point numbers
    62. } else {
    63. return 0;
    64. }
    65. // If fault tolerance is not allowed, exit with an error
    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. // To allow fault tolerance, try to convert str into numbers
    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 < 0 || (cmp == 0 && *str == '-'))) {
    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. }
    Copy code

    The code is relatively long, but if you read it carefully, the rules for converting str to num are still very clear.

    Pay special attention to the allow_errors parameter, which directly determines that "2014-05-01 00:00:00" cannot be converted into a number in this example.

    So in the end, the operation of "2014-04-17 00:00:00"

    Since it is memcmp, it is not difficult to understand why the writing method mentioned at the beginning of the article can also run correctly.

    Fault-tolerant conversion When is allow_errors true? An excellent example is zend_parse_parameters. The implementation of zend_parse_parameters will not be described in detail. Interested readers can study it by themselves. When calling is_numeric_string, allow_errors is set to -1.

    For example:

    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. // The expected second parameter is timestamp, which is long
    8. // Assuming that the string is mistakenly passed in when the upper layer is called, zend_parse_parameters will still try to parse the string into long as much as possible
    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. }
    Copy code

    This is the internal implementation of PHP's date function.

    When calling date, if the second parameter is passed into string, the effect is as follows:

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

    Although a notice level error was reported, '0-1-2' was still successfully converted to 0



Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn