想必phper对date()函数不会陌生,date('Y-m-d H:i:s')是常见的用法。date扩展代码行数不少,而且一大块宏让人摸不着头脑。先根据自己的思路写一个吧,给PHP添加一个打印当前时间格式化形式的函数。函数原型位于ext/standard/basic_functions.stub.php,function pmydate(string $value): void {}函数接收一个格式化字符串,仅支持Y、m、d、H、i、s几种格式,没有返回值,直接输出结果。
// ext/standard/basic_functions.stub.php
function pmydate(string $value): void {}
扩展代码:
// ext/standard/basic_functions.c
PHP_FUNCTION(pmydate)
{
    zval *zv_ptr;
    php_printf("passed %d parameters to the function: pmydate\n", ZEND_NUM_ARGS());
    if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zv_ptr) == FAILURE) {
        return;
    }
    if (Z_TYPE_P(zv_ptr) != IS_STRING) {
        php_printf("Expect one string argument\n");
        return;
    }
    time_t now = time(0);
    struct tm *lt = localtime(&now);
    char c;
    int val;
    char type;
    char prev = '\0';
    for (int i = 0; i < (*zv_ptr).value.str->len; i++) {
        c = (*zv_ptr).value.str->val[i];
        type = 's';
        switch (c) {
        case 'Y':
            val = lt->tm_year + 1900;
            type = 'i';
            break;
        case 'm':
            val = lt->tm_mon + 1;
            break;
        case 'd':
            val = lt->tm_mday;
            break;
        case 'H':
            val = lt->tm_hour;
            break;
        case 'i':
            val = lt->tm_min;
            break;
        case 's':
            val = lt->tm_sec;
            break;
        case '\\':
            val = c;
            type = '\0';
            break;
        default:
            val = c;
            type = 'c';
        }
        if (prev != '\\') {
            switch (type) {
            case 's':
                php_printf("%02d", val);
                break;
            case 'i':
                php_printf("%d", val);
                break;
            case 'c':
                php_printf("%c", val);
                break;
            default:;
            }
        } else {
            php_printf("%c", c);
        }
        prev = c;
    }
    php_printf("\n");
}
用法如下:
$ ./output/bin/php -r 'pmydate("Y-m-d H:i:s \Y-\m-\d \H:\i:\s");'
passed 1 parameters to the function: pmydate
2025-01-08 00:00:00 Y-m-d H:i:s
- 定义函数: 使用
PHP_FUNCTION宏封装函数名; - 解释参数:
zv_ptr是一个指针变量,指向zval类型,zval是PHP变量类型;php_printf("passed %d parameters to the function: pmydate\n", ZEND_NUM_ARGS())仅用于查看函数参数数量;zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zv_ptr)解释函数参数到zv_ptr指针,如果解释参数失败,直接返回; - 判断参数类型:
Z_TYPE_P(zv_ptr)获取解释后的zv_ptr指向的参数类型,如果不是IS_STRING类型,返回; - 获取时间:使用
C语言的时间函数获取当前时间,保存到指向struct tm类型的指针中; - 声明临时变量:
c保存当前指向的字节,val保存格式串对应字节表示的时间,type表示当前字节应该在输出时以什么类型来处理,prev保存上一个字节; - 遍历字符串参数:由于要接收一个格式化字符串,因此遍历的数据位于
(*zv_ptr).value.str中,(*zv_ptr)就是格式化字符串;(*zv_ptr).value是zend_value2.类型,实际上就是一个union,保存具体的参数;字符串参数当然就要通过zend_value的str字段来获取了; - 逐个获取字符串参数中的字节:
(*zv_ptr).value.str是指针,指向zend_string类型,zend_string同时也是一个struct,字符串的字节实际保存在zend_string的val字段中,因此(*zv_ptr).value.str->val[i]指向单个字节; - 根据字符串参数的字节获取对应的时间,其中
\字符特殊处理一下,前一个字节为\时,当前字符不作解释; - 其中大片的
switch分支不作详细解释,应该很好理解; - 为什么不使用
printf而要使用php_printf?因为PHP有多种SAPI,如果只是使用printf只会默认在控制台输出,封装的php_printf则会将数据保存到缓冲中,根据不同的SAPI写入不同的输出流;