Elasticsearch.Nest 教程系列 10-3 常用类型:Date math expressions | 日期数字表达式

  • 本系列博文是“伪”官方文档翻译(更加本土化),并非完全将官方文档进行翻译,而是在查阅、测试原始文档并转换为自己真知灼见后的“准”翻译。有不同见解 / 说明不周的地方,还请海涵、不吝拍砖 :)

  • 官方文档见此:https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/introduction.html

  • 本系列对应的版本环境:ElasticSearch@7.3.1,NEST@7.3.1,IDE 和开发平台默认为 VS2019,.NET CORE 2.1


在“查询/过滤”的时候支持使用日期类型的数学表达式。表达式以“锚定”日期开始,该日期可以是现在,也可以是以“||”结尾的日期字符串。它后面可以是一个数学表达式,支持 +,- 和 /(四舍五入)。支持的单位有:

  • y (year)

  • M (month)

  • w (week)

  • d (day)

  • h (hour)

  • m (minute)

  • s (second)

ES 中的 Date Math

常规示例

使用 DateMath 提供的静态方法:

1
Expect("2020-01-23T00:00:00").WhenSerializing(Nest.DateMath.Anchored(new DateTime(2020,01, 23)));

字符串则会隐式转换为 DateMath

1
Expect("now").WhenSerializing<Nest.DateMath>("now");

DateMath 对于入参会进行一定程度的容错:

1
2
3
4
5
6
7
8
9
var nonsense = "now||*asdaqwe";  //“*asdaqwe”不是一个有效的时间表达式
Expect(nonsense)
.WhenSerializing<Nest.DateMath>(nonsense)
.AssertSubject(dateMath => ((IDateMath)dateMath)
.Anchor.Match(
d => d.Should().NotBe(default(DateTime)),
s => s.Should().Be(nonsense)
)
);

DateTime 还会隐式转换为简单的日期数学表达式

  • 哪怕经过序列化/反序列化后,所得的Anchor 仍将是实际的 DateTime。

1
2
3
4
var date = new DateTime(2020,1, 23); //将会倍序列化为 "2020-01-23T00:00:00"

//UTC 示例
var utcDate = new DateTime(2020, 1, 23, 0, 0, 0, DateTimeKind.Utc); //将会倍序列化为 "2020-01-23T00:00:00Z"

复杂示例

范围可以链接到简单表达式

1
2
3
4
Expect("now+1d").WhenSerializing(Nest.DateMath.Now.Add("1d"));

Expect("now+1d-1m").WhenSerializing(
Nest.DateMath.Now.Add("1d").Subtract(TimeSpan.FromMinutes(1)));

经过四舍五入的表达式,之后不能再使用范围进行链接,如下:

1
2
3
4
Expect("now+1d-1m/d").WhenSerializing(
Nest.DateMath.Now.Add("1d")
.Subtract(TimeSpan.FromMinutes(1))
.RoundTo(DateMathTimeUnit.Day));

锚定日期时,需要在锚点和范围之间使用分隔符 “||”。同样,可以链接多个范围

1
2
3
4
Expect("2020-01-23T00:00:00||+1d-1m").WhenSerializing(
Nest.DateMath.Anchored(new DateTime(2020,1,23))
.Add("1d")
.Subtract(TimeSpan.FromMinutes(1)));

含有小数时间

Elasticsearch中的日期数学表达式不支持小数。

为了在 NEST 中更轻松地使用日期数学,从字符串,TimeSpan 和 double 转换而来的值会将小数部分转换为最大的整数值和单位,四舍五入到最接近的秒数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Expect("now+1w").WhenSerializing(Nest.DateMath.Now.Add(TimeSpan.FromDays(7)));

Expect("now+1w").WhenSerializing(Nest.DateMath.Now.Add("1w"));

Expect("now+1w").WhenSerializing(Nest.DateMath.Now.Add(604800000));

Expect("now+7d").WhenSerializing(Nest.DateMath.Now.Add("7d"));

Expect("now+30h").WhenSerializing(Nest.DateMath.Now.Add(TimeSpan.FromHours(30)));

Expect("now+30h").WhenSerializing(Nest.DateMath.Now.Add("1.25d"));

Expect("now+90001s").WhenSerializing(
Nest.DateMath.Now.Add(TimeSpan.FromHours(25).Add(TimeSpan.FromSeconds(1))));

Expect("now+90000s").WhenSerializing(
Nest.DateMath.Now.Add(TimeSpan.FromHours(25).Add(TimeSpan.FromMilliseconds(1))));

Expect("now+1y").WhenSerializing(Nest.DateMath.Now.Add("1y"));

Expect("now+12M").WhenSerializing(Nest.DateMath.Now.Add("12M"));

Expect("now+18M").WhenSerializing(Nest.DateMath.Now.Add("1.5y"));

Expect("now+52w").WhenSerializing(Nest.DateMath.Now.Add(TimeSpan.FromDays(7 * 52)));

舍入

可以使用构造函数控制舍入,并传递一个值进行舍入:

1
2
3
4
5
6
7
8
9
10
11
Expect("now+2s").WhenSerializing(
Nest.DateMath.Now.Add(new DateMathTime("2.5s", MidpointRounding.ToEven)));

Expect("now+3s").WhenSerializing(
Nest.DateMath.Now.Add(new DateMathTime("2.5s", MidpointRounding.AwayFromZero)));

Expect("now+0s").WhenSerializing(
Nest.DateMath.Now.Add(new DateMathTime(500, MidpointRounding.ToEven)));

Expect("now+1s").WhenSerializing(
Nest.DateMath.Now.Add(new DateMathTime(500, MidpointRounding.AwayFromZero)));

相等性比较

DateMathTime 实现了 equality 和 comparison:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DateMathTime twoSeconds = new DateMathTime(2, DateMathTimeUnit.Second);
DateMathTime twoSecondsFromString = "2s";
DateMathTime twoSecondsFromTimeSpan = TimeSpan.FromSeconds(2);
DateMathTime twoSecondsFromDouble = 2000;

twoSeconds.Should().Be(twoSecondsFromString);
twoSeconds.Should().Be(twoSecondsFromTimeSpan);
twoSeconds.Should().Be(twoSecondsFromDouble);

DateMathTime threeSecondsFromString = "3s";
DateMathTime oneMinuteFromTimeSpan = TimeSpan.FromMinutes(1);

(threeSecondsFromString > twoSecondsFromString).Should().BeTrue();
(oneMinuteFromTimeSpan > threeSecondsFromString).Should().BeTrue();

年份和月份不包含确切值:

  • 一年大约365天

  • 一个月大约(365/12)天

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DateMathTime oneYear = new DateMathTime(1, DateMathTimeUnit.Year);
DateMathTime oneYearFromString = "1y";
DateMathTime twelveMonths = new DateMathTime(12, DateMathTimeUnit.Month);
DateMathTime twelveMonthsFromString = "12M";

oneYear.Should().Be(oneYearFromString);
oneYear.Should().Be(twelveMonths);
twelveMonths.Should().Be(twelveMonthsFromString);

DateMathTime thirteenMonths = new DateMathTime(13, DateMathTimeUnit.Month);
DateMathTime thirteenMonthsFromString = "13M";
DateMathTime fiftyTwoWeeks = "52w";

(oneYear < thirteenMonths).Should().BeTrue();
(oneYear < thirteenMonthsFromString).Should().BeTrue();
(twelveMonths > fiftyTwoWeeks).Should().BeTrue();
(oneYear > fiftyTwoWeeks).Should().BeTrue();