php--常用的时间处理函数

天地四方曰宇,往古来今曰宙

时间是世界的重要组成部分,不论花开花落,还是云卷云舒都有它的影子。

但它源起何处?又将去向何方?没人知道答案,也不需要答案,我们需要的只是一个相对的起点来标识时间,现今世界普遍采用公元纪年法来表示。

公元纪年法以耶稣诞生日记为公元1年(没有公元0年),中国处于汉平帝刘衎(不会读。。。)登基第二年即元始元年。

关于时间的另一个概念是unix时间戳,是从1970年1月1日开始所经过的秒数,不考虑闰秒,什么是闰秒参考这里

下面就来说说php中时间的处理方法,以获取当前时间为例

1 <?php
2     date_default_timezone_set('Asia/Shanghai');
3     echo "now is ".date("Y-m-d H:i:s",time())."
";
4     echo "now is ".date("Y-m-d H:i:s",strtotime("now"))."
";
5     $date = new DateTime();
6     echo "now is ".$date->format("Y-m-d H:i:s")."
";
7 ?>

 

时区设置

date_default_timezone_set用于设置时区,优先级别高于php.ini中设置的date.timezone属性,可设置的时区列表见这里,与之对应的是date_default_timezone_get获取由set函数设置的时区。

1 <?php
2     date_default_timezone_set('Asia/Shanghai');
3     $date_set = date_default_timezone_get();
4     //如果与配置文件中的时区设置相同则设置为美国时区
5     if($date_set == ini_get("date.timezone")){
6         date_default_timezone_set('America/Los_Angeles');
7     }
8     echo date("Y-m-d H:i:s")."
";
9 ?>

获取UNIX时间戳

 常用的方法有三种:time(),microtime(),strotime("now")

1 <?php
2     error_reporting(E_ALL ^E_STRICT);
3     echo "time is ".time()."
";
4     echo "strotime is ".strtotime("now")."
";
5     echo "mktime is ".mktime()."
";
6     echo "microtime is ".microtime()."
";
7     //参数设置为true返回浮点数表示的时间戳
8     echo "in float microtime is ".microtime(true)."
";
9 ?>

mktime

mktime函数的参数全是关键字参数,关键字参数大家懂的可以从右到左省略,格式为时,分,秒,月,日,年

 1 <?php
 2 
 3     //年默认2014
 4     echo "mktime(10,10,10,10,12) is ".date("Y-m-d H:i:s",mktime(10,10,10,10,12))."
";
 5 
 6     //这种写法会将2014当作月数,年还是默认的2014年
 7     echo "mktime(10,10,10,10,2014) is ".date("Y-m-d H:i:s",mktime(10,10,10,10,2014))."
";
 8 
 9     echo "mktime(10,10,10,10,32,2014) is ".date("Y-m-d H:i:s",mktime(10,10,10,10,32,2014))."
";
10 ?>

出现了一些很奇妙的事情,2014年2014月变成了2020年4月,2014年10月32号变成了11月1号,看,mktime自动计算了相差部分。乍看之下感觉很神奇,细想下来又在情理之中,毕竟日期的相互转换是通过unix时间戳进行的,我们可以通过mktime的实现源码管中窥豹一下。

