15.PHP的日期和时间

在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>&nbsp;</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>&nbsp;</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的一些相似函数库学习,例如数学函数库。

本章的学习建议

多通过实例编写,熟练掌握日期和时间函数的应用,并可以灵活地在项目中使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容