Repository: primefaces/primefaces (java)

Issue:
DatePicker: Timezone client issue with java.util.Timezone
### Describe the bug

Continuation of: https://github.com/primefaces/primefaces/issues/12307

It works with new Java8 Time code but legacy java.util.Timezone has an issue.

### Reproducer

Add `timezone="#{java7TimeZone}"` and the toString is sent like "com.sun.util.ZoneId` instead of `America/New_York`.

### Expected behavior

Send `America/New_York`.

### PrimeFaces edition

Community

### PrimeFaces version

14.0.3

### Theme

ALL

### JSF implementation

Mojarra

### JSF version

2.3

### Java version

11

### Browser(s)

_No response_

Pull request:
Fix #12342: 14.0.4 DatePicker better client handling of java.util.Timzone
Fix #12342: 14.0.4 DatePicker better client handling of java.util.Timzone

Patch:
diff --git a/primefaces/src/main/java/org/primefaces/component/datepicker/DatePickerRenderer.java b/primefaces/src/main/java/org/primefaces/component/datepicker/DatePickerRenderer.java
index f54bd909c4..091101ef6d 100644
--- a/primefaces/src/main/java/org/primefaces/component/datepicker/DatePickerRenderer.java
+++ b/primefaces/src/main/java/org/primefaces/component/datepicker/DatePickerRenderer.java
@@ -216,7 +216,7 @@ public class DatePickerRenderer extends BaseCalendarRenderer {
             .attr("timeSeparator", datePicker.getTimeSeparator(), ":")
             .attr("fractionSeparator", datePicker.getFractionSeparator(), ".")
             .attr("timeInput", datePicker.isTimeInput())
-            .attr("timeZone", Objects.toString(datePicker.getTimeZone(), null), null)
+            .attr("timeZone", datePicker.getTimeZone() == null ? null : CalendarUtils.calculateZoneId(datePicker.getTimeZone()).toString(), null)
             .attr("touchable", ComponentUtils.isTouchable(context, datePicker), true)
             .attr("lazyModel", datePicker.getModel() instanceof LazyDateMetadataModel, false);
 
diff --git a/primefaces/src/main/java/org/primefaces/component/spinner/SpinnerBase.java b/primefaces/src/main/java/org/primefaces/component/spinner/SpinnerBase.java
index 7f7ae4f484..e92edbbbf2 100644
--- a/primefaces/src/main/java/org/primefaces/component/spinner/SpinnerBase.java
+++ b/primefaces/src/main/java/org/primefaces/component/spinner/SpinnerBase.java
@@ -60,6 +60,8 @@ public abstract class SpinnerBase extends AbstractPrimeHtmlInputText implements
     public static final String BUTTONS_HORIZONTAL = "horizontal";
     public static final String BUTTONS_HORIZONTAL_AFTER = "horizontal-after";
     public static final String BUTTONS_VERTICAL = "vertical";
+    public static final Double MAX_VALUE = Double.MAX_VALUE;
+    public static final Double MIN_VALUE = -Double.MAX_VALUE;
 
     public SpinnerBase() {
         setRendererType(DEFAULT_RENDERER);
@@ -95,7 +97,7 @@ public abstract class SpinnerBase extends AbstractPrimeHtmlInputText implements
     }
 
     public double getMin() {
-        return (Double) getStateHelper().eval(PropertyKeys.min, -Double.MAX_VALUE);
+        return (Double) getStateHelper().eval(PropertyKeys.min, MIN_VALUE);
     }
 
     public void setMin(double min) {
@@ -103,7 +105,7 @@ public abstract class SpinnerBase extends AbstractPrimeHtmlInputText implements
     }
 
     public double getMax() {
-        return (Double) getStateHelper().eval(PropertyKeys.max, Double.MAX_VALUE);
+        return (Double) getStateHelper().eval(PropertyKeys.max, MAX_VALUE);
     }
 
     public void setMax(double max) {
diff --git a/primefaces/src/main/java/org/primefaces/component/spinner/SpinnerRenderer.java b/primefaces/src/main/java/org/primefaces/component/spinner/SpinnerRenderer.java
index c74b5beacd..721162dc5a 100644
--- a/primefaces/src/main/java/org/primefaces/component/spinner/SpinnerRenderer.java
+++ b/primefaces/src/main/java/org/primefaces/component/spinner/SpinnerRenderer.java
@@ -25,9 +25,11 @@ package org.primefaces.component.spinner;
 
 import java.io.IOException;
 import java.math.BigInteger;
+
 import javax.faces.component.UIComponent;
 import javax.faces.context.FacesContext;
 import javax.faces.context.ResponseWriter;
+
 import org.primefaces.renderkit.InputRenderer;
 import org.primefaces.util.ComponentUtils;
 import org.primefaces.util.HTML;
@@ -98,8 +100,8 @@ public class SpinnerRenderer extends InputRenderer {
         wb.init("Spinner", spinner)
                 .attr("step", spinner.getStepFactor(), 1.0)
                 .attr("round", spinner.isRound(), false)
-                .attr("min", spinner.getMin(), Double.MIN_VALUE)
-                .attr("max", spinner.getMax(), Double.MAX_VALUE)
+                .attr("min", spinner.getMin(), Spinner.MIN_VALUE)
+                .attr("max", spinner.getMax(), Spinner.MAX_VALUE)
                 .attr("prefix", spinner.getPrefix(), null)
                 .attr("suffix", spinner.getSuffix(), null)
                 .attr("required", spinner.isRequired(), false)
diff --git a/primefaces/src/main/java/org/primefaces/metadata/transformer/AbstractInputMetadataTransformer.java b/primefaces/src/main/java/org/primefaces/metadata/transformer/AbstractInputMetadataTransformer.java
index 62dea594a9..77281d8e68 100644
--- a/primefaces/src/main/java/org/primefaces/metadata/transformer/AbstractInputMetadataTransformer.java
+++ b/primefaces/src/main/java/org/primefaces/metadata/transformer/AbstractInputMetadataTransformer.java
@@ -72,7 +72,7 @@ public abstract class AbstractInputMetadataTransformer implements MetadataTransf
         return Integer.MIN_VALUE;
     }
 
-    protected boolean isMaxlenghtSet(UIInput input) {
+    protected boolean isMaxlengthSet(UIInput input) {
         return getMaxlength(input) != Integer.MIN_VALUE;
     }
 
diff --git a/primefaces/src/main/java/org/primefaces/metadata/transformer/impl/BeanValidationInputMetadataTransformer.java b/primefaces/src/main/java/org/primefaces/metadata/transformer/impl/BeanValidationInputMetadataTransformer.java
index 1e5d4e715d..bdd68b94ab 100644
--- a/primefaces/src/main/java/org/primefaces/metadata/transformer/impl/BeanValidationInputMetadataTransformer.java
+++ b/primefaces/src/main/java/org/primefaces/metadata/transformer/impl/BeanValidationInputMetadataTransformer.java
@@ -56,7 +56,7 @@ public class BeanValidationInputMetadataTransformer extends AbstractInputMetadat
 
     @Override
     public void transformInput(FacesContext context, PrimeApplicationContext applicationContext, UIInput input) throws IOException {
-        if (ComponentUtils.isDisabledOrReadonly(input) || (input.isRequired() && isMaxlenghtSet(input))) {
+        if (ComponentUtils.isDisabledOrReadonly(input) || (input.isRequired() && isMaxlengthSet(input))) {
             return;
         }
 
@@ -77,14 +77,14 @@ public class BeanValidationInputMetadataTransformer extends AbstractInputMetadat
         }
     }
 
-    protected void applyConstraint(ConstraintDescriptor constraintDescriptor, UIInput input) {
+    protected void applyConstraint(ConstraintDescriptor<?> constraintDescriptor, UIInput input) {
 
         Annotation constraint = constraintDescriptor.getAnnotation();
         Class<? extends Annotation> annotationType = constraint.annotationType();
         // for BeanValidation 2.0
         String annotationClassName = annotationType.getSimpleName();
 
-        if (!isMaxlenghtSet(input)) {
+        if (!isMaxlengthSet(input)) {
             if (annotationType.equals(Size.class)) {
                 Size size = (Size) constraint;
                 if (size.max() > 0) {
@@ -96,15 +96,15 @@ public class BeanValidationInputMetadataTransformer extends AbstractInputMetadat
         if (input instanceof Spinner) {
             Spinner spinner = (Spinner) input;
 
-            if (annotationType.equals(Max.class) && spinner.getMax() == Double.MAX_VALUE) {
+            if (annotationType.equals(Max.class) && spinner.getMax() == Spinner.MAX_VALUE) {
                 Max max = (Max) constraint;
                 spinner.setMax(max.value());
             }
-            if (annotationType.equals(Min.class) && spinner.getMin() == Double.MIN_VALUE) {
+            if (annotationType.equals(Min.class) && spinner.getMin() == Spinner.MIN_VALUE) {
                 Min min = (Min) constraint;
                 spinner.setMin(min.value());
             }
-            if (annotationType.equals(DecimalMax.class) && spinner.getMax() == Double.MAX_VALUE) {
+            if (annotationType.equals(DecimalMax.class) && spinner.getMax() == Spinner.MAX_VALUE) {
                 DecimalMax max = (DecimalMax) constraint;
                 try {
                     spinner.setMax(Double.parseDouble(max.value()));
@@ -113,7 +113,7 @@ public class BeanValidationInputMetadataTransformer extends AbstractInputMetadat
                     LOGGER.log(Level.WARNING, () -> "Failed setting Spinner max value: " + ex.getMessage());
                 }
             }
-            if (annotationType.equals(DecimalMin.class) && spinner.getMin() == Double.MIN_VALUE) {
+            if (annotationType.equals(DecimalMin.class) && spinner.getMin() == Spinner.MIN_VALUE) {
                 DecimalMin min = (DecimalMin) constraint;
                 try {
                     spinner.setMin(Double.parseDouble(min.value()));
diff --git a/primefaces/src/main/java/org/primefaces/util/LangUtils.java b/primefaces/src/main/java/org/primefaces/util/LangUtils.java
index 8f4a2b5fa7..f1d4c97505 100644
--- a/primefaces/src/main/java/org/primefaces/util/LangUtils.java
+++ b/primefaces/src/main/java/org/primefaces/util/LangUtils.java
@@ -23,6 +23,8 @@
  */
 package org.primefaces.util;
 
+import static java.util.Locale.ENGLISH;
+
 import java.beans.BeanInfo;
 import java.beans.IntrospectionException;
 import java.beans.Introspector;
@@ -484,6 +486,19 @@ public class LangUtils {
         return String.join(" ", values);
     }
 
+    /**
+     * Capitalizes the first character of the given string.
+     *
+     * @param name The string to capitalize.
+     * @return The capitalized string if the input is not blank; otherwise, returns the input string unchanged.
+     */
+    public static String capitalize(String name) {
+        if (isBlank(name)) {
+            return name;
+        }
+        return name.substring(0, 1).toUpperCase(ENGLISH) + name.substring(1);
+    }
+
 
     /**
      * <p>Checks whether the given String is a parsable number.</p>
diff --git a/primefaces/src/main/java/org/primefaces/util/PropertyDescriptorResolver.java b/primefaces/src/main/java/org/primefaces/util/PropertyDescriptorResolver.java
index a1552da866..d6e7f4c5c2 100644
--- a/primefaces/src/main/java/org/primefaces/util/PropertyDescriptorResolver.java
+++ b/primefaces/src/main/java/org/primefaces/util/PropertyDescriptorResolver.java
@@ -23,12 +23,14 @@
  */
 package org.primefaces.util;
 
+
 import java.beans.IntrospectionException;
 import java.beans.PropertyDescriptor;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Pattern;
+
 import javax.faces.FacesException;
 
 public interface PropertyDescriptorResolver {
@@ -99,7 +101,18 @@ public interface PropertyDescriptorResolver {
                     return new PropertyDescriptor(k, klazz);
                 }
                 catch (IntrospectionException e) {
-                    throw new FacesException(e);
+                    // try fallback without write method, our main concern is to read properties here
+                    try {
+                        // "is" + capitalize(k) is actually copied from another PropertyDescriptor constructor
+                        // it will somehow also work for "get" prefix
+                        return new PropertyDescriptor(k,
+                                klazz,
+                                "is" + LangUtils.capitalize(k),
+                                null);
+                    }
+                    catch (IntrospectionException e2) {
+                        throw new FacesException(e2);
+                    }
                 }
             });
         }
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/datepicker/0-datepicker.js b/primefaces/src/main/resources/META-INF/resources/primefaces/datepicker/0-datepicker.js
index 18fdc5050f..b36b8cf291 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/datepicker/0-datepicker.js
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/datepicker/0-datepicker.js
@@ -3284,14 +3284,14 @@
          * @throws {Error} If the input Java time zone string is in an invalid format.
          */
         convertTimeZone: function (javaTimeZone) {
-            if (!javaTimeZone || javaTimeZone.toUpperCase() === 'UTC' || /^(GMT|UTC)$/.test(javaTimeZone)) {
+            if (!javaTimeZone || ['ETC/UTC', 'ETC/GMT', 'UTC', 'GMT'].includes(javaTimeZone.toUpperCase())) {
                 return javaTimeZone;
             }
 
             // Extract the sign and the offset (hours and minutes)
             const matches = javaTimeZone.match(/^(GMT|UTC)([+-])(\d{2}):(\d{2})$/);
             if (!matches) {
-                throw new Error('Invalid GMT/UTC format');
+                return javaTimeZone;
             }
 
             const sign = matches[2];


Test patch:
diff --git a/primefaces/src/test/java/org/primefaces/util/PropertyDescriptorResolverTest.java b/primefaces/src/test/java/org/primefaces/util/PropertyDescriptorResolverTest.java
index 5c0beb1e12..8bf271fdcf 100644
--- a/primefaces/src/test/java/org/primefaces/util/PropertyDescriptorResolverTest.java
+++ b/primefaces/src/test/java/org/primefaces/util/PropertyDescriptorResolverTest.java
@@ -23,9 +23,13 @@
  */
 package org.primefaces.util;
 
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.beans.PropertyDescriptor;
+
 import javax.faces.FacesException;
 
 import org.junit.jupiter.api.Test;
@@ -75,6 +79,20 @@ class PropertyDescriptorResolverTest {
         assertThrows(FacesException.class, () -> propResolver.getValue(b, "unknown"));
     }
 
+    @Test
+    void getValueFromReadOnlyPropertyDescriptor() {
+        PropertyDescriptorResolver propResolver = new PropertyDescriptorResolver.DefaultResolver();
+
+        F f = new F(true, false, "test");
+        assertEquals(true, propResolver.getValue(f, "readOnly1"));
+        assertEquals(false, propResolver.getValue(f, "readOnly2"));
+        assertEquals("test", propResolver.getValue(f, "readOnly3"));
+
+        assertNull(propResolver.get(f.getClass(), "readOnly1").getWriteMethod());
+        assertNull(propResolver.get(f.getClass(), "readOnly2").getWriteMethod());
+        assertNull(propResolver.get(f.getClass(), "readOnly3").getWriteMethod());
+    }
+
     private abstract class A {
 
         private String name;
@@ -169,4 +187,28 @@ class PropertyDescriptorResolverTest {
         Y,
         Z;
     }
+
+    public class F {
+        private boolean readOnly1;
+        private boolean readOnly2;
+        private String readOnly3;
+
+        public F(boolean readOnly1, boolean readOnly2, String readOnly3) {
+            this.readOnly1 = readOnly1;
+            this.readOnly2 = readOnly2;
+            this.readOnly3 = readOnly3;
+        }
+
+        public boolean isReadOnly1() {
+            return readOnly1;
+        }
+
+        public boolean isReadOnly2() {
+            return readOnly2;
+        }
+
+        public String getReadOnly3() {
+            return readOnly3;
+        }
+    }
 }