该函数源码位于ext/date/php_date.c 在1500行实现,篇幅所限只贴部分代码,兴趣的朋友可以下下来自己看,地址在这里.

 1 PHPAPI void php_mktime(INTERNAL_FUNCTION_PARAMETERS, int gmt)
 2 {
 3     zend_long hou = 0, min = 0, sec = 0, mon = 0, day = 0, yea = 0, dst = -1;
 4     timelib_time *now;
 5     timelib_tzinfo *tzi = NULL;
 6     zend_long ts, adjust_seconds = 0;
 7     int error;
 8 
 9     if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|lllllll", &hou, &min, &sec, &mon, &day, &yea, &dst) == FAILURE) {
10         RETURN_FALSE;
11     }
12     /* Initialize structure with current time */
13     now = timelib_time_ctor();
14     if (gmt) {
15         timelib_unixtime2gmt(now, (timelib_sll) time(NULL));
16     } else {
17         tzi = get_timezone_info(TSRMLS_C);
18         now->tz_info = tzi;
19         now->zone_type = TIMELIB_ZONETYPE_ID;
20         timelib_unixtime2local(now, (timelib_sll) time(NULL));
21     }
22     /* Fill in the new data */
23     switch (ZEND_NUM_ARGS()) {
24         case 7:
25             /* break intentionally missing */
26         case 6:
27             if (yea >= 0 && yea < 70) {
28                 yea += 2000;
29             } else if (yea >= 70 && yea <= 100) {
30                 yea += 1900;
31             }
32             now->y = yea;
33             /* break intentionally missing again */
34         case 5:
35             now->d = day;
36             /* break missing intentionally here too */
37         case 4:
38             now->m = mon;
39             /* and here */
40         case 3:
41             now->s = sec;
42             /* yup, this break isn't here on purpose too */
43         case 2:
44             now->i = min;
45             /* last intentionally missing break */
46         case 1:
47             now->h = hou;
48             break;
49         default:
50             php_error_docref(NULL TSRMLS_CC, E_STRICT, "You should be using the time() function instead");
51     }
52     /* Update the timestamp */
53     if (gmt) {
54         timelib_update_ts(now, NULL);
55     } else {
56         timelib_update_ts(now, tzi);
57     }
58     /* Support for the deprecated is_dst parameter */
59     if (dst != -1) {
60         php_error_docref(NULL TSRMLS_CC, E_DEPRECATED, "The is_dst parameter is deprecated");
61         if (gmt) {
62             /* GMT never uses DST */
63             if (dst == 1) {
64                 adjust_seconds = -3600;
65             }
66         } else {
67             /* Figure out is_dst for current TS */
68             timelib_time_offset *tmp_offset;
69             tmp_offset = timelib_get_time_zone_info(now->sse, tzi);
70             if (dst == 1 && tmp_offset->is_dst == 0) {
71                 adjust_seconds = -3600;
72             }
73             if (dst == 0 && tmp_offset->is_dst == 1) {
74                 adjust_seconds = +3600;
75             }
76             timelib_time_offset_dtor(tmp_offset);
77         }
78     }
79     /* Clean up and return */
80     ts = timelib_date_to_int(now, &error);
81     ts += adjust_seconds;
82     timelib_time_dtor(now);
83 
84     if (error) {
85         RETURN_FALSE;
86     } else {
87         RETURN_LONG(ts);
88     }
89 }
View Code

 阅读这段代码需知道一个重要的结构体timelib_time,在ext/date/lib/timelib_structs.h中声明

typedef struct timelib_time {
    timelib_sll      y, m, d;     /* Year, Month, Day */
    timelib_sll      h, i, s;     /* Hour, mInute, Second */
    double           f;           /* Fraction */
    int              z;           /* GMT offset in minutes */
    char            *tz_abbr;     /* Timezone abbreviation (display only) */
    timelib_tzinfo  *tz_info;     /* Timezone structure */
    signed int       dst;         /* Flag if we were parsing a DST zone */
    timelib_rel_time relative;

    timelib_sll      sse;         /* Seconds since epoch */

    unsigned int   have_time, have_date, have_zone, have_relative, have_weeknr_day;

    unsigned int   sse_uptodate; /* !0 if the sse member is up to date with the date/time members */
    unsigned int   tim_uptodate; /* !0 if the date/time members are up to date with the sse member */
    unsigned int   is_localtime; /*  1 if the current struct represents localtime, 0 if it is in GMT */
    unsigned int   zone_type;    /*  1 time offset,
                                  *  3 TimeZone identifier,
                                  *  2 TimeZone abbreviation */
} timelib_time;

现在来看看mktime,56行的timelib_update_ts函数位于ext/date/lib/tm2unixtime.c文件中,其作用是根据now中的日期信息计算相应的秒数并存入now->sse,来看看

 1 void timelib_update_ts(timelib_time* time, timelib_tzinfo* tzi)
 2 {
 3     timelib_sll res = 0;
 4 
 5     do_adjust_special_early(time);
 6     do_adjust_relative(time);
 7     do_adjust_special(time);
 8     res += do_years(time->y);
 9     res += do_months(time->m, time->y);
10     res += do_days(time->d);
11     res += do_time(time->h, time->i, time->s);
12     time->sse = res;
13 
14     res += do_adjust_timezone(time, tzi);
15     time->sse = res;
16 
17     time->sse_uptodate = 1;
18     time->have_relative = time->relative.have_weekday_relative = time->relative.have_special_relative = 0;
19 }

8-11行计算相应时间类型的秒数,到这里已可了解mktime自动增减日期的原理,让我们看看do_days是如何实现的。day-1应该不难理解,mktime前面需要传入小时分钟等参数,在处理具体的某一天时默认为当天的0点0分0秒,所以要比实际的天数少一天。do_months,do_years等机制都相同,不再细述。

