日期格式化與日期運算

日期格式化

Solr 的日期欄位 (DatePointFieldDateRangeField 和已棄用的 TrieDateField) 將「日期」表示為具有毫秒精度的時間點。使用的格式是 XML 綱要規格中日期時間標準表示法的限制形式,它是 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 年之前的日期必須帶有前導 '-',而 Solr 會將西元 9999 年之後的日期格式化為帶有前導 '+'。西元 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 月 1 日的結束。

  • [* TO 2014-12-01] – 從最早可表示的時間到 2014 年 12 月 1 日的結束。

限制:範圍語法不支援內嵌的日期運算。如果您指定一個由 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 會影響日期運算表達式的所有情況。

它必須指定為自 epoch 以來的毫秒數 (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 回應格式會將儲存的資料公開為 String 而不是 Date。此欄位的底層索引資料會稍微大一些。與 TrieDateField 相比,對齊到一秒以上的時間單位的查詢應該更快,尤其是在 UTC 中時。

DateRangeField 的主要重點,正如其名稱所示,是允許索引日期範圍。為此,只需提供以上所示格式的字串。它還支援在索引資料和查詢範圍之間指定 3 個不同的關係謂詞

  • Intersects(預設值)

  • Contains

  • Within

您可以使用 op local-params 參數查詢來指定謂詞,如下所示

fq={!field f=dateRange op=Contains}[2013 TO 2018]

與大多數 local params 不同,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 個小時內有多少餐廳會營業,並詳細列出連續的六個一小時間隔。