PhpSpreadsheet 计算引擎完全指南:公式求值、缓存管理与日期时间处理

  • 后端

【免费下载链接】PhpSpreadsheet

A pure PHP library for reading and writing spreadsheet files

项目地址: https://gitcode.com/gh_mirrors/ph/PhpSpreadsheet
点击查看 免费下载

本文是 PhpSpreadsheet 计算引擎(Calculation Engine)的完整技术指南。PhpSpreadsheet 将整个电子表格保存在内存中,因此它不仅能够读写文件,还能像 Excel 一样对单元格中的公式进行实时求值。本文覆盖公式求值 API(getCalculatedValue())、插入/删除行列时的公式自动调整、计算缓存控制、引擎的已知限制,以及 Excel 与 PHP 之间日期时间值的完整换算机制,并给出数据库函数与日期时间函数的逐项参考(含可运行的 PHP 示例)。读完本文,你将能够在自己的 PHP 应用中直接对工作簿执行公式计算,并正确处理跨 Excel/PHP 的日期时间语义。

在命令行中调用 getCalculatedValue 对发票示例中的 E11 单元格求值,得到结果 64

使用 PhpSpreadsheet 计算引擎

执行公式计算

在 PhpSpreadsheet 中,单元格既可以保存值类型数据(数字或文本),也可以保存公式类型数据(一个可以被求值的公式)。例如公式 =SUM(A1:A10) 会被求值为 A1、A2、……、A10 中所有值的和。

对包含公式的单元格调用 getValue(),返回的是公式本身(字符串),而不是计算结果。要真正执行公式求值,需要调用该单元格的 getCalculatedValue() 方法:

$spreadsheet->getActiveSheet()->getCell('E11')->getCalculatedValue();

以仓库自带的发票示例(invoice demo)为例,对 E11 单元格调用上述代码会得到数值 "64"(见上图命令行输出)。对一个不含公式的单元格调用 getCalculatedValue(),则直接返回该单元格的值,不会触发任何求值过程。

在 Cell 实现 中可以看到,getCalculatedValue() 会先判断单元格的数据类型:当 $this->dataType === DataType::TYPE_FORMULA 时,才通过 Calculation::getInstance($worksheet->getParent()) 取得计算引擎实例并执行公式解析与求值;否则直接返回原始值。

在调用求值前,可以用 isFormula() 判断单元格是否包含公式,从而决定是否需要执行一次真正的求值:

$spreadsheet->getActiveSheet()->getCell('E11')->isFormula();

该方法返回布尔值 true/false(实现见 Cell.php),用于区分普通值与公式值。

关于数组公式(Array Formulas)的更多细节,请参阅 recipes 文档。

公式的书写规则

向单元格写入公式时,公式必须按照 英文版 Microsoft Office Excel 的写法书写,PhpSpreadsheet 内部也始终以这一格式处理所有公式。具体规则如下:

  • 小数分隔符为 .(句点)
  • 函数参数分隔符为 ,(逗号)
  • 矩阵(数组)行分隔符为 ;(分号)
  • 必须使用英文函数名

插入/删除行列时公式的自动调整

PhpSpreadsheet 的公式解析器有一项很实用的能力:当插入或删除行/列时,自动调整受影响的公式引用。下面是一个具体例子。

插入行之前,单元格 E11 中的公式为 SUM(E4:E9)

如上图所示,单元格 E11 中的公式是 SUM(E4:E9)。现在执行下面的代码,插入两行新的产品记录:

$spreadsheet->getActiveSheet()->insertNewRowBefore(7, 2);

插入两行之后,公式随单元格一起移动并自动扩展为 SUM(E4:E11)

可以看到,原 E11 单元格(插入两行后移动到 E13)中的公式被自动改写为 SUM(E4:E11),求和范围随新插入的行扩展。同时,被插入的单元格还会像 Excel 一样复制上一单元格的样式信息。需要注意,行列插入同时支持行(insertNewRowBefore)与列(insertNewColumnBefore)方向。

如果希望"锚定"公式中的某个特定单元格,可以使用 $ 符号前缀列和/或行号,用法与 MS Excel 完全一致。例如公式 SUM(E$4:E9) 在行 1 之后插入 2 行后,会被调整为 SUM(E$4:E11)——$ 固定了第 4 行作为范围的起点,范围终点则随插入行为自动延伸。

计算缓存(Calculation Cache)

默认缓存行为

计算引擎在完成某个单元格公式的求值后,会把结果缓存起来。这意味着对同一个单元格第二次调用 getCalculatedValue() 时,结果直接从缓存返回,而不会再次执行公式求值。公式求值是开销较大的操作,缓存机制能显著提升批量读取时的性能。缓存实现在 Calculation.php 中,由 calculationCache 属性按工作表维度保存结果。

禁用与清空缓存

但有些场景下你并不希望使用缓存——例如底层数据发生了变化,需要让同一公式基于新数据重新求值。此时可以禁用缓存:

Calculation::getInstance($spreadsheet)->disableCalculationCache();

disableCalculationCache() 会关闭计算缓存,并同时清空当前已有的缓存内容。从源码看,它内部等价于调用 setCalculationCacheEnabled(false),而后者在切换开关的同时会执行 clearCalculationCache()(见 Calculation.php)。

