在Web程序开发时,时间发挥着重要的作用。不仅在数据存储和显示时需要日期和时间的参与,很多功能模块的开发,时间通常都是至关重要的。
1.UNIX时间戳
- 以32位的整数表示格林尼治标准时间,例如,使用整数11230499325表示当前时间的时间戳。
- UNIX时间戳是从1970年1月1日零点(UTC/GMT的午夜)开始起到当前时间所经过的秒数。
- 1970年1月1日零点作为所有日期计算的基础,这个日期通常称为UNIX纪元。
因为UNIX时间戳是一个32位的数字格式,所以特别适用于计算机处理,例如计算两个时间点之间相差的天数。
另外,由于文化和地区的差异,存在不同的时间格式,以及时区的问题。
所以UNIX时间戳也是根据一个时区进行标准化而设计的一种通用格式,并且这种格式可以很容易地转换为任何格式。
也因为UNIX时间戳是一个32位的整数表示的,所以在处理1970年以前或2038年以后的事件时,将会遇到一些问题。
另外,在Windows系统下,由于时间戳不能为负数,如果使用PHP中提供的时间戳函数处理1970年之前的日期,就会发生错误。要使PHP代码具有可移植性,必须记住这一点。
1.1. 将日期和时间转变成UNIX时间戳
mktime()
原型:
mktime ([ int $hour = date("H") [, int $minute = date("i") [, int $second = date("s") [, int $month = date("n") [, int $day = date("j") [, int $year = date("Y") [, int $is_dst = -1 ]]]]]]] )
作用:将日期和时间转为UNIX时间戳
详情:https://www.php.net/manual/zh/function.mktime.php
该函数中所有参数都是可选的,如果参数为空,默认将当前时间转变成UNIX时间戳。
这样,和直接调用time()函数获取当前的UNIX时间戳功能相同。
参数也可以从右向左省略,任何省略的参数会被设置成本地日期和时间的当前值。
如果只想转变日期,对具体的时间不在乎,可以将前三个转变时间的参数都设置为0。
mktime()函数对于日期运算和验证非常有用,它可以自动校正越界的输入。如下所示:
//日期超过31天,计算后输出Jan-05-2008
echo date("M-d-Y", mktime(0, 0, 0, 12, 36, 2007))."\n";
//月份超过12月,计算后输出Feb-01-2009
echo date("M-d-Y", mktime(0, 0, 0, 14, 1, 2008))."\n";
//没有问题的转变,输出结果 Jan-01-2009
echo date("M-d-Y", mktime(0, 0, 0, 1, 1, 2009))."\n";
//会将99年转变为1999年, Jan-01-1999
echo date("M-d-Y", mktime(0, 0, 0, 1, 1, 99))."\n";
strtotime()
原型:
int strtotime( string $time[, int $now = time()] )
作用: 将任何英文文本的日期时间描述直接解析为UNIX时间戳 。
1.2.日期的计算
在PHP中,计算两个日期之间相隔的长度,最简单的方法就是通过计算两个UNIX时间戳之差来获得。例如,在PHP脚本中接收来自HTML表单用户提交的出生日期,计算这个用户的年龄。代码如下所示:
image-20191207221437140.png在以上脚本中,调用mktime()函数将从用户出生日期转变为UNIX时间戳,再调用time()函数获取当前时间的UNIX时间戳。因为这个日期的格式都是使用整数表示的,所以可以将它们相减。又将计算后获取的UNIX时间戳除以一年的秒数,将UNIX时间戳转变为以年度量的单位。
2.在PHP中获取日期和时间
PHP提供了多种获取日期和时间的函数 。
2.1. 调用getdate()函数取得日期/时间信息
2.2. 日期和时间格式化输出date()
当日期和时间需要保存或计算时,应该使用UNIX时间戳作为标准格式,这可以作为一条重要的规则。但UNIX时间戳的格式可读性比较差,所以要把时间戳格式化为可读性更好的日期和时间,或格式化为其他软件需要的格式。在PHP中可以调用date()函数格式化一个本地日期和时间 。
3.修改PHP的默认时区
每个地区都有自己的本地时间,在网上及无线电通信中,时间的转换问题显得格外突出。
整个地球分为24个时区,每个时区都有自己的本地时间。
在国际无线电或网络通信场合,为了统一起见,使用一个统一的时间,称为通用协调时(Universal Time Coordinated,UTC),是由世界时间标准设定的全球标准时间。UTC原先也被称为格林尼治标准时间(Greenwich Mean Time,GMT),都与英国伦敦的本地时间相同。
PHP默认的时区设置是UTC时间,而北京正好位于时区的东八区,领先UTC 8个小时。所以在使用PHP中像time()等获取当前时间的函数时,得到的时间总是不对,其表现是和北京时间相差8个小时。如果希望正确地显示北京时间,就需要修改默认的时区设置。可以通过以下两种方式完成。
- 如果使用的是独立的服务器,有权限修改配置文件,设置时区就可以通过修改php.ini中的date.timezone属性完成。我们可以将这个属性的值设置为Asia/Shang、Asia/Chongqing、Etc/GMT-8或PRC等中的一个,再在PHP脚本中获取的当前时间就是北京时间。
- 如果使用的是共享服务器,没有权限修改配置文件php.ini,并且PHP版本又在5.1.0以上,也可以在输出时间之前调用date_default_timezone_set()函数设置时区。该函数需要提供一个时区标识符作为参数,和配置文件中date.timezone属性的值相同。
4. 使用微秒计算PHP脚本执行时间
在PHP中,大多数的时间格式都是以UNIX时间戳表示的,而UNIX时间戳是以s(秒)为最小的计量时间的单位。这对某些应用程序来说不够精确,所以可以调用microtime()函数返回当前UNIX时间戳和微秒数。该函数的原型如下:
mixed microtime([ bool $get_as_float] )
可以为该函数提供一个可选的布尔型参数,如果在调用时不提供这个参数,本函数以“msec sec”的格式返回一个字符串。其中sec是自UNIX纪元起到现在的秒数,而msec是微秒部分,字符串的两部分都是以秒为单位返回的。
如果给出了get_as_float参数并且其值等价于TRUE,则microtime()函数将返回一个浮点数。
在小数点前面还是以时间戳的格式表示,而小数点后面则表示微秒的值。
但要注意参数get_as_float是在PHP 5中新加的,所以在PHP 5以前的版本中,不能直接使用该参数请求一个浮点数。
在下面的例子中通过两次调用microtime()函数,计算运行PHP脚本所需的时间。代码如下所示:
class Timer { //声明一个计算脚本运行时间的类
private $startTime; //保存脚本开始执行时的时间(以微秒的形式保存)
private $stopTime; //保存脚本结束执行时的时间(以微秒的形式保存)
function __construct(){ //构造方法,在创建对象时初使化成员属性
$this->startTime=0; //初使化成员属性startTime的值为0
$this->stopTime=0; //初使化成员属性stopTime的值为0
}
function start(){ //在脚本开始处调用获取脚本开始时间的微秒值
$this->startTime = microtime(true); //将获取的时间赋给成员属性$startTime
}
function stop(){ //在脚本结束处调用获取脚本结束时间的微秒值
$this->stopTime= microtime(true); //将获取的时间赋给成员属性$stopTime
}
function spent(){ //返回同一脚本中两次获取时间的差值
return round(($this->stopTime- $this->startTime) , 4); //计算后以4舍5入保留4位返回
}
}
$timer = new Timer(); //创建Timer类的对象
$timer->start(); //在脚本文件开始执行时调用这个方法
usleep(1000); //脚本的主体内容,这里以休眠一毫秒为例
$timer->stop(); //在脚本文件结尾处调用这个方法
echo "执行该脚本用时<b>".$timer->spent()."</b>秒"; //输出页面执行时运行的时间
5.日历类
说到对日期和时间的处理,就一定要介绍一下日历程序的编写。但一提起编写日历,大多数读者都会认为日历的作用只是为了在页面上显示当前的日期,其实日历在我们的开发中有着更重要的作用。
例如,我们开发一个“记事本”就需要通过日历设定日期,在一些系统中需要按日期去安排任务也需要日历,等等。
本例涉及的日期和时间函数并不是很多,都是前面介绍的内容,主要是通过一个日历类的编写,巩固一下前面介绍过的面向对象的语法知识,以及时间函数应用,最主要的是可以提升初学者的思维逻辑和程序设计能力。
将日历类Calendar声明在文件calendar.class.php中,代码如下所示:
/*
file:calendar.class.php 日历类原文件
声明一个日历类,名称为Calendar,用来显示一个可以设置日期的日历
*/
class Calendar {
private $year; //当前的年
private $month; //当前的月
private $start_weekday; //当月的第一天对应的是周几,作为当月开始遍历日期的开始
private $days; //当前月一总天数
/* 构造方法,用来初使化一些日期属性 */
function __construct(){
/* 如果用户没有设置年份数,则使用当前系统时间的年份 */
$this->year = isset($_GET["year"]) ? $_GET["year"] : date("Y");
/* 如果用户没有设置月份数,则使用当前系统时间的用份 */
$this->month = isset($_GET["month"]) ? $_GET["month"] : date("m");
/* 通过具体的年份和月份,利用date()函数的w参数获取当月第一天对应的是周几 */
$this->start_weekday = date("w", mktime(0, 0, 0, $this->month, 1, $this->year));
/* 通过具体的年份和月份,利用date()函数的t参数获取当月的天数 */
$this->days = date("t", mktime(0, 0, 0, $this->month, 1, $this->year));
}
/* 魔术方法用于打印整个日历 */
function __toString(){
$out .= '<table align="center">'; //日历以表格形式打印
$out .= $this->chageDate(); //调用内部私有方法用于用户自己设置日期
$out .= $this->weeksList(); //调用内部私有方法打印“周”列表
$out .= $this->daysList(); //调用内部私有方法打印“日”列表
$out .= '</table>'; //表格结束
return $out; //返回整个日历输出需要的全部字符串
}
/* 内部调用的私有方法,用于输出周列表 */
private function weeksList(){
$week = array('日','一','二','三','四','五','六');
$out .= '<tr>';
for($i = 0; $i < count($week); $i++)
$out .= '<th class="fontb">'.$week[$i].'</th>'; //第一行以表格<th>形式输出周列表
$out .= '</tr>';
return $out; //返回周列表字符串
}
/* 内部调用的私有方法,用于输出日列表 */
private function daysList(){
$out .= '<tr>';
/* 输出空格(当前一月第一天前面要空出来) */
for($j = 0; $j < $this->start_weekday; $j++)
$out .= '<td> </td>';
/* 将当月的所有日期循环遍历出来,如果是当前日期,为其设置深色背景 */
for($k = 1; $k <= $this->days; $k++){
$j++;
if($k == date('d'))
$out .= '<td class="fontb">'.$k.'</td>';
else
$out .= '<td>'.$k.'</td>';
if($j%7 == 0) //每输出7个日期,就换一行
$out .= '</tr><tr>'; //输出行结束和下一行开始
}
while($j%7 !== 0){ //遍历完日期后,将后面用空格补齐
$out .= '<td> </td>'; //使用空格去补
$j++;
}
$out .= '</tr>';
return $out; //返回当月日期列表
}
/* 内部调用的私有方法,用于处理当前年份的上一年需要的数据 */
private function prevYear($year, $month){
$year = $year-1; //上一年是当前年减1
if($year < 1970) //如果设置的年份小于1970年
$year = 1970; //年份设置最小值是1970年
return "year={$year}&month={$month}"; //返回最终的年份和月份设置参数
}
/* 内部调用的私有方法,用于处理当前月份的上一月份的数据 */
private function prevMonth($year, $month){
if($month == 1) { //如果月份已经是1月
$year = $year -1; //则上一个月份,就是上一年的最后一月
if($year < 1970) //和前面一样,上一年如果是最1970年
$year = 1970; //最小年数不能小于1970年
$month=12; //如果月是1月,上一月就是上一年的最后一月
}else{
$month--; //上一月就是当前月减1
}
return "year={$year}&month={$month}"; //返回最终的年份和月份设置参数
}
/* 内部调用的私有方法,用于处理当前年份的下一年份的数据 */
private function nextYear($year, $month){
$year = $year + 1; //下一年是当前年加1
if($year > 2038) //如果设置的年份大于2038年
$year = 2038; //最大年份不能超过2038年
return "year={$year}&month={$month}"; //返回最终的年份和月份设置参数
}
/* 内部调用的私有方法,用于处理当前月份的下一个月份的数据 */
private function nextMonth($year, $month){
if($month == 12){ //如果已经是当年的最后一个月
$year++; //下一个月就是下一年的第一个月,让年份加1年
if($year > 2038) //如果设置的年份大于2038年
$year = 2038; //最大年份不能超过2038年
$month = 1; //设置月份为下一年的第1月
}else{
$month++; //其它月份的下一个月都是当前月份加1即可
}
return "year={$year}&month={$month}"; //返回最终的年份和月份设置参数
}
//内部调用的私有方法,用于用户操作去调整年份和月份的设置
private function chageDate($url="index.php"){
$out .= '<tr>';
$out .= '<td><a href="'.$url.'?'.$this->prevYear($this->year, $this->month).'">'.'<<'.'</a></td>';
$out .= '<td><a href="'.$url.'?'.$this->prevMonth($this->year, $this->month).'">'.'<'.'</a></td>';
$out .= '<td colspan="3">';
$out .= '<form>';
$out .= '<select name="year" onchange="window.location=\''.$url.'?year=\'+this.options[selectedIndex].value+\'&month='.$this->month.'\'">';
for($sy=1970; $sy <= 2038; $sy++){
$selected = ($sy==$this->year) ? "selected" : "";
$out .= '<option '.$selected.' value="'.$sy.'">'.$sy.'</option>';
}
$out .= '</select>';
$out .= '<select name="month" onchange="window.location=\''.$url.'?year='.$this->year.'&month=\'+this.options[selectedIndex].value">';
for($sm=1; $sm<=12; $sm++){
$selected1 = ($sm==$this->month) ? "selected" : "";
$out .= '<option '.$selected1.' value="'.$sm.'">'.$sm.'</option>';
}
$out .= '</select>';
$out .= '</form>';
$out .= '</td>';
$out .= '<td><a href="'.$url.'?'.$this->nextYear($this->year, $this->month).'">'.'>>'.'</a></td>';
$out .= '<td><a href="'.$url.'?'.$this->nextMonth($this->year, $this->month).'">'.'>'.'</a></td>';
$out .= '</tr>';
return $out; //返回调整日期的表单
}
}
在主程序中还需要先设置一下日历输出的样式,代码如下所示:
<html>
<head>
<title>《细说PHP》日历示例</title>
<style>
table { border:1px solid #050;} /*给表格加一个外边框*/
.fontb { color:white; background:blue;} /*设置周列表的背景和字体颜色*/
th { width:30px;} /*设置单元格子的宽度*/
td,th { height:30px;text-align:center;} /*设置单元高度,和字段显示位置*/
form { margin:0px;padding:0px; } /*清除表单原有的样式*/
</style>
</head>
<body>
<?php
require "calendar.class.php"; //加载日历类
echo new Calendar; //直接输出日历对象,自动调用魔术方法__toString()打印日历
?>
</body>
</html>
6.小结
本章必须掌握的知识点
- UNIX时间戳。
- 将其他格式的日期转成UNIX时间戳的格式。
- 基于UNIX时间戳的日期计算。
- 获取并格式化输出日期。
- 修改PHP的默认时间。
- 微秒的使用。
本章需要了解的内容
- 日历程序编写。
- 本章没有介绍到的其他和日期及时间有关的函数。
- 以同样的学习方法去扩展PHP的一些相似函数库学习,例如数学函数库。
本章的学习建议
多通过实例编写,熟练掌握日期和时间函数的应用,并可以灵活地在项目中使用。
网友评论