【免费下载链接】PhpSpreadsheet
A pure PHP library for reading and writing spreadsheet files
本文是 PhpSpreadsheet 计算引擎(Calculation Engine)的完整技术指南。PhpSpreadsheet 将整个电子表格保存在内存中,因此它不仅能够读写文件,还能像 Excel 一样对单元格中的公式进行实时求值。本文覆盖公式求值 API(getCalculatedValue())、插入/删除行列时的公式自动调整、计算缓存控制、引擎的已知限制,以及 Excel 与 PHP 之间日期时间值的完整换算机制,并给出数据库函数与日期时间函数的逐项参考(含可运行的 PHP 示例)。读完本文,你将能够在自己的 PHP 应用中直接对工作簿执行公式计算,并正确处理跨 Excel/PHP 的日期时间语义。
使用 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)。现在执行下面的代码,插入两行新的产品记录:
$spreadsheet->getActiveSheet()->insertNewRowBefore(7, 2);
可以看到,原 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 日。如果字符串内容无法匹配 PHPDateTime对象所实现的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
延伸阅读与源码入口
- 公式计算与单元格 API:
getValue()/getCalculatedValue()/isFormula()/setCalculateDateTimeType()见 src/PhpSpreadsheet/Cell/Cell.php - 计算引擎与缓存控制:
getInstance()/disableCalculationCache()/clearCalculationCache()/setCalculationCacheEnabled()见 src/PhpSpreadsheet/Calculation/Calculation.php - 日期返回类型常量与切换:
RETURNDATE_*、setReturnDateType()/getReturnDateType()见 src/PhpSpreadsheet/Calculation/Functions.php - 日历、时区与转换辅助方法:
setExcelCalendar()/excelToTimestamp()/excelToDateTimeObject()/PHPToExcel()/formattedPHPToExcel()见 src/PhpSpreadsheet/Shared/Date.php - 各函数实现:数据库函数位于 src/PhpSpreadsheet/Calculation/Database,日期时间函数位于 src/PhpSpreadsheet/Calculation/DateTimeExcel
- 可运行示例:公式与计算引擎基础示例见 samples/Basic1/13_Calculation.php 与 samples/Basic1/13_CalculationCyclicFormulae.php;各函数示例见 samples/Database 与 samples/DateTime
- 函数级测试覆盖见 tests/PhpSpreadsheetTests/Calculation 目录
- 完整函数清单见 按名称排序的函数列表 与 按类别排序的函数列表
【免费下载链接】PhpSpreadsheet
A pure PHP library for reading and writing spreadsheet files
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考






