Skip to content

Commit c27742f

Browse files
authored
Merge pull request #15146 from codeconsole/7.0.x-dateTimeFormatting
7.0.x Fix for Fields Rendering of java.time.*
2 parents 9936ba7 + 48a710e commit c27742f

File tree

7 files changed

+62
-6
lines changed

7 files changed

+62
-6
lines changed

grails-databinding/src/main/groovy/org/grails/databinding/converters/DefaultConvertersConfiguration.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,11 @@ ValueConverter instantValueConverter() {
177177
return jsr310ConvertersConfiguration.instantValueConverter();
178178
}
179179

180+
@Bean("instantStructuredBindingEditor")
181+
TypedStructuredBindingEditor instantStructuredBindingEditor() {
182+
return jsr310ConvertersConfiguration.instantStructuredBindingEditor();
183+
}
184+
180185
@Bean("defaultUUIDConverter")
181186
protected UUIDConverter defaultuuidConverter() {
182187
return new UUIDConverter();

grails-databinding/src/main/groovy/org/grails/databinding/converters/Jsr310ConvertersConfiguration.groovy

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,21 @@ class Jsr310ConvertersConfiguration {
388388
}
389389
}
390390

391+
@Bean
392+
TypedStructuredBindingEditor instantStructuredBindingEditor() {
393+
new CustomDateBindingEditor<Instant>() {
394+
@Override
395+
Instant getDate(Calendar c) {
396+
c.toInstant()
397+
}
398+
399+
@Override
400+
Class<?> getTargetType() {
401+
Instant
402+
}
403+
}
404+
}
405+
391406
abstract class Jsr310DateValueConverter<T> implements ValueConverter {
392407

393408
@Override

grails-fields/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,12 @@ package grails.plugin.formfields
2020

2121
import java.sql.Blob
2222
import java.text.NumberFormat
23+
import java.time.Instant
2324
import java.time.LocalDate
2425
import java.time.LocalDateTime
26+
import java.time.LocalTime
27+
import java.time.OffsetDateTime
28+
import java.time.ZonedDateTime
2529

2630
import groovy.transform.CompileStatic
2731
import groovy.util.logging.Slf4j
@@ -725,7 +729,7 @@ class FormFieldsTagLib {
725729
}
726730

727731
// TODO: https://github.com/apache/grails-core/issues/14198
728-
boolean datePicker = model.type in [Date, Calendar, java.sql.Date, java.sql.Time, LocalDate, LocalDateTime]
732+
boolean datePicker = model.type in [Date, Calendar, java.sql.Date, java.sql.Time, java.sql.Timestamp, LocalDate, LocalDateTime, Instant, ZonedDateTime, OffsetDateTime]
729733
if (!datePicker) {
730734
attrs.remove('selectDateClass')
731735
}
@@ -947,12 +951,21 @@ class FormFieldsTagLib {
947951
case Boolean:
948952
g.formatBoolean(boolean: model.value)
949953
break
950-
case Calendar:
951-
case Date:
954+
case LocalDate:
952955
case java.sql.Date:
956+
g.formatDate(date: model.value, type: 'DATE')
957+
break
953958
case java.sql.Time:
954-
case LocalDate:
959+
case LocalTime:
960+
g.formatDate(date: model.value, type: 'TIME')
961+
break
962+
case Calendar:
963+
case Date:
955964
case LocalDateTime:
965+
case java.sql.Timestamp:
966+
case Instant:
967+
case ZonedDateTime:
968+
case OffsetDateTime:
956969
g.formatDate(date: model.value)
957970
break
958971
default:

grails-fields/src/test/groovy/grails/plugin/formfields/DisplayWidgetSpec.groovy

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package grails.plugin.formfields
2020

21+
import grails.plugin.formfields.mock.Cyborg
2122
import grails.plugin.formfields.mock.Person
2223
import grails.plugin.formfields.taglib.AbstractFormFieldsTagLibSpec
2324
import grails.testing.web.taglib.TagLibUnitTest
@@ -27,7 +28,7 @@ class DisplayWidgetSpec extends AbstractFormFieldsTagLibSpec implements TagLibUn
2728
def mockFormFieldsTemplateService = Mock(FormFieldsTemplateService)
2829

2930
def setupSpec() {
30-
mockDomain(Person)
31+
mockDomains(Person, Cyborg)
3132
}
3233

3334
def setup() {
@@ -40,6 +41,16 @@ class DisplayWidgetSpec extends AbstractFormFieldsTagLibSpec implements TagLibUn
4041
applyTemplate('<f:displayWidget bean="personInstance" property="dateOfBirth"/>', [personInstance: personInstance]) == applyTemplate('<g:formatDate date="${personInstance.dateOfBirth}"/>', [personInstance: personInstance])
4142
}
4243

44+
void 'f:displayWidget without template and an instant value renders the formatted date'() {
45+
expect:
46+
applyTemplate('<f:displayWidget bean="cyborgInstance" property="timestamp"/>', [cyborgInstance: cyborgInstance]) == applyTemplate('<g:formatDate date="${cyborgInstance.timestamp}"/>', [cyborgInstance: cyborgInstance])
47+
}
48+
49+
void 'f:displayWidget without template and a LocalDate value renders the formatted date'() {
50+
expect:
51+
applyTemplate('<f:displayWidget bean="cyborgInstance" property="birthDate"/>', [cyborgInstance: cyborgInstance]) == applyTemplate('<g:formatDate date="${cyborgInstance.birthDate}" type="DATE"/>', [cyborgInstance: cyborgInstance])
52+
}
53+
4354
void 'f:displayWidget without template and a boolean value renders the formatted boolean'() {
4455
expect:
4556
applyTemplate('<f:displayWidget bean="personInstance" property="minor"/>', [personInstance: personInstance]) == applyTemplate('<g:formatBoolean boolean="${personInstance.minor}"/>', [personInstance: personInstance])

grails-fields/src/test/groovy/grails/plugin/formfields/mock/Person.groovy

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,18 @@
1818
*/
1919
package grails.plugin.formfields.mock
2020

21+
import java.time.Instant
22+
import java.time.LocalDate
23+
2124
import grails.gorm.annotation.AutoTimestamp
2225
import grails.persistence.Entity
2326

2427
@Entity
2528
class Cyborg extends Person {
2629
@AutoTimestamp(AutoTimestamp.EventType.CREATED) Date created
2730
@AutoTimestamp Date modified
31+
Instant timestamp
32+
LocalDate birthDate
2833
}
2934

3035
@Entity

grails-fields/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919

2020
package grails.plugin.formfields.taglib
2121

22+
import java.time.Instant
23+
import java.time.LocalDate
24+
2225
import grails.core.support.proxy.DefaultProxyHandler
2326
import grails.plugin.formfields.BeanPropertyAccessorFactory
2427
import grails.plugin.formfields.FieldsGrailsPlugin
@@ -45,7 +48,7 @@ abstract class AbstractFormFieldsTagLibSpec extends Specification implements Gra
4548
personInstance.address = new Address(street: "94 Evergreen Terrace", city: "Springfield", country: "USA")
4649
personInstance.emails = [home: "[email protected]", school: "[email protected]"]
4750
productInstance = new Product(netPrice: 12.33, name: "<script>alert('XSS');</script>")
48-
cyborgInstance = new Cyborg(name: "Hal", password: "monolith", gender: null)
51+
cyborgInstance = new Cyborg(name: "Hal", password: "monolith", gender: null, timestamp: Instant.parse("2025-10-16T00:12:15.195Z"), birthDate: LocalDate.of(2025, 10, 15))
4952
}
5053

5154
def cleanup() {

grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/DefaultGrailsTagDateHelper.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ class DefaultGrailsTagDateHelper implements GrailsTagDateHelper {
9393
TemporalAccessor instant
9494
if (date instanceof java.sql.Date) {
9595
instant = date.toLocalDate()
96+
} else if (date instanceof java.sql.Time) {
97+
instant = date.toLocalTime()
9698
} else if (date instanceof Date) {
9799
instant = date.toInstant()
98100
} else if (date instanceof Calendar) {
@@ -134,6 +136,8 @@ class DefaultGrailsTagDateHelper implements GrailsTagDateHelper {
134136
zonedDateTime = ZonedDateTime.of(date, ZoneId.systemDefault())
135137
} else if (date instanceof LocalDate) {
136138
zonedDateTime = ZonedDateTime.of(date, LocalTime.MIN, ZoneId.systemDefault())
139+
} else if (date instanceof Instant) {
140+
zonedDateTime = ZonedDateTime.ofInstant(date, ZoneId.systemDefault())
137141
} else if (date instanceof OffsetDateTime) {
138142
zonedDateTime = ((OffsetDateTime) date).toZonedDateTime()
139143

0 commit comments

Comments
 (0)