如果只想清空缓存而保留缓存开关,可以调用:

Calculation::getInstance($spreadsheet)->clearCalculationCache();

此外引擎还提供了一些细粒度方法(同样定义在 Calculation.php):

  • enableCalculationCache():重新开启缓存
  • clearCalculationCacheForWorksheet(string $worksheetName):只清空指定工作表的缓存项
  • renameCalculationCacheForWorksheet($fromWorksheetName, $toWorksheetName):工作表重命名时同步迁移缓存

已知限制

PhpSpreadsheet 计算引擎存在一些已知限制,大部分源于其实现方式:Excel 公式在执行前会被转换为 PHP 代码,因此公式求值不可避免会受 PHP 语言特性的影响。

不支持数组参数的函数调用

虽然大多数 Excel 函数实现已支持数组参数,仍有少数函数本应接受数组作为参数但没有实现。在这些情况下,结果可能只是一个单值而不是数组,或者返回 #VALUE! 错误。

运算符优先级差异

在 Excel 中,+ 的优先级高于 &(文本连接符),就像普通代数中 * 优先于 + 一样。而 PhpSpreadsheet 内置的计算引擎并未遵循这条规则(其行为与 PHP 运算符优先级一致)。在设计依赖运算符优先级的公式时需要注意这一差异。

数字与文本混合的公式

涉及数字和文本的公式可能产生意料之外的结果,甚至生成无法读取的文件内容。例如公式 =3+"Hello " 在 Excel 中预期产生 #VALUE! 错误;但由于 PHP 会把 "Hello " 转换为数值(0),该公式在 PhpSpreadsheet 中的求值结果是 3,而不是错误。这还会导致生成的 Excel 文档包含不可读的内容。

数组函数与 Xls 写入器

并非所有函数都受支持,完整清单请参阅 按名称排序的函数列表。另外,在 Excel 2003 兼容包(compatibility pack)中公式"似乎不计算"是兼容包的正常行为,Xlsx 格式显示正常;如果确实需要计算后的值,应改用 \PhpOffice\PhpSpreadsheet\Writer\Xls 写入,或在 Excel 2003 中强制重新计算。

PAD(Precision As Displayed,按显示精度计算)不支持

引擎目前没有支持 Precision As Displayed 的计划,即不会按单元格显示精度(小数位数)参与后续计算。

处理日期和时间值

Excel 日期/时间函数的返回值类型

任何在 Excel 中返回日期值的日期时间函数,在 PhpSpreadsheet 中都可以返回三种类型之一:Excel 时间戳、PHP 时间戳或 DateTime 对象。脚本可以通过 \PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType() 改变返回类型:

\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType($returnDateType);

$returnDateType 可以使用以下常量(定义见 Functions.php):

  • \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_PHP_NUMERIC(即 RETURNDATE_UNIX_TIMESTAMP)
  • \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_PHP_OBJECT(即 RETURNDATE_PHP_DATETIME_OBJECT)
  • \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL

方法在成功时返回布尔值 true,失败(例如传入非法值)时返回 false。当前设置可以通过以下方法查询:

$returnDateType = \PhpOffice\PhpSpreadsheet\Calculation\Functions::getReturnDateType();

注意:本文所引文档曾说明默认值为 RETURNDATE_PHP_NUMERIC;但当前仓库源码中静态属性 $returnDateType 的初始值为 RETURNDATE_EXCEL(见 Functions.php),即以当前源码为准,默认返回 Excel 时间戳。实现中每个返回日期的函数都会先判断当前 Return Date Type,再决定返回哪种类型。

PHP 时间戳

当 Return Date Type 设置为 RETURNDATE_PHP_NUMERIC 时,任何访问 Excel 日期时间函数所返回的日期值都是一个整数,表示自 PHP/Unix 基准日期以来的秒数。PHP/Unix 基准日期(0)是 1970 年 1 月 1 日 00:00 UTC。该值可以为正或负:-3600 表示 1969 年 12 月 31 日 23:00;+3600 表示 1970 年 1 月 1 日 01:00。这使 32 位 PHP 的日期范围介于 1901 年 12 月 14 日至 2038 年 1 月 19 日之间。

PHP DateTime 对象

当 Return Date Type 设置为 RETURNDATE_PHP_OBJECT 时,任何返回的日期值都是一个 PHP DateTime 对象。

Excel 时间戳

Excel 时间戳以整数或浮点数存储:整数部分表示自基准日期以来的天数,小数部分表示一天中的时间(0 为午夜,0.5 为正午,0.999… 为次日午夜之前)。Excel 的基准日期取决于所使用的日历:Windows 1900 日历的基准日期是 1900 年 1 月 1 日;Mac 1904 日历的基准日期是 1904 年 1 月 1 日。

当 Return Date Type 设置为 RETURNDATE_EXCEL 时,返回值为上述 Excel 时间戳格式的浮点数。

脚本可以通过以下方式切换计算 Excel 日期值所使用的日历:

\PhpOffice\PhpSpreadsheet\Shared\Date::setExcelCalendar($baseDate); // 静态属性,不推荐
$spreadsheet->setExcelCalendar($baseDate); // 实例属性,推荐