static timelib_sll do_days(timelib_ull day)
{
    return ((day - 1) * SECS_PER_DAY);
}

当日期处理完成,我们回到php_date.c,在80行处通过timelib_date_to_int函数将now->sse返回,该函数位于ext/date/lib/timelib.c,具体的代码就不贴了。

strtotime

聊完了mktime,再来看看strtotime,同样先来几个例子

<?php

    echo "strtotime('+1 day',strtotime('2014/10/19')) is ".date("Y-m-d",strtotime("+1 day",strtotime("2014/10/19")))."
";
    echo "strtotime('-30 day') is ".date("Y-m-d",strtotime("-30 day"))."
";
    echo "strtotime('+1 week') is ".date("Y-m-d",strtotime("+1 week"))."
";
    echo "strtotime('last Monday) is ".date("Y-m-d",strtotime("last Monday"))."
";
?>

strtotime相较mktime要复杂很多,在于其对英文文本的解析过程,这里用到了词法解析工具re2c,原始文件位于ext/date/lib/parse_date.re,同目录下还有个parse_date.c是编译后的文件,parse_date.c太大,我们分析parse_date.re就可以了,以strtotime("+1 day")为例,下面是php_date.c中的实现代码

 1 PHP_FUNCTION(strtotime)
 2 {
 3     char *times, *initial_ts;
 4     size_t   time_len;
 5     int error1, error2;
 6     struct timelib_error_container *error;
 7     zend_long preset_ts = 0, ts;
 8 
 9     timelib_time *t, *now;
10     timelib_tzinfo *tzi;
11 
12     tzi = get_timezone_info(TSRMLS_C);
13 
14     if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "sl", &times, &time_len, &preset_ts) != FAILURE) {
15         /* We have an initial timestamp */
16         now = timelib_time_ctor();
17 
18         initial_ts = emalloc(25);
19         snprintf(initial_ts, 24, "@" ZEND_LONG_FMT " UTC", preset_ts);
20         t = timelib_strtotime(initial_ts, strlen(initial_ts), NULL, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper); /* we ignore the error here, as this should never fail */
21         timelib_update_ts(t, tzi);
22         now->tz_info = tzi;
23         now->zone_type = TIMELIB_ZONETYPE_ID;
24         timelib_unixtime2local(now, t->sse);
25         timelib_time_dtor(t);
26         efree(initial_ts);
27     } else if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &times, &time_len, &preset_ts) != FAILURE) {
28         /* We have no initial timestamp */
29         now = timelib_time_ctor();
30         now->tz_info = tzi;
31         now->zone_type = TIMELIB_ZONETYPE_ID;
32         timelib_unixtime2local(now, (timelib_sll) time(NULL));
33     } else {
34         RETURN_FALSE;
35     }
36 
37     if (!time_len) {
38         timelib_time_dtor(now);    
39         RETURN_FALSE;
40     }
41 
42     t = timelib_strtotime(times, time_len, &error, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
43     error1 = error->error_count;
44     timelib_error_container_dtor(error);
45     timelib_fill_holes(t, now, TIMELIB_NO_CLONE);
46     timelib_update_ts(t, tzi);
47     ts = timelib_date_to_int(t, &error2);
48 
49     timelib_time_dtor(now);
50     timelib_time_dtor(t);
51 
52     if (error1 || error2) {
53         RETURN_FALSE;
54     } else {
55         RETURN_LONG(ts);
56     }
57 }
View Code

if用来解析参数,分为有第二个参数和没有第二个参数的情况,这里略过不提,主要关注timelib_strtotime函数,它在parse_date.re中定义,返回解析后的timelib_time结构体。而timelib_strtotime又会调用parse_date.re中的scan方法来解析字符串,这里有几个重要的正则表达式

reltextnumber = 'first'|'second'|'third'|'fourth'|'fifth'|'sixth'|'seventh'|'eight'|'eighth'|'ninth'|'tenth'|'eleventh'|'twelfth';
reltexttext = 'next'|'last'|'previous'|'this';
reltextunit = (('sec'|'second'|'min'|'minute'|'hour'|'day'|'fortnight'|'forthnight'|'month'|'year') 's'?) | 'weeks' | daytext;

