Repository: jakartaee/data (java)

Issue:
LocalDate and LocalDateTime don't work with ComparableAttribute
Entity attribute types java.time.LocalDate and java.time.LocalDateTime are Comparable, but they implement `Comparable<ChronoLocalDate>` and `Comparable<ChronoLocalDateTime>` respectively rather than `Comparable<LocalDate>` and `Comparable<LocalDateTime>`.

I noticed this when attempting to use the static metamodel for `LocalDate`, where the following doesn't compile,

```
        ComparableAttribute<Author, LocalDate> birthday =
                ComparableAttribute.of(Author.class, BIRTHDAY, LocalDate.class);
```


Pull request:
add TemporalAttribute and related classes
Add TemporalAttribute and related static metamodel classes

Fixes #1022 

Patch:
diff --git a/api/src/main/java/jakarta/data/metamodel/Attribute.java b/api/src/main/java/jakarta/data/metamodel/Attribute.java
index c57e5b8f..4304891c 100644
--- a/api/src/main/java/jakarta/data/metamodel/Attribute.java
+++ b/api/src/main/java/jakarta/data/metamodel/Attribute.java
@@ -27,9 +27,11 @@ package jakarta.data.metamodel;
  * <li>{@link NumericAttribute} for entity attributes that represent numeric
  *     values, such as {@code int}, {@link Long}, and {@link java.math.BigDecimal}.
  *     </li>
+ * <li>{@link TemporalAttribute} for entity attributes that represent temporal
+ *     values, such as {@link java.time.LocalDateTime} and
+ *     {@link java.time.Instant}.</li>
  * <li>{@link ComparableAttribute} for entity attributes that represent other
- *     sortable and comparable values, such as {@code boolean},
- *     {@link java.time.LocalDateTime}, and enumerations.</li>
+ *     sortable and comparable values, such as {@code boolean} and enumerations.</li>
  * <li>{@link SortableAttribute} for entity types that are sortable, but not
  *     comparable. Generally this subtype is unused but is applicable for
  *     databases that allow sorting on attributes of type {@code byte[]}.</li>
diff --git a/api/src/main/java/jakarta/data/metamodel/ComparableAttribute.java b/api/src/main/java/jakarta/data/metamodel/ComparableAttribute.java
index 2aa5d7e6..551ea340 100644
--- a/api/src/main/java/jakarta/data/metamodel/ComparableAttribute.java
+++ b/api/src/main/java/jakarta/data/metamodel/ComparableAttribute.java
@@ -24,26 +24,33 @@ import java.util.Objects;
  * Comparable entity attributes can be sorted on in query results and can be
  * compared against values in query restrictions.</p>
  *
- * <p>Entity attribute types that are comparable include:</p>
+ * <p>Where possible, always use the more specific subtypes of
+ * {@code ComparableAttribute}:</p>
+ *
+ * <ul>
+ * <li>{@link TextAttribute} for entity attributes that represent text,
+ *     typically of type {@link String}.</li>
+ * <li>{@link NumericAttribute} for entity attributes that represent numeric
+ *     values, such as {@code long}, {@link Float}, and
+ *     {@link java.math.BigInteger}.</li>
+ * <li>{@link TemporalAttribute} for entity attributes that represent temporal
+ *     values, such as {@link java.time.LocalDate} and {@link java.time.Instant}.
+ *     </li>
+ * </ul>
+ *
+ * <p>Entity attribute types for which {@code ComparableAttribute} is appropriate
+ * include:</p>
  *
  * <ul>