$baseDate 可以使用以下常量:

  • \PhpOffice\PhpSpreadsheet\Shared\Date::CALENDAR_WINDOWS_1900
  • \PhpOffice\PhpSpreadsheet\Shared\Date::CALENDAR_MAC_1904

方法成功时返回布尔值 true,失败(例如传入非法值)时返回 false。当前设置可通过以下方式查询:

$baseDate = \PhpOffice\PhpSpreadsheet\Shared\Date::getExcelCalendar(); // 静态
$baseDate = $spreadsheet->getExcelCalendar(); // 实例

默认值为 CALENDAR_WINDOWS_1900。

返回日期/时间值的函数:DATE、DATEVALUE、EDATE、EOMONTH、NOW、TIME、TIMEVALUE、TODAY。

接受日期和时间值作为参数的 Excel 函数

作为参数传入函数的日期值可以是:Excel 时间戳、PHP 时间戳、DateTime 对象,或包含日期值的字符串(如 '1-Jan-2009')。PhpSpreadsheet 会根据 PHP 数据类型尝试识别其类型:

  • 整数数值 → 视为 PHP/Unix 时间戳
  • 实数(浮点)数值 → 视为 Excel 日期/时间戳
  • PHP DateTime 对象 → 直接作为 DateTime 对象
  • 字符串(即使是纯数字字符串)→ 会根据服务器本地化设置转换为 DateTime 对象进行日期验证。因此传入有歧义的值 '07/08/2008',在英国服务器设置下会被视为 2008 年 8 月 7 日,而在美国服务器设置下会被视为 2008 年 7 月 8 日。但如果传入 '31/12/2008' 这类对美国服务器而言是错误但并没有歧义的值,PhpSpreadsheet 会尝试纠正为 2008 年 12 月 31 日。如果字符串内容无法匹配 PHP DateTime 对象所实现的 strtotime() 支持的任何格式(它能处理比普通 strtotime() 更广泛的格式),函数将返回 #VALUE 错误。

需要强调的是,Excel 官方建议日期函数一律使用日期/时间戳参数,PhpSpreadsheet 同样如此建议:尽量避免使用字符串,因为结果不可预测。

同样的原则适用于向 Excel 写入数据:包含实际日期值的单元格(而非返回日期的 Excel 函数)总是被写成 Excel 日期,必要时进行转换。如果格式化为日期的单元格包含整数或 DateTime 对象值,写盘时会被转换为 Excel 值;如果包含实数,则无需转换。注意字符串值会作为字符串写入,而不会被转换为 Excel 日期时间戳。

期望日期/时间值作为参数的函数:DATEDIF、DAY、DAYS360、EDATE、EOMONTH、HOUR、MINUTE、MONTH、NETWORKDAYS、SECOND、WEEKDAY、WEEKNUM、WORKDAY、YEAR、YEARFRAC。

辅助方法(Helper Methods)

除 setExcelCalendar()/getExcelCalendar() 外,\PhpOffice\PhpSpreadsheet\Shared\Date 类还提供以下与日期处理相关的辅助方法:

Date::excelToTimestamp($excelDate)

将 Excel 日期时间戳转换为 PHP 序列化日期/时间戳。注意:该方法不会对超出 PHP 日期时间戳有效范围的 Excel 日期进行捕获(即不做范围检查)。

Date::excelToDateTimeObject($excelDate)

将 Excel 日期/时间戳转换为 PHP DateTime 对象。

Date::PHPToExcel($PHPDate)

将 PHP 序列化日期/时间戳或 PHP DateTime 对象转换为 Excel 日期时间戳。

Date::formattedPHPToExcel($year, $month, $day, $hours = 0, $minutes = 0, $seconds = 0)

接收年、月、日(以及可选的小时、分钟、秒)值,返回 Excel 日期时间戳值。

日期时间戳转换的时区支持

PhpSpreadsheet 日期函数默认使用的时区是 UST(Universal Standard Time)。如果需要使用其他时区,可以使用以下方法:

Date::getDefaultTimezone()

返回 PhpSpreadsheet 当前用于处理日期和时间的时区值。

Date::setDefaultTimezone($timeZone)

将 Excel 日期时间戳转换所使用的时区设置为 $timeZone,该值必须是合法的 PHP DateTimeZone 值。返回值为布尔值:true 表示成功,false 表示失败(例如传入了非法的 DateTimeZone 值)。

Date::excelToDateTimeObject($excelDate, $timeZone) 与 Date::excelToTimeStamp($excelDate, $timeZone)

这两个函数支持可选的第二个参数指定时区,只对本次调用生效,不影响 PhpSpreadsheet 的默认时区。

计算从工作表读取的日期/时间值

对于直接输入到电子表格中的日期/时间值,通常无需特殊处理:它们会以数字形式存储,并带有相应的数字格式。但根据具体值不同,可能以整数或浮点数存储。如果这造成问题,可以通过 setCalculateDateTimeType() 强制 getCalculatedValue() 根据单元格数字格式返回浮点数而非整数:

