在 Painless 中使用日期时间
编辑在 Painless 中使用日期时间
编辑日期时间表示
编辑Painless 中的日期时间最常见的表示形式是数值、字符串值或复杂值。
- 数值
- 日期时间表示为自称为纪元的起始偏移量开始的数字;在 Painless 中,这通常是一个 long,表示自 1970-01-01 00:00:00 祖鲁时间纪元以来的毫秒数
- 字符串
- 日期时间表示为由标准格式或自定义格式定义的字符序列;在 Painless 中,这通常是一个 字符串,采用 ISO 8601 标准格式
- 复杂
- 日期时间表示为复杂类型(对象),它抽象了日期时间存储方式的内部细节,并经常提供用于修改和比较的实用程序;在 Painless 中,这通常是一个 ZonedDateTime
为了实现脚本的目标,通常需要在不同的日期时间表示形式之间进行切换。脚本中的典型模式是将数字或字符串日期时间切换为复杂日期时间,修改或比较复杂日期时间,然后将其切换回数字或字符串日期时间以进行存储或返回结果。
日期时间解析和格式化
编辑日期时间解析是从字符串日期时间切换到复杂日期时间,而日期时间格式化是从复杂日期时间切换到字符串日期时间。
DateTimeFormatter 是一种复杂类型(对象),它定义了字符串日期时间允许的字符序列。日期时间解析和格式化通常需要 DateTimeFormatter。有关如何使用 DateTimeFormatter 的更多信息,请参阅 Java 文档。
日期时间解析示例
编辑-
从毫秒解析
String milliSinceEpochString = "434931330000"; long milliSinceEpoch = Long.parseLong(milliSinceEpochString); Instant instant = Instant.ofEpochMilli(milliSinceEpoch); ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of('Z'));
-
从 ISO 8601 解析
-
从 RFC 1123 解析
-
从自定义格式解析
日期时间格式化示例
编辑-
格式化为 ISO 8601
-
格式化为自定义格式
日期时间转换
编辑日期时间转换是从数字日期时间切换到复杂日期时间,反之亦然。
日期时间转换示例
编辑-
从毫秒转换
long milliSinceEpoch = 434931330000L; Instant instant = Instant.ofEpochMilli(milliSinceEpoch); ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of('Z'));
-
转换为毫秒
ZonedDateTime zdt = ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); long milliSinceEpoch = zdt.toInstant().toEpochMilli();
日期时间片段
编辑日期时间表示形式通常包含提取各个日期时间片段(如年、小时、时区等)的数据。使用日期时间的各个片段来创建复杂日期时间,并使用复杂日期时间来提取各个片段。
日期时间片段示例
编辑-
从片段创建复杂日期时间
int year = 1983; int month = 10; int day = 13; int hour = 22; int minutes = 15; int seconds = 30; int nanos = 0; ZonedDateTime zdt = ZonedDateTime.of( year, month, day, hour, minutes, seconds, nanos, ZoneId.of('Z'));
-
从复杂日期时间提取片段
ZonedDateTime zdt = ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 100, ZoneId.of(tz)); int year = zdt.getYear(); int month = zdt.getMonthValue(); int day = zdt.getDayOfMonth(); int hour = zdt.getHour(); int minutes = zdt.getMinute(); int seconds = zdt.getSecond(); int nanos = zdt.getNano();
日期时间修改
编辑使用数字日期时间或复杂日期时间进行修改,例如向日期时间添加几秒钟或从日期时间减去几天。使用标准 数值运算符 来修改数字日期时间。使用 方法(或字段)来修改复杂日期时间。请注意,许多复杂日期时间是不可变的,因此在修改后会创建一个新的复杂日期时间,需要 赋值 或立即使用。
日期时间修改示例
编辑-
从数字日期时间中减去三秒(以毫秒为单位)
long milliSinceEpoch = 434931330000L; milliSinceEpoch = milliSinceEpoch - 1000L*3L;
-
向复杂日期时间添加三天
ZonedDateTime zdt = ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); ZonedDateTime updatedZdt = zdt.plusDays(3);
-
从复杂日期时间中减去 125 分钟
ZonedDateTime zdt = ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); ZonedDateTime updatedZdt = zdt.minusMinutes(125);
-
设置复杂日期时间的年份
ZonedDateTime zdt = ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); ZonedDateTime updatedZdt = zdt.withYear(1976);
日期时间差(经过时间)
编辑使用两个数字日期时间或两个复杂日期时间来计算两个不同日期时间之间的差值(经过的时间)。使用 减法 来计算两个具有相同时间单位(如毫秒)的数字日期时间之间的差值。对于复杂日期时间,通常有一种方法或另一种复杂类型(对象)可用于计算差值。如果支持,请使用 ChronoUnit 来计算两个复杂日期时间之间的差值。
日期时间差值示例
编辑-
两个数字日期时间之间的毫秒差值
long startTimestamp = 434931327000L; long endTimestamp = 434931330000L; long differenceInMillis = endTimestamp - startTimestamp;
-
两个复杂日期时间之间的毫秒差值
ZonedDateTime zdt1 = ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 11000000, ZoneId.of('Z')); ZonedDateTime zdt2 = ZonedDateTime.of(1983, 10, 13, 22, 15, 35, 0, ZoneId.of('Z')); long differenceInMillis = ChronoUnit.MILLIS.between(zdt1, zdt2);
-
两个复杂日期时间之间的天数差值
ZonedDateTime zdt1 = ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 11000000, ZoneId.of('Z')); ZonedDateTime zdt2 = ZonedDateTime.of(1983, 10, 17, 22, 15, 35, 0, ZoneId.of('Z')); long differenceInDays = ChronoUnit.DAYS.between(zdt1, zdt2);
日期时间比较
编辑使用两个数字日期时间或两个复杂日期时间进行日期时间比较。使用标准 比较运算符 来比较两个具有相同时间单位(如毫秒)的数字日期时间。对于复杂日期时间,通常有一种方法或另一种复杂类型(对象)可用于进行比较。
日期时间比较示例
编辑-
两个数字日期时间的毫秒大于比较
long timestamp1 = 434931327000L; long timestamp2 = 434931330000L; if (timestamp1 > timestamp2) { // handle condition }
-
两个复杂日期时间的相等比较
ZonedDateTime zdt1 = ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); ZonedDateTime zdt2 = ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); if (zdt1.equals(zdt2)) { // handle condition }
-
两个复杂日期时间的 less than 比较
ZonedDateTime zdt1 = ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); ZonedDateTime zdt2 = ZonedDateTime.of(1983, 10, 17, 22, 15, 35, 0, ZoneId.of('Z')); if (zdt1.isBefore(zdt2)) { // handle condition }
-
两个复杂日期时间的 greater than 比较
ZonedDateTime zdt1 = ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); ZonedDateTime zdt2 = ZonedDateTime.of(1983, 10, 17, 22, 15, 35, 0, ZoneId.of('Z')); if (zdt1.isAfter(zdt2)) { // handle condition }
日期时间时区
编辑字符串日期时间和复杂日期时间都有一个时区,默认值为 UTC
。数字日期时间没有足够的显式信息来拥有时区,因此始终假定为 UTC
。结合使用 方法(或字段)和 ZoneId 来更改复杂日期时间的时区。将字符串日期时间解析为复杂日期时间以更改时区,然后将复杂日期时间格式化回所需的字符串日期时间。请注意,许多复杂日期时间是不可变的,因此在修改后会创建一个新的复杂日期时间,需要 赋值 或立即使用。
日期时间时区示例
编辑-
修改复杂日期时间的时区
ZonedDateTime utc = ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); ZonedDateTime pst = utc.withZoneSameInstant(ZoneId.of('America/Los_Angeles'));
-
修改字符串日期时间的时区
String gmtString = 'Thu, 13 Oct 1983 22:15:30 GMT'; ZonedDateTime gmtZdt = ZonedDateTime.parse(gmtString, DateTimeFormatter.RFC_1123_DATE_TIME); ZonedDateTime pstZdt = gmtZdt.withZoneSameInstant(ZoneId.of('America/Los_Angeles')); String pstString = pstZdt.format(DateTimeFormatter.RFC_1123_DATE_TIME);
日期时间输入
编辑根据 Painless 上下文,有几种常见的方式将日期时间用作脚本的输入。通常,日期时间输入将从用户指定的参数、原始源文档或索引文档中访问。
来自用户参数的日期时间输入
编辑在脚本规范期间使用 params 部分,以将数字日期时间或字符串日期时间作为脚本输入传递。对脚本中用户定义参数的访问取决于 Painless 上下文,但是,这些参数最常见的情况是通过名为 params
的输入进行访问。
示例
-
将来自用户参数的数字日期时间解析为复杂日期时间
-
输入
... "script": { ... "params": { "input_datetime": 434931327000 } } ...
-
脚本
long inputDateTime = params['input_datetime']; Instant instant = Instant.ofEpochMilli(inputDateTime); ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of('Z'));
-
-
将来自用户参数的字符串日期时间解析为复杂日期时间
-
输入
... "script": { ... "params": { "input_datetime": "custom y 1983 m 10 d 13 22:15:30 Z" } } ...
-
脚本
-
来自源文档的日期时间输入
编辑使用原始 源文档作为脚本输入,以访问该文档中特定字段的数字日期时间或字符串日期时间。在脚本中访问原始源文档取决于 Painless 上下文,并且并非总是可用。原始源文档最常见的情况是通过名为 ctx['_source']
或 params['_source']
的输入进行访问。
示例
-
将来自源文档的数字日期时间解析为复杂日期时间
-
将来自源文档的字符串日期时间解析为复杂日期时间
来自索引文档的日期时间输入
编辑使用索引文档作为脚本输入,以访问该文档中特定字段的复杂日期时间,其中该字段被映射为 标准日期或纳秒日期。映射为 数字的数字日期时间字段和映射为 关键字的字符串日期时间字段也可以通过索引文档访问。在脚本中访问索引文档取决于 Painless 上下文,并且并非总是可用。索引文档最常见的情况是通过名为 doc
的输入进行访问。
示例
-
将来自索引文档的复杂日期时间格式化为字符串日期时间
-
查找来自索引文档的两个复杂日期时间之间的差值
-
假设
- 字段
start
和end
可能 不 作为查询的一部分存在于所有索引中 - 字段
start
和end
可能 不 在所有索引文档中都有值
- 字段
-
映射
{ "mappings": { ... "properties": { ... "start": { "type": "date" }, "end": { "type": "date" } ... } ... } }
-
脚本
if (doc.containsKey('start') && doc.containsKey('end')) { if (doc['start'].size() > 0 && doc['end'].size() > 0) { ZonedDateTime start = doc['start'].value; ZonedDateTime end = doc['end'].value; long differenceInMillis = ChronoUnit.MILLIS.between(start, end); // handle difference in times } else { // handle fields without values } } else { // handle index with missing fields }
-
当前日期时间
编辑在大多数 Painless 上下文中,不支持当前日期时间 now
。这主要有两个原因。首先,脚本通常每个文档运行一次,因此每次运行脚本都会返回不同的 now
。其次,脚本通常以分布式方式运行,而没有适当同步 now
的方法。 相反,请传入一个用户定义的参数,该参数包含字符串日期时间或数值日期时间以表示 now
。 最好使用数值日期时间,因为它无需解析即可进行比较。
当前日期时间示例
编辑-
使用数值日期时间作为
now
-
假设
- 字段
input_datetime
作为查询的一部分存在于所有索引中 - 所有索引文档都包含字段
input_datetime
- 字段
-
映射
{ "mappings": { ... "properties": { ... "input_datetime": { "type": "date" } ... } ... } }
-
输入
... "script": { ... "params": { "now": <generated numeric datetime in milliseconds since epoch> } } ...
-
脚本
long now = params['now']; ZonedDateTime inputDateTime = doc['input_datetime']; long millisDateTime = inputDateTime.toInstant().toEpochMilli(); long elapsedTime = now - millisDateTime;
-
-
使用字符串日期时间作为
now
-
假设
- 字段
input_datetime
作为查询的一部分存在于所有索引中 - 所有索引文档都包含字段
input_datetime
- 字段
-
映射
{ "mappings": { ... "properties": { ... "input_datetime": { "type": "date" } ... } ... } }
-
输入
... "script": { ... "params": { "now": "<generated string datetime in ISO-8601>" } } ...
-
脚本
-
上下文中日期时间示例
编辑加载示例数据
编辑运行以下 curl 命令,将上下文示例所需的数据加载到 Elasticsearch 集群中
-
为示例数据创建 映射。
PUT /messages { "mappings": { "properties": { "priority": { "type": "integer" }, "datetime": { "type": "date" }, "message": { "type": "text" } } } }
-
加载示例数据。
POST /_bulk { "index" : { "_index" : "messages", "_id" : "1" } } { "priority": 1, "datetime": "2019-07-17T12:13:14Z", "message": "m1" } { "index" : { "_index" : "messages", "_id" : "2" } } { "priority": 1, "datetime": "2019-07-24T01:14:59Z", "message": "m2" } { "index" : { "_index" : "messages", "_id" : "3" } } { "priority": 2, "datetime": "1983-10-14T00:36:42Z", "message": "m3" } { "index" : { "_index" : "messages", "_id" : "4" } } { "priority": 3, "datetime": "1983-10-10T02:15:15Z", "message": "m4" } { "index" : { "_index" : "messages", "_id" : "5" } } { "priority": 3, "datetime": "1983-10-10T17:18:19Z", "message": "m5" } { "index" : { "_index" : "messages", "_id" : "6" } } { "priority": 1, "datetime": "2019-08-03T17:19:31Z", "message": "m6" } { "index" : { "_index" : "messages", "_id" : "7" } } { "priority": 3, "datetime": "2019-08-04T17:20:00Z", "message": "m7" } { "index" : { "_index" : "messages", "_id" : "8" } } { "priority": 2, "datetime": "2019-08-04T18:01:01Z", "message": "m8" } { "index" : { "_index" : "messages", "_id" : "9" } } { "priority": 3, "datetime": "1983-10-10T19:00:45Z", "message": "m9" } { "index" : { "_index" : "messages", "_id" : "10" } } { "priority": 2, "datetime": "2019-07-23T23:39:54Z", "message": "m10" }
按星期几进行桶聚合示例
编辑以下示例使用 词条聚合 作为 桶脚本聚合上下文 的一部分,以显示每个星期几的消息数量。
GET /messages/_search?pretty=true { "aggs": { "day-of-week-count": { "terms": { "script": "return doc[\"datetime\"].value.getDayOfWeekEnum();" } } } }
早晚桶聚合示例
编辑以下示例使用 词条聚合 作为 桶脚本聚合上下文 的一部分,以显示早上和晚上接收到的消息数量。
GET /messages/_search?pretty=true { "aggs": { "am-pm-count": { "terms": { "script": "return doc[\"datetime\"].value.getHour() < 12 ? \"AM\" : \"PM\";" } } } }
消息存在时间的脚本字段示例
编辑以下示例使用 脚本字段 作为 字段上下文 的一部分,以显示“now”与接收到消息的时间之间的间隔。
GET /_search?pretty=true { "query": { "match_all": {} }, "script_fields": { "message_age": { "script": { "source": "ZonedDateTime now = ZonedDateTime.ofInstant(Instant.ofEpochMilli(params[\"now\"]), ZoneId.of(\"Z\")); ZonedDateTime mdt = doc[\"datetime\"].value; String age; long years = mdt.until(now, ChronoUnit.YEARS); age = years + \"Y \"; mdt = mdt.plusYears(years); long months = mdt.until(now, ChronoUnit.MONTHS); age += months + \"M \"; mdt = mdt.plusMonths(months); long days = mdt.until(now, ChronoUnit.DAYS); age += days + \"D \"; mdt = mdt.plusDays(days); long hours = mdt.until(now, ChronoUnit.HOURS); age += hours + \"h \"; mdt = mdt.plusHours(hours); long minutes = mdt.until(now, ChronoUnit.MINUTES); age += minutes + \"m \"; mdt = mdt.plusMinutes(minutes); long seconds = mdt.until(now, ChronoUnit.SECONDS); age += hours + \"s\"; return age;", "params": { "now": 1574005645830 } } } } }
以下显示了分解为多行的脚本
ZonedDateTime now = ZonedDateTime.ofInstant( Instant.ofEpochMilli(params['now']), ZoneId.of('Z')); ZonedDateTime mdt = doc['datetime'].value; String age; long years = mdt.until(now, ChronoUnit.YEARS); age = years + 'Y '; mdt = mdt.plusYears(years); long months = mdt.until(now, ChronoUnit.MONTHS); age += months + 'M '; mdt = mdt.plusMonths(months); long days = mdt.until(now, ChronoUnit.DAYS); age += days + 'D '; mdt = mdt.plusDays(days); long hours = mdt.until(now, ChronoUnit.HOURS); age += hours + 'h '; mdt = mdt.plusHours(hours); long minutes = mdt.until(now, ChronoUnit.MINUTES); age += minutes + 'm '; mdt = mdt.plusMinutes(minutes); long seconds = mdt.until(now, ChronoUnit.SECONDS); age += hours + 's'; return age;