Merge pull request #686 from jamezp/issue685

[685] Ensure the value type of a container being deserialized uses th…
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 3b21094..4f5adfe 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -37,10 +37,10 @@
       - name: Copyright
         run: bash etc/copyright.sh
       - name: Checkstyle
-        run: mvn -B checkstyle:checkstyle -Pstaging
+        run: mvn -B checkstyle:checkstyle
       - name: Yasson install
-        run: mvn -U -C clean install -Pstaging -DskipTests
+        run: mvn -U -C clean install -DskipTests
       - name: Yasson tests
-        run: mvn -U -B -C -Dmaven.javadoc.skip=true -Pstaging verify
+        run: mvn -U -B -C -Dmaven.javadoc.skip=true verify
       - name: JSONB-API TCK
-        run: cd yasson-tck && mvn -U -B test -DargLine="-Djava.locale.providers=COMPAT" -Pstaging
+        run: cd yasson-tck && mvn -U -B test -DargLine="-Djava.locale.providers=COMPAT"
diff --git a/pom.xml b/pom.xml
index f3f048a..28c0b35 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
 
-    Copyright (c) 2016, 2024 Oracle and/or its affiliates. All rights reserved.
+    Copyright (c) 2016, 2026 Oracle and/or its affiliates. All rights reserved.
 
     This program and the accompanying materials are made available under the
     terms of the Eclipse Public License v. 2.0 which is available at
@@ -15,17 +15,17 @@
 
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
 
     <parent>
         <groupId>org.eclipse.ee4j</groupId>
         <artifactId>project</artifactId>
-        <version>1.0.9</version>
+        <version>2.0.2</version>
     </parent>
 
-    <modelVersion>4.0.0</modelVersion>
     <groupId>org.eclipse</groupId>
     <artifactId>yasson</artifactId>
-    <version>3.0.4-SNAPSHOT</version>
+    <version>3.0.5-SNAPSHOT</version>
     <packaging>jar</packaging>
     <name>Yasson</name>
 
diff --git a/src/main/java/org/eclipse/yasson/internal/deserializer/types/DateDeserializer.java b/src/main/java/org/eclipse/yasson/internal/deserializer/types/DateDeserializer.java
index 723cdbe..4e3704a 100644
--- a/src/main/java/org/eclipse/yasson/internal/deserializer/types/DateDeserializer.java
+++ b/src/main/java/org/eclipse/yasson/internal/deserializer/types/DateDeserializer.java
@@ -13,13 +13,30 @@
 package org.eclipse.yasson.internal.deserializer.types;
 
 import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.YearMonth;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
+import java.time.temporal.TemporalAccessor;
 import java.util.Date;
 import java.util.Locale;
 
 /**
  * Deserializer of the {@link Date} type.
+ *
+ * <p>
+ * For date-only patterns (e.g., "yyyy-MM-dd"), this deserializer uses {@link DateTimeFormatter#parseBest} to detect the
+ * appropriate temporal type [ZonedDateTime, LocalDateTime, LocalDate, or YearMonth]
+ * and creates the Date object at midnight in the specified
+ * timezone. When no timezone is specified in the pattern, UTC is used as required by Jakarta JSON Binding specification
+ * section 3.5.
+ * </p>
+ * <p>
+ * critical, use {@link java.time.LocalDate} (recommended) or {@link java.sql.Date}.
+ * </p>
  */
 class DateDeserializer extends AbstractDateDeserializer<Date> {
 
@@ -45,13 +62,31 @@
     }
 
     private static Date parseWithOrWithoutZone(String jsonValue, DateTimeFormatter formatter) {
-        ZonedDateTime parsed;
-        if (formatter.getZone() == null) {
-            parsed = ZonedDateTime.parse(jsonValue, formatter.withZone(UTC));
+        final TemporalAccessor best = formatter.parseBest(jsonValue,
+                ZonedDateTime::from,
+                LocalDateTime::from,
+                LocalDate::from,
+                YearMonth::from);
+
+        // If no zone provided in string, use the formatter's zone or UTC per the Jakarta JSON Binding specification
+        // section 3.5
+        final ZoneId zone = formatter.getZone() != null ? formatter.getZone() : ZoneOffset.UTC;
+
+        // Determine the type of the best option
+        final Instant instant;
+        if (best instanceof ZonedDateTime) {
+            instant = ((ZonedDateTime) best).toInstant();
+        } else if (best instanceof LocalDateTime) {
+            instant = ((LocalDateTime) best).atZone(zone).toInstant();
+        } else if (best instanceof LocalDate) {
+            instant = LocalDate.from(best).atStartOfDay(zone).toInstant();
+        } else if (best instanceof YearMonth) {
+            instant = ((YearMonth) best).atDay(1).atStartOfDay(zone).toInstant();
         } else {
-            parsed = ZonedDateTime.parse(jsonValue, formatter);
+            // Fallback
+            instant = Instant.from(best);
         }
-        return Date.from(parsed.toInstant());
+        return Date.from(instant);
     }
 
 }