// 所有带 Date、Time 或 DateTime 样式的字段都返回浮点数
\PhpOffice\PhpSpreadsheet\Cell\Cell::setCalculateDateTimeType(\PhpOffice\PhpSpreadsheet\Cell\Cell::CALCULATE_DATE_TIME_FLOAT);
// 所有带 Time 或 DateTime 样式的字段都返回浮点数
\PhpOffice\PhpSpreadsheet\Cell\Cell::setCalculateDateTimeType(\PhpOffice\PhpSpreadsheet\Cell\Cell::CALCULATE_TIME_FLOAT);
// 默认——带 Date、Time 或 DateTime 样式的字段按原存储类型返回
\PhpOffice\PhpSpreadsheet\Cell\Cell::setCalculateDateTimeType(\PhpOffice\PhpSpreadsheet\Cell\Cell::CALCULATE_DATE_TIME_ASIS);

在 Cell.php 的源码中可以看到,这三个常量对应三种模式,setCalculateDateTimeType() 通过 match 校验传入值,非法值会抛出 CalculationException;实际转换由内部方法 convertDateTimeInt() 配合 SharedDate::isDateTime() 判断单元格是否属于日期/时间类型后完成。注意该方法是静态的,作用于全局设置。

函数参考

本部分以仓库 docs/topics/calculation-engine.md 的函数参考为骨架,结合 samples/Database 与 samples/DateTime 目录中的可运行示例整理。所有示例中 $worksheet 均指当前活动工作表。

数据库函数(Database Functions)

以下数据库函数的 database、field、criteria 三个参数具有相同的语义:

  • database:构成列表或数据库的单元格区域。数据库是一组相关数据的列表,其中相关信息的行是记录,数据列是字段;列表的第一行包含每列的标签。
  • field:指示函数使用数据库中哪一列。可以输入列标签字符串(用双引号括起,如 "Age" 或 "Yield"),也可以输入不带引号的数字表示列在列表中的位置:1 表示第一列,2 表示第二列,依此类推。
  • criteria:包含所指定条件的单元格区域。criteria 可以使用任意区域,只要它包含至少一个列标签,以及该列标签下方至少一个用于为该列指定条件的单元格。

各函数均返回 float(DGET 返回 mixed)。

DAVERAGE

返回列表中与指定条件匹配的某列单元格的平均值(统计均值)。

语法:DAVERAGE(database, field, criteria)

$database = [
    [ 'Tree',  'Height', 'Age', 'Yield', 'Profit' ],
    [ 'Apple',  18,       20,    14,      105.00  ],
    [ 'Pear',   12,       12,    10,       96.00  ],
    [ 'Cherry', 13,       14,     9,      105.00  ],
    [ 'Apple',  14,       15,    10,       75.00  ],
    [ 'Pear',    9,        8,     8,       76.80  ],
    [ 'Apple',   8,        9,     6,       45.00  ],
];

$criteria = [
    [ 'Tree',      'Height', 'Age', 'Yield', 'Profit', 'Height' ],
    [ '="=Apple"', '>10',    NULL,  NULL,    NULL,     '<16'    ],
    [ '="=Pear"',  NULL,     NULL,  NULL,    NULL,     NULL     ],
];

$worksheet->fromArray( $criteria, NULL, 'A1' )
    ->fromArray( $database, NULL, 'A4' );

$worksheet->setCellValue('A12', '=DAVERAGE(A4:E10,"Yield",A1:B2)');

$retVal = $worksheet->getCell('A12')->getCalculatedValue();
// $retVal = 12
DCOUNT

返回列表中与指定条件匹配的某列中包含数字的单元格个数。

语法:DCOUNT(database, [field], criteria)

$worksheet->setCellValue('A12', '=DCOUNT(A4:E10,"Height",A1:B3)');
$retVal = $worksheet->getCell('A12')->getCalculatedValue();
// $retVal = 3

注意:在 MS Excel 中 field 参数是可选的,省略时 DCOUNT 对匹配条件的所有记录计数;该逻辑尚未在 PhpSpreadsheet 中实现。

DCOUNTA

返回列表中与指定条件匹配的某列中非空单元格的个数。

语法:DCOUNTA(database, [field], criteria)

$worksheet->setCellValue('A12', '=DCOUNTA(A4:E10,"Yield",A1:A3)');
$retVal = $worksheet->getCell('A12')->getCalculatedValue();
// $retVal = 5

注意:同 DCOUNT,省略 field 参数的逻辑尚未在 PhpSpreadsheet 中实现。

DGET

从列表或数据库中提取与指定条件匹配的单个值。

语法:DGET(database, field, criteria)

$worksheet->setCellValue('A12', '=DGET(A4:E10,"Age",A1:F2)');
$retVal = $worksheet->getCell('A12')->getCalculatedValue();
// $retVal = 14
DMAX

返回列表中与指定条件匹配的某列的最大值。

语法:DMAX(database, field, criteria)

$worksheet->setCellValue('A12', '=DMAX(A4:E10,"Profit",A1:B2)');
$retVal = $worksheet->getCell('A12')->getCalculatedValue();
// $retVal = 105
DMIN

返回列表中与指定条件匹配的某列的最小值。

语法:DMIN(database, field, criteria)

$worksheet->setCellValue('A12', '=DMIN(A4:E10,"Yield",A1:A3)');
$retVal = $worksheet->getCell('A12')->getCalculatedValue();
// $retVal = 6
DPRODUCT

