899 lines
25 KiB
Plaintext
899 lines
25 KiB
Plaintext
[[painless-datetime]]
|
|
=== Using Datetime in Painless
|
|
|
|
==== Datetime API
|
|
|
|
Datetimes in Painless use the standard Java libraries and are available through
|
|
the Painless <<painless-api-reference-shared, Shared API>>. Most of the classes
|
|
from the following Java packages are available to use in Painless scripts:
|
|
|
|
* <<painless-api-reference-shared-java-time, java.time>>
|
|
* <<painless-api-reference-shared-java-time-chrono, java.time.chrono>>
|
|
* <<painless-api-reference-shared-java-time-format, java.time.format>>
|
|
* <<painless-api-reference-shared-java-time-temporal, java.time.temporal>>
|
|
* <<painless-api-reference-shared-java-time-zone, java.time.zone>>
|
|
|
|
==== Datetime Representation
|
|
|
|
Datetimes in Painless are most commonly represented as a numeric value, a
|
|
string value, or a complex value.
|
|
|
|
numeric:: a datetime representation as a number from a starting offset called
|
|
an epoch; in Painless this is typically a <<primitive-types, long>> as
|
|
milliseconds since an epoch of 1970-01-01 00:00:00 Zulu Time
|
|
string:: a datetime representation as a sequence of characters defined by
|
|
a standard format or a custom format; in Painless this is typically a
|
|
<<string-type, String>> of the standard format
|
|
https://en.wikipedia.org/wiki/ISO_8601[ISO 8601]
|
|
complex:: a datetime representation as a complex type
|
|
(<<reference-types, object>>) that abstracts away internal details of how the
|
|
datetime is stored and often provides utilities for modification and
|
|
comparison; in Painless this is typically a
|
|
<<painless-api-reference-shared-ZonedDateTime, ZonedDateTime>>
|
|
|
|
Switching between different representations of datetimes is often necessary to
|
|
achieve a script's objective(s). A typical pattern in a script is to switch a
|
|
numeric or string datetime to a complex datetime, modify or compare the complex
|
|
datetime, and then switch it back to a numeric or string datetime for storage
|
|
or to return a result.
|
|
|
|
==== Datetime Parsing and Formatting
|
|
|
|
Datetime parsing is a switch from a string datetime to a complex datetime, and
|
|
datetime formatting is a switch from a complex datetime to a string datetime.
|
|
|
|
A <<painless-api-reference-shared-DateTimeFormatter, DateTimeFormatter>> is a
|
|
complex type (<<reference-types, object>>) that defines the allowed sequence
|
|
of characters for a string datetime. Datetime parsing and formatting often
|
|
requires a DateTimeFormatter. For more information about how to use a
|
|
DateTimeFormatter see the
|
|
{java11-javadoc}/java.base/java/time/format/DateTimeFormatter.html[Java documentation].
|
|
|
|
===== Datetime Parsing Examples
|
|
|
|
* parse from milliseconds
|
|
+
|
|
[source,Painless]
|
|
----
|
|
String milliSinceEpochString = "434931330000";
|
|
long milliSinceEpoch = Long.parseLong(milliSinceEpochString);
|
|
Instant instant = Instant.ofEpochMilli(milliSinceEpoch);
|
|
ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of('Z'));
|
|
----
|
|
+
|
|
* parse from ISO 8601
|
|
+
|
|
[source,Painless]
|
|
----
|
|
String datetime = '1983-10-13T22:15:30Z';
|
|
ZonedDateTime zdt = ZonedDateTime.parse(datetime); <1>
|
|
----
|
|
<1> Note the parse method uses ISO 8601 by default.
|
|
+
|
|
* parse from RFC 1123
|
|
+
|
|
[source,Painless]
|
|
----
|
|
String datetime = 'Thu, 13 Oct 1983 22:15:30 GMT';
|
|
ZonedDateTime zdt = ZonedDateTime.parse(datetime,
|
|
DateTimeFormatter.RFC_1123_DATE_TIME); <1>
|
|
----
|
|
<1> Note the use of a built-in DateTimeFormatter.
|
|
+
|
|
* parse from a custom format
|
|
+
|
|
[source,Painless]
|
|
----
|
|
String datetime = 'custom y 1983 m 10 d 13 22:15:30 Z';
|
|
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(
|
|
"'custom' 'y' yyyy 'm' MM 'd' dd HH:mm:ss VV");
|
|
ZonedDateTime zdt = ZonedDateTime.parse(datetime, dtf); <1>
|
|
----
|
|
<1> Note the use of a custom DateTimeFormatter.
|
|
|
|
===== Datetime Formatting Examples
|
|
|
|
* format to ISO 8601
|
|
+
|
|
[source,Painless]
|
|
----
|
|
ZonedDateTime zdt =
|
|
ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z'));
|
|
String datetime = zdt.format(DateTimeFormatter.ISO_INSTANT); <1>
|
|
----
|
|
<1> Note the use of a built-in DateTimeFormatter.
|
|
+
|
|
* format to a custom format
|
|
+
|
|
[source,Painless]
|
|
----
|
|
ZonedDateTime zdt =
|
|
ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z'));
|
|
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(
|
|
"'date:' yyyy/MM/dd 'time:' HH:mm:ss");
|
|
String datetime = zdt.format(dtf); <1>
|
|
----
|
|
<1> Note the use of a custom DateTimeFormatter.
|
|
|
|
==== Datetime Conversion
|
|
|
|
Datetime conversion is a switch from a numeric datetime to a complex datetime
|
|
and vice versa.
|
|
|
|
===== Datetime Conversion Examples
|
|
|
|
* convert from milliseconds
|
|
+
|
|
[source,Painless]
|
|
----
|
|
long milliSinceEpoch = 434931330000L;
|
|
Instant instant = Instant.ofEpochMilli(milliSinceEpoch);
|
|
ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of('Z'));
|
|
----
|
|
+
|
|
* convert to milliseconds
|
|
+
|
|
[source,Painless]
|
|
-----
|
|
ZonedDateTime zdt =
|
|
ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z'));
|
|
long milliSinceEpoch = zdt.toInstant().toEpochMilli();
|
|
-----
|
|
|
|
==== Datetime Pieces
|
|
|
|
Datetime representations often contain the data to extract individual datetime
|
|
pieces such as year, hour, timezone, etc. Use individual pieces of a datetime
|
|
to create a complex datetime, and use a complex datetime to extract individual
|
|
pieces.
|
|
|
|
===== Datetime Pieces Examples
|
|
|
|
* create a complex datetime from pieces
|
|
+
|
|
[source,Painless]
|
|
----
|
|
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'));
|
|
----
|
|
+
|
|
* extract pieces from a complex datetime
|
|
+
|
|
[source,Painless]
|
|
----
|
|
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();
|
|
----
|
|
|
|
==== Datetime Modification
|
|
|
|
Use either a numeric datetime or a complex datetime to do modification such as
|
|
adding several seconds to a datetime or subtracting several days from a
|
|
datetime. Use standard <<painless-operators-numeric, numeric operators>> to
|
|
modify a numeric datetime. Use
|
|
<<painless-api-reference-shared-ZonedDateTime, methods>> (or fields) to modify
|
|
a complex datetime. Note many complex datetimes are immutable so upon
|
|
modification a new complex datetime is created that requires
|
|
<<variable-assignment, assignment>> or immediate use.
|
|
|
|
===== Datetime Modification Examples
|
|
|
|
* Subtract three seconds from a numeric datetime in milliseconds
|
|
+
|
|
[source,Painless]
|
|
----
|
|
long milliSinceEpoch = 434931330000L;
|
|
milliSinceEpoch = milliSinceEpoch - 1000L*3L;
|
|
----
|
|
+
|
|
* Add three days to a complex datetime
|
|
+
|
|
[source,Painless]
|
|
----
|
|
ZonedDateTime zdt =
|
|
ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z'));
|
|
ZonedDateTime updatedZdt = zdt.plusDays(3);
|
|
----
|
|
+
|
|
* Subtract 125 minutes from a complex datetime
|
|
+
|
|
[source,Painless]
|
|
----
|
|
ZonedDateTime zdt =
|
|
ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z'));
|
|
ZonedDateTime updatedZdt = zdt.minusMinutes(125);
|
|
----
|
|
+
|
|
* Set the year on a complex datetime
|
|
+
|
|
[source,Painless]
|
|
----
|
|
ZonedDateTime zdt =
|
|
ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z'));
|
|
ZonedDateTime updatedZdt = zdt.withYear(1976);
|
|
----
|
|
|
|
==== Datetime Difference (Elapsed Time)
|
|
|
|
Use either two numeric datetimes or two complex datetimes to calculate the
|
|
difference (elapsed time) between two different datetimes. Use
|
|
<<subtraction-operator, subtraction>> to calculate the difference between
|
|
between two numeric datetimes of the same time unit such as milliseconds. For
|
|
complex datetimes there is often a method or another complex type
|
|
(<<reference-types, object>>) available to calculate the difference. Use
|
|
<<painless-api-reference-shared-ChronoUnit, ChronoUnit>>
|
|
to calculate the difference between two complex datetimes if supported.
|
|
|
|
===== Datetime Difference Examples
|
|
|
|
* Difference in milliseconds between two numeric datetimes
|
|
+
|
|
[source,Painless]
|
|
----
|
|
long startTimestamp = 434931327000L;
|
|
long endTimestamp = 434931330000L;
|
|
long differenceInMillis = endTimestamp - startTimestamp;
|
|
----
|
|
+
|
|
* Difference in milliseconds between two complex datetimes
|
|
+
|
|
[source,Painless]
|
|
----
|
|
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);
|
|
----
|
|
+
|
|
* Difference in days between two complex datetimes
|
|
+
|
|
[source,Painless]
|
|
----
|
|
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);
|
|
----
|
|
|
|
==== Datetime Comparison
|
|
|
|
Use either two numeric datetimes or two complex datetimes to do a datetime
|
|
comparison. Use standard <<painless-operators-boolean, comparison operators>>
|
|
to compare two numeric datetimes of the same time unit such as milliseconds.
|
|
For complex datetimes there is often a method or another complex type
|
|
(<<reference-types, object>>) available to do the comparison.
|
|
|
|
===== Datetime Comparison Examples
|
|
|
|
* Greater than comparison of two numeric datetimes in milliseconds
|
|
+
|
|
[source,Painless]
|
|
----
|
|
long timestamp1 = 434931327000L;
|
|
long timestamp2 = 434931330000L;
|
|
|
|
if (timestamp1 > timestamp2) {
|
|
// handle condition
|
|
}
|
|
----
|
|
+
|
|
* Equality comparison of two complex datetimes
|
|
+
|
|
[source,Painless]
|
|
----
|
|
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 comparison of two complex datetimes
|
|
+
|
|
[source,Painless]
|
|
----
|
|
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 comparison of two complex datetimes
|
|
+
|
|
[source,Painless]
|
|
----
|
|
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
|
|
}
|
|
----
|
|
|
|
==== Datetime Zone
|
|
|
|
Both string datetimes and complex datetimes have a timezone with a default of
|
|
`UTC`. Numeric datetimes do not have enough explicit information to
|
|
have a timezone, so `UTC` is always assumed. Use
|
|
<<painless-api-reference-shared-ZonedDateTime, methods>> (or fields) in
|
|
conjunction with a <<painless-api-reference-shared-ZoneId, ZoneId>> to change
|
|
the timezone for a complex datetime. Parse a string datetime into a complex
|
|
datetime to change the timezone, and then format the complex datetime back into
|
|
a desired string datetime. Note many complex datetimes are immutable so upon
|
|
modification a new complex datetime is created that requires
|
|
<<variable-assignment, assignment>> or immediate use.
|
|
|
|
===== Datetime Zone Examples
|
|
|
|
* Modify the timezone for a complex datetime
|
|
+
|
|
[source,Painless]
|
|
----
|
|
ZonedDateTime utc =
|
|
ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z'));
|
|
ZonedDateTime pst = utc.withZoneSameInstant(ZoneId.of('America/Los_Angeles'));
|
|
----
|
|
+
|
|
* Modify the timezone for a string datetime
|
|
+
|
|
[source,Painless]
|
|
----
|
|
String gmtString = 'Thu, 13 Oct 1983 22:15:30 GMT';
|
|
ZonedDateTime gmtZdt = ZonedDateTime.parse(gmtString,
|
|
DateTimeFormatter.RFC_1123_DATE_TIME); <1>
|
|
ZonedDateTime pstZdt =
|
|
gmtZdt.withZoneSameInstant(ZoneId.of('America/Los_Angeles'));
|
|
String pstString = pstZdt.format(DateTimeFormatter.RFC_1123_DATE_TIME);
|
|
----
|
|
<1> Note the use of a built-in DateTimeFormatter.
|
|
|
|
==== Datetime Input
|
|
|
|
There are several common ways datetimes are used as input for a script
|
|
determined by the <<painless-contexts, Painless context>>. Typically, datetime
|
|
input will be accessed from parameters specified by the user, from an original
|
|
source document, or from an indexed document.
|
|
|
|
===== Datetime Input From User Parameters
|
|
|
|
Use the {ref}/modules-scripting-using.html#_script_parameters[params section]
|
|
during script specification to pass in a numeric datetime or string datetime as
|
|
a script input. Access to user-defined parameters within a script is dependent
|
|
on the Painless context, though, the parameters are most commonly accessible
|
|
through an input called `params`.
|
|
|
|
*Examples*
|
|
|
|
* Parse a numeric datetime from user parameters to a complex datetime
|
|
+
|
|
** Input:
|
|
+
|
|
[source,JSON]
|
|
----
|
|
...
|
|
"script": {
|
|
...
|
|
"params": {
|
|
"input_datetime": 434931327000
|
|
}
|
|
}
|
|
...
|
|
----
|
|
+
|
|
** Script:
|
|
+
|
|
[source,Painless]
|
|
----
|
|
long inputDateTime = params['input_datetime'];
|
|
Instant instant = Instant.ofEpochMilli(inputDateTime);
|
|
ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of('Z'));
|
|
----
|
|
+
|
|
* Parse a string datetime from user parameters to a complex datetime
|
|
+
|
|
** Input:
|
|
+
|
|
[source,JSON]
|
|
----
|
|
...
|
|
"script": {
|
|
...
|
|
"params": {
|
|
"input_datetime": "custom y 1983 m 10 d 13 22:15:30 Z"
|
|
}
|
|
}
|
|
...
|
|
----
|
|
+
|
|
** Script:
|
|
+
|
|
[source,Painless]
|
|
----
|
|
String datetime = params['input_datetime'];
|
|
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(
|
|
"'custom' 'y' yyyy 'm' MM 'd' dd HH:mm:ss VV");
|
|
ZonedDateTime zdt = ZonedDateTime.parse(datetime, dtf); <1>
|
|
----
|
|
<1> Note the use of a custom DateTimeFormatter.
|
|
|
|
===== Datetime Input From a Source Document
|
|
|
|
Use an original {ref}/mapping-source-field.html[source] document as a script
|
|
input to access a numeric datetime or string datetime for a specific field
|
|
within that document. Access to an original source document within a script is
|
|
dependent on the Painless context and is not always available. An original
|
|
source document is most commonly accessible through an input called
|
|
`ctx['_source']` or `params['_source']`.
|
|
|
|
*Examples*
|
|
|
|
* Parse a numeric datetime from a sourced document to a complex datetime
|
|
+
|
|
** Input:
|
|
+
|
|
[source,JSON]
|
|
----
|
|
{
|
|
...
|
|
"input_datetime": 434931327000
|
|
...
|
|
}
|
|
----
|
|
+
|
|
** Script:
|
|
+
|
|
[source,Painless]
|
|
----
|
|
long inputDateTime = ctx['_source']['input_datetime']; <1>
|
|
Instant instant = Instant.ofEpochMilli(inputDateTime);
|
|
ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of('Z'));
|
|
----
|
|
<1> Note access to `_source` is dependent on the Painless context.
|
|
+
|
|
* Parse a string datetime from a sourced document to a complex datetime
|
|
+
|
|
** Input:
|
|
+
|
|
[source,JSON]
|
|
----
|
|
{
|
|
...
|
|
"input_datetime": "1983-10-13T22:15:30Z"
|
|
...
|
|
}
|
|
----
|
|
+
|
|
** Script:
|
|
+
|
|
[source,Painless]
|
|
----
|
|
String datetime = params['_source']['input_datetime']; <1>
|
|
ZonedDateTime zdt = ZonedDateTime.parse(datetime); <2>
|
|
----
|
|
<1> Note access to `_source` is dependent on the Painless context.
|
|
<2> Note the parse method uses ISO 8601 by default.
|
|
|
|
===== Datetime Input From an Indexed Document
|
|
|
|
Use an indexed document as a script input to access a complex datetime for a
|
|
specific field within that document where the field is mapped as a
|
|
{ref}/date.html[standard date] or a {ref}/date_nanos.html[nanosecond date].
|
|
Numeric datetime fields mapped as {ref}/number.html[numeric] and string
|
|
datetime fields mapped as {ref}/keyword.html[keyword] are accessible through an
|
|
indexed document as well. Access to an indexed document within a script is
|
|
dependent on the Painless context and is not always available. An indexed
|
|
document is most commonly accessible through an input called `doc`.
|
|
|
|
*Examples*
|
|
|
|
* Format a complex datetime from an indexed document to a string datetime
|
|
+
|
|
** Assumptions:
|
|
+
|
|
*** The field `input_datetime` exists in all indexes as part of the query
|
|
*** All indexed documents contain the field `input_datetime`
|
|
+
|
|
** Mappings:
|
|
+
|
|
[source,JSON]
|
|
----
|
|
{
|
|
"mappings": {
|
|
...
|
|
"properties": {
|
|
...
|
|
"input_datetime": {
|
|
"type": "date"
|
|
}
|
|
...
|
|
}
|
|
...
|
|
}
|
|
}
|
|
----
|
|
+
|
|
** Script:
|
|
+
|
|
[source,Painless]
|
|
----
|
|
ZonedDateTime input = doc['input_datetime'].value;
|
|
String output = input.format(DateTimeFormatter.ISO_INSTANT); <1>
|
|
----
|
|
<1> Note the use of a built-in DateTimeFormatter.
|
|
+
|
|
* Find the difference between two complex datetimes from an indexed document
|
|
+
|
|
** Assumptions:
|
|
+
|
|
*** The fields `start` and `end` may *not* exist in all indexes as part of the
|
|
query
|
|
*** The fields `start` and `end` may *not* have values in all indexed documents
|
|
+
|
|
** Mappings:
|
|
+
|
|
[source,JSON]
|
|
----
|
|
{
|
|
"mappings": {
|
|
...
|
|
"properties": {
|
|
...
|
|
"start": {
|
|
"type": "date"
|
|
},
|
|
"end": {
|
|
"type": "date"
|
|
}
|
|
...
|
|
}
|
|
...
|
|
}
|
|
}
|
|
----
|
|
+
|
|
** Script:
|
|
+
|
|
[source,Painless]
|
|
----
|
|
if (doc.containsKey('start') && doc.containsKey('end')) { <1>
|
|
|
|
if (doc['start'].size() > 0 && doc['end'].size() > 0) { <2>
|
|
|
|
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
|
|
}
|
|
----
|
|
<1> When a query's results span multiple indexes, some indexes may not
|
|
contain a specific field. Use the `containsKey` method call on the `doc` input
|
|
to ensure a field exists as part of the index for the current document.
|
|
<2> Some fields within a document may have no values. Use the `size` method
|
|
call on a field within the `doc` input to ensure that field has at least one
|
|
value for the current document.
|
|
|
|
==== Datetime Now
|
|
|
|
Under most Painless contexts the current datetime, `now`, is not supported.
|
|
There are two primary reasons for this. The first is scripts are often run once
|
|
per document, so each time the script is run a different `now` is returned. The
|
|
second is scripts are often run in a distributed fashion without a way to
|
|
appropriately synchronize `now`. Instead, pass in a user-defined parameter with
|
|
either a string datetime or numeric datetime for `now`. A numeric datetime is
|
|
preferred as there is no need to parse it for comparison.
|
|
|
|
===== Datetime Now Examples
|
|
|
|
* Use a numeric datetime as `now`
|
|
+
|
|
** Assumptions:
|
|
+
|
|
*** The field `input_datetime` exists in all indexes as part of the query
|
|
*** All indexed documents contain the field `input_datetime`
|
|
+
|
|
** Mappings:
|
|
+
|
|
[source,JSON]
|
|
----
|
|
{
|
|
"mappings": {
|
|
...
|
|
"properties": {
|
|
...
|
|
"input_datetime": {
|
|
"type": "date"
|
|
}
|
|
...
|
|
}
|
|
...
|
|
}
|
|
}
|
|
----
|
|
+
|
|
** Input:
|
|
+
|
|
[source,JSON]
|
|
----
|
|
...
|
|
"script": {
|
|
...
|
|
"params": {
|
|
"now": <generated numeric datetime in milliseconds since epoch>
|
|
}
|
|
}
|
|
...
|
|
----
|
|
+
|
|
** Script:
|
|
+
|
|
[source,Painless]
|
|
----
|
|
long now = params['now'];
|
|
ZonedDateTime inputDateTime = doc['input_datetime'];
|
|
long millisDateTime = zdt.toInstant().toEpochMilli();
|
|
long elapsedTime = now - millisDateTime;
|
|
----
|
|
+
|
|
* Use a string datetime as `now`
|
|
+
|
|
** Assumptions:
|
|
+
|
|
*** The field `input_datetime` exists in all indexes as part of the query
|
|
*** All indexed documents contain the field `input_datetime`
|
|
+
|
|
** Mappings:
|
|
+
|
|
[source,JSON]
|
|
----
|
|
{
|
|
"mappings": {
|
|
...
|
|
"properties": {
|
|
...
|
|
"input_datetime": {
|
|
"type": "date"
|
|
}
|
|
...
|
|
}
|
|
...
|
|
}
|
|
}
|
|
----
|
|
+
|
|
** Input:
|
|
+
|
|
[source,JSON]
|
|
----
|
|
...
|
|
"script": {
|
|
...
|
|
"params": {
|
|
"now": "<generated string datetime in ISO-8601>"
|
|
}
|
|
}
|
|
...
|
|
----
|
|
+
|
|
** Script:
|
|
+
|
|
[source,Painless]
|
|
----
|
|
String nowString = params['now'];
|
|
ZonedDateTime nowZdt = ZonedDateTime.parse(datetime); <1>
|
|
long now = ZonedDateTime.toInstant().toEpochMilli();
|
|
ZonedDateTime inputDateTime = doc['input_datetime'];
|
|
long millisDateTime = zdt.toInstant().toEpochMilli();
|
|
long elapsedTime = now - millisDateTime;
|
|
----
|
|
<1> Note this parses the same string datetime every time the script runs. Use a
|
|
numeric datetime to avoid a significant performance hit.
|
|
|
|
==== Datetime Examples in Contexts
|
|
|
|
===== Load the Example Data
|
|
|
|
Run the following curl commands to load the data necessary for the context
|
|
examples into an Elasticsearch cluster:
|
|
|
|
. Create {ref}/mapping.html[mappings] for the sample data.
|
|
+
|
|
[source,console]
|
|
----
|
|
PUT /messages
|
|
{
|
|
"mappings": {
|
|
"properties": {
|
|
"priority": {
|
|
"type": "integer"
|
|
},
|
|
"datetime": {
|
|
"type": "date"
|
|
},
|
|
"message": {
|
|
"type": "text"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
----
|
|
+
|
|
. Load the sample data.
|
|
+
|
|
[source,console]
|
|
----
|
|
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" }
|
|
----
|
|
// TEST[continued]
|
|
|
|
===== Day-of-the-Week Bucket Aggregation Example
|
|
|
|
The following example uses a
|
|
{ref}/search-aggregations-bucket-terms-aggregation.html#search-aggregations-bucket-terms-aggregation-script[terms aggregation]
|
|
as part of the
|
|
<<painless-bucket-script-agg-context, bucket script aggregation context>> to
|
|
display the number of messages from each day-of-the-week.
|
|
|
|
[source,console]
|
|
----
|
|
GET /messages/_search?pretty=true
|
|
{
|
|
"aggs": {
|
|
"day-of-week-count": {
|
|
"terms": {
|
|
"script": "return doc[\"datetime\"].value.getDayOfWeekEnum();"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
----
|
|
// TEST[continued]
|
|
|
|
===== Morning/Evening Bucket Aggregation Example
|
|
|
|
The following example uses a
|
|
{ref}/search-aggregations-bucket-terms-aggregation.html#search-aggregations-bucket-terms-aggregation-script[terms aggregation]
|
|
as part of the
|
|
<<painless-bucket-script-agg-context, bucket script aggregation context>> to
|
|
display the number of messages received in the morning versus the evening.
|
|
|
|
[source,console]
|
|
----
|
|
GET /messages/_search?pretty=true
|
|
{
|
|
"aggs": {
|
|
"am-pm-count": {
|
|
"terms": {
|
|
"script": "return doc[\"datetime\"].value.getHour() < 12 ? \"AM\" : \"PM\";"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
----
|
|
// TEST[continued]
|
|
|
|
===== Age of a Message Script Field Example
|
|
|
|
The following example uses a
|
|
{ref}/search-request-script-fields.html[script field] as part of the
|
|
<<painless-field-context, field context>> to display the elapsed time between
|
|
"now" and when a message was received.
|
|
|
|
[source,console]
|
|
----
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
----
|
|
// TEST[continued]
|
|
|
|
The following shows the script broken into multiple lines:
|
|
|
|
[source,Painless]
|
|
----
|
|
ZonedDateTime now = ZonedDateTime.ofInstant(
|
|
Instant.ofEpochMilli(params['now']), ZoneId.of('Z')); <1>
|
|
ZonedDateTime mdt = doc['datetime'].value; <2>
|
|
|
|
String age;
|
|
|
|
long years = mdt.until(now, ChronoUnit.YEARS); <3>
|
|
age = years + 'Y '; <4>
|
|
mdt = mdt.plusYears(years); <5>
|
|
|
|
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; <6>
|
|
----
|
|
<1> Parse the datetime "now" as input from the user-defined params.
|
|
<2> Store the datetime the message was received as a `ZonedDateTime`.
|
|
<3> Find the difference in years between "now" and the datetime the message was
|
|
received.
|
|
<4> Add the difference in years later returned in the format
|
|
`Y <years> ...` for the age of a message.
|
|
<5> Add the years so only the remainder of the months, days, etc. remain as the
|
|
difference between "now" and the datetime the message was received. Repeat this
|
|
pattern until the desired granularity is reached (seconds in this example).
|
|
<6> Return the age of the message in the format
|
|
`Y <years> M <months> D <days> h <hours> m <minutes> s <seconds>`.
|