日期格式化與日期運算
日期格式化
Solr 的日期欄位 (DatePointField
、DateRangeField
和已棄用的 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 最常見的查詢剖析器的特殊字元,因此有時需要逸出,具體取決於您嘗試執行的操作。 這通常是無效查詢: 以下是有效查詢 |
日期範圍格式化
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
影響日期運算的請求參數
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 個小時內有多少餐廳會營業,並詳細列出連續的六個一小時間隔。