在 Painless 中使用日期时间
编辑在 Painless 中使用日期时间
编辑日期时间表示
编辑Painless 中的日期时间最常用数值、字符串或复杂值表示。
- 数值
- 从起始偏移量(称为纪元)开始计算的数值表示日期时间;在 Painless 中,这通常是自 1970-01-01 00:00:00 协调世界时 (Zulu Time) 纪元以来的毫秒数,以 长整型 (long) 表示。
- 字符串
- 由标准格式或自定义格式定义的字符序列表示日期时间;在 Painless 中,这通常是 字符串 (String),采用标准格式 ISO 8601。
- 复杂类型
- 作为复杂类型 (对象 (object)) 表示日期时间,该类型隐藏了日期时间存储方式的内部细节,并通常提供修改和比较的实用程序;在 Painless 中,这通常是 ZonedDateTime。
为了实现脚本的目标,通常需要在日期时间的不同表示形式之间进行转换。脚本中的典型模式是将数值或字符串日期时间转换为复杂日期时间,修改或比较复杂日期时间,然后将其转换回数值或字符串日期时间以进行存储或返回结果。
日期时间解析和格式化
编辑日期时间解析是从字符串日期时间转换为复杂日期时间,日期时间格式化是从复杂日期时间转换为字符串日期时间。
DateTimeFormatter 是一种复杂类型 (对象 (object)),它定义了字符串日期时间允许的字符序列。日期时间解析和格式化通常需要 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);
日期时间差(经过时间)
编辑使用两个数值日期时间或两个复杂日期时间来计算两个不同日期时间之间的差(经过时间)。使用减法计算相同时间单位(如毫秒)的两个数值日期时间之间的差。对于复杂日期时间,通常可以使用一种方法或另一种复杂类型 (对象 (object)) 来计算差值。如果支持,可以使用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);
日期时间比较
编辑使用两个数值日期时间或两个复杂日期时间进行日期时间比较。使用标准比较运算符比较相同时间单位(如毫秒)的两个数值日期时间。对于复杂日期时间,通常可以使用一种方法或另一种复杂类型 (对象 (object)) 进行比较。
日期时间比较示例
编辑-
以毫秒为单位的两个数值日期时间的“大于”比较
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 }
-
两个复杂日期时间的“小于”比较
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 }
-
两个复杂日期时间的“大于”比较
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 示例
编辑-
使用数字日期时间作为
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" }
星期几桶聚合示例
编辑以下示例使用 terms 聚合 作为 桶脚本聚合上下文 的一部分,以显示每周每一天的消息数量。
GET /messages/_search?pretty=true { "aggs": { "day-of-week-count": { "terms": { "script": "return doc[\"datetime\"].value.getDayOfWeekEnum();" } } } }
早晚桶聚合示例
编辑以下示例使用 terms 聚合 作为 桶脚本聚合上下文 的一部分,以显示在早上和晚上接收到的消息数量。
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;