diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/day_name.md b/docs/reference/query-languages/esql/_snippets/functions/description/day_name.md
new file mode 100644
index 0000000000000..3153a68b54da6
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/description/day_name.md
@@ -0,0 +1,6 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Description**
+
+Returns the name of the weekday for date based on the configured Locale.
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/day_name.md b/docs/reference/query-languages/esql/_snippets/functions/examples/day_name.md
new file mode 100644
index 0000000000000..5c246f632f223
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/examples/day_name.md
@@ -0,0 +1,10 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Example**
+
+```esql
+ROW dt = to_datetime("1953-09-02T00:00:00.000Z")
+| EVAL weekday = DAY_NAME(dt);
+```
+
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/layout/day_name.md b/docs/reference/query-languages/esql/_snippets/functions/layout/day_name.md
new file mode 100644
index 0000000000000..4cd3f1ec90c30
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/layout/day_name.md
@@ -0,0 +1,26 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+## `DAY_NAME` [esql-day_name]
+```{applies_to}
+stack: ga 9.2.0
+```
+
+**Syntax**
+
+:::{image} ../../../images/functions/day_name.svg
+:alt: Embedded
+:class: text-center
+:::
+
+
+:::{include} ../parameters/day_name.md
+:::
+
+:::{include} ../description/day_name.md
+:::
+
+:::{include} ../types/day_name.md
+:::
+
+:::{include} ../examples/day_name.md
+:::
diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/day_name.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/day_name.md
new file mode 100644
index 0000000000000..43182ad4b2d26
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/day_name.md
@@ -0,0 +1,7 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Parameters**
+
+`date`
+: Date expression. If `null`, the function returns `null`.
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/day_name.md b/docs/reference/query-languages/esql/_snippets/functions/types/day_name.md
new file mode 100644
index 0000000000000..097955acd2cfa
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/types/day_name.md
@@ -0,0 +1,9 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Supported types**
+
+| date | result |
+| --- | --- |
+| date | keyword |
+| date_nanos | keyword |
+
diff --git a/docs/reference/query-languages/esql/_snippets/lists/date-time-functions.md b/docs/reference/query-languages/esql/_snippets/lists/date-time-functions.md
index a9b6b090e0472..b59db4d50bf58 100644
--- a/docs/reference/query-languages/esql/_snippets/lists/date-time-functions.md
+++ b/docs/reference/query-languages/esql/_snippets/lists/date-time-functions.md
@@ -3,4 +3,5 @@
* [`DATE_FORMAT`](../../functions-operators/date-time-functions.md#esql-date_format)
* [`DATE_PARSE`](../../functions-operators/date-time-functions.md#esql-date_parse)
* [`DATE_TRUNC`](../../functions-operators/date-time-functions.md#esql-date_trunc)
+* [`DAY_NAME`](../../functions-operators/date-time-functions.md#esql-day_name)
* [`NOW`](../../functions-operators/date-time-functions.md#esql-now)
diff --git a/docs/reference/query-languages/esql/functions-operators/date-time-functions.md b/docs/reference/query-languages/esql/functions-operators/date-time-functions.md
index f11a3a76e0f75..4823468b0bb97 100644
--- a/docs/reference/query-languages/esql/functions-operators/date-time-functions.md
+++ b/docs/reference/query-languages/esql/functions-operators/date-time-functions.md
@@ -28,6 +28,9 @@ mapped_pages:
:::{include} ../_snippets/functions/layout/date_trunc.md
:::
+:::{include} ../_snippets/functions/layout/day_name.md
+:::
+
:::{include} ../_snippets/functions/layout/now.md
:::
diff --git a/docs/reference/query-languages/esql/images/functions/day_name.svg b/docs/reference/query-languages/esql/images/functions/day_name.svg
new file mode 100644
index 0000000000000..6bf8cdf0e4951
--- /dev/null
+++ b/docs/reference/query-languages/esql/images/functions/day_name.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/day_name.json b/docs/reference/query-languages/esql/kibana/definition/functions/day_name.json
new file mode 100644
index 0000000000000..3f2933b5231f6
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/definition/functions/day_name.json
@@ -0,0 +1,37 @@
+{
+ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.",
+ "type" : "scalar",
+ "name" : "day_name",
+ "description" : "Returns the name of the weekday for date based on the configured Locale.",
+ "signatures" : [
+ {
+ "params" : [
+ {
+ "name" : "date",
+ "type" : "date",
+ "optional" : false,
+ "description" : "Date expression. If `null`, the function returns `null`."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "keyword"
+ },
+ {
+ "params" : [
+ {
+ "name" : "date",
+ "type" : "date_nanos",
+ "optional" : false,
+ "description" : "Date expression. If `null`, the function returns `null`."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "keyword"
+ }
+ ],
+ "examples" : [
+ "ROW dt = to_datetime(\"1953-09-02T00:00:00.000Z\")\n| EVAL weekday = DAY_NAME(dt);"
+ ],
+ "preview" : false,
+ "snapshot_only" : false
+}
diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/day_name.md b/docs/reference/query-languages/esql/kibana/docs/functions/day_name.md
new file mode 100644
index 0000000000000..ff8371538ba55
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/docs/functions/day_name.md
@@ -0,0 +1,9 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+### DAY NAME
+Returns the name of the weekday for date based on the configured Locale.
+
+```esql
+ROW dt = to_datetime("1953-09-02T00:00:00.000Z")
+| EVAL weekday = DAY_NAME(dt);
+```
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/date.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/date.csv-spec
index 788a5f9877dea..2b97b2985e7b9 100644
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/date.csv-spec
+++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/date.csv-spec
@@ -1836,3 +1836,97 @@ y:date | c:long
1987-07-01T00:00:00.000Z | 1
1987-08-01T00:00:00.000Z | 2
;
+
+dayNameRowTest
+required_capability:fn_day_name
+// tag::docsDayName[]
+ROW dt = to_datetime("1953-09-02T00:00:00.000Z")
+| EVAL weekday = DAY_NAME(dt);
+// end::docsDayName[]
+
+dt:date | weekday:keyword
+1953-09-02T00:00:00.000Z | Wednesday
+;
+
+dayNameSimple
+required_capability:fn_day_name
+from employees
+| sort emp_no
+| keep emp_no, hire_date
+| eval day = day_name(hire_date)
+| limit 1;
+
+emp_no:integer | hire_date:date | day:keyword
+10001 | 1986-06-26T00:00:00.000Z | Thursday
+;
+
+dayNameTruncate
+required_capability:fn_day_name
+FROM employees
+| EVAL a = hire_date
+| RENAME hire_date as b
+| WHERE a > "1987-01-01" and a < "1988-01-01"
+| EVAL y = date_trunc(1 month, b)
+| STATS c = count(emp_no) by y
+| EVAL week_day = day_name(y)
+| SORT y
+| KEEP y, c, week_day
+| LIMIT 5;
+
+y:date | c:long | week_day:keyword
+1987-03-01T00:00:00.000Z | 5 | Sunday
+1987-04-01T00:00:00.000Z | 3 | Wednesday
+1987-05-01T00:00:00.000Z | 1 | Friday
+1987-07-01T00:00:00.000Z | 1 | Wednesday
+1987-08-01T00:00:00.000Z | 2 | Saturday
+;
+
+dayNameNestedCall
+required_capability:fn_day_name
+from employees
+| sort emp_no
+| eval first_day_of_month = day_name(date_trunc(1 month, hire_date))
+| keep emp_no, hire_date, first_day_of_month
+| limit 10;
+
+emp_no:integer | hire_date:datetime | first_day_of_month:keyword
+10001 | 1986-06-26T00:00:00.000Z | Sunday
+10002 | 1985-11-21T00:00:00.000Z | Friday
+10003 | 1986-08-28T00:00:00.000Z | Friday
+10004 | 1986-12-01T00:00:00.000Z | Monday
+10005 | 1989-09-12T00:00:00.000Z | Friday
+10006 | 1989-06-02T00:00:00.000Z | Thursday
+10007 | 1989-02-10T00:00:00.000Z | Wednesday
+10008 | 1994-09-15T00:00:00.000Z | Thursday
+10009 | 1985-02-18T00:00:00.000Z | Friday
+10010 | 1989-08-24T00:00:00.000Z | Tuesday
+;
+
+dayNameNestedCall
+required_capability:fn_day_name
+from employees
+| sort emp_no desc
+| eval first_day_of_month = to_upper(day_name(date_trunc(1 month, hire_date)))
+| keep emp_no, hire_date, first_day_of_month
+| limit 10;
+
+emp_no:integer | hire_date:datetime | first_day_of_month:keyword
+10100 | 1987-09-21T00:00:00.000Z | TUESDAY
+10099 | 1988-10-18T00:00:00.000Z | SATURDAY
+10098 | 1985-05-13T00:00:00.000Z | WEDNESDAY
+10097 | 1990-09-15T00:00:00.000Z | SATURDAY
+10096 | 1990-01-14T00:00:00.000Z | MONDAY
+10095 | 1986-07-15T00:00:00.000Z | TUESDAY
+10094 | 1987-04-18T00:00:00.000Z | WEDNESDAY
+10093 | 1996-11-05T00:00:00.000Z | FRIDAY
+10092 | 1989-09-22T00:00:00.000Z | FRIDAY
+10091 | 1992-11-18T00:00:00.000Z | SUNDAY
+;
+
+dayNameNull
+required_capability:fn_day_name
+from employees | where emp_no == 10040 | eval x = day_name(birth_date) | keep emp_no, birth_date, hire_date, x;
+
+emp_no:integer | birth_date:date | hire_date:date | x:keyword
+10040 | null | 1993-02-14T00:00:00.000Z | null
+;
diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameMillisEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameMillisEvaluator.java
new file mode 100644
index 0000000000000..9edbe5d1146df
--- /dev/null
+++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameMillisEvaluator.java
@@ -0,0 +1,139 @@
+// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+// or more contributor license agreements. Licensed under the Elastic License
+// 2.0; you may not use this file except in compliance with the Elastic License
+// 2.0.
+package org.elasticsearch.xpack.esql.expression.function.scalar.date;
+
+import java.lang.IllegalArgumentException;
+import java.lang.Override;
+import java.lang.String;
+import java.time.ZoneId;
+import java.util.Locale;
+import org.elasticsearch.compute.data.Block;
+import org.elasticsearch.compute.data.BytesRefBlock;
+import org.elasticsearch.compute.data.BytesRefVector;
+import org.elasticsearch.compute.data.LongBlock;
+import org.elasticsearch.compute.data.LongVector;
+import org.elasticsearch.compute.data.Page;
+import org.elasticsearch.compute.operator.DriverContext;
+import org.elasticsearch.compute.operator.EvalOperator;
+import org.elasticsearch.compute.operator.Warnings;
+import org.elasticsearch.core.Releasables;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+
+/**
+ * {@link EvalOperator.ExpressionEvaluator} implementation for {@link DayName}.
+ * This class is generated. Edit {@code EvaluatorImplementer} instead.
+ */
+public final class DayNameMillisEvaluator implements EvalOperator.ExpressionEvaluator {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator val;
+
+ private final ZoneId zoneId;
+
+ private final Locale locale;
+
+ private final DriverContext driverContext;
+
+ private Warnings warnings;
+
+ public DayNameMillisEvaluator(Source source, EvalOperator.ExpressionEvaluator val, ZoneId zoneId,
+ Locale locale, DriverContext driverContext) {
+ this.source = source;
+ this.val = val;
+ this.zoneId = zoneId;
+ this.locale = locale;
+ this.driverContext = driverContext;
+ }
+
+ @Override
+ public Block eval(Page page) {
+ try (LongBlock valBlock = (LongBlock) val.eval(page)) {
+ LongVector valVector = valBlock.asVector();
+ if (valVector == null) {
+ return eval(page.getPositionCount(), valBlock);
+ }
+ return eval(page.getPositionCount(), valVector).asBlock();
+ }
+ }
+
+ public BytesRefBlock eval(int positionCount, LongBlock valBlock) {
+ try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) {
+ position: for (int p = 0; p < positionCount; p++) {
+ if (valBlock.isNull(p)) {
+ result.appendNull();
+ continue position;
+ }
+ if (valBlock.getValueCount(p) != 1) {
+ if (valBlock.getValueCount(p) > 1) {
+ warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value"));
+ }
+ result.appendNull();
+ continue position;
+ }
+ result.appendBytesRef(DayName.processMillis(valBlock.getLong(valBlock.getFirstValueIndex(p)), this.zoneId, this.locale));
+ }
+ return result.build();
+ }
+ }
+
+ public BytesRefVector eval(int positionCount, LongVector valVector) {
+ try(BytesRefVector.Builder result = driverContext.blockFactory().newBytesRefVectorBuilder(positionCount)) {
+ position: for (int p = 0; p < positionCount; p++) {
+ result.appendBytesRef(DayName.processMillis(valVector.getLong(p), this.zoneId, this.locale));
+ }
+ return result.build();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "DayNameMillisEvaluator[" + "val=" + val + ", zoneId=" + zoneId + ", locale=" + locale + "]";
+ }
+
+ @Override
+ public void close() {
+ Releasables.closeExpectNoException(val);
+ }
+
+ private Warnings warnings() {
+ if (warnings == null) {
+ this.warnings = Warnings.createWarnings(
+ driverContext.warningsMode(),
+ source.source().getLineNumber(),
+ source.source().getColumnNumber(),
+ source.text()
+ );
+ }
+ return warnings;
+ }
+
+ static class Factory implements EvalOperator.ExpressionEvaluator.Factory {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator.Factory val;
+
+ private final ZoneId zoneId;
+
+ private final Locale locale;
+
+ public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory val, ZoneId zoneId,
+ Locale locale) {
+ this.source = source;
+ this.val = val;
+ this.zoneId = zoneId;
+ this.locale = locale;
+ }
+
+ @Override
+ public DayNameMillisEvaluator get(DriverContext context) {
+ return new DayNameMillisEvaluator(source, val.get(context), zoneId, locale, context);
+ }
+
+ @Override
+ public String toString() {
+ return "DayNameMillisEvaluator[" + "val=" + val + ", zoneId=" + zoneId + ", locale=" + locale + "]";
+ }
+ }
+}
diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameNanosEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameNanosEvaluator.java
new file mode 100644
index 0000000000000..9f6b955c7787b
--- /dev/null
+++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameNanosEvaluator.java
@@ -0,0 +1,139 @@
+// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+// or more contributor license agreements. Licensed under the Elastic License
+// 2.0; you may not use this file except in compliance with the Elastic License
+// 2.0.
+package org.elasticsearch.xpack.esql.expression.function.scalar.date;
+
+import java.lang.IllegalArgumentException;
+import java.lang.Override;
+import java.lang.String;
+import java.time.ZoneId;
+import java.util.Locale;
+import org.elasticsearch.compute.data.Block;
+import org.elasticsearch.compute.data.BytesRefBlock;
+import org.elasticsearch.compute.data.BytesRefVector;
+import org.elasticsearch.compute.data.LongBlock;
+import org.elasticsearch.compute.data.LongVector;
+import org.elasticsearch.compute.data.Page;
+import org.elasticsearch.compute.operator.DriverContext;
+import org.elasticsearch.compute.operator.EvalOperator;
+import org.elasticsearch.compute.operator.Warnings;
+import org.elasticsearch.core.Releasables;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+
+/**
+ * {@link EvalOperator.ExpressionEvaluator} implementation for {@link DayName}.
+ * This class is generated. Edit {@code EvaluatorImplementer} instead.
+ */
+public final class DayNameNanosEvaluator implements EvalOperator.ExpressionEvaluator {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator val;
+
+ private final ZoneId zoneId;
+
+ private final Locale locale;
+
+ private final DriverContext driverContext;
+
+ private Warnings warnings;
+
+ public DayNameNanosEvaluator(Source source, EvalOperator.ExpressionEvaluator val, ZoneId zoneId,
+ Locale locale, DriverContext driverContext) {
+ this.source = source;
+ this.val = val;
+ this.zoneId = zoneId;
+ this.locale = locale;
+ this.driverContext = driverContext;
+ }
+
+ @Override
+ public Block eval(Page page) {
+ try (LongBlock valBlock = (LongBlock) val.eval(page)) {
+ LongVector valVector = valBlock.asVector();
+ if (valVector == null) {
+ return eval(page.getPositionCount(), valBlock);
+ }
+ return eval(page.getPositionCount(), valVector).asBlock();
+ }
+ }
+
+ public BytesRefBlock eval(int positionCount, LongBlock valBlock) {
+ try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) {
+ position: for (int p = 0; p < positionCount; p++) {
+ if (valBlock.isNull(p)) {
+ result.appendNull();
+ continue position;
+ }
+ if (valBlock.getValueCount(p) != 1) {
+ if (valBlock.getValueCount(p) > 1) {
+ warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value"));
+ }
+ result.appendNull();
+ continue position;
+ }
+ result.appendBytesRef(DayName.processNanos(valBlock.getLong(valBlock.getFirstValueIndex(p)), this.zoneId, this.locale));
+ }
+ return result.build();
+ }
+ }
+
+ public BytesRefVector eval(int positionCount, LongVector valVector) {
+ try(BytesRefVector.Builder result = driverContext.blockFactory().newBytesRefVectorBuilder(positionCount)) {
+ position: for (int p = 0; p < positionCount; p++) {
+ result.appendBytesRef(DayName.processNanos(valVector.getLong(p), this.zoneId, this.locale));
+ }
+ return result.build();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "DayNameNanosEvaluator[" + "val=" + val + ", zoneId=" + zoneId + ", locale=" + locale + "]";
+ }
+
+ @Override
+ public void close() {
+ Releasables.closeExpectNoException(val);
+ }
+
+ private Warnings warnings() {
+ if (warnings == null) {
+ this.warnings = Warnings.createWarnings(
+ driverContext.warningsMode(),
+ source.source().getLineNumber(),
+ source.source().getColumnNumber(),
+ source.text()
+ );
+ }
+ return warnings;
+ }
+
+ static class Factory implements EvalOperator.ExpressionEvaluator.Factory {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator.Factory val;
+
+ private final ZoneId zoneId;
+
+ private final Locale locale;
+
+ public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory val, ZoneId zoneId,
+ Locale locale) {
+ this.source = source;
+ this.val = val;
+ this.zoneId = zoneId;
+ this.locale = locale;
+ }
+
+ @Override
+ public DayNameNanosEvaluator get(DriverContext context) {
+ return new DayNameNanosEvaluator(source, val.get(context), zoneId, locale, context);
+ }
+
+ @Override
+ public String toString() {
+ return "DayNameNanosEvaluator[" + "val=" + val + ", zoneId=" + zoneId + ", locale=" + locale + "]";
+ }
+ }
+}
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
index 6a2b112b58deb..3b62a43b6d9d8 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
@@ -235,6 +235,11 @@ public enum Cap {
*/
FN_SCALB,
+ /**
+ * Support for function DAY_NAME
+ */
+ FN_DAY_NAME,
+
/**
* Fixes for multiple functions not serializing their source, and emitting warnings with wrong line number and text.
*/
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
index 649503b1443d2..959602cc4e20d 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
@@ -85,6 +85,7 @@
import org.elasticsearch.xpack.esql.expression.function.scalar.date.DateFormat;
import org.elasticsearch.xpack.esql.expression.function.scalar.date.DateParse;
import org.elasticsearch.xpack.esql.expression.function.scalar.date.DateTrunc;
+import org.elasticsearch.xpack.esql.expression.function.scalar.date.DayName;
import org.elasticsearch.xpack.esql.expression.function.scalar.date.Now;
import org.elasticsearch.xpack.esql.expression.function.scalar.ip.CIDRMatch;
import org.elasticsearch.xpack.esql.expression.function.scalar.ip.IpPrefix;
@@ -390,6 +391,7 @@ private static FunctionDefinition[][] functions() {
def(DateFormat.class, DateFormat::new, "date_format"),
def(DateParse.class, DateParse::new, "date_parse"),
def(DateTrunc.class, DateTrunc::new, "date_trunc"),
+ def(DayName.class, DayName::new, "day_name"),
def(Now.class, Now::new, "now") },
// spatial
new FunctionDefinition[] {
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ScalarFunctionWritables.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ScalarFunctionWritables.java
index 7913d5e11a5a5..684e685e2eafb 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ScalarFunctionWritables.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ScalarFunctionWritables.java
@@ -18,6 +18,7 @@
import org.elasticsearch.xpack.esql.expression.function.scalar.date.DateFormat;
import org.elasticsearch.xpack.esql.expression.function.scalar.date.DateParse;
import org.elasticsearch.xpack.esql.expression.function.scalar.date.DateTrunc;
+import org.elasticsearch.xpack.esql.expression.function.scalar.date.DayName;
import org.elasticsearch.xpack.esql.expression.function.scalar.date.Now;
import org.elasticsearch.xpack.esql.expression.function.scalar.ip.CIDRMatch;
import org.elasticsearch.xpack.esql.expression.function.scalar.ip.IpPrefix;
@@ -82,6 +83,7 @@ public static List getNamedWriteables() {
entries.add(DateFormat.ENTRY);
entries.add(DateParse.ENTRY);
entries.add(DateTrunc.ENTRY);
+ entries.add(DayName.ENTRY);
entries.add(IpPrefix.ENTRY);
entries.add(Least.ENTRY);
entries.add(Left.ENTRY);
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayName.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayName.java
new file mode 100644
index 0000000000000..a5872d105180c
--- /dev/null
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayName.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.expression.function.scalar.date;
+
+import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.compute.ann.Evaluator;
+import org.elasticsearch.compute.ann.Fixed;
+import org.elasticsearch.compute.operator.EvalOperator;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.expression.TypeResolutions;
+import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.expression.function.Example;
+import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo;
+import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle;
+import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
+import org.elasticsearch.xpack.esql.expression.function.Param;
+import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlConfigurationFunction;
+import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
+import org.elasticsearch.xpack.esql.session.Configuration;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.TextStyle;
+import java.util.List;
+import java.util.Locale;
+
+import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST;
+
+public class DayName extends EsqlConfigurationFunction {
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "DayName", DayName::new);
+
+ private final Expression field;
+
+ @FunctionInfo(
+ returnType = "keyword",
+ description = "Returns the name of the weekday for date based on the configured Locale.",
+ examples = @Example(file = "date", tag = "docsDayName"),
+ appliesTo = { @FunctionAppliesTo(lifeCycle = FunctionAppliesToLifecycle.GA, version = "9.2.0") }
+ )
+ public DayName(
+ Source source,
+ @Param(
+ name = "date",
+ type = { "date", "date_nanos" },
+ description = "Date expression. If `null`, the function returns `null`."
+ ) Expression date,
+ Configuration configuration
+ ) {
+ super(source, List.of(date), configuration);
+ this.field = date;
+ }
+
+ private DayName(StreamInput in) throws IOException {
+ this(Source.readFrom((PlanStreamInput) in), in.readNamedWriteable(Expression.class), ((PlanStreamInput) in).configuration());
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ source().writeTo(out);
+ out.writeNamedWriteable(field);
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
+ }
+
+ Expression field() {
+ return field;
+ }
+
+ @Override
+ public DataType dataType() {
+ return DataType.KEYWORD;
+ }
+
+ @Override
+ protected TypeResolution resolveType() {
+ if (childrenResolved() == false) {
+ return new TypeResolution("Unresolved children");
+ }
+
+ String operationName = sourceText();
+ TypeResolution resolution = TypeResolutions.isType(field, DataType::isDate, operationName, FIRST, "datetime or date_nanos");
+ if (resolution.unresolved()) {
+ return resolution;
+ }
+
+ return TypeResolution.TYPE_RESOLVED;
+ }
+
+ @Override
+ public boolean foldable() {
+ return field.foldable();
+ }
+
+ @Override
+ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) {
+ var fieldEvaluator = toEvaluator.apply(field);
+ if (field().dataType() == DataType.DATE_NANOS) {
+ return new DayNameNanosEvaluator.Factory(source(), fieldEvaluator, configuration().zoneId(), configuration().locale());
+ }
+ return new DayNameMillisEvaluator.Factory(source(), fieldEvaluator, configuration().zoneId(), configuration().locale());
+ }
+
+ @Override
+ public Expression replaceChildren(List newChildren) {
+ return new DayName(source(), newChildren.get(0), configuration());
+ }
+
+ @Override
+ protected NodeInfo extends Expression> info() {
+ return NodeInfo.create(this, DayName::new, field, configuration());
+ }
+
+ @Evaluator(extraName = "Millis")
+ static BytesRef processMillis(long val, @Fixed ZoneId zoneId, @Fixed Locale locale) {
+ ZonedDateTime dateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(val), zoneId);
+ String displayName = dateTime.getDayOfWeek().getDisplayName(TextStyle.FULL, locale);
+ return new BytesRef(displayName);
+ }
+
+ @Evaluator(extraName = "Nanos")
+ static BytesRef processNanos(long val, @Fixed ZoneId zoneId, @Fixed Locale locale) {
+ ZonedDateTime dateTime = ZonedDateTime.ofInstant(Instant.ofEpochSecond(0L, val), zoneId);
+ String displayName = dateTime.getDayOfWeek().getDisplayName(TextStyle.FULL, locale);
+ return new BytesRef(displayName);
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameErrorTests.java
new file mode 100644
index 0000000000000..b47f8dd8ec1cb
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameErrorTests.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.expression.function.scalar.date;
+
+import org.elasticsearch.xpack.esql.EsqlTestUtils;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.expression.function.ErrorsForCasesWithoutExamplesTestCase;
+import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
+import org.hamcrest.Matcher;
+
+import java.util.List;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class DayNameErrorTests extends ErrorsForCasesWithoutExamplesTestCase {
+ @Override
+ protected List cases() {
+ return paramsToSuppliers(DayNameTests.parameters());
+ }
+
+ @Override
+ protected Expression build(Source source, List args) {
+ return new DayName(source, args.get(0), EsqlTestUtils.TEST_CFG);
+ }
+
+ @Override
+ protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) {
+ // Single argument version
+ String source = sourceForSignature(signature);
+ String name = signature.get(0).typeName();
+ return equalTo("first argument of [" + source + "] must be [datetime or date_nanos], found value [] type [" + name + "]");
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameSerializationTests.java
new file mode 100644
index 0000000000000..49173d4db9c6d
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameSerializationTests.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.expression.function.scalar.date;
+
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.expression.AbstractExpressionSerializationTests;
+
+import java.io.IOException;
+
+public class DayNameSerializationTests extends AbstractExpressionSerializationTests {
+
+ @Override
+ protected DayName createTestInstance() {
+ Source source = randomSource();
+ Expression date = randomChild();
+ return new DayName(source, date, configuration());
+ }
+
+ @Override
+ protected DayName mutateInstance(DayName instance) throws IOException {
+ Source source = instance.source();
+ Expression date = instance.field();
+ return new DayName(source, randomValueOtherThan(date, AbstractExpressionSerializationTests::randomChild), configuration());
+ }
+
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameTests.java
new file mode 100644
index 0000000000000..9d6630d0d9dbd
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameTests.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.expression.function.scalar.date;
+
+import com.carrotsearch.randomizedtesting.annotations.Name;
+import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
+
+import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.common.lucene.BytesRefs;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.time.DateUtils;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.expression.FoldContext;
+import org.elasticsearch.xpack.esql.core.expression.Literal;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
+import org.elasticsearch.xpack.esql.expression.function.scalar.AbstractConfigurationFunctionTestCase;
+import org.elasticsearch.xpack.esql.plugin.EsqlPlugin;
+import org.elasticsearch.xpack.esql.plugin.QueryPragmas;
+import org.elasticsearch.xpack.esql.session.Configuration;
+import org.hamcrest.Matchers;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.TextStyle;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.function.Supplier;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class DayNameTests extends AbstractConfigurationFunctionTestCase {
+
+ public DayNameTests(@Name("TestCase") Supplier testCaseSupplier) {
+ this.testCase = testCaseSupplier.get();
+ }
+
+ @ParametersFactory
+ public static Iterable