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 }
阅读这段代码需知道一个重要的结构体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", ×, &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", ×, &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 }