diff --git a/src/test/java/org/eclipse/yasson/defaultmapping/dates/DatesTest.java b/src/test/java/org/eclipse/yasson/defaultmapping/dates/DatesTest.java
index 287afca..79ad21e 100644
--- a/src/test/java/org/eclipse/yasson/defaultmapping/dates/DatesTest.java
+++ b/src/test/java/org/eclipse/yasson/defaultmapping/dates/DatesTest.java
@@ -102,6 +102,10 @@
     public static class SqlDateFormatted {
         @JsonbDateFormat(value = "yyyy-MM-dd")
         public java.sql.Date sqlDate;
+        @JsonbDateFormat(value = "yyyy-MM-dd")
+        public java.util.Date utilDate;
+
+
     }
 
     @Test
@@ -126,7 +130,101 @@
         assertEquals("2018-01-31", result.sqlDate.toString());
         assertEquals("2018-01-31", result.utilDate.toString());
     }
-    
+
+    @Test
+    public void testMarshallSqlDateFormatted() {
+        final String date = "2026-02-25";
+        final String expectedJson = String.format("{\"sqlDate\":\"%1$s\",\"utilDate\":\"%1$s\"}", date);
+
+        final SqlDateFormatted sqlDateFormatted = new SqlDateFormatted();
+        sqlDateFormatted.sqlDate = java.sql.Date.valueOf(date);
+        // We use a java.sql.Date here as we want to test as if this was a Jakarta Persistence temporal date
+        sqlDateFormatted.utilDate = java.sql.Date.valueOf(date);
+        String jsonString = bindingJsonb.toJson(sqlDateFormatted);
+        assertEquals(expectedJson, jsonString);
+
+        // Unmarshal the object
+        final SqlDateFormatted result = bindingJsonb.fromJson(jsonString, SqlDateFormatted.class);
+        assertEquals(sqlDateFormatted.sqlDate, result.sqlDate);
+        // The Date objects will not be equal unless user.timezone is set to UTC. The sqlDateFormatted.utilDate is
+        // created at midnight in the current timezone (via valueOf()), while result.utilDate is created at midnight UTC
+        // per the JSON-B specification. To verify both represent the same calendar date, we convert each to LocalDate
+        // using its respective timezone: the original uses systemDefault(), the deserialized uses UTC.
+        assertEquals(Instant.ofEpochMilli(sqlDateFormatted.utilDate.getTime()).atZone(ZoneId.systemDefault()).toLocalDate(),
+                result.utilDate.toInstant().atZone(ZoneOffset.UTC).toLocalDate());
+    }
+
+    @Test
+    public void testUnmarshallSqlDateFormatted() {
+        final String date = "2026-02-25";
+        final String expectedString = String.format("{\"sqlDate\":\"%1$s\",\"utilDate\":\"%1$s\"}", date);
+
+        final SqlDateFormatted sqlDateFormatted = bindingJsonb.fromJson(expectedString, SqlDateFormatted.class);
+        assertEquals(date, sqlDateFormatted.sqlDate.toString());
+        // Convert java.util.Date to LocalDate for comparison
+        final LocalDate resultDate = sqlDateFormatted.utilDate.toInstant()
+                .atZone(ZoneOffset.UTC)
+                .toLocalDate();
+        assertEquals(LocalDate.parse(date),  resultDate);
+
+        // Unmarshal the object
+        final String result = bindingJsonb.toJson(sqlDateFormatted);
+        assertEquals(expectedString, result);
+    }
+
+    public static class YearMonthFormatted {
+        @JsonbDateFormat(value = "yyyy-MM")
+        public java.util.Date date;
+    }
+
+    @Test
+    public void testMarshallYearMonthFormat() {
+        final YearMonthFormatted yearMonthFormatted = new YearMonthFormatted();
+        yearMonthFormatted.date = java.sql.Date.valueOf("2026-02-25");
+        String jsonString = bindingJsonb.toJson(yearMonthFormatted);
+        assertEquals("{\"date\":\"2026-02\"}", jsonString);
+    }
+
+    @Test
+    public void testUnmarshallYearMonthFormat() {
+        final YearMonthFormatted yearMonthFormatted = bindingJsonb.fromJson(
+                "{\"date\":\"2026-02\"}",
+                YearMonthFormatted.class);
+        final LocalDate resultDate = yearMonthFormatted.date.toInstant()
+                .atZone(ZoneOffset.UTC)
+                .toLocalDate();
+        assertEquals(LocalDate.of(2026, 2, 1), resultDate);
+    }
+
+    @Test
+    public void testDateOnlyPatternEdgeCases() {
+        // Test various edge cases to ensure date values are preserved correctly
+        testDateRoundTrip("2028-03-01"); // Day after leap year
+        testDateRoundTrip("2026-12-31"); // Last day of year
+        testDateRoundTrip("2028-02-29"); // Leap year day
+        testDateRoundTrip("2027-01-01"); // First day of year
+        testDateRoundTrip("2028-01-31"); // Last day of January
+        testDateRoundTrip("2028-02-01"); // First day of February
+    }
+
+    private void testDateRoundTrip(final String date) {
+        final String json = String.format("{\"sqlDate\":\"%1$s\",\"utilDate\":\"%1$s\"}", date);
+
+        // Deserialize
+        final SqlDateFormatted deserialized = bindingJsonb.fromJson(json, SqlDateFormatted.class);
+
+        // Verify utilDate represents midnight UTC for the specified date
+        final LocalDate resultDate = deserialized.utilDate.toInstant()
+                .atZone(ZoneOffset.UTC)
+                .toLocalDate();
+        assertEquals(LocalDate.parse(date), resultDate, () -> String.format("Date should be %s when viewed in UTC", date));
+
+        // Verify JSON round-trip
+        final String roundTripped = bindingJsonb.toJson(deserialized);
+        assertEquals(json, roundTripped, () -> String.format("JSON should round-trip correctly for %s", date));
+    }
+
+
     @Test
     public void testSqlDateTimeZonesFormatted() {
         testSqlDateWithTZFormatted(TimeZone.getTimeZone(ZoneId.of("Europe/Sofia")));
diff --git a/src/test/java/org/eclipse/yasson/records/CarWithGenerics.java b/src/test/java/org/eclipse/yasson/records/CarWithGenerics.java
new file mode 100644
index 0000000..619817a
--- /dev/null
+++ b/src/test/java/org/eclipse/yasson/records/CarWithGenerics.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2025 IBM and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+package org.eclipse.yasson.records;
+
+public record CarWithGenerics<T> (String type, T color)  {
+}
diff --git a/src/test/java/org/eclipse/yasson/records/Color.java b/src/test/java/org/eclipse/yasson/records/Color.java
new file mode 100644
index 0000000..8744500
--- /dev/null
+++ b/src/test/java/org/eclipse/yasson/records/Color.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2025 IBM and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+package org.eclipse.yasson.records;
+    
+public record Color(String name, String code) {
+}
diff --git a/src/test/java/org/eclipse/yasson/records/RecordTest.java b/src/test/java/org/eclipse/yasson/records/RecordTest.java
index 2b6a61e..3c1b72a 100644
--- a/src/test/java/org/eclipse/yasson/records/RecordTest.java
+++ b/src/test/java/org/eclipse/yasson/records/RecordTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2025 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0 which is available at
@@ -15,6 +15,7 @@
 import jakarta.json.bind.JsonbException;
 
 import org.eclipse.yasson.Jsonbs;
+import org.eclipse.yasson.TestTypeToken;
 import org.eclipse.yasson.internal.properties.MessageKeys;
 import org.eclipse.yasson.internal.properties.Messages;
 import org.junit.jupiter.api.Test;
@@ -106,4 +107,16 @@
         assertThrows(JsonbException.class, () -> Jsonbs.defaultJsonb.fromJson(expected, CarWithDefaultConstructor.class));
     }
 
