日期格式化和日期运算

日期格式化

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 最常用的查询解析器的特殊字符,因此有时需要转义,具体取决于您要执行的操作。

这通常是无效查询:datefield:1972-05-20T17:33:18.772Z

以下是有效查询
datefield:1972-05-20T17\:33\:18.772Z
datefield:"1972-05-20T17:33:18.772Z"
datefield:[1972-05-20T17:33:18.772Z TO *]

日期范围格式化

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

日期数学单位选项

以下单位在日期数学表达式中有效。第一列是在 Solr 的日期数学表达式中使用的值。第二列是它映射到的时间单位,因为给定的时间单位存在多个别名。

日期数学表达式单位 时间单位

YEAR

YEARS

MONTH

MONTHS

DAY

DAYS

DATE

HOUR

小时

HOURS

小时

MINUTE

分钟

MINUTES

分钟

SECOND

SECONDS

MILLI

毫秒

MILLIS

毫秒

MILLISECOND

毫秒

MILLISECONDS

毫秒

影响日期数学的请求参数

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 个一小时间隔进行分解。