relnumber = ([+-]*[ 	]*[0-9]+);
relative = relnumber space? (reltextunit | 'week' );
relativetext = (reltextnumber|reltexttext) space reltextunit;
relativetextweek = reltexttext space 'week';

"+1 day"会被解析为relative,并进行relative的相关操作

    relative
    {
        timelib_ull i;
        DEBUG_OUTPUT("relative");
        TIMELIB_INIT;
        TIMELIB_HAVE_RELATIVE();

        while(*ptr) {
            i = timelib_get_unsigned_nr((char **) &ptr, 24);
            timelib_eat_spaces((char **) &ptr);
            timelib_set_relative((char **) &ptr, i, 1, s);
        }
        TIMELIB_DEINIT;
        return TIMELIB_RELATIVE;
    }

timelib_get_unsigned_nr 用来判断是"+"还是"-",参数24用来指定最大的字符串长度,然后调用timelib_get_nr获取后面的具体数字,最后调用timelib_set_relative设置timelib_time 结构体中relative.d=1。

static timelib_ull timelib_get_unsigned_nr(char **ptr, int max_length)
{
    timelib_ull dir = 1;

    while (((**ptr < '0') || (**ptr > '9')) && (**ptr != '+') && (**ptr != '-')) {
        if (**ptr == '') {
            return TIMELIB_UNSET;
        }
        ++*ptr;
    }

    while (**ptr == '+' || **ptr == '-')
    {
        if (**ptr == '-') {
            dir *= -1;
        }
        ++*ptr;
    }
    return dir * timelib_get_nr(ptr, max_length);
} 
 1 static void timelib_set_relative(char **ptr, timelib_sll amount, int behavior, Scanner *s)
 2 {
 3     const timelib_relunit* relunit;
 4 
 5     if (!(relunit = timelib_lookup_relunit(ptr))) {
 6         return;
 7     }
 8 
 9     switch (relunit->unit) {
10         case TIMELIB_SECOND: s->time->relative.s += amount * relunit->multiplier; break;
11         case TIMELIB_MINUTE: s->time->relative.i += amount * relunit->multiplier; break;
12         case TIMELIB_HOUR:   s->time->relative.h += amount * relunit->multiplier; break;
13         case TIMELIB_DAY:    s->time->relative.d += amount * relunit->multiplier; break;
14         case TIMELIB_MONTH:  s->time->relative.m += amount * relunit->multiplier; break;
15         case TIMELIB_YEAR:   s->time->relative.y += amount * relunit->multiplier; break;
16 
17         case TIMELIB_WEEKDAY:
18             TIMELIB_HAVE_WEEKDAY_RELATIVE();
19             TIMELIB_UNHAVE_TIME();
20             s->time->relative.d += (amount > 0 ? amount - 1 : amount) * 7;
21             s->time->relative.weekday = relunit->multiplier;
22             s->time->relative.weekday_behavior = behavior;
23             break;
24 
25         case TIMELIB_SPECIAL:
26             TIMELIB_HAVE_SPECIAL_RELATIVE();
27             TIMELIB_UNHAVE_TIME();
28             s->time->relative.special.type = relunit->multiplier;
29             s->time->relative.special.amount = amount;
30     }
31 }
timelib_set_relative

随后就和mktime一样调用相关处理函数转换为时间戳,有一点需要注意,timelib_set_relative的实现方式是直接在日期或月份上操作,下面这样的代码会出现问题

echo date("Y-m-d",strtotime("-1 month",strtotime("2014/03/31")))."
";

  输出结果为2014-03-03,按照上述流程,2014/03/31会转为2014/02/31处理,等价于mktime(00,00,00,2,31,2014),mktime的处理方式上文已经说过了:P

