日期和时间处理,是PHP开发中非常常见的需求。文章发布时间、用户注册时间、订单时间、日志时间、倒计时、时间戳转换、时区处理……这些都需要用到日期和时间函数。
PHP提供了丰富的日期和时间处理函数,从基础的date()、time(),到面向对象的DateTime类,再到时区处理、日期计算、格式化输出等,功能非常强大。
但日期和时间处理,也是容易出错的地方。时区问题、时间戳溢出、格式化错误、日期计算、闰年处理……这些坑,很多开发者都踩过。
今天,我们来详细学习PHP的日期和时间处理,从基础函数到DateTime类,从时区处理到日期计算,帮你掌握这个常用但容易出错的技能。
基础日期时间函数
time()
获取当前的Unix时间戳(从1970年1月1日00:00:00 UTC到现在的秒数)。
echo time(); // 输出类似1438723200的数字时间戳是一个整数,方便计算和存储。但32位系统的时间戳在2038年1月19日会溢出(2038年问题),所以推荐用64位系统或DateTime类。
date()
格式化时间戳为可读的日期字符串。
echo date('Y-m-d H:i:s'); // 输出2015-08-07 22:00:00
echo date('Y年m月d日'); // 输出2015年08月07日
echo date('l, F j, Y'); // 输出Friday, August 7, 2015date()的第一个参数是格式字符串,常用格式字符:
Y:4位年份(2015)y:2位年份(15)m:2位月份(01-12)n:月份(1-12,无前导零)d:2位日期(01-31)j:日期(1-31,无前导零)H:24小时制小时(00-23)h:12小时制小时(01-12)i:分钟(00-59)s:秒(00-59)a:am/pmA:AM/PMl:星期几的完整名称(Friday)D:星期几的缩写(Fri)w:星期几的数字(0周日-6周六)F:月份的完整名称(August)M:月份的缩写(Aug)t:本月有多少天(28-31)L:是否闰年(1是,0否)U:Unix时间戳
date()的第二个参数是时间戳,可选,默认是当前时间。
echo date('Y-m-d', time() - 86400); // 昨天的日期
echo date('Y-m-d', 1438723200); // 指定时间戳的日期strtotime()
将英文文本日期解析为时间戳,非常强大。
echo strtotime('2015-08-07'); // 解析日期字符串
echo strtotime('now'); // 当前时间
echo strtotime('+1 day'); // 明天
echo strtotime('-1 day'); // 昨天
echo strtotime('+1 week'); // 下周
echo strtotime('+1 month'); // 下个月
echo strtotime('last Monday'); // 上周一
echo strtotime('next Friday'); // 下周五
echo strtotime('first day of next month'); // 下个月第一天
echo strtotime('last day of next month'); // 下个月最后一天strtotime()非常灵活,能解析各种英文日期表达式,但要注意:
- 不同版本的PHP解析可能有差异
- 某些模糊的表达式可能解析不符合预期
- 建议用DateTime类替代,更可靠
mktime()
根据指定的时分秒月日年,获取时间戳。
// mktime(时, 分, 秒, 月, 日, 年)
echo mktime(0, 0, 0, 8, 7, 2015); // 2015年8月7日0点的时间戳mktime()会自动处理越界,比如mktime(0,0,0,13,1,2015)会自动变成2016年1月1日。
getdate()
获取日期信息数组。
$today = getdate();
print_r($today);
// Array (
// [seconds] => 0
// [minutes] => 0
// [hours] => 22
// [mday] => 7
// [wday] => 5
// [mon] => 8
// [year] => 2015
// [yday] => 218
// [weekday] => Friday
// [month] => August
// [0] => 1438953600
// )checkdate()
验证日期是否合法。
var_dump(checkdate(2, 29, 2015)); // bool(false),2015不是闰年
var_dump(checkdate(2, 29, 2016)); // bool(true),2016是闰年
var_dump(checkdate(13, 1, 2015)); // bool(false),月份不合法时区处理
时区,是日期时间处理中最容易出错的地方。PHP默认使用服务器的时区,如果服务器时区和用户时区不一致,就会出现时间错误。
设置时区
// 在php.ini中设置
date.timezone = Asia/Shanghai
// 在代码中设置
date_default_timezone_set('Asia/Shanghai');常用时区:
Asia/Shanghai:中国标准时间(UTC+8)Asia/Hong_Kong:香港时间Asia/Tokyo:东京时间America/New_York:纽约时间America/Los_Angeles:洛杉矶时间Europe/London:伦敦时间UTC:协调世界时
获取时区
echo date_default_timezone_get(); // 获取当前时区时区转换
// 方法1:用date()和时间戳(时间戳是UTC的,date()会根据当前时区转换)
date_default_timezone_set('Asia/Shanghai');
echo date('Y-m-d H:i:s', 1438953600); // 北京时间
date_default_timezone_set('America/New_York');
echo date('Y-m-d H:i:s', 1438953600); // 纽约时间
// 方法2:用DateTime类(推荐,更清晰)
$date = new DateTime('2015-08-07 22:00:00', new DateTimeZone('Asia/Shanghai'));
$date->setTimezone(new DateTimeZone('America/New_York'));
echo $date->format('Y-m-d H:i:s'); // 转换为纽约时间DateTime类(推荐)
PHP 5.2引入了DateTime类,提供了面向对象的日期时间处理方式,比过程式函数更强大、更可靠、更易读。推荐使用DateTime类替代date()、strtotime()等函数。
创建DateTime对象
// 当前时间
$date = new DateTime();
echo $date->format('Y-m-d H:i:s');
// 指定时间
$date = new DateTime('2015-08-07 22:00:00');
echo $date->format('Y-m-d H:i:s');
// 指定时间和时区
$date = new DateTime('2015-08-07 22:00:00', new DateTimeZone('Asia/Shanghai'));
echo $date->format('Y-m-d H:i:s');
// 从时间戳创建
$date = new DateTime();
$date->setTimestamp(1438953600);
echo $date->format('Y-m-d H:i:s');
// 从格式创建(指定格式解析)
$date = DateTime::createFromFormat('Y-m-d', '2015-08-07');
echo $date->format('Y-m-d H:i:s');格式化输出
$date = new DateTime('2015-08-07 22:00:00');
echo $date->format('Y-m-d H:i:s'); // 2015-08-07 22:00:00
echo $date->format('Y年m月d日 H:i'); // 2015年08月07日 22:00
echo $date->format('l, F j, Y'); // Friday, August 7, 2015format()方法的格式字符和date()一样。
获取和设置日期时间
$date = new DateTime();
// 设置日期
$date->setDate(2015, 8, 7); // setDate(年, 月, 日)
// 设置时间
$date->setTime(22, 0, 0); // setTime(时, 分, 秒)
// 设置时间戳
$date->setTimestamp(1438953600);
// 获取时间戳
echo $date->getTimestamp();
// 获取时区
$timezone = $date->getTimezone();
echo $timezone->getName();日期计算(DateInterval)
DateInterval类表示时间间隔,可以用来对DateTime进行加减运算。
$date = new DateTime('2015-08-07');
// 加1天
$date->add(new DateInterval('P1D'));
echo $date->format('Y-m-d'); // 2015-08-08
// 减1天
$date->sub(new DateInterval('P1D'));
echo $date->format('Y-m-d'); // 2015-08-07
// 加1周
$date->add(new DateInterval('P1W'));
echo $date->format('Y-m-d'); // 2015-08-14
// 加1个月
$date->add(new DateInterval('P1M'));
echo $date->format('Y-m-d'); // 2015-09-07
// 加1年
$date->add(new DateInterval('P1Y'));
echo $date->format('Y-m-d'); // 2016-08-07
// 加1年2个月3天
$date->add(new DateInterval('P1Y2M3D'));
echo $date->format('Y-m-d'); // 2017-10-10
// 加时间(1小时2分钟3秒)
$date->add(new DateInterval('PT1H2M3S'));
echo $date->format('Y-m-d H:i:s');DateInterval的格式:
P:周期(Period),必须在最前面Y:年M:月D:日W:周T:时间(Time),分隔日期和时间H:小时M:分钟(在T后面是分钟,在T前面是月)S:秒
例子:
P1D:1天P1M:1个月PT1M:1分钟P1Y2M3DT4H5M6S:1年2个月3天4小时5分钟6秒
日期比较
$date1 = new DateTime('2015-08-07');
$date2 = new DateTime('2015-09-01');
// 比较大小
if ($date1 < $date2) {
echo 'date1在date2之前';
}
// 计算差值
$diff = $date1->diff($date2);
echo $diff->format('%a天'); // 25天
echo $diff->format('%m个月%d天'); // 0个月25天
echo $diff->y . '年' . $diff->m . '月' . $diff->d . '天';diff()方法返回DateInterval对象,表示两个日期的差值。
常用格式:
%a:总天数%y:年%m:月%d:日%h:小时%i:分钟%s:秒%R:正负号(+或-)
修改日期(modify)
modify()方法可以用字符串修改日期,类似strtotime()。
$date = new DateTime('2015-08-07');
$date->modify('+1 day');
echo $date->format('Y-m-d'); // 2015-08-08
$date->modify('last Monday');
echo $date->format('Y-m-d'); // 上周一
$date->modify('first day of next month');
echo $date->format('Y-m-d'); // 下个月第一天常用日期时间操作
获取今天/昨天/明天
$today = new DateTime();
echo $today->format('Y-m-d');
$yesterday = new DateTime('-1 day');
echo $yesterday->format('Y-m-d');
$tomorrow = new DateTime('+1 day');
echo $tomorrow->format('Y-m-d');获取本周/上周/下周
// 本周一
$monday = new DateTime('monday this week');
echo $monday->format('Y-m-d');
// 本周日
$sunday = new DateTime('sunday this week');
echo $sunday->format('Y-m-d');
// 上周一
$lastMonday = new DateTime('monday last week');
echo $lastMonday->format('Y-m-d');获取本月/上月/下月
// 本月第一天
$firstDay = new DateTime('first day of this month');
echo $firstDay->format('Y-m-d');
// 本月最后一天
$lastDay = new DateTime('last day of this month');
echo $lastDay->format('Y-m-d');
// 上月第一天
$lastMonthFirst = new DateTime('first day of last month');
echo $lastMonthFirst->format('Y-m-d');
// 下月第一天
$nextMonthFirst = new DateTime('first day of next month');
echo $nextMonthFirst->format('Y-m-d');获取今年/去年/明年
// 今年第一天
$yearFirst = new DateTime('first day of January this year');
echo $yearFirst->format('Y-m-d');
// 今年最后一天
$yearLast = new DateTime('last day of December this year');
echo $yearLast->format('Y-m-d');判断闰年
$year = 2015;
$date = new DateTime("$year-02-01");
if ($date->format('L') == 1) {
echo "$year是闰年";
} else {
echo "$year不是闰年";
}
// 或者用date()
if (date('L', mktime(0,0,0,2,1,$year)) == 1) { ... }计算两个日期相差天数
$date1 = new DateTime('2015-02-15');
$date2 = new DateTime('2015-08-07');
$diff = $date1->diff($date2);
echo '相差' . $diff->days . '天'; // 相差173天倒计时
$target = new DateTime('2016-01-01 00:00:00');
$now = new DateTime();
$diff = $now->diff($target);
echo '距离2016年还有' . $diff->days . '天' . $diff->h . '小时' . $diff->i . '分钟';友好的时间显示("刚刚"、"x分钟前")
function friendlyTime($datetime) {
$time = is_numeric($datetime) ? $datetime : strtotime($datetime);
$diff = time() - $time;
if ($diff < 60) return '刚刚';
if ($diff < 3600) return floor($diff / 60) . '分钟前';
if ($diff < 86400) return floor($diff / 3600) . '小时前';
if ($diff < 2592000) return floor($diff / 86400) . '天前';
if ($diff < 31536000) return floor($diff / 2592000) . '个月前';
return floor($diff / 31536000) . '年前';
}
echo friendlyTime('2015-08-07 22:00:00');常见坑和注意事项
1. 时区问题
最常见的坑。服务器时区和用户时区不一致,导致时间显示错误。
解决:
- 在php.ini中设置date.timezone = Asia/Shanghai
- 或者在代码入口处datedefaulttimezone_set('Asia/Shanghai')
- 存储时间用UTC时间戳,显示时根据用户时区转换
- 用DateTime类明确指定时区
2. 2038年问题
32位系统的时间戳是32位有符号整数,最大值是2147483647,对应2038年1月19日03:14:07 UTC。超过这个时间,时间戳会溢出变成负数。
解决:
- 使用64位系统
- 使用DateTime类(PHP 5.2+支持64位日期)
- 数据库用DATETIME类型而不是INT存储时间
3. strtotime()的模糊解析
strtotime()对某些模糊的表达式解析可能不符合预期,比如:
- "08/07/2015"会被解析为月/日/年(美式),而不是日/月/年
- 不同版本的PHP解析可能有差异
解决:
- 用DateTime::createFromFormat()明确指定格式
- 用标准的ISO格式(Y-m-d)存储和传输日期
4. 月份加减的边界问题
给1月31日加1个月,会变成3月3日(因为2月没有31天),而不是2月28日。这可能不符合预期。
$date = new DateTime('2015-01-31');
$date->modify('+1 month');
echo $date->format('Y-m-d'); // 2015-03-03,不是2015-02-28解决:根据需求处理,比如先设为月初再加减月,或者用其他方式。
5. 夏令时
某些国家和地区有夏令时,切换时会出现时间跳跃(少1小时或多1小时)。
解决:用DateTime类和DateTimeZone,PHP会自动处理夏令时。
6. 微秒处理
PHP 5.2.2+的DateTime支持微秒,但date()不支持。需要用u格式字符。
$date = new DateTime();
echo $date->format('Y-m-d H:i:s.u'); // 带微秒7. 数据库时间类型
MySQL中:
- DATETIME:范围1000-9999年,无时区,8字节
- TIMESTAMP:范围1970-2038年,自动转时区,4字节
- 推荐用DATETIME,范围大,不受2038年问题影响
最佳实践
- 统一时区:在应用入口设置统一时区,所有时间基于这个时区处理。
- 存储用UTC:数据库存储UTC时间,显示时转换为用户时区。或者统一用北京时间存储和显示(国内应用)。
- 用DateTime类:优先用DateTime类,而不是date()、strtotime()等过程式函数。DateTime更强大、更可靠、更易读。
- 用标准格式:日期用Y-m-d,时间用Y-m-d H:i:s,避免用模糊格式。
- 验证日期:用户输入的日期,用checkdate()或DateTime::createFromFormat()验证合法性。
- 避免2038年问题:用64位系统,数据库用DATETIME,用DateTime类。
- 缓存时间戳:在一次请求中,多次用到当前时间时,缓存time()的结果,避免不一致。
- 友好时间显示:文章、评论等的发布时间,用"x分钟前"、"x小时前"等友好显示,比具体时间更直观。
总结
PHP日期和时间处理,是开发中非常常用但容易出错的技能。
核心要点:
- 基础函数:time()(时间戳)、date()(格式化)、strtotime()(文本转时间戳)、mktime()(指定时间转时间戳)、checkdate()(验证日期)
- 时区处理:datedefaulttimezone_set()设置时区,DateTimeZone处理时区转换
- DateTime类(推荐):创建、格式化、设置、计算(DateInterval)、比较(diff)、修改(modify)
- 常用操作:今天/昨天/明天、本周/上周/下周、本月/上月/下月、闰年判断、天数差、倒计时、友好时间显示
- 常见坑:时区问题、2038年问题、strtotime模糊解析、月份加减边界、夏令时、微秒处理、数据库时间类型
- 最佳实践:统一时区、存储用UTC、用DateTime类、用标准格式、验证日期、避免2038问题、缓存时间戳、友好时间显示
日期和时间处理,看起来简单,但细节很多,坑也很多。掌握DateTime类,注意时区和边界问题,就能避免大部分错误。希望这篇文章能帮你更好地处理PHP中的日期和时间。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录