Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ ValueConverter instantValueConverter() {
return jsr310ConvertersConfiguration.instantValueConverter();
}

@Bean("instantStructuredBindingEditor")
TypedStructuredBindingEditor instantStructuredBindingEditor() {
return jsr310ConvertersConfiguration.instantStructuredBindingEditor();
}

@Bean("defaultUUIDConverter")
protected UUIDConverter defaultuuidConverter() {
return new UUIDConverter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,21 @@ class Jsr310ConvertersConfiguration {
}
}

@Bean
TypedStructuredBindingEditor instantStructuredBindingEditor() {
new CustomDateBindingEditor<Instant>() {
@Override
Instant getDate(Calendar c) {
c.toInstant()
}

@Override
Class<?> getTargetType() {
Instant
}
}
}

abstract class Jsr310DateValueConverter<T> implements ValueConverter {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ package grails.plugin.formfields

import java.sql.Blob
import java.text.NumberFormat
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.time.ZonedDateTime

import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
Expand Down Expand Up @@ -725,7 +728,7 @@ class FormFieldsTagLib {
}

// TODO: https://github.com/apache/grails-core/issues/14198
boolean datePicker = model.type in [Date, Calendar, java.sql.Date, java.sql.Time, LocalDate, LocalDateTime]
boolean datePicker = model.type in [Date, Calendar, java.sql.Date, java.sql.Time, LocalDate, LocalDateTime, Instant, ZonedDateTime, OffsetDateTime]
if (!datePicker) {
attrs.remove('selectDateClass')
}
Expand Down Expand Up @@ -947,12 +950,17 @@ class FormFieldsTagLib {
case Boolean:
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 LocalDate:
case LocalDateTime:
case Instant:
case ZonedDateTime:
case OffsetDateTime:
g.formatDate(date: model.value)
break
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package grails.plugin.formfields

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

def setupSpec() {
mockDomain(Person)
mockDomains(Person, Cyborg)
}

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

void 'f:displayWidget without template and an instant value renders the formatted date'() {
expect:
applyTemplate('<f:displayWidget bean="cyborgInstance" property="timestamp"/>', [cyborgInstance: cyborgInstance]) == applyTemplate('<g:formatDate date="${cyborgInstance.timestamp}"/>', [cyborgInstance: cyborgInstance])
}

void 'f:displayWidget without template and a LocalDate value renders the formatted date'() {
expect:
applyTemplate('<f:displayWidget bean="cyborgInstance" property="birthDate"/>', [cyborgInstance: cyborgInstance]) == applyTemplate('<g:formatDate date="${cyborgInstance.birthDate}" format="yyyy-MM-dd"/>', [cyborgInstance: cyborgInstance])
}

void 'f:displayWidget without template and a boolean value renders the formatted boolean'() {
expect:
applyTemplate('<f:displayWidget bean="personInstance" property="minor"/>', [personInstance: personInstance]) == applyTemplate('<g:formatBoolean boolean="${personInstance.minor}"/>', [personInstance: personInstance])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@
*/
package grails.plugin.formfields.mock

import java.time.Instant
import java.time.LocalDate

import grails.gorm.annotation.AutoTimestamp
import grails.persistence.Entity

@Entity
class Cyborg extends Person {
@AutoTimestamp(AutoTimestamp.EventType.CREATED) Date created
@AutoTimestamp Date modified
Instant timestamp
LocalDate birthDate
}

@Entity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

package grails.plugin.formfields.taglib

import java.time.Instant
import java.time.LocalDate

import grails.core.support.proxy.DefaultProxyHandler
import grails.plugin.formfields.BeanPropertyAccessorFactory
import grails.plugin.formfields.FieldsGrailsPlugin
Expand All @@ -45,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: "[email protected]", school: "[email protected]"]
productInstance = new Product(netPrice: 12.33, name: "<script>alert('XSS');</script>")
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"), birthDate: LocalDate.of(2025, 10, 15))
}

def cleanup() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Comment on lines +137 to +138
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps, for a future version, make ZoneId.systemDefault() into something that could be configured?

} else if (date instanceof OffsetDateTime) {
zonedDateTime = ((OffsetDateTime) date).toZonedDateTime()

Expand Down
Loading