将列表中与指定条件匹配的某列各值相乘。

语法:DPRODUCT(database, field, criteria)

$worksheet->setCellValue('A12', '=DPRODUCT(A4:E10,"Yield",A1:B2)');
$retVal = $worksheet->getCell('A12')->getCalculatedValue();
// $retVal = 140
DSTDEV

基于样本估算总体标准差,使用列表中与指定条件匹配的某列数值。

语法:DSTDEV(database, field, criteria)

$worksheet->setCellValue('A12', '=DSTDEV(A4:E10,"Yield",A1:A3)');
$retVal = $worksheet->getCell('A12')->getCalculatedValue();
// $retVal = 2.97
DSTDEVP

基于整个总体计算标准差,使用列表中与指定条件匹配的某列数值。

语法:DSTDEVP(database, field, criteria)

$worksheet->setCellValue('A12', '=DSTDEVP(A4:E10,"Yield",A1:A3)');
$retVal = $worksheet->getCell('A12')->getCalculatedValue();
// $retVal = 2.65
DSUM

将列表中与指定条件匹配的某列数值求和。

语法:DSUM(database, field, criteria)

$worksheet->setCellValue('A12', '=DSUM(A4:E10,"Profit",A1:B2)');
$retVal = $worksheet->getCell('A12')->getCalculatedValue();
// $retVal = 225

原文档中此示例的公式写作 =DMIN(A4:E10,"Profit",A1:A2),按其所在的 DSUM 章节上下文应为 =DSUM(...)。

DVAR / DVARP

原文档标注为 "Not yet documented"(尚未编写文档)。其实现位于 src/PhpSpreadsheet/Calculation/Database 目录,仓库中也提供了对应的可运行示例 samples/Database/DVAR.php 与 samples/Database/DVARP.php。

日期和时间函数(Date and Time Functions)

Excel 提供大量用于日期时间操作与计算的函数。强烈建议先阅读上文"处理日期和时间值"一节,理解 PhpSpreadsheet 如何在 Excel 与 PHP 的日期时间语义之间做调和,再使用以下函数。

DATE

返回由参数引用日期对应的 Excel 时间戳、PHP 时间戳或 DateTime 对象。

语法:DATE(year, month, day)

  • year:年份数字。若该值介于 0(含)到 1899(含)之间(Windows 1900 日历),或介于 4(含)到 1903(含)之间(Mac 1904 日历),则 PhpSpreadsheet 会将其加上日历基准年:例如 Windows 1900 日历下传入 108 会解释为 2008 年,Mac 1904 日历下则解释为 2012 年。
  • month:月份数字。若大于 12,DATE 会将该月数加到指定年份的第一个月:例如 DATE(2008,14,2) 返回 2009 年 2 月 2 日。若小于 1,则该值先减 1,再从指定年份的第一个月减去:例如 DATE(2008,0,2) 返回 2007 年 12 月 2 日;DATE(2008,-1,2) 返回 2007 年 11 月 2 日。
  • day:日期数字。若大于指定月份(和年份)的天数,DATE 会将该天数加到该月第一天:例如 DATE(2008,1,35) 返回 2008 年 2 月 4 日。若小于 1,则该值先减 1,再从指定年份的第一个月减去:例如 DATE(2008,3,0) 返回 2008 年 2 月 29 日;DATE(2008,3,-2) 返回 2008 年 2 月 27 日。

返回值:mixed,可能是 PHP 时间戳(整数)、PHP DateTime 对象或 Excel 时间戳(实数),取决于 Functions::getReturnDateType() 的设置。

$worksheet->setCellValue('A1', 'Year')
    ->setCellValue('A2', 'Month')
    ->setCellValue('A3', 'Day');

$worksheet->setCellValue('B1', 2008)
    ->setCellValue('B2', 12)
    ->setCellValue('B3', 31);

$worksheet->setCellValue('D1', '=DATE(B1,B2,B3)');

$retVal = $worksheet->getCell('D1')->getCalculatedValue();
// $retVal = 1230681600

下面的示例展示同一函数在不同 Return Date Type 下的返回差异,并演示了通过 setCalculationCacheEnabled(FALSE) 关闭缓存以获取不同返回值:

// 需要对同一个单元格计算多次并期望不同的返回值,因此禁用计算缓存
\PhpOffice\PhpSpreadsheet\Calculation\Calculation::getInstance()->setCalculationCacheEnabled(FALSE);

$saveFormat = \PhpOffice\PhpSpreadsheet\Calculation\Functions::getReturnDateType();

\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType(
    \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL
);

$retVal = call_user_func_array(
    ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DATE'],
    [2008, 12, 31]
);
// $retVal = 39813.0

\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType(
    \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_PHP_NUMERIC
);

$retVal = call_user_func_array(
    ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DATE'],
    [2008, 12, 31]
);
// $retVal = 1230681600

\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType($saveFormat);
DATEDIF

以多种不同间隔计算两个日期之间的差值,例如年数、月数或天数。

语法:DATEDIF(date1, date2 [, unit])

  • date1 / date2:日期,可为 Excel 日期值、PHP 日期时间戳、PHP DateTime 对象或日期字符串。
  • unit:计算所用的间隔类型字符串:
