PHP 日期与时间
PHP 提供了丰富的日期时间函数,以及面向对象的 DateTime 类。日期时间处理是 Web 开发中的高频需求,无论是记录用户操作时间、计算活动倒计时,还是处理跨时区的国际化应用,都需要对 PHP 的时间处理机制有深入了解。
基础函数
PHP 的时间基础是 Unix 时间戳,即从 1970 年 1 月 1 日 00:00:00 UTC 到当前时刻的秒数。time() 函数返回当前时间戳,date() 函数将时间戳格式化为可读字符串。
// 获取当前时间戳(Unix 时间戳:1970-01-01 起的秒数) echo time(); // 如:1714000000date()// 格式化当前时间 echo date('Y-m-d'); // 2024-01-15 echo date('Y-m-d H:i:s'); // 2024-01-15 14:30:00 echo date('Y年m月d日 H:i'); // 2024年01月15日 14:30 echo date('D, d M Y'); // Mon, 15 Jan 2024 ``
函数的第二个参数可以传入时间戳,不传则默认使用当前时间。需要注意的是,date()输出的时间受date_default_timezone_set()设置的时区影响,在项目入口文件中统一设置时区是良好的实践。strtotime()date() 格式字符
字符 说明 示例 Y 四位年份 2024 m 两位月份 01-12 d 两位日期 01-31 H 24小时制小时 00-23 i 分钟 00-59 s 秒 00-59 N 星期(1=周一) 1-7 U Unix 时间戳 1714000000 时间戳转换
是 PHP 中非常强大的函数,可以将人类可读的日期字符串解析为时间戳,还支持相对时间表达式,如+7 days、next Monday、-1 month等。`php // 字符串转时间戳 $ts = strtotime('2024-06-01'); $ts = strtotime('next Monday'); $ts = strtotime('+7 days'); $ts = strtotime('-1 month');`echo date('Y-m-d', strtotime('+30 days')); // 30天后的日期
// mktime() 创建指定时间的时间戳 $ts = mktime(12, 0, 0, 6, 1, 2024); // 2024-06-01 12:00:00 echo date('Y-m-d H:i:s', $ts);
strtotime()解析失败时返回false,使用前应做检查。对于用户输入的日期字符串,建议使用DateTime::createFromFormat()指定格式解析,更加可靠。mktime()参数顺序是(时, 分, 秒, 月, 日, 年),注意月和日的顺序与直觉相反。DateTimeDateTime 类(推荐)
面向对象的
类比函数式 API 更加灵活,支持链式调用,并且可以与DateTimeZone、DateInterval等类配合使用,处理复杂的日期时间逻辑。`php // 创建 $now = new DateTime(); $date = new DateTime('2024-06-01'); $date = new DateTime('now', new DateTimeZone('Asia/Shanghai'));`// 格式化 echo $now->format('Y-m-d H:i:s');
// 修改 $date->modify('+1 month'); $date->modify('next Friday');
// 设置 $date->setDate(2024, 12, 25); $date->setTime(10, 30, 0);
DateTime::createFromFormat()静态方法可以按指定格式解析日期字符串,例如DateTime::createFromFormat('d/m/Y', '25/12/2024')可以正确解析欧式日期格式。解析失败时返回false,可以通过DateTime::getLastErrors()获取详细的错误信息。DateTime::diff()日期计算
方法返回一个DateInterval对象,包含两个日期之间的年、月、日、时、分、秒差值,以及总天数(days属性)。这是计算日期差值最准确的方式。`php $start = new DateTime('2024-01-01'); $end = new DateTime('2024-12-31');`// 计算差值 $diff = $start->diff($end); echo $diff->days; // 365(总天数) echo $diff->m; // 月数 echo $diff->y; // 年数
// 判断是否过期 $expire = new DateTime('2024-06-01'); $now = new DateTime(); if ($now > $expire) { echo '已过期'; }
DateInterval对象的invert属性表示方向,0表示正向(start < end),1表示反向(start > end)。DateTime对象支持直接用比较运算符(>、<、==)进行比较,非常直观。DateTimeDateTimeImmutable(不可变对象)
的modify()、setDate()等方法会直接修改原对象,在函数式编程或需要保留原始值的场景中容易引发 bug。DateTimeImmutable解决了这个问题,所有修改操作都返回新对象,原对象保持不变。`php // DateTime 会修改原对象,DateTimeImmutable 返回新对象 $date = new DateTimeImmutable('2024-01-01'); $newDate = $date->modify('+1 month'); // $date 不变`echo $date->format('Y-m-d'); // 2024-01-01(不变) echo $newDate->format('Y-m-d'); // 2024-02-01
DateTimeImmutable在现代 PHP 项目中,推荐优先使用
而不是DateTime,因为不可变对象更安全,不会因为意外修改而引发难以追踪的 bug。DateTimeImmutable和DateTime都实现了DateTimeInterface接口,函数参数类型提示应使用接口类型。Asia/Shanghai时区设置
时区处理是国际化应用的难点之一。PHP 使用 IANA 时区数据库,中国大陆应使用
(而不是Asia/Beijing,后者不存在)。`php // 全局设置(推荐放在入口文件) date_default_timezone_set('Asia/Shanghai');`echo date('Y-m-d H:i:s'); // 北京时间
// 单独指定时区 $tz = new DateTimeZone('America/New_York'); $date = new DateTime('now', $tz); echo $date->format('Y-m-d H:i:s');
DateTime处理跨时区数据时,推荐在数据库中统一存储 UTC 时间,在展示时再转换为用户所在时区。
的setTimezone()方法可以转换时区,时间戳本身不受时区影响,是跨时区传递时间的最安全方式。`实用示例
日期时间处理在实际项目中有很多常见场景,下面是两个典型的工具函数:计算年龄和格式化"多久前"。
php // 计算年龄 function calcAge(string $birthday): int { return (new DateTime($birthday))->diff(new DateTime())->y; } echo calcAge('1995-08-15'); // 28`// 格式化为"多久前" function timeAgo(int $timestamp): string { $diff = time() - $timestamp; if ($diff < 60) return $diff . ' 秒前'; if ($diff < 3600) return floor($diff / 60) . ' 分钟前'; if ($diff < 86400) return floor($diff / 3600) . ' 小时前'; if ($diff < 604800) return floor($diff / 86400) . ' 天前'; return date('Y-m-d', $timestamp); } echo timeAgo(time() - 3600); // 1 小时前
timeAgo()函数在社交应用、评论系统中非常常见,可以根据需要扩展支持"X 个月前"、"X 年前"等更多粒度。对于需要本地化的场景,可以结合 PHP 的IntlDateFormatter类或第三方库 Carbon 来实现多语言的时间格式化。DateTimeCarbon 库简介
Carbon 是 PHP 中最流行的日期时间处理库,基于
扩展,提供了更丰富的 API 和更友好的链式调用语法。`php // 安装:composer require nesbot/carbon use Carbon\Carbon;$now = Carbon::now('Asia/Shanghai'); echo $now->format('Y-m-d H:i:s');
// 链式操作 $date = Carbon::parse('2024-01-01') ->addMonths(3) ->subDays(5) ->startOfDay();
// 人性化输出 echo Carbon::parse('2024-01-01')->diffForHumans(); // 3 months ago
// 判断 $date = Carbon::now(); echo $date->isWeekend() ? '周末' : '工作日'; echo $date->isToday() ? '今天' : '不是今天';
Carbon 的 diffForHumans() 方法支持多语言,通过 Carbon::setLocale('zh') 设置中文后,输出会变为"3 个月前"等中文格式。在需要复杂日期计算的项目中,Carbon 可以大幅提升开发效率。
常见问题
Q1:strtotime() 和 DateTime::createFromFormat() 有什么区别,应该用哪个?
A:strtotime() 会尝试自动识别日期格式,灵活但不可靠,对于格式不标准的字符串可能解析错误(例如 01/02/2024 在不同系统上可能被解析为 1 月 2 日或 2 月 1 日)。DateTime::createFromFormat() 需要明确指定格式,解析结果确定可靠。处理用户输入或外部数据时,应优先使用 createFromFormat();处理标准 ISO 格式(Y-m-d)时,两者都可以。
Q2:为什么数据库中建议存储 UTC 时间而不是本地时间?
A:存储 UTC 时间有几个优势:① 避免夏令时切换导致的时间歧义(某些时间点会出现两次);② 方便跨时区的数据比较和计算;③ 应用迁移到不同时区的服务器时不需要转换数据。实践中,数据库存储 UTC 时间戳或 UTC 的 DATETIME,在应用层根据用户时区转换为本地时间展示。
Q3:PHP 的时间戳在 2038 年会有问题吗?
A:32 位系统上的 Unix 时间戳会在 2038 年 1 月 19 日溢出(Y2K38 问题),但现代 64 位系统上 PHP 的时间戳是 64 位整数,可以表示到数十亿年后,不存在这个问题。如果你的应用运行在 64 位系统上(现代服务器几乎都是),无需担心。数据库层面,MySQL 的 TIMESTAMP 类型有 2038 年限制,应改用 DATETIME 类型。
Q4:如何精确计算两个日期之间的工作日天数(排除周末)?
A:PHP 没有内置的工作日计算函数,需要手动实现。基本思路是遍历日期范围,用 date('N', $ts) 判断是否为周末(6=周六,7=周日)。如果还需要排除法定节假日,则需要维护一份节假日列表(可以从政府 API 获取)。Carbon 库提供了 diffInWeekdays()` 方法可以直接计算工作日差值,但不包含法定节假日的处理。