42

Why is it so hard to extract the date from the view via the params in a grails controller?

I don't want to extract the date by hand like this:

instance.dateX = parseDate(params["dateX_value"])//parseDate is from my helper class

I just want to use instance.properties = params.

In the model the type is java.util.Date and in the params is all the information: [dateX_month: 'value', dateX_day: 'value', ...]

I searched on the net and found nothing on this. I hoped that Grails 1.3.0 could help but still the same thing.

I can't and will not believe that extracting the date by hand is necessary!

Rob Hruska
  • 118,520
  • 32
  • 167
  • 192
nils petersohn
  • 2,317
  • 2
  • 25
  • 27
  • note that in recent (2.0.x) versions of Grails there is a bug that affects date binding: http://jira.grails.org/browse/GRAILS-9165 – Ken Liu Jul 17 '12 at 13:43
  • For posterity: note that the Joda Time plugin implements databinding automatically. – Charles Wood Aug 15 '14 at 21:30

6 Answers6

89

Grails Version >= 2.3

A setting in Config.groovy defines the date formats which will be used application-wide when binding params to a Date

grails.databinding.dateFormats = [
        'MMddyyyy', 'yyyy-MM-dd HH:mm:ss.S', "yyyy-MM-dd'T'hh:mm:ss'Z'"
]

The formats specified in grails.databinding.dateFormats will be attempted in the order in which they are included in the List.

You can override these application-wide formats for an individual command object using @BindingFormat

import org.grails.databinding.BindingFormat

class Person { 
    @BindingFormat('MMddyyyy') 
    Date birthDate 
}

Grails Version < 2.3

i can't and will not belief that extracting the date by hand is nessesary!

Your stubbornness is rewarded, it has been possible to bind a date directly since long before Grails 1.3. The steps are:

(1) Create a class that registers an editor for your date format

import org.springframework.beans.PropertyEditorRegistrar
import org.springframework.beans.PropertyEditorRegistry
import org.springframework.beans.propertyeditors.CustomDateEditor
import java.text.SimpleDateFormat

public class CustomDateEditorRegistrar implements PropertyEditorRegistrar {

    public void registerCustomEditors(PropertyEditorRegistry registry) {

        String dateFormat = 'yyyy/MM/dd'
        registry.registerCustomEditor(Date, new CustomDateEditor(new SimpleDateFormat(dateFormat), true))
    }
}

(2) Make Grails aware of this date editor by registering the following bean in grails-app/conf/spring/resources.groovy

beans = {
    customPropertyEditorRegistrar(CustomDateEditorRegistrar)
}

(3) Now when you send a date in a parameter named foo in the format yyyy/MM/dd it will automatically be bound to a property named foo using either:

myDomainObject.properties = params

or