unit含义说明
m月两个日期之间的完整日历月数。
d天两个日期之间的天数。
y年两个日期之间的完整日历年数。
ym排除年份的月视为同一年时,两个日期之间的完整日历月数。
yd排除年份的天视为同一年时,两个日期之间的完整日历天数。
md排除年份和月份的天视为同一年同一个月时,两个日期之间的完整日历天数。

unit 值不区分大小写,默认值为 d。

返回值:integer,取决于传入的间隔单位,可能是两个日期之间的完整天数、月数或年数。

$worksheet->setCellValue('A1', 'Year')
    ->setCellValue('A2', 'Month')
    ->setCellValue('A3', 'Day');

$worksheet->setCellValue('B1', 2001)
    ->setCellValue('C1', 2009)
    ->setCellValue('B2', 7)
    ->setCellValue('C2', 12)
    ->setCellValue('B3', 1)
    ->setCellValue('C3', 31);

$worksheet->setCellValue('D1', '=DATEDIF(DATE(B1,B2,B3),DATE(C1,C2,C3),"d")')
    ->setCellValue('D2', '=DATEDIF(DATE(B1,B2,B3),DATE(C1,C2,C3),"m")')
    ->setCellValue('D3', '=DATEDIF(DATE(B1,B2,B3),DATE(C1,C2,C3),"y")')
    ->setCellValue('D4', '=DATEDIF(DATE(B1,B2,B3),DATE(C1,C2,C3),"ym")')
    ->setCellValue('D5', '=DATEDIF(DATE(B1,B2,B3),DATE(C1,C2,C3),"yd")')
    ->setCellValue('D6', '=DATEDIF(DATE(B1,B2,B3),DATE(C1,C2,C3),"md")');

// d -> 3105, m -> 101, y -> 8, ym -> 5, yd -> 183, md -> 30

也可直接以 PHP 时间戳调用:

$date1 = 1193317015; // 25-Oct-2007 的 PHP 时间戳
$date2 = 1449579415; // 8-Dec-2015 的 PHP 时间戳

$retVal = call_user_func_array(
    ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DATEDIF'],
    [$date1, $date2, 'd']
);
// d -> 2966, m -> 97, y -> 8, ym -> 1, yd -> 44, md -> 13

注意:如果 date1 晚于 date2,DATEDIF 将返回 #NUM! 错误。

DATEVALUE

返回以文本字符串表示的日期所对应的日期值(序列号),用于将文本日期转换为序列号。

语法:DATEVALUE(dateString)

$worksheet->setCellValue('A1', 'Date String');
$worksheet->setCellValue('A2', '31-Dec-2008')
    ->setCellValue('A3', '31/12/2008')
    ->setCellValue('A4', '12-31-2008');

$worksheet->setCellValue('B2', '=DATEVALUE(A2)')
    ->setCellValue('B3', '=DATEVALUE(A3)')
    ->setCellValue('B4', '=DATEVALUE(A4)');

\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType(
    \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL
);

// B2、B3、B4 均得到 $retVal = 39813.0

以静态方式调用并切换返回类型:

$saveFormat = \PhpOffice\PhpSpreadsheet\Calculation\Functions::getReturnDateType();

\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType(
    \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL
);
$retVal = call_user_func_array(
    ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DATEVALUE'],
    ['31-Dec-2008']
);
// $retVal = 39813.0

\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType(
    \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_PHP_NUMERIC
);
$retVal = call_user_func_array(
    ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DATEVALUE'],
    ['31-Dec-2008']
);
// $retVal = 1230681600

\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType($saveFormat);

注意:DATEVALUE 使用 PHP DateTime 对象实现的 strtotime()(比普通 strtotime() 支持更广泛的格式),其他日期函数(如 DATEDIF)的参数为字符串时也会调用它进行解析。

警告:PhpSpreadsheet 接受的日期格式范围比 MS Excel 更广,因此可能出现 Excel 对无法解释的日期字符串返回 #VALUE! 错误、而 PhpSpreadsheet 却能将其翻译为正确日期值的情况。在写入 Xls 或 Xlsx 时,应谨慎对待在计算中使用字符串格式日期的那些工作簿。

DAY

返回某日期的日。返回 1 到 31 之间的整数。

语法:DAY(datetime)

$worksheet->setCellValue('A1', 'Date String')
    ->setCellValue('A2', '31-Dec-2008')
    ->setCellValue('A3', '14-Feb-2008');

$worksheet->setCellValue('B2', '=DAY(A2)')
    ->setCellValue('B3', '=DAY(A3)');

// B2 -> 31, B3 -> 14

以静态方式调用时,PhpSpreadsheet 的函数名为 \PhpOffice\PhpSpreadsheet\Calculation\Functions::DAYOFMONTH()(如 DAYOFMONTH('25-Dec-2008') 返回 25)。

DAYS360

基于 360 天的一年(12 个等长的 30 天月)计算两个日期之间的天数,用于某些会计系统。

语法:DAYS360(date1, date2 [, method])

  • date1 / date2:日期,可为 Excel 日期值、PHP 日期时间戳、PHP DateTime 对象或日期字符串。
  • method:布尔标志,决定计算方法:
