134. 日期与时间
本篇讲述Drupal系统中和日期时间相关的内容
计时器:
系统提供了一个计时器程序(秒表),用于帮助计时或调式,在批处理、计划任务中都有使用,类如下:
\Drupal\Component\Utility\Timer
该计时器有个特点:在计时过程中可以反复暂停、继续开始计时,获取到的是总用时间
有三个静态方法,可在请求中全局使用,说明如下:
public static function start($name)
在内部创建一个计时器,并开始计时,参数为计时器名(任意字符串值,计时器标识符),无返回值,在调用暂停计时方法后可调用该方法继续计时
public static function read($name)
读取一个计时器所计时间总数(如有暂停,则暂停期间的时间不会计算),返回一个浮点数表示的时间,单位为毫秒(不是秒也不是微妙),精度为小数点后2位,如果之前没有设置过参数指定的计时器,那么将返回NULL,
public static function stop($name)
暂停计时,暂停后可调用开始计算方法继续计时,暂停期内的时间不会被计算,返回一个数组,该数组是内部表示的计时器,有如下键:
time:所计时间的总数,毫秒为单位,精确到小数点后两位
count:一个整数,表示该计数器开启计时的次数
时间服务:
用于以oop方式提供系统常用的四个时间:请求时间、当前时间以及她们对应的微秒时间
服务id:datetime.time
类:Drupal\Component\Datetime\Time
获取方式:\Drupal::time()
该服务在创建测试时很有用,有4个可用方法:
public function getRequestTime()
得到当前请求的请求时间,也就是“$_SERVER['REQUEST_TIME'];”,整数时间戳,和以下值完全等同:
REQUEST_TIME:系统全局常量
$requestStack->getCurrentRequest()->server->get('REQUEST_TIME');
$request_time = $request->server->get('REQUEST_TIME');
public function getRequestMicroTime()
得到当前请求的浮点时间,如:“1575421124.0707”精确到微秒,和以下值等同:
$_SERVER['REQUEST_TIME_FLOAT'];
$requestStack->getCurrentRequest()->server->get('REQUEST_TIME_FLOAT');
$request->server->get('REQUEST_TIME_FLOAT');
public function getCurrentTime()
当前时间,也就是函数time()
public function getCurrentMicroTime()
当前微秒时间,精确到微秒的浮点数,如“1575421778.3079”也就是函数microtime(TRUE)
Drupal日期时间类:
在组件中提供了如下类:
\Drupal\Component\Datetime\DateTimePlus
这是对PHP原始全局类“\DateTime”的进一步包装,提供了更多的灵活性和使用方法,示例如下:
$dateTimePlus=\Drupal\Component\Datetime\DateTimePlus::createFromTimestamp(time(),'Asia/Shanghai');
echo $dateTimePlus->render();
核心中提供了如下类:
\Drupal\Core\Datetime\DrupalDateTime
核心类继承自组件类,主要附加了时区处理和翻译功能,该类是drupal使用的主要日期时间类
日期辅助类:
类:\Drupal\Core\Datetime\DateHelper
该类提供了许多日期操作方面的辅助方法
public static function monthNamesUntranslated()
返回未翻译的长月名数组
public static function monthNamesAbbrUntranslated()
返回未翻译的短月名数组
public static function monthNames($required = FALSE)
返回已翻译的长月名
public static function monthNamesAbbr($required = FALSE)
返回已翻译的短月名
public static function weekDaysUntranslated()
返回未翻译周名数组
public static function weekDays($required = FALSE)
返回已翻译周名数组
public static function weekDaysAbbr($required = FALSE)
public static function weekDaysAbbr2($required = FALSE)
public static function weekDaysAbbr1($required = FALSE)
返回已翻译短周名数组
public static function weekDaysOrdered($weekdays)
依据周中设定的第一天重新排序周数组
public static function years($min = 0, $max = 0, $required = FALSE)
构造一个在指定范围的年数组
public static function days($required = FALSE, $month = NULL, $year = NULL)
根据指定的年、月构造天数组
public static function hours($format = 'H', $required = FALSE)
构造小时数组
public static function minutes($format = 'i', $required = FALSE, $increment = 1)
构造分钟数组
public static function seconds($format = 's', $required = FALSE, $increment = 1)
构造秒数组
public static function ampm($required = FALSE)
构造上下午选项数组
public static function daysInMonth($date = NULL)
返回给定时间所在月的天数
public static function daysInYear($date = NULL)
返回给定时间所在年的天数
public static function dayOfWeek($date = NULL)
返回给定时间是一周中的第几天,星期日将当做第0天,返回0,该方法不参考系统设置的星期第一天
public static function dayOfWeekName($date = NULL, $abbr = TRUE)
返回给定日期的已翻译星期名
时间相关函数:
drupal_get_user_timezone()
得到当前用户的时区,在用户是登录用户且系统配置为允许用户自定义时区时,如果用户有设定时区那么返回这个时区,否则返回系统默认时区
时间相关配置:
系统时间相关配置在以下页面设置:
/admin/config/regional/settings
数据储存于以下配置中:
$config = \Drupal::config('system.date');
说明如下:
country:
default: '' #国家代码,中国为“CN”
first_day: 0 #一周中的第一天
timezone:
default: '' #系统默认时区
user:
configurable: true #用户是否可以设置他们自己的时区
warn: false #如果用户没有设置时区,则在登录时是否提醒设置
default: 0 #新用户的默认时区,0为系统默认时区,1空时区,2注册时选择
是一个配置实体,用于储存日期时间的格式化信息,当配置时间显示时提供选项支持,定义如下:
实体类型id:date_format
实体类:\Drupal\Core\Datetime\Entity\DateFormat
相关表单:
添加:Drupal\system\Form\DateFormatAddForm
编辑:Drupal\system\Form\DateFormatEditForm
删除:Drupal\system\Form\DateFormatDeleteForm
列表构建器:
Drupal\system\DateFormatListBuilder
该实体的实现比较简单,这里仅说明如下几点:
label:即列表页中的名称
id:即添加表单中的机读名称
pattern:即列表页中所示的模式,换句话说即PHP函数date的第一个参数
locked:指示在用户UI接口中是否允许更新或删除
管理该实体的后台地址为:
/admin/config/regional/date-time
在程序中,日期格式化器服务可直接使用该实体去格式化时间,示例如下:
$entity=\Drupal::entityManager()->getStorage('date_format')->load('html_datetime');
echo \Drupal::service('date.formatter')->format(time(), $entity->id());
系统默认提供的格式类型如下“id(lable):示例”:
Array
(
[html_datetime] => Array
(
[0] => 名称:HTML 日期与时间
[1] => 模式:Y-m-d\TH:i:sO
[2] => 示例:2019-12-04T15:03:17+0800
)
[fallback] => Array
(
[0] => 名称:回滚日期格式
[1] => 模式:D, m/d/Y - H:i
[2] => 示例:周三, 12/04/2019 - 15:03
)
[html_date] => Array
(
[0] => 名称:HTML 日期
[1] => 模式:Y-m-d
[2] => 示例:2019-12-04
)
[html_month] => Array
(
[0] => 名称:HTML 月份
[1] => 模式:Y-m
[2] => 示例:2019-12
)
[html_time] => Array
(
[0] => 名称:HTML 时间
[1] => 模式:H:i:s
[2] => 示例:15:03:17
)
[html_week] => Array
(
[0] => 名称:HTML 周
[1] => 模式:Y-\WW
[2] => 示例:2019-W49
)
[html_year] => Array
(
[0] => 名称:HTML 年份
[1] => 模式:Y
[2] => 示例:2019
)
[html_yearless_date] => Array
(
[0] => 名称:HTML 无年份日期
[1] => 模式:m-d
[2] => 示例:12-04
)
[long] => Array
(
[0] => 名称:默认长日期
[1] => 模式:l, F j, Y - H:i
[2] => 示例:星期三, 十二月 4, 2019 - 15:03
)
[medium] => Array
(
[0] => 名称:默认中等日期
[1] => 模式:D, m/d/Y - H:i
[2] => 示例:周三, 12/04/2019 - 15:03
)
[short] => Array
(
[0] => 名称:默认短日期
[1] => 模式:m/d/Y - H:i
[2] => 示例:12/04/2019 - 15:03
)
)
日期格式化器服务:
这是一个工具型服务,用于提供日期时间相关的辅助方法,如格式化时间、计算时间间隔等
服务id:date.formatter
类:Drupal\Core\Datetime\DateFormatter
twig模板中的format_date过滤器在PHP层即调用该服务的format方法,各方法说明如下:
public function format($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL)
格式化一个时间,返回一个经过翻译的日期字符串,各参数含义如下:
$timestamp:时间戳
$type:时间格式类型,也就是前文的日期格式实体的实体id ,默认为:medium
$format:格式类型也可以被指定为“custom”,此时将使用该参数指定的格式(同date函数的第一个参数),如未指定且类型为“custom”,则使用类型“fallback”
$timezone:时区标识符,字符串值,默认使用系统默认时区
$langcode:语言代码,用于翻译,默认使用界面语言id
注意:该方法的格式化字符串如果包含了用户输入,安全起见返回值务必进行实体转义
public function formatInterval($interval, $granularity = 2, $langcode = NULL)
格式化一个时间间隔,返回翻译过的字符串,以年、月、周、天、时、分、秒表示,如:
2 年 3 个月 2 周 6 天 14 小时 20 分钟 50 秒
2 周 6 天 14 小时 20 分钟 50 秒
14 小时 20 分钟
注意:在返回的表示中,单位一定是从大到小连续的,最小间隔单位为秒,不会出现类似以下的形式:
6 天20 分钟 50 秒 (这跳过了时)
2 年 3 个月6 天 (这跳过了周)
该方法一年按365天算,一月按30天算,即不考虑闰年和大小月
参数说明如下:
$interval:时间间隔,浮点数,单位秒
$granularity:可包含的单位最大个数,默认2,合理值应小于等于7,大于等于1,但无强制要求,假设完整应返回“2 年 3 个月 2 周 6 天 14 小时 20 分钟 50 秒”,此时该参数设为3会导致仅返回“2 年 3 个月 2 周”,仅有年周月3个单位
$langcode:用于翻译的语言代码,默认为系统当前语言的代码
public function getSampleDateFormats($langcode = NULL, $timestamp = NULL, $timezone = NULL)
返回一个时间格式样本数组,键名为可用的时间格式字符(仅一个字符),键值为对应的格式化时间,如下:
Array
(
[d] => 04
[D] => 周三
[j] => 4
[l] => 星期三
[N] => 3
[S] => th
[w] => 3
[z] => 337
[W] => 49
[F] => 十二月
[m] => 12
[M] => 12月
[n] => 12
[t] => 31
[L] => 0
[o] => 2019
[Y] => 2019
[y] => 19
[a] => 下午
[A] => 下午
[B] => 406
[g] => 4
[G] => 16
[h] => 04
[H] => 16
[i] => 44
[s] => 39
[u] => 000000
[e] => Asia/Hong_Kong
[I] => 0
[O] => +0800
[P] => +08:00
[T] => HKT
[Z] => 28800
[c] => 2019-12-04T16:44:39+08:00
[r] => Wed, 04 Dec 2019 16:44:39 +0800
[U] => 1575449079
)
参数如下:
$langcode:用于翻译的语言代码,默认为系统当前语言的代码
$timestamp:时间戳,用这个时间来生成样本,默认为当前时间
$timezone:时区标识符,默认为系统默认时区
public function formatDiff($from, $to, $options = [])
返回两个时间点之间的间隔,最小间隔单位为秒,和formatInterval方法一样,但该方法还可以以对象方式返回,此时返回以下类对象:
\Drupal\Core\Datetime\FormattedDateDiff
该对象除了包含时间间隔字符串外,还包含最大缓存时间,用于显示时失效缓存,缓存时间应以字符串中最后一个单位的1单位来算,比如返回“2天3小时”,那么缓存时间就应该是1小时,以便在间隔为“2天2小时”时刷新页面。
参数$from和$to都是时间戳,分别表示起点和终点,$options为选项数组,可选的有如下键:
granularity:见formatInterval方法的第二个参数
langcode:用于翻译的语言代码
strict:布尔值,是否采用严格模式,如为true(默认值),则在$from大于$to时将返回0秒,否则将计算差值,返回不分正负
return_as_object:布尔值,是否以对象方式返回,默认FALSE
public function formatTimeDiffUntil($timestamp, $options = [])
返回从当前请求的请求时间到给定时间戳时间之间的间隔,详见formatDiff方法
formatTimeDiffSince($timestamp, $options = [])
返回给定时间戳到当前请求的请求时间之间的间隔,详见formatDiff方法
简单日期表单元素:
元素类型:date
类:Drupal\Core\Render\Element\Date
提供一个日期输入表单元素,即type为"date"的input元素,用户可用日期选择器控件datepicker进行输入
示例如下:
$form['yunke_date'] = [
'#type' => 'date',
'#title' => '日期元素',
'#min' => '2010-02-05',
'#max' => '2020-02-05',
'#step' => 5,
'#default_value' => '2019-03-05',
'#date_date_format' => 'Y-m-d',
];
在提交处理器中:
$date = $form_state->getValue($form['yunke_date']['#parents']);
得到的$date是一个字符串表示的日期,如“2019-02-15”
注意该日期元素不涉及时区,也不涉及时分秒,如需这些请见下文其他两个日期元素类型
各选项解释如下:
#min
前端可选择的最小日期
#max
前端可选择的最大日期
#step
日期增加的步长
#date_date_format
用于表示日期的格式
日期时间表单元素:
元素类型:datetime:
类:\Drupal\Core\Datetime\Element\Datetime
选项及默认值如下:
$form['yunke_datetime'] = [
'#type' => 'datetime',
'#date_date_format' => 'Y-m-d',
'#date_date_element' => 'date',
'#date_date_callbacks' => [],
'#date_time_format' => 'H:i:s',
'#date_time_element' => 'time',
'#date_time_callbacks' => [],
'#date_year_range' => '1900:2050',
'#date_increment' => 1,
'#date_timezone' => date_default_timezone_get(),
'#default_value'=>NULL,
];
在提交处理器中:
$date=$form_state->getValue($form['yunke_datetime']['#parents']);
得到的$date值是一个日期时间对象,即以下类的实例:
Drupal\Core\Datetime\DrupalDateTime
如不要求必选,没填时也可能为NULL
该元素各主要选项及其含义如下:
#date_date_format
日期格式,见date函数第一个参数,默认为格式实体html_date的模式,即“Y-m-d”,如果显示使用H5控件元素,那么格式必须指定为对应格式,否则控件不工作
#date_date_element
指示显示该日期的控件类型,可选值如下:
date:标准H5日期输入控件,也就是type为"date"的input元素,弹框点击方式输入
datetime:标准H5日期时间输入控件,也就是type为" datetime "的input元素,须手动输入
datetime-local:标准H5日期时间输入控件,也就是type为" datetime-local"的input元素,须手动输入
text:纯文本方式输入,也就是type为" text"的input元素
none:不使用也不显示日期输入控件
#date_date_callbacks
可选,日期回调数组,每一个元素均是一个回调,接收参数如下:
callback($element, $form_state, $date);
第一个参数为本日期时间元素,其“date”子健为日期元素,最后一个参数为日期时间对象,在客户端不支持H5时,可以用回调来添加一个日历控件
#date_time_format
时间格式,见date函数第一个参数,默认为格式实体html_time的模式,即“H:i:s”,如果使用H5元素,那么格式必须指定为对应格式,否则控件不工作
#date_time_element
指示时间控件类型,可选值如下:
time:标准H5日期输入控件,也就是type为" time"的input元素,可通过方向键或滑动输入
text:纯文本方式输入,也就是type为" text"的input元素
none:不显示也不使用时间输入控件
#date_time_callbacks
可选,时间回调数组,每一个元素均是一个回调,接收参数如下:
callback($element, $form_state, $date);
第一个参数为本日期时间元素,其“time”子健为时间元素,最后一个参数为日期时间对象,回调须是一个函数,即function_exists($callback)需要为true
#date_year_range
可选,表示可选择的年份范围,一个冒号分隔的年份标识,年份标识可以是相对的也可以是绝对的,用4位数字表示绝对年份,用“+或-”加一个1至4位的数字表示相对年份(相对于当前年份),默认为1900:2050,年份大小和在冒号前后的位置无关,如“2011:2019”也可以写为“2019:2011”,但如果设定了默认值,则会强行拉长范围以将默认值包含在内,更多示例如:
1900:2050(绝对时间表示的范围,1900年到2050年,也可写为2050:1900)
-3:+3(当前年份的前后三年,如当前为2019,那么其为2016:2022)
2000:+3(如果当前年份是2019年,那么表示2000-2022年)
范围均包含起始结、束年份,如2010:2019,可选择日期将是2010-01-01到2019-12-31
注意:该项仅用于前端验证,且仅在有默认值和采用日期控件时才有效,后端对该范围不做验证
#date_increment
可选,用于type为time的input元素的step值,调整时间元素部分增加或减少的步长值,整数,单位秒,默认为1秒,如果是60的倍数,那么秒组件不被受影响,该选项的效果依赖浏览器,实测chrom支持的很好,但火狐、微软无效
#date_timezone
时区标识符,字符串值,默认为date_default_timezone_get(),用于显示时间所使用的时区,也隐含意味着客户端在使用该时区,因此应是系统中用户设置的时区,即用户账户信息中使用的时区
#default_value
默认值,须用\Drupal\Core\Datetime\DrupalDateTime的实例来表示
BUG提示(Drupal8.8):在该方法中:
\Drupal\Core\Datetime\Element\Datetime::getInfo
由于返回值会被缓存,所以不能在该方法中赋值默认时区,而应该在valueCallback方法中判断没有默认值后才赋值,云客发现已经有开发者报告了该问题,详见:
https://www.drupal.org/project/drupal/issues/3087606
下拉选择型日期表单元素:
元素类型id:datelist
类:\Drupal\Core\Datetime\Element\Datelist
该类型元素用下拉列表的方式指定一个日期时间,从年精确到分钟
选项及默认值如下:
$form['yunke_datelist'] = [
'#type' => 'datelist',
'#date_part_order' => ['year', 'month', 'day', 'hour', 'minute'],
'#date_text_parts'=>[],
'#date_year_range' => '1900:2050',
'#date_increment' => 1,
'#date_date_callbacks' => [],
'#date_timezone' => date_default_timezone_get(),
];
在提交处理器中:
$date=$form_state->getValue($form['yunke_datelist']['#parents']);
得到的$date值是一个日期时间对象,即以下类的实例:
Drupal\Core\Datetime\DrupalDateTime
如不要求必选,没填时也可能为NULL
以下选项及其含义同datetime元素类型选项完全相同(见前文):
#date_year_range
#date_date_callbacks
#date_timezone
其他主要选项解释如下:
#date_part_order
一个数组,指定了要出现在控件中的时间组件及其出现顺序,可用的时间组件如下:
'year', 'month', 'day', 'ampm', 'hour', 'minute'
没有秒,如果有ampm那么小时将用12小时制,否则用24小时制
#date_text_parts
默认为空数组,可包含的元素同#date_part_order选项一样,表示如果某个时间组件出现在该选项数组中,那么该组件将不以下拉框形式出现,而是以文本框
#date_increment
同datetime元素类型选项完全相同,但表示分钟的步进
日期时间字段:
字段类型id:datetime
列表类:\Drupal\datetime\Plugin\Field\FieldType\DateTimeFieldItemList
条目类:Drupal\datetime\Plugin\Field\FieldType\DateTimeItem
字段默认控件:Drupal\datetime\Plugin\Field\FieldWidget\DateTimeDefaultWidget
可用字段控件:Drupal\datetime\Plugin\Field\FieldWidget\DateTimeDatelistWidget
该字段在储存时间时,并没有将其转化为时间戳,而是以时间字符串方式进行储存,但储存的字符串已经被转化成标准UTC时区,如果直接查看数据库,中国用户会发现少了8小时,如“2019-12-09T02:15:30”,实际上表示的北京时间为“2019-12-09T10:15:30”,因此储存的值可以视为是一个绝对时间值,和时间戳无异,只是以字符串方式储存,这导致以该字段作为实体查询的条件时,不能以时间方式进行大小于比较
时间戳字段:
条目类:Drupal\Core\Field\Plugin\Field\FieldType\TimestampItem
字段默认控件:\Drupal\Core\Datetime\Plugin\Field\FieldWidget\TimestampDatetimeWidget
该字段以时间戳方式储存时间
BUG提示(Drupal8.8):该字段默认控件实现有问题,在字段默认值设置中,留空表示字段值为表单提交时间,但实际上默认值留空时,字段采用了字段设置表单提交的时间,云客发现已有开发者提交了该问题,详见:
https://www.drupal.org/project/drupal/issues/2978467
https://www.drupal.org/project/drupal/issues/2978727
创建与修改时间字段:
创建时间字段:
字段类型id:created
条目类:Drupal\Core\Field\Plugin\Field\FieldType\CreatedItem
修改时间字段:
字段类型id:changed
条目类:Drupal\Core\Field\Plugin\Field\FieldType\ChangedItem
列表类:\Drupal\Core\Field\ChangedFieldItemList
以上两个字段没有UI接口,仅在程序中使用,她们均储存时间戳
时间范围字段:
字段类型id:daterange
条目类:Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem
列表类:\Drupal\datetime_range\Plugin\Field\FieldType\DateRangeFieldItemList
该字段由核心模块datetime_range提供,该模块默认没有启用,该字段用于储存一个日期范围,以字符串方式的绝对时间值(UTC时区)进行储存
用户时区设置:
Drupal在时间的储存上使用的是绝对时间值(时间戳或UTC时区表示的时间字符串),这些值和时区无关;站点可以配置用户是否可以配置他们自己的时区,管理地址如下:
/admin/config/regional/settings
如果允许用户配置,那么在全站时间显示时将以用户配置的时区为准,在用户录入信息时,页面时间输入控件默认采用用户自己设定的时区,但并无显式给出时区信息,此时如果时区设置不当,真实时间将会出现偏差,在有些对时间特别敏感的站点上可能会出问题,因此是否允许用户设置自己的时区很重要。
比如一个位于中国的网站,系统默认时区设置为Asia/Shanghai,即表示网站默认使用北京时间,在中国可能很多网站用户对时区概念不是很了解,甚至不知道为什么要多此一举去设置时区,如果允许用户自己设置时区,就可能会出现用户明明在中国,却胡乱选择设置,假设被设置为了“UTC”,那么此时网站给该用户显示的时间全部会慢8小时,用户在录入信息的时候,比如添加文章,在时间控件上可能又强行以北京时间去设置,此时系统储存的真实时间将相差8小时。
因此如果你的网站只是面对区域用户,那么不应允许用户设置自己的时区,如果是面向全球用户,那么必须允许用户能设置自己的时区