new MyDomainClass(params)
Dónal
  • 185,044
  • 174
  • 569
  • 824
  • thanx for the quick answer, i tried this before (already had the customPropertyEditorRegistrar in my spring resources, and i saw the old stackoverflow question you mentioned above) but this does not work for me somehow. my class looks like this: class CustomDateEditorRegistrar implements PropertyEditorRegistrar { public void registerCustomEditors(PropertyEditorRegistry registry) { String dateFormat = 'dd.MM.yyyy' registry.registerCustomEditor(Date, new CustomDateEditor(new SimpleDateFormat(dateFormat), true)) } } and the debugger is picking it up properly. – nils petersohn May 22 '10 at 13:28
  • is there maybe a problem with my dateformat (dots)? or something? – nils petersohn May 22 '10 at 13:29
  • i'm getting the following warning: `project/src/java/CustomEditorRegistrar.java uses or overrides a deprecated API`. is there a 'new' way of doing this? – zoran119 Oct 30 '12 at 22:39
  • 1
    @zoran119 I've no idea. You'll need to make some small changes to the code above if you're going to use Java instead of Groovy – Dónal Oct 31 '12 at 09:41
  • sorry don, i posted the comment in the wrong question :( my comment was for this question: http://stackoverflow.com/questions/963922/grails-date-unmarshalling – zoran119 Oct 31 '12 at 21:09
  • @Don, great answer however to the OP's point, it seems absurd that this is required for a Date. Perhaps if you want a non-standard Date format. However, it seems there should be a default format that just works without having to add anything custom. – Josh Petitt Sep 27 '13 at 22:39
  • I think it is worth mentioning that not all these solutions work if you want your application to be internationalized. If you do, you can use @BindingFormat(code='default.date.format') or see the answer from @mpccolorado. – Cookalino Nov 05 '18 at 16:40
15

Grails 2.1.1 has new method in params for easy null safe parsing.

def val = params.date('myDate', 'dd-MM-yyyy')
// or a list for formats
def val = params.date('myDate', ['yyyy-MM-dd', 'yyyyMMdd', 'yyMMdd']) 
// or the format read from messages.properties via the key 'date.myDate.format'
def val = params.date('myDate')

Find it in doc here

Kumar Sambhav
  • 7,503
  • 15
  • 63
  • 86
  • 3
    I consider this reading a date from `params` rather than "binding a date" – Dónal Feb 17 '14 at 23:00
  • Seems to be the best answer. But I tested it and found an issue. Grails raises a "No message found under code date.myDate.format" exception if I don't have the code in my messages.properties. I think the engine should search for a general format (default.date.format) before raising such exception. – Cléssio Mendes Apr 14 '15 at 05:02
12

Grails Version >= 3.x

You can set in application.yml the date formats following this syntax:

grails:
  databinding:
    dateFormats:
      - 'dd/MM/yyyy'
      - 'dd/MM/yyyy HH:mm:ss'
      - 'yyyy-MM-dd HH:mm:ss.S'
      - "yyyy-MM-dd'T'hh:mm:ss'Z'"
      - "yyyy-MM-dd HH:mm:ss.S z"
      - "yyyy-MM-dd'T'HH:mm:ssX"
tgarcia
  • 675
  • 9
  • 21
2

@Don Thanks for the answer above.

Here's an alternative to the custom editor that checks first date time then date format.

Groovy, just add semi colons back in for java

import java.text.DateFormat
import java.text.ParseException
import org.springframework.util.StringUtils
import java.beans.PropertyEditorSupport

class CustomDateTimeEditor extends PropertyEditorSupport {
    private final java.text.DateFormat dateTimeFormat
    private final java.text.DateFormat dateFormat
    private final boolean allowEmpty

    public CustomDateTimeEditor(DateFormat dateTimeFormat, DateFormat dateFormat, boolean allowEmpty) {
        this.dateTimeFormat = dateTimeFormat
        this.dateFormat = dateFormat
        this.allowEmpty = allowEmpty
    }

    /**
     * Parse the Date from the given text, using the specified DateFormat.
     */
    public void setAsText(String   text) throws IllegalArgumentException   {
        if (this.allowEmpty && !StringUtils.hasText(text)) {
            // Treat empty String as null value.
            setValue(null)
        }
        else {
            try {
                setValue(this.dateTimeFormat.parse(text))
            }
            catch (ParseException dtex) {
                try {
                    setValue(this.dateFormat.parse(text))
                }
                catch ( ParseException dex ) {
                    throw new IllegalArgumentException  ("Could not parse date: " + dex.getMessage() + " " + dtex.getMessage() )
                }
            }
        }
    }

    /**
     * Format the Date as String, using the specified DateFormat.
     */
    public String   getAsText() {
        Date   value = (Date) getValue()
        return (value != null ? this.dateFormat.format(value) : "")
    }
}
chim
  • 8,407
  • 3
  • 52
  • 60
2

Have you tried using any of the Grails date picker plugins?

Ive had good experiences with the calendar plugin.

(When using the calendar plugin) When you submit the request of the date selection you can automatically bind the query parameter to the domain object you want to populate with the request.

E.g.

new DomainObject(params)

You can also parse a "yyyy/MM/dd" date string like so...

new Date().parse("yyyy/MM/dd", "2010/03/18")
Dónal
  • 185,044
  • 174
  • 569
  • 824
tinny
  • 4,232
  • 7
  • 28
  • 38
  • 2
    I think calling `new Date.parse()` explicitly is exactly what he wants to avoid. AFAIK, if you bind the parameters directly, you still need to register a custom date editor (as described in my reply). Otherwise, how could the databinder possibly know which field is the month and which is the year (for example). I agree that the calendar plugin is the best of the date-picker plugins available. – Dónal May 21 '10 at 07:47
  • Yep, definitely go with the parameter binding approach. new Date().parse() can be very useful elsewhere though. – tinny May 21 '10 at 07:59
  • my approach right now is very bad i think. i am first removing the date value from the params like this: (params.remove("datefield")) than i am doing something like this: instance.datefield = hlpr.exDate(params["datefield_value"] as String) i know that sounds really strange but thats somehow the only way that it works right now... – nils petersohn May 22 '10 at 13:33
1

Grails Version >= 2.3

A localeAware version to convert strings to date

In src/groovy:

package test

import org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequest
import org.grails.databinding.converters.ValueConverter
import org.springframework.context.MessageSource
import org.springframework.web.servlet.LocaleResolver

import javax.servlet.http.HttpServletRequest
import java.text.SimpleDateFormat

class StringToDateConverter implements ValueConverter {
    MessageSource messageSource
    LocaleResolver localeResolver

    @Override
    boolean canConvert(Object value) {
        return value instanceof String
    }

    @Override
    Object convert(Object value) {
        String format = messageSource.getMessage('default.date.format', null, "dd/MM/yyyy", getLocale())
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format)
        return simpleDateFormat.parse(value)
    }

    @Override
    Class<?> getTargetType() {
        return Date.class
    }

    protected Locale getLocale() {
        def locale
        def request = GrailsWebRequest.lookup()?.currentRequest
        if(request instanceof HttpServletRequest) {
            locale = localeResolver?.resolveLocale(request)
        }
        if(locale == null) {
            locale = Locale.default
        }
        return locale
    }
}

In conf/spring/resources.groovy:

beans = {
    defaultDateConverter(StringToDateConverter){
        messageSource = ref('messageSource')
        localeResolver = ref('localeResolver')
    }
}

The bean's name 'defaultDateConverter' is really important (to override the default date converter)

mpccolorado
  • 817
  • 8
  • 16
  • Nice solution that changes all data binding across the app AND is locale-aware. I'm amazed Grails doesn't do this by default as it would fit with convention over configuration. – Cookalino Nov 05 '18 at 16:43