method说明
FALSE美国(NASD)方法。若起始日期是某月最后一天,则视为该月 30 日;若结束日期是某月最后一天且起始日期早于某月 30 日,则结束日期变为下月 1 日;否则结束日期变为同月 30 日。
TRUE欧洲方法。发生在某月 31 日的起始日期和结束日期都变为同月 30 日。

method 默认值为 FALSE。

返回值:integer,基于 360 天年计算的两个日期之间的完整天数。

$worksheet->setCellValue('B1', 'Start Date')
    ->setCellValue('C1', 'End Date')
    ->setCellValue('A2', 'Year')
    ->setCellValue('A3', 'Month')
    ->setCellValue('A4', 'Day');

$worksheet->setCellValue('B2', 2003)
    ->setCellValue('B3', 2)
    ->setCellValue('B4', 3);

$worksheet->setCellValue('C2', 2007)
    ->setCellValue('C3', 5)
    ->setCellValue('C4', 31);

$worksheet->setCellValue('E2', '=DAYS360(DATE(B2,B3,B4),DATE(C2,C3,C4))')
    ->setCellValue('E4', '=DAYS360(DATE(B2,B3,B4),DATE(C2,C3,C4),FALSE)');

// E2 -> 1558, E4 -> 1557
$date1 = 37655.0; // 25-Oct-2007 的 Excel 时间戳
$date2 = 39233.0; // 8-Dec-2015 的 Excel 时间戳

$retVal = call_user_func_array(
    ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DAYS360'],
    [$date1, $date2]
);
// $retVal = 1558

$retVal = call_user_func_array(
    ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DAYS360'],
    [$date1, $date2, TRUE]
);
// $retVal = 1557

警告:当第三个(可选)参数使用 PHP 布尔值时,该函数目前与 Xls Writer 不兼容,写入器会报错。改用数值 0 或 1,或使用 Excel 的 TRUE()/FALSE() 函数则可正常工作。

EDATE

返回指定日期(start_date)之前或之后指示月数的日期所对应的 Excel 时间戳、PHP 时间戳或 DateTime 对象。常用于计算与发行日同一天到期的到期日或应付日。

语法:EDATE(baseDate, months)

  • baseDate:起始日期,可为 Excel 日期值、PHP 日期时间戳、PHP DateTime 对象或日期字符串。
  • months:要加上的月数,整数。正数得到未来日期,负数得到过去日期。

返回值:mixed,取决于 Functions::getReturnDateType()。

$worksheet->setCellValue('A1', 'Date String')
    ->setCellValue('A2', '1-Jan-2008')
    ->setCellValue('A3', '29-Feb-2008');

$worksheet->setCellValue('B2', '=EDATE(A2,5)')
    ->setCellValue('B3', '=EDATE(A3,-12)');

\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType(
    \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL
);

// B2 -> 39600.0 (1-Jun-2008)
// B3 -> 39141.0 (28-Feb-2007)
\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType(
    \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL
);

$retVal = call_user_func_array(
    ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'EDATE'],
    ['31-Oct-2008', 25]
);
// $retVal = 40512.0 (30-Nov-2010)

警告:该函数目前不受 Xls Writer 支持,因为它不是 Excel 5 的标准函数,而是 Analysis ToolPak 加载项提供的函数。

EOMONTH

返回指定日期(start_date)之前或之后指示月数所在月份的最后一天所对应的日期。

语法:EOMONTH(baseDate, months)

$worksheet->setCellValue('A1', 'Date String')
    ->setCellValue('A2', '1-Jan-2000')
    ->setCellValue('A3', '14-Feb-2009');

$worksheet->setCellValue('B2', '=EOMONTH(A2,5)')
    ->setCellValue('B3', '=EOMONTH(A3,-12)');

\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType(\PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL);

// B2 -> 39629.0 (30-Jun-2008)
// B3 -> 39507.0 (29-Feb-2008)
\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType(
    \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL
);

$retVal = call_user_func_array(
    ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'EOMONTH'],
    ['31-Oct-2008', 13]
);
// $retVal = 40147.0 (30-Nov-2010)

警告:同 EDATE,该函数目前不受 Xls Writer 支持(Analysis ToolPak 加载项函数)。

HOUR

返回时间值的小时。返回 0(上午 12:00)到 23(下午 11:00)之间的整数。

语法:HOUR(datetime)

$worksheet->setCellValue('A1', 'Time String')
    ->setCellValue('A2', '31-Dec-2008 17:30')
    ->setCellValue('A3', '14-Feb-2008 4:20 AM')
    ->setCellValue('A4', '14-Feb-2008 4:20 PM');

$worksheet->setCellValue('B2', '=HOUR(A2)')
    ->setCellValue('B3', '=HOUR(A3)')
    ->setCellValue('B4', '=HOUR(A4)');

// B2 -> 17, B3 -> 4, B4 -> 16

以静态方式调用时,函数名为 \PhpOffice\PhpSpreadsheet\Calculation\Functions::HOUROFDAY()(如 HOUROFDAY('09:30') 返回 9)。

MINUTE

返回时间值的分钟。返回 0 到 59 之间的整数。

语法:MINUTE(datetime)

