[[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>>

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 comparision 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 comparision 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 comparision 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 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]
----
def 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_datetime` and `end_datetime` may *not* exist in all
indexes as part of the query
*** The fields `start_datetime` and `end_datetime` may *not* have values in all
indexed documents
+
** Mappings:
+
[source,JSON]
----
{
    "mappings": {
        ...
        "properties": {
            ...
            "start_datetime": {
                "type": "date"
            },
            "end_datetime": {
                "type": "date"
            }
            ...
        }
        ...
    }
}
----
+
** Script:
+
[source,Painless]
----
if (doc.containsKey('start_datetime') && doc.containsKey('end_datetime')) { <1>

    if (doc['start_datetime'].size() > 0 && doc['end_datetime'].size() > 0) { <2>

        def startDatetime = doc['start_datetime'].value;
        def endDatetime = doc['end_datetime'].value;
        long differenceInMillis =
            ChronoUnit.MILLIS.between(startDateTime, endDateTime);

        // 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 field's 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.