- * <li>time attributes, such as {@link java.time.LocalDateTime} and
- *     {@link java.time.Instant}</li>
  * <li>boolean attributes: {@code boolean} and {@link Boolean}</li>
  * <li>enum attributes - Note that it is provider-specific whether order is
  *     based on {@link Enum#ordinal()} or {@link Enum#name()}.
  *     The Jakarta Persistence default of {@code ordinal} can be overridden
  *     with the {@code jakarta.persistence.Enumerated} annotation.</li>
- * <li>numeric attributes, such as {@code long}, {@link Float}, and
- *     {@link java.math.BigInteger} - Use the {@link NumericAttribute} subtype
- *     instead</li>
- * <li>textual attributes - Use the {@link TextAttribute} subtype instead</li>
  * </ul>
  *
- * <p>Where possible, {@code ComparableAttribute}, which provides more function,
- * is preferred over {@link SortableAttribute}. Likewise, subtypes of
- * {@code ComparableAttribute}, such as {@link NumericAttribute} and
- * {@link TextAttribute}, are preferred over {@code ComparableAttribute}.</p>
+ * <p>In the above cases, {@code ComparableAttribute}, which provides more
+ * function, is preferred over {@link SortableAttribute}.</p>
  *
  * @param <T> entity class of the static metamodel.
  * @param <V> type of entity attribute (or wrapper type if primitive).
diff --git a/api/src/main/java/jakarta/data/metamodel/NavigableExpression.java b/api/src/main/java/jakarta/data/metamodel/NavigableExpression.java
index c7864d39..0c2ab9d2 100644
--- a/api/src/main/java/jakarta/data/metamodel/NavigableExpression.java
+++ b/api/src/main/java/jakarta/data/metamodel/NavigableExpression.java
@@ -17,9 +17,12 @@
  */
 package jakarta.data.metamodel;
 
+import java.time.temporal.Temporal;
+
 import jakarta.data.metamodel.path.ComparablePath;
 import jakarta.data.metamodel.path.NavigablePath;
 import jakarta.data.metamodel.path.NumericPath;
+import jakarta.data.metamodel.path.TemporalPath;
 import jakarta.data.metamodel.path.TextPath;
 
 public interface NavigableExpression<T,U> {
@@ -40,4 +43,9 @@ public interface NavigableExpression<T,U> {
         return NumericPath.of(this, attribute);
     }
 
+    default <V extends Temporal & Comparable<? extends Temporal>> TemporalExpression<T, V> navigate(
+            TemporalAttribute<U, V> attribute) {
+        return TemporalPath.of(this, attribute);
+    }
+
 }
diff --git a/api/src/main/java/jakarta/data/metamodel/StaticMetamodel.java b/api/src/main/java/jakarta/data/metamodel/StaticMetamodel.java
index c224d3c4..a987d41c 100644
--- a/api/src/main/java/jakarta/data/metamodel/StaticMetamodel.java
+++ b/api/src/main/java/jakarta/data/metamodel/StaticMetamodel.java
@@ -40,9 +40,12 @@ import jakarta.data.Sort;
  *     typically of type {@link String}.</li>
  * <li>{@link NumericAttribute} for entity attributes of numeric types, such as
  *     {@code int}, {@link Double}, and {@link java.math.BigInteger}.</li>
+ * <li>{@link TemporalAttribute} for entity attributes of temporal types, such as
+ *     {@link java.time.LocalDate}, {@link java.time.LocalTime}, and
+ *     {@link java.time.Instant}.</li>
  * <li>{@link ComparableAttribute} for entity attributes that represent other
- *     sortable and comparable values, such as
- *     {@code boolean}, {@link java.time.LocalDateTime}, and enumerations.</li>
+ *     sortable and comparable values, such as {@code boolean} and enumerations.
+ *     </li>
  * <li>{@link NavigableAttribute} for entity attributes that are embeddables
  *     or relations.</li>
  * <li>{@link BasicAttribute} for other types of entity attributes, such as
@@ -64,13 +67,13 @@ import jakarta.data.Sort;
  * <pre>
  * &#64;Entity
  * public class Person {
- *     &#64;Id
- *     public long ssn;
+ *     public LocalDate dateOfBirth;
  *
  *     &#64;Embedded
  *     public Name name;
  *
- *     public int yearOfBirth;
+ *     &#64;Id
+ *     public long ssn;
  * }
  *
  * &#64;Embeddable
@@ -85,22 +88,22 @@ import jakarta.data.Sort;
  * <pre>
  * &#64;StaticMetamodel(Person.class)
  * public interface _Person {
- *     String SSN = "ssn";
+ *     String DATEOFBIRTH = "dateOfBirth";
  *     String NAME = "name";
  *     String NAME_FIRST = "name.first";
  *     String NAME_LAST = "name.last";
- *     String YEAROFBIRTH = "yearOfBirth";
+ *     String SSN = "ssn";
  *
- *     NumericAttribute&lt;Person,Long&gt; ssn = NumericAttribute.of(
- *             Person.class, SSN, long.class);
+ *     TemporalAttribute&lt;Person,LocalDate&gt; dateOfBirth = TemporalAttribute.of(
+ *             Person.class, DATEOFBIRTH, LocalDate.class);
  *     NavigableAttribute&lt;Person,Name&gt; name = NavigableAttribute.of(
  *             Person.class, NAME, Name.class);
  *     TextAttribute&lt;Person&gt; name_first = TextAttribute.of(
  *             Person.class, NAME_FIRST);
  *     TextAttribute&lt;Person&gt; name_last = TextAttribute.of(
  *             Person.class, NAME_LAST);
- *     NumericAttribute&lt;Person,Integer&gt; yearOfBirth = NumericAttribute.of(
- *             Person.class, YEAROFBIRTH, int.class);
+ *     NumericAttribute&lt;Person,Long&gt; ssn = NumericAttribute.of(
+ *             Person.class, SSN, long.class);
  * }
  * </pre>
  *
@@ -108,7 +111,7 @@ import jakarta.data.Sort;
  *
  * <pre>
  * Order&lt;Person&gt; order =
- *         Order.by(_Person.yearOfBirth.desc(),
+ *         Order.by(_Person.dateOfBirth.desc(),
  *                  _Person.name_last.asc(),
  *                  _Person.name_first.asc(),
  *                  _Person.ssn.asc());
diff --git a/api/src/main/java/jakarta/data/metamodel/TemporalAttribute.java b/api/src/main/java/jakarta/data/metamodel/TemporalAttribute.java
new file mode 100644
index 00000000..3a613c96
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/TemporalAttribute.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package jakarta.data.metamodel;
+
+import java.time.temporal.Temporal;
+import java.util.Objects;
+
+/**
+ * <p>Represents a {@linkplain Temporal temporal} entity attribute in the
+ * {@link StaticMetamodel}. Temporal entity attributes can be sorted on in
+ * query results and can be compared against values in query restrictions.
+ * </p>
+ *
+ * <p>Jakarta Data supports the following entity attribute types for temporal
+ * data:</p>
+ *
+ * <ul>
+ * <li>{@link java.time.Instant}</li>
+ * <li>{@link java.time.LocalDate}</li>
+ * <li>{@link java.time.LocalDateTime}</li>
+ * <li>{@link java.time.LocalTime}</li>
+ * </ul>
+ *
+ * <p>Where possible, {@code TemporalAttribute} is preferred over
+ * {@link ComparableAttribute} and {@link SortableAttribute}.</p>
+ *
+ * @param <T> entity class of the static metamodel.
+ * @param <V> type of entity attribute.
+ */
+
+public interface TemporalAttribute<T,V extends Temporal & Comparable<? extends Temporal>>
+        extends ComparableAttribute<T,V>, TemporalExpression<T, V> {
+
+    /**
+     * <p>Creates a static metamodel {@code TemporalAttribute} representing the
+     * entity attribute with the specified name.</p>
+     *
+     * @param <T> entity class of the static metamodel.
+     * @param <V> type of entity attribute.
+     * @param entityClass   the entity class.
+     * @param name          the name of the entity attribute.
+     * @param attributeType type of the entity attribute.
+     * @return instance of {@code TemporalAttribute}.
+     */
+    static <T,V extends Temporal & Comparable<? extends Temporal>> TemporalAttribute<T,V> of(
+            Class<T> entityClass, String name, Class<V> attributeType) {
+        Objects.requireNonNull(entityClass, "entity class is required");
+        Objects.requireNonNull(name, "entity attribute name is required");
+        Objects.requireNonNull(attributeType, "entity attribute type is required");
+
+        return new TemporalAttributeRecord<T, V>(entityClass, name);
+    }
+}
+
diff --git a/api/src/main/java/jakarta/data/metamodel/TemporalAttributeRecord.java b/api/src/main/java/jakarta/data/metamodel/TemporalAttributeRecord.java
new file mode 100644
index 00000000..077e96e3
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/TemporalAttributeRecord.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package jakarta.data.metamodel;
+
+import java.time.temporal.Temporal;
+
+record TemporalAttributeRecord<T,V extends Temporal & Comparable<? extends Temporal>>(
+        Class<T> declaringType,
+        String name)
+        implements TemporalAttribute<T,V> {
+}
\ No newline at end of file
diff --git a/api/src/main/java/jakarta/data/metamodel/TemporalExpression.java b/api/src/main/java/jakarta/data/metamodel/TemporalExpression.java
new file mode 100644
index 00000000..88d42631
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/TemporalExpression.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package jakarta.data.metamodel;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.temporal.Temporal;
+
+import jakarta.data.metamodel.expression.CurrentDate;
+import jakarta.data.metamodel.expression.CurrentDateTime;
+import jakarta.data.metamodel.expression.CurrentTime;
+
+public interface TemporalExpression<T, V extends Temporal & Comparable<? extends Temporal>>
+        extends ComparableExpression<T, V> {
+
+    static TemporalExpression<Object, LocalDate> localDate() {
+        return CurrentDate.now();
+    }
+
+    static TemporalExpression<Object, LocalDateTime> localDateTime() {
+        return CurrentDateTime.now();
+    }
+
+    static TemporalExpression<Object, LocalTime> localTime() {
+        return CurrentTime.now();
+    }
+
+    // TODO add methods to obtain the year, month, day, hour, and so forth
+    // after EXTRACT is added to the query language in some future release
+
+    // Methods for year/month/day would apply to Instant, LocalDateTime, and LocalDate
+    // while hour/minute/second would apply to Instant, LocalDateTime, and LocalTime.
+    // We need to also consider if it is okay to have some methods that wouldn't
+    // apply to LocalDate or LocalTime, or if we should have more specific types
+    // instead of Temporal.
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/expression/CurrentDate.java b/api/src/main/java/jakarta/data/metamodel/expression/CurrentDate.java
new file mode 100644
index 00000000..b821469a
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/expression/CurrentDate.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package jakarta.data.metamodel.expression;
+
+import jakarta.data.metamodel.TemporalExpression;
+
+import java.time.LocalDate;
+
+public interface CurrentDate<T> extends TemporalExpression<T,LocalDate> {
+
+    static <T> CurrentDate<T> now() {
+        return new CurrentDate<>() {};
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/expression/CurrentDateTime.java b/api/src/main/java/jakarta/data/metamodel/expression/CurrentDateTime.java
new file mode 100644
index 00000000..d46d4c1e
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/expression/CurrentDateTime.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package jakarta.data.metamodel.expression;
+
+import jakarta.data.metamodel.TemporalExpression;
+
+import java.time.LocalDateTime;
+
+public interface CurrentDateTime<T> extends TemporalExpression<T,LocalDateTime> {
+
+    static <T> CurrentDateTime<T> now() {
+        return new CurrentDateTime<>() {};
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/expression/CurrentTime.java b/api/src/main/java/jakarta/data/metamodel/expression/CurrentTime.java
new file mode 100644
index 00000000..bb44e0f3
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/expression/CurrentTime.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package jakarta.data.metamodel.expression;
+
+import jakarta.data.metamodel.TemporalExpression;
+
+import java.time.LocalTime;
+
+public interface CurrentTime<T> extends TemporalExpression<T,LocalTime> {
+
+    static <T> CurrentTime<T> now() {
+        return new CurrentTime<>() {};
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/path/TemporalPath.java b/api/src/main/java/jakarta/data/metamodel/path/TemporalPath.java
new file mode 100644
index 00000000..3d8374dd
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/path/TemporalPath.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package jakarta.data.metamodel.path;
+
+import java.time.temporal.Temporal;
+
+import jakarta.data.metamodel.NavigableExpression;
+import jakarta.data.metamodel.Path;
+import jakarta.data.metamodel.TemporalAttribute;
+import jakarta.data.metamodel.TemporalExpression;
+
+public interface TemporalPath<T,U,V extends Temporal & Comparable<? extends Temporal>>
+        extends Path<T,U>, TemporalExpression<T,V> {
+
+    static <T,U,V extends Temporal & Comparable<? extends Temporal>> TemporalPath<T, U, V> of(
+        NavigableExpression<T, U> expression,
+        TemporalAttribute<U, V> attribute) {
+
+        return new TemporalPathRecord<>(expression, attribute);
+    }
+}
diff --git a/api/src/main/java/jakarta/data/metamodel/path/TemporalPathRecord.java b/api/src/main/java/jakarta/data/metamodel/path/TemporalPathRecord.java
new file mode 100644
index 00000000..f1a22c34
--- /dev/null
+++ b/api/src/main/java/jakarta/data/metamodel/path/TemporalPathRecord.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package jakarta.data.metamodel.path;
+
+import java.time.temporal.Temporal;
+
+import jakarta.data.metamodel.NavigableExpression;
+import jakarta.data.metamodel.TemporalAttribute;
+
+record TemporalPathRecord<T,U,V extends Temporal & Comparable<? extends Temporal>>(
+        NavigableExpression<T,U> expression,
+        TemporalAttribute<U,V> attribute) implements TemporalPath<T,U,V> {
+}
diff --git a/spec/src/main/asciidoc/entity.asciidoc b/spec/src/main/asciidoc/entity.asciidoc
index e371fcc6..50b120a9 100644
--- a/spec/src/main/asciidoc/entity.asciidoc
+++ b/spec/src/main/asciidoc/entity.asciidoc
@@ -427,9 +427,10 @@ The following subinterfaces of `Attribute` are recommended to obtain the full be
 
 - `TextAttribute` for entity attributes that represent text, typically of type `String`.
 - `NumericAttribute` for entity attributes of numeric types, such as `long`, `Integer`, and `BigDecimal`.
-- `ComparableAttribute` for entity attributes that represent other comparable values, such as `boolean`, `java.time.Instant`, and enumerations.
-- `NavigableAttribute` for entity attributes that are embeddables or relations.
-- `BasicAttribute` for other types of entity attributes, such as collections, embeddables, and other relation attributes.
+- `TemporalAttribute` for entity attributes of temporal types, such as `LocalDateTime`, and `Instant`.
+- `ComparableAttribute` for entity attributes that represent other comparable values, such as `boolean` and enumerations.
+- `NavigableAttribute` for entity attributes that are embeddables or associations.
+- `BasicAttribute` for other types of entity attributes, such as collections.
 
 ==== Example metamodel class and usage
 


Test patch:
diff --git a/api/src/test/java/jakarta/data/metamodel/expression/TemporalExpressionTest.java b/api/src/test/java/jakarta/data/metamodel/expression/TemporalExpressionTest.java
new file mode 100644
index 00000000..1c844252
--- /dev/null
+++ b/api/src/test/java/jakarta/data/metamodel/expression/TemporalExpressionTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ */
+package jakarta.data.metamodel.expression;
+
+import jakarta.data.metamodel.TemporalExpression;
+import jakarta.data.metamodel.constraint.*;
+import jakarta.data.metamodel.restrict.BasicRestriction;
+import jakarta.data.metamodel.restrict.Restriction;
+import jakarta.data.mock.entity.Book;
+import jakarta.data.mock.entity._Book;
+
+import java.time.LocalDate;
+import java.time.Month;
+
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.Test;
+
+class TemporalExpressionTest {
+
+    @Test
+    void shouldRestrictLessThanCurrentDate() {
+        TemporalExpression<Object, LocalDate> today =
+            TemporalExpression.localDate();
+
+        // TODO write the test once comparisons with expressions are added
+        //Restriction<Book> beforeCurrentDate =
+        //    _Book.publicationDate.lessThan(today);
+        // ...
+    }
+
+    @Test
+    void shouldRestrictLocalDateBetween() {
+
+        Restriction<Book> publishedInApril2025 =
+            _Book.publicationDate.between(LocalDate.of(2025, Month.APRIL, 1),
+                                          LocalDate.of(2025, Month.APRIL, 30));
+
+        @SuppressWarnings("unchecked")
+        BasicRestriction<Book, LocalDate> restriction =
+            (BasicRestriction<Book, LocalDate>) publishedInApril2025;
+
+        Constraint<LocalDate> constraint = restriction.constraint();
+
+        TemporalExpression<Book, LocalDate> betweenExpression =
+            (TemporalExpression<Book, LocalDate>) restriction.expression();
+
+        SoftAssertions.assertSoftly(soft -> {
+            soft.assertThat(betweenExpression)
+                .isEqualTo(_Book.publicationDate);
+
+            soft.assertThat(constraint)
+                .isInstanceOf(Between.class);
+
+            soft.assertThat(((Between<LocalDate>) constraint).lowerBound())
+                .isEqualTo(LocalDate.of(2025, Month.APRIL, 1));
+
+            soft.assertThat(((Between<LocalDate>) constraint).upperBound())
+                .isEqualTo(LocalDate.of(2025, Month.APRIL, 30));
+        });
+    }
+}
diff --git a/api/src/test/java/jakarta/data/mock/entity/Book.java b/api/src/test/java/jakarta/data/mock/entity/Book.java
index 5cd348d0..9d115622 100644
--- a/api/src/test/java/jakarta/data/mock/entity/Book.java
+++ b/api/src/test/java/jakarta/data/mock/entity/Book.java
@@ -17,13 +17,18 @@
  */
 package jakarta.data.mock.entity;
 
+import java.time.Instant;
+import java.time.LocalDate;
+
 /**
  * A mock entity class for tests
  */
 public class Book {
     String author;
+    Instant copyrightDate;
     String id;
     int numChapters;
     int numPages;
+    LocalDate publicationDate;
     String title;
 }
diff --git a/api/src/test/java/jakarta/data/mock/entity/_Book.java b/api/src/test/java/jakarta/data/mock/entity/_Book.java
index 48b83145..595ee6ed 100644
--- a/api/src/test/java/jakarta/data/mock/entity/_Book.java
+++ b/api/src/test/java/jakarta/data/mock/entity/_Book.java
@@ -17,8 +17,12 @@
  */
 package jakarta.data.mock.entity;
 
+import java.time.Instant;
+import java.time.LocalDate;
+
 import jakarta.data.metamodel.BasicAttribute;
 import jakarta.data.metamodel.ComparableAttribute;
+import jakarta.data.metamodel.TemporalAttribute;
 import jakarta.data.metamodel.TextAttribute;
 
 /**
@@ -26,19 +30,25 @@ import jakarta.data.metamodel.TextAttribute;
  */
 public interface _Book {
     String AUTHOR = "author";
+    String COPYRIGHTDATE = "copyrightDate";
     String ID = "id";
     String NUMCHAPTERS = "numChapters";
     String NUMPAGES = "numPages";
+    String PUBLICATIONDATE = "publicationDate";
     String TITLE = "title";
 
     BasicAttribute<Book, String> author = BasicAttribute.of(
             Book.class, AUTHOR, String.class);
+    TemporalAttribute<Book, Instant> copyrightDate = TemporalAttribute.of(
+            Book.class, COPYRIGHTDATE, Instant.class);
     TextAttribute<Book> id = TextAttribute.of(
             Book.class, ID);
     ComparableAttribute<Book, Integer> numChapters = ComparableAttribute.of(
             Book.class, NUMCHAPTERS, int.class);
     ComparableAttribute<Book, Integer> numPages = ComparableAttribute.of(
             Book.class, NUMPAGES, int.class);
+    TemporalAttribute<Book, LocalDate> publicationDate = TemporalAttribute.of(
+            Book.class, PUBLICATIONDATE, LocalDate.class);
     TextAttribute<Book> title = TextAttribute.of(
             Book.class, TITLE);
 }
\ No newline at end of file