$worksheet->setCellValue('A1', 'Time String')
    ->setCellValue('A2', '31-Dec-2008 17:30')
    ->setCellValue('A3', '14-Feb-2008 4:20 AM')
    ->setCellValue('A4', '14-Feb-2008 4:45 PM');

$worksheet->setCellValue('B2', '=MINUTE(A2)')
    ->setCellValue('B3', '=MINUTE(A3)')
    ->setCellValue('B4', '=MINUTE(A4)');

// B2 -> 30, B3 -> 20, B4 -> 45
MONTH

返回日期的月份。返回 1 到 12 之间的整数。

语法:MONTH(datetime)

$worksheet->setCellValue('A1', 'Date String');
$worksheet->setCellValue('A2', '31-Dec-2008');
$worksheet->setCellValue('A3', '14-Feb-2008');

$worksheet->setCellValue('B2', '=MONTH(A2)');
$worksheet->setCellValue('B3', '=MONTH(A3)');

// B2 -> 12, B3 -> 2

以静态方式调用时,函数名为 \PhpOffice\PhpSpreadsheet\Calculation\Functions::MONTHOFYEAR()(如 MONTHOFYEAR('14-July-2008') 返回 7)。

NETWORKDAYS

返回 起始日期 与 结束日期 之间的完整工作日数。工作日排除周末以及 holidays 中标识的任何日期。

语法:NETWORKDAYS(startDate, endDate [, holidays])

  • startDate / endDate:期间起止日期,可为 Excel 日期值、PHP 日期时间戳、PHP DateTime 对象或日期字符串。
  • holidays:可选的一个或多个要排除在工作日历之外的日期,如州/联邦假日和浮动假日。列表可以是包含日期的单元格区域,也可以是包含 Excel 日期值、PHP 日期时间戳、PHP 日期对象或日期字符串的数组常量。

返回值:integer,startDate 与 endDate 之间的工作日数。

NOW

返回当前日期和时间。

语法:NOW()

无参数。返回值:mixed,取决于 Functions::getReturnDateType()。以静态方式调用时,函数名为 \PhpOffice\PhpSpreadsheet\Calculation\Functions::DATETIMENOW()。

SECOND

返回时间值的秒。返回 0 到 59 之间的整数。

语法:SECOND(datetime)

$worksheet->setCellValue('A1', 'Time String')
    ->setCellValue('A2', '31-Dec-2008 17:30:20')
    ->setCellValue('A3', '14-Feb-2008 4:20 AM')
    ->setCellValue('A4', '14-Feb-2008 4:45:59 PM');

$worksheet->setCellValue('B2', '=SECOND(A2)')
    ->setCellValue('B3', '=SECOND(A3)')
    ->setCellValue('B4', '=SECOND(A4)');

// B2 -> 20, B3 -> 0, B4 -> 59
WEEKDAY

返回给定日期的星期几。默认返回 1 到 7 之间的整数,也可修改为返回 0 到 6。

语法:WEEKDAY(datetime [, method])

  • method:整数标志(0、1 或 2):
method说明
0返回 1(星期日)到 7(星期六)。
1返回 1(星期一)到 7(星期日)。
2返回 0(星期一)到 6(星期日)。

method 默认值为 1。

$worksheet->setCellValue('A1', 'Date String')
    ->setCellValue('A2', '31-Dec-2008')
    ->setCellValue('A3', '14-Feb-2008');

$worksheet->setCellValue('B2', '=WEEKDAY(A2)')
    ->setCellValue('B3', '=WEEKDAY(A3,0)')
    ->setCellValue('B4', '=WEEKDAY(A3,2)');

// B2 -> 12, B3 -> 2, B4 -> 2

注:原文档示例中 =WEEKDAY(A2) 的注释为 12,结合 B3/B4 的预期返回值,按上下文应为 3(2008-12-31 是星期三);此处保留文档原文,实际结果以当前实现为准。

以静态方式调用时,函数名为 \PhpOffice\PhpSpreadsheet\Calculation\Functions::WEEKDAY()。

YEAR

返回日期的年份。

语法:YEAR(datetime)

$worksheet->setCellValue('A1', 'Date String')
    ->setCellValue('A2', '17-Jul-1982')
    ->setCellValue('A3', '16-Apr-2009');

$worksheet->setCellValue('B2', '=YEAR(A2)')
    ->setCellValue('B3', '=YEAR(A3)');

// B2 -> 1982, B3 -> 2009

以静态方式调用:YEAR('14-July-2001') 返回 2001。

尚未编写文档的函数

以下函数在原文档中标注为 "Not yet documented",但实现与可运行示例均存在于仓库中,可参考 samples/DateTime 目录下对应的示例文件(如 TIME.php、TIMEVALUE.php、TODAY.php、WEEKNUM.php、WORKDAY.php、YEARFRAC.php):

  • TIME、TIMEVALUE、TODAY、WEEKNUM、WORKDAY、YEARFRAC

延伸阅读与源码入口

  • 后端

【免费下载链接】PhpSpreadsheet

A pure PHP library for reading and writing spreadsheet files

项目地址: https://gitcode.com/gh_mirrors/ph/PhpSpreadsheet
点击查看 免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值