日期格式化和日期运算
日期格式化
Solr 的日期字段 (DatePointField
, DateRangeField
和已弃用的 TrieDateField
) 将“日期”表示为具有毫秒精度的某个时间点。使用的格式是 XML 模式规范中 dateTime 的规范表示形式的受限形式 – ISO-8601 的受限子集。对于那些熟悉 Java 日期处理的人来说,Solr 使用 DateTimeFormatter.ISO_INSTANT 进行格式化,并以“宽松”方式进行解析。
YYYY-MM-DDThh:mm:ssZ
-
YYYY
是年份。 -
MM
是月份。 -
DD
是月份中的日期。 -
hh
是 24 小时制中的一天中的小时。 -
mm
是分钟。 -
ss
是秒。 -
Z
是一个字面“Z”字符,表示此日期的字符串表示形式以 UTC 表示
请注意,不能指定时区;日期的字符串表示形式始终以协调世界时 (UTC) 表示。这是一个示例值
1972-05-20T17:33:18Z
您可以选择包含小数秒,尽管任何超过毫秒的精度都将被忽略。以下是带有亚秒的示例值
-
1972-05-20T17:33:18.772Z
-
1972-05-20T17:33:18.77Z
-
1972-05-20T17:33:18.7Z
对于 0000 年之前的日期,必须有一个前导 '-'
,对于 9999 年之后的年份,Solr 将使用前导 '+'
来格式化日期。0000 年被认为是公元前 1 年;没有公元 0 年或公元前 0 年。
可能需要查询转义
如您所见,日期格式包括用冒号分隔小时、分钟和秒的字符。由于冒号是 Solr 最常用的查询解析器的特殊字符,因此有时需要转义,具体取决于您要执行的操作。 这通常是无效查询: 以下是有效查询 |
日期范围格式化
Solr 的 DateRangeField
支持上面描述的相同时间点日期语法(带有下面描述的日期运算)以及更多用于表达日期范围的内容。一类示例是截断日期,它表示整个日期跨度到指示的精度。另一类使用范围语法 ([ TO ]
)。以下是一些示例
-
2000-11
– 2000 年的整个 11 月。 -
1605-11-05
– 11 月 5 日。 -
2000-11-05T13
– 同样,但对于一天中的一个小时(1300 到 1400 之前,即下午 1 点到下午 2 点)。 -
-0009
– 公元前 10 年。年份位置中的 0 是公元 0 年,也被认为是公元前 1 年。 -
[2000-11-01 TO 2014-12-01]
– 指定的日期范围,以天为分辨率。 -
[2014 TO 2014-12-01]
– 从 2014 年初到 12 月第一天的结束。 -
[* TO 2014-12-01]
– 从最早的可表示时间到 2014-12-01 当天的结束。
限制:范围语法不支持嵌入式日期运算。如果使用日期运算截断指定 DatePointField
支持的日期实例,如 NOW/DAY
,您仍然会获得当天的第一个毫秒,而不是整个日期的范围。排他性范围(使用 {
& }
)在查询中起作用,但不适用于索引范围。
日期运算
Solr 的日期字段类型也支持日期数学表达式,这使得创建相对于固定时间点的时刻变得容易,包括可以使用特殊值“NOW
”表示的当前时间。
日期数学语法
日期数学表达式包括添加指定单位的时间量,或按指定单位舍入当前时间。表达式可以链接在一起,并从左到右进行计算。
例如:这表示从现在起两个月后的时间点
NOW+2MONTHS
这是一天前的时间
NOW-1DAY
斜杠用于指示舍入。这表示当前小时的开始
NOW/HOUR
以下示例计算(以毫秒精度)未来六个月零三天的时间点,然后将该时间舍入到该天的开始
NOW+6MONTHS+3DAYS/DAY
请注意,虽然日期数学最常相对于 NOW
使用,但它也可以应用于任何固定时间点
1972-05-20T17:33:18.772Z+6MONTHS+3DAYS/DAY
影响日期数学的请求参数
NOW
NOW
参数在 Solr 内部使用,以确保分布式请求中多个节点之间的日期数学表达式解析一致。但是可以指定它来指示 Solr 使用任意时间点(过去或未来)来覆盖所有特殊值 NOW
会影响日期数学表达式的情况。
它必须指定为自纪元以来的毫秒数(long 值)。
示例
q=solr&fq=start_date:[* TO NOW]&NOW=1384387200000
TZ
默认情况下,所有日期数学表达式都相对于 UTC 时区进行计算,但可以指定 TZ
参数来覆盖此行为,方法是强制所有基于日期的加法和舍入都相对于指定的时区。
例如,以下请求将使用范围分面来对当前月份进行分面,相对于 UTC “每天”进行分面
https://127.0.0.1:8983/solr/my_collection/select?q=*:*&facet.range=my_date_field&facet=true&facet.range.start=NOW/MONTH&facet.range.end=NOW/MONTH%2B1MONTH&facet.range.gap=%2B1DAY&wt=xml
<int name="2013-11-01T00:00:00Z">0</int>
<int name="2013-11-02T00:00:00Z">0</int>
<int name="2013-11-03T00:00:00Z">0</int>
<int name="2013-11-04T00:00:00Z">0</int>
<int name="2013-11-05T00:00:00Z">0</int>
<int name="2013-11-06T00:00:00Z">0</int>
<int name="2013-11-07T00:00:00Z">0</int>
...
而在本示例中,“天”将相对于指定的时区进行计算 - 包括任何适用的夏令时调整
https://127.0.0.1:8983/solr/my_collection/select?q=*:*&facet.range=my_date_field&facet=true&facet.range.start=NOW/MONTH&facet.range.end=NOW/MONTH%2B1MONTH&facet.range.gap=%2B1DAY&TZ=America/Los_Angeles&wt=xml
<int name="2013-11-01T07:00:00Z">0</int>
<int name="2013-11-02T07:00:00Z">0</int>
<int name="2013-11-03T07:00:00Z">0</int>
<int name="2013-11-04T08:00:00Z">0</int>
<int name="2013-11-05T08:00:00Z">0</int>
<int name="2013-11-06T08:00:00Z">0</int>
<int name="2013-11-07T08:00:00Z">0</int>
...
更多 DateRangeField 详细信息
DateRangeField
几乎可以完全替代使用 DatePointField
的地方。唯一的区别是 Solr 的 XML 或 SolrJ 响应格式将存储的数据公开为字符串而不是 Date。此字段的底层索引数据会稍微大一些。与时间单位对齐的查询(一秒及以上)应该比 TrieDateField 快,尤其是当它使用 UTC 时。
DateRangeField
的主要重点,顾名思义,是允许索引日期范围。为此,只需提供以上格式的字符串。它还支持在索引数据和查询范围之间指定 3 个不同的关系谓词
-
Intersects
(默认) -
Contains
-
Within
您可以使用 op
本地参数通过查询来指定谓词,如下所示
fq={!field f=dateRange op=Contains}[2013 TO 2018]
与大多数本地参数不同,op
实际上不是由任何查询解析器 (field
) 定义的,而是由字段类型定义的,在本例中是 DateRangeField
。在上面的示例中,它会查找具有索引范围包含(或等于)2013 年到 2018 年范围的文档。文档中多值的重叠索引范围会被有效地合并。
一个用例示例
假设我们要查找在某个时间窗口内营业的所有餐厅。让我们向 schema.xml 添加一个日期范围字段,以便我们可以索引有关餐厅营业时间的信息
<field name="opening_hours" type="date_range" indexed="true" stored="true" multiValued="true"/>
<fieldType name="date_range" class="solr.DateRangeField"/>
接下来,我们将向索引添加两家餐厅
JSON
[{ "id": "r01",
"opening_hours": [ "[2016-02-01T03:00Z TO 2016-02-01T15:00Z]",
"[2016-02-02T03:00Z TO 2016-02-02T15:00Z]",
"[2016-02-03T03:00Z TO 2016-02-03T15:00Z]",
"[2016-02-04T03:00Z TO 2016-02-04T15:00Z]",
"[2016-02-05T03:00Z TO 2016-02-05T16:00Z]",
"[2016-02-06T03:00Z TO 2016-02-06T16:00Z]",
"[2016-02-07T03:00Z TO 2016-02-07T15:00Z]" ]},
{ "id": "r02",
"opening_hours": [ "[2016-02-06T10:00Z TO 2016-02-06T12:00Z]",
"[2016-02-06T14:00Z TO 2016-02-06T16:00Z]",
"[2016-02-07T12:00Z TO 2016-02-07T16:00Z]" ]}
]
每家餐厅在一天中可以有多个营业时间,并且不同天的营业时间可能不同。
opening_hours 中的日期范围应在索引之前转换为 UTC。 |
现在,要查找在特定时间窗口内营业的餐厅,我们可以使用过滤器查询
fq={!field f=opening_hours op=Contains}[2016-02-02T14:50 TO 2016-02-02T15:00]
{
"responseHeader":{
"status":0,
"QTime":29,
"params":{
"q":"id:*",
"fl":"id",
"fq":"{!field f=opening_hours op=Contains}[2016-02-02T14:50 TO 2016-02-02T15:00]",
"wt":"json"}},
"response":{"numFound":1,"start":0,"numFoundExact":true,"docs":[
{
"id":"r01"}]
}}
如果我们需要获取营业时间范围,我们可以使用分面查询
q=id:*
rows=0
facet=true
facet.range=opening_hours
f.opening_hours.facet.range.start=NOW
f.opening_hours.facet.range.end=NOW+6HOUR
f.opening_hours.facet.range.gap=+1HOUR
{
"responseHeader":{
"status":0,
"QTime":16,
"params":{
"q":"id:*",
"facet":"true",
"facet.range":"opening_hours",
"f.opening_hours.facet.range.start":"NOW",
"f.opening_hours.facet.range.gap":"+1HOUR",
"f.opening_hours.facet.range.end":"NOW+6HOUR",
"rows":"0",
"wt":"json"}},
"response":{"numFound":2,"start":0,"numFoundExact":true,"docs":[]
},
"facet_counts":{
"facet_queries":{},
"facet_fields":{},
"facet_ranges":{
"opening_hours":{
"counts":[
"2016-02-06T11:01:00Z",2,
"2016-02-06T12:01:00Z",1,
"2016-02-06T13:01:00Z",2,
"2016-02-06T14:01:00Z",2,
"2016-02-06T15:01:00Z",2,
"2016-02-06T16:01:00Z",0],
"gap":"+1HOUR",
"start":"2016-02-06T11:01:00Z",
"end":"2016-02-06T17:01:00Z"}},
"facet_intervals":{},
"facet_heatmaps":{}}}
查询结果显示了未来 6 个小时内将有多少家餐厅营业,并按连续的 6 个一小时间隔进行分解。