From 759efbddf3796fe57284a0a44b433deecbf16400 Mon Sep 17 00:00:00 2001 From: Scott Murphy Heiberg Date: Wed, 15 Oct 2025 21:31:44 -0700 Subject: [PATCH 1/5] Uniform formatting for ', [personInstance: personInstance]) == applyTemplate('', [personInstance: personInstance]) } + void 'f:displayWidget without template and an instant value renders the formatted date'() { + expect: + applyTemplate('', [cyborgInstance: cyborgInstance]) == applyTemplate('', [cyborgInstance: cyborgInstance]) + } + void 'f:displayWidget without template and a boolean value renders the formatted boolean'() { expect: applyTemplate('', [personInstance: personInstance]) == applyTemplate('', [personInstance: personInstance]) diff --git a/grails-fields/src/test/groovy/grails/plugin/formfields/mock/Person.groovy b/grails-fields/src/test/groovy/grails/plugin/formfields/mock/Person.groovy index e452e722649..d33172459ca 100644 --- a/grails-fields/src/test/groovy/grails/plugin/formfields/mock/Person.groovy +++ b/grails-fields/src/test/groovy/grails/plugin/formfields/mock/Person.groovy @@ -18,6 +18,8 @@ */ package grails.plugin.formfields.mock +import java.time.Instant + import grails.gorm.annotation.AutoTimestamp import grails.persistence.Entity @@ -25,6 +27,7 @@ import grails.persistence.Entity class Cyborg extends Person { @AutoTimestamp(AutoTimestamp.EventType.CREATED) Date created @AutoTimestamp Date modified + Instant timestamp } @Entity diff --git a/grails-fields/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy b/grails-fields/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy index 7d39cf98e24..3d385250614 100644 --- a/grails-fields/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy +++ b/grails-fields/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy @@ -19,6 +19,8 @@ package grails.plugin.formfields.taglib +import java.time.Instant + import grails.core.support.proxy.DefaultProxyHandler import grails.plugin.formfields.BeanPropertyAccessorFactory import grails.plugin.formfields.FieldsGrailsPlugin @@ -45,7 +47,7 @@ abstract class AbstractFormFieldsTagLibSpec extends Specification implements Gra personInstance.address = new Address(street: "94 Evergreen Terrace", city: "Springfield", country: "USA") personInstance.emails = [home: "bart@thesimpsons.net", school: "bart.simpson@springfieldelementary.edu"] productInstance = new Product(netPrice: 12.33, name: "") - cyborgInstance = new Cyborg(name: "Hal", password: "monolith", gender: null) + cyborgInstance = new Cyborg(name: "Hal", password: "monolith", gender: null, timestamp: Instant.parse("2025-10-16T00:12:15.195Z")) } def cleanup() { From 158078986890ee74c1a76e498b5fbaaeb9bd424f Mon Sep 17 00:00:00 2001 From: Scott Murphy Heiberg Date: Wed, 15 Oct 2025 21:52:12 -0700 Subject: [PATCH 2/5] Fix for LocalDate --- .../taglib/grails/plugin/formfields/FormFieldsTagLib.groovy | 4 +++- .../groovy/grails/plugin/formfields/DisplayWidgetSpec.groovy | 5 +++++ .../test/groovy/grails/plugin/formfields/mock/Person.groovy | 2 ++ .../formfields/taglib/AbstractFormFieldsTagLibSpec.groovy | 3 ++- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/grails-fields/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy b/grails-fields/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy index e1481f43a3f..6949d1518d9 100644 --- a/grails-fields/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy +++ b/grails-fields/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy @@ -950,11 +950,13 @@ class FormFieldsTagLib { case Boolean: g.formatBoolean(boolean: model.value) break + case LocalDate: + g.formatDate(date: model.value, format: 'yyyy-MM-dd') + break case Calendar: case Date: case java.sql.Date: case java.sql.Time: - case LocalDate: case LocalDateTime: case Instant: case ZonedDateTime: diff --git a/grails-fields/src/test/groovy/grails/plugin/formfields/DisplayWidgetSpec.groovy b/grails-fields/src/test/groovy/grails/plugin/formfields/DisplayWidgetSpec.groovy index 77571b69d80..66c88af9eae 100644 --- a/grails-fields/src/test/groovy/grails/plugin/formfields/DisplayWidgetSpec.groovy +++ b/grails-fields/src/test/groovy/grails/plugin/formfields/DisplayWidgetSpec.groovy @@ -46,6 +46,11 @@ class DisplayWidgetSpec extends AbstractFormFieldsTagLibSpec implements TagLibUn applyTemplate('', [cyborgInstance: cyborgInstance]) == applyTemplate('', [cyborgInstance: cyborgInstance]) } + void 'f:displayWidget without template and a LocalDate value renders the formatted date'() { + expect: + applyTemplate('', [cyborgInstance: cyborgInstance]) == applyTemplate('', [cyborgInstance: cyborgInstance]) + } + void 'f:displayWidget without template and a boolean value renders the formatted boolean'() { expect: applyTemplate('', [personInstance: personInstance]) == applyTemplate('', [personInstance: personInstance]) diff --git a/grails-fields/src/test/groovy/grails/plugin/formfields/mock/Person.groovy b/grails-fields/src/test/groovy/grails/plugin/formfields/mock/Person.groovy index d33172459ca..b14749a7e4b 100644 --- a/grails-fields/src/test/groovy/grails/plugin/formfields/mock/Person.groovy +++ b/grails-fields/src/test/groovy/grails/plugin/formfields/mock/Person.groovy @@ -19,6 +19,7 @@ package grails.plugin.formfields.mock import java.time.Instant +import java.time.LocalDate import grails.gorm.annotation.AutoTimestamp import grails.persistence.Entity @@ -28,6 +29,7 @@ class Cyborg extends Person { @AutoTimestamp(AutoTimestamp.EventType.CREATED) Date created @AutoTimestamp Date modified Instant timestamp + LocalDate birthDate } @Entity diff --git a/grails-fields/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy b/grails-fields/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy index 3d385250614..28f8850be5e 100644 --- a/grails-fields/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy +++ b/grails-fields/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy @@ -20,6 +20,7 @@ package grails.plugin.formfields.taglib import java.time.Instant +import java.time.LocalDate import grails.core.support.proxy.DefaultProxyHandler import grails.plugin.formfields.BeanPropertyAccessorFactory @@ -47,7 +48,7 @@ abstract class AbstractFormFieldsTagLibSpec extends Specification implements Gra personInstance.address = new Address(street: "94 Evergreen Terrace", city: "Springfield", country: "USA") personInstance.emails = [home: "bart@thesimpsons.net", school: "bart.simpson@springfieldelementary.edu"] productInstance = new Product(netPrice: 12.33, name: "") - cyborgInstance = new Cyborg(name: "Hal", password: "monolith", gender: null, timestamp: Instant.parse("2025-10-16T00:12:15.195Z")) + cyborgInstance = new Cyborg(name: "Hal", password: "monolith", gender: null, timestamp: Instant.parse("2025-10-16T00:12:15.195Z"), birthDate: LocalDate.of(2025, 10, 15)) } def cleanup() { From 9d464cc8c7f090af2d84c8219ea69c743cc429f6 Mon Sep 17 00:00:00 2001 From: Scott Murphy Heiberg Date: Wed, 15 Oct 2025 22:02:25 -0700 Subject: [PATCH 3/5] Instant support for DefaultGrailsTagDateHelper --- .../org/grails/plugins/web/DefaultGrailsTagDateHelper.groovy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/DefaultGrailsTagDateHelper.groovy b/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/DefaultGrailsTagDateHelper.groovy index 0678aa34891..0ba8e39fc52 100644 --- a/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/DefaultGrailsTagDateHelper.groovy +++ b/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/DefaultGrailsTagDateHelper.groovy @@ -134,6 +134,8 @@ class DefaultGrailsTagDateHelper implements GrailsTagDateHelper { zonedDateTime = ZonedDateTime.of(date, ZoneId.systemDefault()) } else if (date instanceof LocalDate) { zonedDateTime = ZonedDateTime.of(date, LocalTime.MIN, ZoneId.systemDefault()) + } else if (date instanceof Instant) { + zonedDateTime = ZonedDateTime.ofInstant(date, ZoneId.systemDefault()) } else if (date instanceof OffsetDateTime) { zonedDateTime = ((OffsetDateTime) date).toZonedDateTime() From 44ee7d6bf12dc95e7d675bdfd16adb6644aca7c3 Mon Sep 17 00:00:00 2001 From: Scott Murphy Heiberg Date: Fri, 17 Oct 2025 13:20:23 -0700 Subject: [PATCH 4/5] Fix instant binding --- .../DefaultConvertersConfiguration.java | 5 +++++ .../Jsr310ConvertersConfiguration.groovy | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/grails-databinding/src/main/groovy/org/grails/databinding/converters/DefaultConvertersConfiguration.java b/grails-databinding/src/main/groovy/org/grails/databinding/converters/DefaultConvertersConfiguration.java index 44e6099b7fc..1907a9423ef 100644 --- a/grails-databinding/src/main/groovy/org/grails/databinding/converters/DefaultConvertersConfiguration.java +++ b/grails-databinding/src/main/groovy/org/grails/databinding/converters/DefaultConvertersConfiguration.java @@ -177,6 +177,11 @@ ValueConverter instantValueConverter() { return jsr310ConvertersConfiguration.instantValueConverter(); } + @Bean("instantStructuredBindingEditor") + TypedStructuredBindingEditor instantStructuredBindingEditor() { + return jsr310ConvertersConfiguration.instantStructuredBindingEditor(); + } + @Bean("defaultUUIDConverter") protected UUIDConverter defaultuuidConverter() { return new UUIDConverter(); diff --git a/grails-databinding/src/main/groovy/org/grails/databinding/converters/Jsr310ConvertersConfiguration.groovy b/grails-databinding/src/main/groovy/org/grails/databinding/converters/Jsr310ConvertersConfiguration.groovy index ca8f0cf556b..f89991491ed 100644 --- a/grails-databinding/src/main/groovy/org/grails/databinding/converters/Jsr310ConvertersConfiguration.groovy +++ b/grails-databinding/src/main/groovy/org/grails/databinding/converters/Jsr310ConvertersConfiguration.groovy @@ -388,6 +388,21 @@ class Jsr310ConvertersConfiguration { } } + @Bean + TypedStructuredBindingEditor instantStructuredBindingEditor() { + new CustomDateBindingEditor() { + @Override + Instant getDate(Calendar c) { + c.toInstant() + } + + @Override + Class getTargetType() { + Instant + } + } + } + abstract class Jsr310DateValueConverter implements ValueConverter { @Override From 9f99635b1a370f9d108c98cebf82d5653337a741 Mon Sep 17 00:00:00 2001 From: Scott Murphy Heiberg Date: Fri, 17 Oct 2025 14:14:45 -0700 Subject: [PATCH 5/5] fix for java.sql.Date throwing an exception --- .../taglib/grails/plugin/formfields/FormFieldsTagLib.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grails-fields/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy b/grails-fields/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy index 6949d1518d9..ec88cf18df0 100644 --- a/grails-fields/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy +++ b/grails-fields/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy @@ -951,11 +951,11 @@ class FormFieldsTagLib { g.formatBoolean(boolean: model.value) break case LocalDate: + case java.sql.Date: g.formatDate(date: model.value, format: 'yyyy-MM-dd') break case Calendar: case Date: - case java.sql.Date: case java.sql.Time: case LocalDateTime: case Instant: