>  기사  >  백엔드 개발  >  PHP 날짜 문자열 비교 예

PHP 날짜 문자열 비교 예

WBOY
WBOY원래의
2016-07-25 09:13:13945검색

프로젝트에 회원 만료 여부를 비교하는 기능이 있습니다. 동료의 코드를 검토한 결과 작성 방법이 다소 이상한 것을 발견했지만 온라인에서는 버그가 없었습니다.

구현:

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

두 개의 시간을 비교해야 하는 경우 일반적으로 유닉스 타임스탬프로 변환하여 두 개의 int 유형 숫자와 비교합니다. 이 구현은 구체적으로 시간을 문자열로 표현한 다음 두 문자열에 대해 비교 작업을 수행합니다.

작성방법은 차치하고, PHP 내부에서는 어떻게 비교가 이루어지는지 매우 궁금합니다.

더 이상 고민하지 말고 소스 코드부터 추적을 시작해 보겠습니다.

편집 기간 다음과 유사한 구문은 zend_언어_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 < expr { zend_do_binary_op(ZEND_IS_SMALLER, &$$, &$1, &$3 TSRMLS_CC) }
  6. expr <= expr { zend_do_binary_op(ZEND_IS_SMALLER_OR_EQUAL, &$$, &$1, &$3 TSRMLS_CC) }
  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만 저장합니다.

집행기간 Opcode에 따라 해당 처리 함수인 ZEND_IS_SMALLER_SPEC_CONST_CONST_HANDLER로 점프합니다.

  1. 정적 정수 ZEND_FASTCALL ZEND_IS_SMALLER_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
  2. {
  3. zend_op *opline = EX(opline);
  4. z 발 *결과 = &EX_T(opline->result.u.var).tmp_var;
  5. Compare_function(result,
  6. &opline->op1.u.constant,
  7. &opline->op2.u. 상수 TSRMLS_CC);
  8. ZVAL_BOOL(결과, (Z_LVAL_P(결과) < 0));
  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 변환 = 0;
  5. zval op1_copy, op2_copy;
  6. zval *op_free;
  7. while (1) {
  8. 스위치 (TYPE_PAIR(Z_TYPE_P(op1)), Z_TYPE_P( op2))) {
  9. 케이스 TYPE_PAIR(IS_LONG, IS_LONG):
  10. ...
  11. 케이스 TYPE_PAIR(IS_DOUBLE, IS_LONG):
  12. ...
  13. 케이스 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. }
코드 복사

이 함수는 이 기사의 사례에 따라 몇 가지 상황을 예시합니다. 자세히 살펴보려면 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의 1계층 캡슐화
  15. Z_LVAL_P(결과) = 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'을 숫자로 변환할 수 있나요?

is_numeric_string의 구현 규칙을 살펴봐야 합니다.

  1. 정적 인라인 zend_uchar is_numeric_string(const char *str, int length, long *lval, double *dval, intallow_errors)
  2. {
  3. const char *ptr;
  4. int base = 10, 숫자 = 0, dp_or_e = 0;
  5. double local_dval;
  6. zend_uchar type;
  7. if (!length) {
  8. return 0;
  9. }
  10. /* 문자열 시작 부분의 공백 부분을 잘라냅니다*/
  11. while (*str == ' ' || *str == 't' || *str = = 'n' || *str == 'r' || *str == 'v' || *str == 'f') {
  12. str ;
  13. 길이--;
  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 || 허용_오류 == 1)); 숫자 , ptr ) {
  31. check_digits:
  32. if (ZEND_IS_DIGIT(*ptr) || (base == 16 && ZEND_IS_XDIGIT(*ptr)) ) {
  33. 계속;
  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. // 부동 소수점 숫자 처리
  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, "잘못된 형식의 숫자 값이 발생했습니다.");
  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 < 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. }
코드 복사

코드는 비교적 길지만 주의 깊게 읽어보면 str을 num으로 변환하는 규칙은 여전히 ​​매우 명확합니다.

이 예에서 "2014-05-01 00:00:00"을 숫자로 변환할 수 없음을 직접 결정하는 Allow_errors 매개변수에 특히 주의하세요.

결국 "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. // 예상되는 두 번째 매개변수는 긴 타임스탬프입니다.
  8. // 상위 계층이 호출될 때 문자열이 실수로 전달되었다고 가정하면 zend_parse_parameters는 여전히 그만큼 시도합니다. 가능한 한 긴 문자열로 구문 분석
  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를 호출할 때 두 번째 매개변수를 string으로 전달하면 효과는 다음과 같습니다.

  1. echo date('Y-m-d', '0-1-2');
  2. // 출력
  3. PHP 공지사항 : 1행의 명령줄 코드에서 잘못된 형식의 숫자 값이 발견되었습니다.
  4. 1970-01-01
코드 복사

알림은 다음과 같습니다. 레벨 오류가 보고되었지만 여전히 '0-1-2'를 0으로 성공적으로 변환했습니다



성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.