+    @Test
+    public void testRecordWithGenerics() {
+        CarWithGenerics<Color> car = new CarWithGenerics<>("skoda", new Color("green", "#00FF00"));
+        String expected = "{\"color\":{\"code\":\"#00FF00\",\"name\":\"green\"},\"type\":\"skoda\"}";
+
+        String json = Jsonbs.defaultJsonb.toJson(car);
+        assertThat(json, is(expected));
+        
+        CarWithGenerics<Color> deserialized = Jsonbs.defaultJsonb
+                .fromJson(expected, new TestTypeToken<CarWithGenerics<Color>>() {}.getType());
+        assertThat(deserialized, is(car));  
+    }
 }
diff --git a/yasson-tck/pom.xml b/yasson-tck/pom.xml
index bd0d76e..c3d36c7 100644
--- a/yasson-tck/pom.xml
+++ b/yasson-tck/pom.xml
@@ -11,22 +11,13 @@
 
     <properties>
         <jsonb.tck.version>3.0.0</jsonb.tck.version>
-        <yasson.version>3.0.4-SNAPSHOT</yasson.version>
+        <yasson.version>3.0.5-SNAPSHOT</yasson.version>
         <jakarta.json.bind.version>3.0.1</jakarta.json.bind.version>
         <jakarta.json.version>2.1.3</jakarta.json.version>
         <maven.compiler.source>11</maven.compiler.source>
         <maven.compiler.target>11</maven.compiler.target>
     </properties>
 
-    <!-- TODO: Temporarily enable snapshot repository -->
-    <!-- This can be removed once an official release of jakarta.json.bind-tck is available -->
-    <repositories>
-        <repository>
-            <id>jakarta-snapshots</id>
-            <url>https://jakarta.oss.sonatype.org/content/repositories/staging/</url>
-        </repository>
-    </repositories>
-
     <dependencies>
         <dependency>
             <groupId>jakarta.json.bind</groupId>