写一些常用的方法 

 知道了原理,用起来就很方便了。下面是使用频率较高的时间处理方法,例如获取前几周,前几天,取得本周第一天和最后一天的时间等等,不定期更新,github地址。

  1 <?php
  2 
  3     /**
  4     * process date
  5     * @author huntstack
  6     * @time 2014-10-18
  7     */
  8     class Process_Date{
  9         
 10         private $date;
 11         private $M,$D,$Y;
 12 
 13         /**
 14         * set the date for next operator
 15         * @parameter date:time string or timestamp
 16         */
 17         function __construct($date=""){
 18             if($date == "" || empty($date)){
 19                 $this->date = strtotime("now");
 20             }else if(gettype($date) == "string"){
 21                 $this->date = strtotime($date);
 22             }else if(gettype($date) == "integer"){
 23                 $this->date = $date;
 24             }else{
 25                 throw new Exception("paramter must be timestamp or date string or empty for current time");
 26             }
 27             $this->set_varibales();
 28         }
 29 
 30         public function set_date($date){
 31             $this->date = strtotime($date);
 32             $this->set_varibales();
 33         }
 34 
 35         private function set_varibales(){
 36             $this->M = date("m",$this->date);
 37             $this->D = date("d",$this->date);
 38             $this->Y = date("Y",$this->date);
 39         }
 40 
 41         /**
 42         * get the specified weeks
 43         * @parameter $i:numbers of week
 44         * @parameter $flag:0->last,1->next
 45         */
 46         public function get_week($i=0,$flag=1){
 47             if($flag == 0) return date("YW",strtotime("-$i week",$this->date));
 48             else if($flag == 1) return date("YW",strtotime("+$i week",$this->date));
 49         }
 50 
 51         /**
 52         * get the specified months
 53         * @parameter $i:numbers of month
 54         * @parameter $flag:0->last,1->next
 55         */
 56         public function get_month($i=0,$flag=1){
 57             if($flag == 0) return date("Y-m",mktime(0,0,0, $this->M-$i, 1, $this->Y));
 58             else if($flag == 1) return date("Y-m",mktime(0,0,0, $this->M+$i, 1, $this->Y));
 59         }
 60 
 61         /**
 62         * get the specified days
 63         * @parameter $i:numbers of day
 64         * @parameter $flag:0->last,1->next
 65         */
 66         public function get_day($i=0,$flag=1){
 67             if($flag == 0) return date("Y-m-d",mktime(0,0,0, $this->M, $this->D-$i, $this->Y));
 68             else if($flag == 1) return date("Y-m-d",mktime(0,0,0, $this->M, $this->D+$i, $this->Y));
 69         }
 70 
 71         /**
 72         * get the last $count days 
 73         * @parameter count:number
 74         */
 75         public function get_last_days($count){
 76             $return  = array();
 77             for($i=1;$i<=$count;$i++){
 78                 array_push($return, $this->get_day($i,0));
 79             }
 80             return $return;
 81         }
 82 
 83         /**
 84         * get the next $count days 
 85         * @parameter count:number
 86         */
 87         public function get_next_days($count){
 88             $return  = array();
 89             for($i=1;$i<=$count;$i++){
 90                 array_push($return, $this->get_day($i,1));
 91             }
 92             return $return;
 93         }
 94 
 95         /**
 96         * get the last $count weeks 
 97         * @parameter count:number
 98         */
 99         public function get_last_weeks($count){
100             $return  = array();
101             for($i=1;$i<=$count;$i++){
102                 array_push($return, $this->get_week($i,0));
103             }
104             return $return;
105         }
106 
107         /**
108         * get the next $count weeks 
109         * @parameter count:number
110         */
111         public function get_next_weeks($count){
112             $return  = array();
113             for($i=1;$i<=$count;$i++){
114                 array_push($return, $this->get_week($i,1));
115             }
116             return $return;
117         }
118 
119         /**
120         * get the last $count months
121         * @parameter count:number
122         */
123         public function get_last_month($count){
124             $return  = array();
125             for($i=1;$i<=$count;$i++){
126                 array_push($return, $this->get_month($i,0));
127             }
128             return $return;
129         }
130 
131         /**
132         * get the next $count months
133         * @parameter count:number
134         */
135         public function get_next_month($count){
136             $return  = array();
137             for($i=1;$i<=$count;$i++){
138                 array_push($return, $this->get_month($i,1));
139             }
140             return $return;
141         }
142 
143         /**
144         * get the first day and the last day of a week
145         */
146         public function get_week_begin_end(){
147             $return["begin"] = mktime(0,0,0, $this->M, $this->D-date("w",$this->date)+1, $this->Y);
148             $return["end"] = mktime(23,59,59, $this->M, $this->D-date("w",$this->date)+7, $this->Y);
149             return $return;
150         }
151 
152         /**
153         * get the first day and the last day of a month
154         */
155         public function get_month_begin_end(){
156             $return["begin"] = strtotime("first day of",$this->date);
157             $return["end"] = strtotime("last day of",$this->date);
158             return $return;
159         }
160     }
161 ?>
View Code

参考资料:

1.http://php.net/manual/zh/book.datetime.php

2.http://www.phppan.com/2011/06/php-strtotime/

原文地址:https://www.cnblogs.com/michaelle/p/4027686.html