Grails date validation - validation

I am new to Grails/GSP and want to implement date validation in Grails. Validation should be for input text in text field as required format is mm/dd/yyyy. Any guidance is appreciated. Also please point me to some good Grails and GSP tutorials if any one knows. Thanks in advance.

To require a specific date format for date input, you can use a custom property editor.
In src/groovy/ you can add a class to register the custom editor which will be used in data binding.
class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
void registerCustomEditors(PropertyEditorRegistry registry) {
def format = 'MM/dd/yyyy'
def dateFormat = new SimpleDateFormat(format)
dateFormat.setLenient(false)
registry.registerCustomEditor(Date, new CustomDateEditor(dateFormat, true, format.size()))
}
}
and register the bean in resources.groovy
beans = {
customPropertyEditorRegistrar(CustomPropertyEditorRegistrar)
}

Grails has a default date and time picker that it uses in scaffolds, you can use that but if you want a textbox to enter the date, you can validate the format using jQuery or javascript, then parse the string into date using:
try{
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm");
date = format.parse(stringDate);
}catch (Exception e) {
//something
}
You can use regular expression with javascript to validate the format of entered date in view. This might be useful for that.

Related

Parse all ISO 8601 DateTime formats using Java 1.8 [duplicate]

I am trying to convert an ISO 8601 formatted String to a java.util.Date.
I found the pattern yyyy-MM-dd'T'HH:mm:ssZ to be ISO8601-compliant if used with a Locale (compare sample).
However, using the java.text.SimpleDateFormat, I cannot convert the correctly formatted String 2010-01-01T12:00:00+01:00. I have to convert it first to 2010-01-01T12:00:00+0100, without the colon.
So, the current solution is
SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.GERMANY);
String date = "2010-01-01T12:00:00+01:00".replaceAll("\\+0([0-9]){1}\\:00", "+0$100");
System.out.println(ISO8601DATEFORMAT.parse(date));
which obviously isn't that nice. Am I missing something or is there a better solution?
Answer
Thanks to JuanZe's comment, I found the Joda-Time magic, it is also described here.
So, the solution is
DateTimeFormatter parser2 = ISODateTimeFormat.dateTimeNoMillis();
String jtdate = "2010-01-01T12:00:00+01:00";
System.out.println(parser2.parseDateTime(jtdate));
Or more simply, use the default parser via the constructor:
DateTime dt = new DateTime( "2010-01-01T12:00:00+01:00" ) ;
To me, this is nice.
Unfortunately, the time zone formats available to SimpleDateFormat (Java 6 and earlier) are not ISO 8601 compliant. SimpleDateFormat understands time zone strings like "GMT+01:00" or "+0100", the latter according to RFC # 822.
Even if Java 7 added support for time zone descriptors according to ISO 8601, SimpleDateFormat is still not able to properly parse a complete date string, as it has no support for optional parts.
Reformatting your input string using regexp is certainly one possibility, but the replacement rules are not as simple as in your question:
Some time zones are not full hours off UTC, so the string does not necessarily end with ":00".
ISO8601 allows only the number of hours to be included in the time zone, so "+01" is equivalent to "+01:00"
ISO8601 allows the usage of "Z" to indicate UTC instead of "+00:00".
The easier solution is possibly to use the data type converter in JAXB, since JAXB must be able to parse ISO8601 date string according to the XML Schema specification. javax.xml.bind.DatatypeConverter.parseDateTime("2010-01-01T12:00:00Z") will give you a Calendar object and you can simply use getTime() on it, if you need a Date object.
You could probably use Joda-Time as well, but I don't know why you should bother with that (Update 2022; maybe because the entire javax.xml.bind section is missing from Android's javax.xml package).
The way that is blessed by Java 7 documentation:
DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
String string1 = "2001-07-04T12:08:56.235-0700";
Date result1 = df1.parse(string1);
DateFormat df2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
String string2 = "2001-07-04T12:08:56.235-07:00";
Date result2 = df2.parse(string2);
You can find more examples in section Examples at SimpleDateFormat javadoc.
UPD 02/13/2020: There is a completely new way to do this in Java 8
Okay, this question is already answered, but I'll drop my answer anyway. It might help someone.
I've been looking for a solution for Android (API 7).
Joda was out of the question - it is huge and suffers from slow initialization. It also seemed a major overkill for that particular purpose.
Answers involving javax.xml won't work on Android API 7.
Ended up implementing this simple class. It covers only the most common form of ISO 8601 strings, but this should be enough in some cases (when you're quite sure that the input will be in this format).
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
/**
* Helper class for handling a most common subset of ISO 8601 strings
* (in the following format: "2008-03-01T13:00:00+01:00"). It supports
* parsing the "Z" timezone, but many other less-used features are
* missing.
*/
public final class ISO8601 {
/** Transform Calendar to ISO 8601 string. */
public static String fromCalendar(final Calendar calendar) {
Date date = calendar.getTime();
String formatted = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.format(date);
return formatted.substring(0, 22) + ":" + formatted.substring(22);
}
/** Get current date and time formatted as ISO 8601 string. */
public static String now() {
return fromCalendar(GregorianCalendar.getInstance());
}
/** Transform ISO 8601 string to Calendar. */
public static Calendar toCalendar(final String iso8601string)
throws ParseException {
Calendar calendar = GregorianCalendar.getInstance();
String s = iso8601string.replace("Z", "+00:00");
try {
s = s.substring(0, 22) + s.substring(23); // to get rid of the ":"
} catch (IndexOutOfBoundsException e) {
throw new ParseException("Invalid length", 0);
}
Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(s);
calendar.setTime(date);
return calendar;
}
}
Performance note: I instantiate new SimpleDateFormat every time as means to avoid a bug in Android 2.1. If you're as astonished as I was, see this riddle. For other Java engines, you may cache the instance in a private static field (using ThreadLocal, to be thread safe).
java.time
The java.time API (built into Java 8 and later), makes this a little easier.
If you know the input is in UTC, such as the Z (for Zulu) on the end, the Instant class can parse.
java.util.Date date = Date.from( Instant.parse( "2014-12-12T10:39:40Z" ));
If your input may be another offset-from-UTC values rather than UTC indicated by the Z (Zulu) on the end, use the OffsetDateTime class to parse.
OffsetDateTime odt = OffsetDateTime.parse( "2010-01-01T12:00:00+01:00" );
Then extract an Instant, and convert to a java.util.Date by calling from.
Instant instant = odt.toInstant(); // Instant is always in UTC.
java.util.Date date = java.util.Date.from( instant );
tl;dr
OffsetDateTime.parse ( "2010-01-01T12:00:00+01:00" )
Or for Java 12+, use Instant.parse as seen in the Answer by Arvind Kumar Avinash.
Using java.time
The new java.time package in Java 8 and later was inspired by Joda-Time.
The OffsetDateTime class represents a moment on the timeline with an offset-from-UTC but not a time zone.
OffsetDateTime odt = OffsetDateTime.parse ( "2010-01-01T12:00:00+01:00" );
Calling toString generates a string in standard ISO 8601 format:
2010-01-01T12:00+01:00
To see the same value through the lens of UTC, extract an Instant or adjust the offset from +01:00 to 00:00.
Instant instant = odt.toInstant();
…or…
OffsetDateTime odtUtc = odt.withOffsetSameInstant( ZoneOffset.UTC );
Adjust into a time zone if desired. A time zone is a history of offset-from-UTC values for a region, with a set of rules for handling anomalies such as Daylight Saving Time (DST). So apply a time zone rather than a mere offset whenever possible.
ZonedDateTime zonedDateTimeMontréal = odt.atZoneSameInstant( ZoneId.of( "America/Montreal" ) );
For a date-only value, use LocalDate.
LocalDate ld = LocalDate.of( 2010 , Month.JANUARY , 1 ) ;
Or:
LocalDate ld = LocalDate.parse( "2010-01-01" ) ;
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes. Hibernate 5 & JPA 2.2 support java.time.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
Java 9 brought some minor features and fixes.
Java SE 6 and Java SE 7
Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android (26+) bundle implementations of the java.time classes.
For earlier Android (<26), the process of API desugaring brings a subset of the java.time functionality not originally built into Android.
If the desugaring does not offer what you need, the ThreeTenABP project adapts ThreeTen-Backport (mentioned above) to Android. See How to use ThreeTenABP….
Starting from Java 8, there is a completely new officially supported way to do this:
String s = "2020-02-13T18:51:09.840Z";
TemporalAccessor ta = DateTimeFormatter.ISO_INSTANT.parse(s);
Instant i = Instant.from(ta);
Date d = Date.from(i);
The Jackson-databind library also has ISO8601DateFormat class that does that (actual implementation in ISO8601Utils.
ISO8601DateFormat df = new ISO8601DateFormat();
Date d = df.parse("2010-07-28T22:25:51Z");
For Java version 7
You can follow Oracle documentation:
http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html
X - is used for ISO 8601 time zone
TimeZone tz = TimeZone.getTimeZone("UTC");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
df.setTimeZone(tz);
String nowAsISO = df.format(new Date());
System.out.println(nowAsISO);
DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
//nowAsISO = "2013-05-31T00:00:00Z";
Date finalResult = df1.parse(nowAsISO);
System.out.println(finalResult);
The DatatypeConverter solution doesn't work in all VMs. The following works for me:
javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar("2011-01-01Z").toGregorianCalendar().getTime()
I've found that joda does not work out of the box (specifically for the example I gave above with the timezone on a date, which should be valid)
I think we should use
DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
for Date 2010-01-01T12:00:00Z
Another very simple way to parse ISO8601 timestamps is to use org.apache.commons.lang.time.DateUtils:
import static org.junit.Assert.assertEquals;
import java.text.ParseException;
import java.util.Date;
import org.apache.commons.lang.time.DateUtils;
import org.junit.Test;
public class ISO8601TimestampFormatTest {
#Test
public void parse() throws ParseException {
Date date = DateUtils.parseDate("2010-01-01T12:00:00+01:00", new String[]{ "yyyy-MM-dd'T'HH:mm:ssZZ" });
assertEquals("Fri Jan 01 12:00:00 CET 2010", date.toString());
}
}
The workaround for Java 7+ is using SimpleDateFormat:
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);
This code can parse ISO8601 format like:
2017-05-17T06:01:43.785Z
2017-05-13T02:58:21.391+01:00
But on Java6, SimpleDateFormat doesn't understand X character and will throw
IllegalArgumentException: Unknown pattern character 'X'
We need to normalize ISO8601 date to the format readable in Java 6 with SimpleDateFormat.
public static Date iso8601Format(String formattedDate) throws ParseException {
try {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);
return df.parse(formattedDate);
} catch (IllegalArgumentException ex) {
// error happen in Java 6: Unknown pattern character 'X'
if (formattedDate.endsWith("Z")) formattedDate = formattedDate.replace("Z", "+0000");
else formattedDate = formattedDate.replaceAll("([+-]\\d\\d):(\\d\\d)\\s*$", "$1$2");
DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US);
return df1.parse(formattedDate);
}
}
Method above to replace [Z with +0000] or [+01:00 with +0100] when error occurs in Java 6 (you can detect Java version and replace try/catch with if statement).
After I searched a lot to convert ISO8601 to date I suddenly found a java class that is ISO8601Util.java and this was part of com.google.gson.internal.bind.util.
So you can use it to convert dates.
ISO8601Utils.parse("2010-01-01T12:00:00Z" , ParsePosition(0))
and you can simply use this kotlin extension function
fun String.getDateFromString() : Date? = ISO8601Utils.parse(this ,
ParsePosition(0))
Java 8+
Simple one liner that I didn't found in answers:
Date date = Date.from(ZonedDateTime.parse("2010-01-01T12:00:00+01:00").toInstant());
Date doesn't contain timezone, it will be stored in UTC, but will be properly converted to your JVM timezone even during simple output with System.out.println(date).
Java has a dozen different ways to parse a date-time, as the excellent answers here demonstrate. But somewhat amazingly, none of Java's time classes fully implement ISO 8601!
With Java 8, I'd recommend:
ZonedDateTime zp = ZonedDateTime.parse(string);
Date date = Date.from(zp.toInstant());
That will handle examples both in UTC and with an offset, like "2017-09-13T10:36:40Z" or "2017-09-13T10:36:40+01:00". It will do for most use cases.
But it won't handle examples like "2017-09-13T10:36:40+01", which is a valid ISO 8601 date-time.
It also won't handle date only, e.g. "2017-09-13".
If you have to handle those, I'd suggest using a regex first to sniff the syntax.
There's a nice list of ISO 8601 examples here with lots of corner cases: https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/ I'm not aware of any Java class that could cope with all of them.
java.time
Note that in Java 8, you can use the java.time.ZonedDateTime class and its static parse(CharSequence text) method.
I faced the same problem and solved it by the following code .
public static Calendar getCalendarFromISO(String datestring) {
Calendar calendar = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault()) ;
SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
try {
Date date = dateformat.parse(datestring);
date.setHours(date.getHours() - 1);
calendar.setTime(date);
String test = dateformat.format(calendar.getTime());
Log.e("TEST_TIME", test);
} catch (ParseException e) {
e.printStackTrace();
}
return calendar;
}
Earlier I was using
SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.getDefault());
But later i found the main cause of the exception was the yyyy-MM-dd'T'HH:mm:ss.SSSZ ,
So i used
SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
It worked fine for me .
Also you can use the following class -
org.springframework.extensions.surf.util.ISO8601DateFormat
Date date = ISO8601DateFormat.parse("date in iso8601");
Link to the Java Doc - Hierarchy For Package org.springframework.extensions.surf.maven.plugin.util
As others have mentioned Android does not have a good way to support parsing/formatting ISO 8601 dates using classes included in the SDK. I have written this code multiple times so I finally created a Gist that includes a DateUtils class that supports formatting and parsing ISO 8601 and RFC 1123 dates. The Gist also includes a test case showing what it supports.
https://gist.github.com/mraccola/702330625fad8eebe7d3
SimpleDateFormat for JAVA 1.7 has a cool pattern for ISO 8601 format.
Class SimpleDateFormat
Here is what I did:
Date d = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
Locale.ENGLISH).format(System.currentTimeMillis());
Use string like
LocalDate.parse(((String) data.get("d_iso8601")),DateTimeFormatter.ISO_DATE)
I am surprised that not even one java library supports all ISO 8601 date formats as per https://en.wikipedia.org/wiki/ISO_8601. Joda DateTime was supporting most of them, but not all and hence I added custom logic to handle all of them. Here is my implementation.
import java.text.ParseException;
import java.util.Date;
import org.apache.commons.lang3.time.DateUtils;
import org.joda.time.DateTime;
public class ISO8601DateUtils {
/**
* It parses all the date time formats from https://en.wikipedia.org/wiki/ISO_8601 and returns Joda DateTime.
* Zoda DateTime does not support dates of format 20190531T160233Z, and hence added custom logic to handle this using SimpleDateFormat.
* #param dateTimeString ISO 8601 date time string
* #return
*/
public static DateTime parse(String dateTimeString) {
try {
return new DateTime( dateTimeString );
} catch(Exception e) {
try {
Date dateTime = DateUtils.parseDate(dateTimeString, JODA_NOT_SUPPORTED_ISO_DATES);
return new DateTime(dateTime.getTime());
} catch (ParseException e1) {
throw new RuntimeException(String.format("Date %s could not be parsed to ISO date", dateTimeString));
}
}
}
private static String[] JODA_NOT_SUPPORTED_ISO_DATES = new String[] {
// upto millis
"yyyyMMdd'T'HHmmssSSS'Z'",
"yyyyMMdd'T'HHmmssSSSZ",
"yyyyMMdd'T'HHmmssSSSXXX",
"yyyy-MM-dd'T'HHmmssSSS'Z'",
"yyyy-MM-dd'T'HHmmssSSSZ",
"yyyy-MM-dd'T'HHmmssSSSXXX",
// upto seconds
"yyyyMMdd'T'HHmmss'Z'",
"yyyyMMdd'T'HHmmssZ",
"yyyyMMdd'T'HHmmssXXX",
"yyyy-MM-dd'T'HHmmss'Z'",
"yyyy-MM-dd'T'HHmmssZ",
"yyyy-MM-dd'T'HHmmssXXX",
// upto minutes
"yyyyMMdd'T'HHmm'Z'",
"yyyyMMdd'T'HHmmZ",
"yyyyMMdd'T'HHmmXXX",
"yyyy-MM-dd'T'HHmm'Z'",
"yyyy-MM-dd'T'HHmmZ",
"yyyy-MM-dd'T'HHmmXXX",
//upto hours is already supported by Joda DateTime
};
}
Java 12
Since Java 12, Instant#parse can parse a date-time string containing time-zone offset.
Date dt = Date.from(Instant.parse(your-date-time-string));
Parse your ISO 8601 formatted date-time strings using Instant#parse and convert the result into java.util.Date using Date#from. See this code run at Ideone.com.
Demo:
import java.time.Instant;
import java.util.Date;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
Stream.of(
"2010-01-01T12:00:00+01:00",
"2010-01-01T12:00:00-01:00",
"2010-01-01T12:00:00Z"
)
.map(Instant::parse)
.map(Date::from)
.forEach(System.out::println);
}
}
See this code run at Ideone.com.
Learn more about the modern Date-Time API from Trail: Date Time.
Do it like this:
public static void main(String[] args) throws ParseException {
String dateStr = "2016-10-19T14:15:36+08:00";
Date date = javax.xml.bind.DatatypeConverter.parseDateTime(dateStr).getTime();
System.out.println(date);
}
Here is the output:
Wed Oct 19 15:15:36 CST 2016
A little test that shows how to parse a date in ISO8601 and that LocalDateTime does not handle DSTs.
#Test
public void shouldHandleDaylightSavingTimes() throws ParseException {
//ISO8601 UTC date format
SimpleDateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
// 1 hour of difference between 2 dates in UTC happening at the Daylight Saving Time
Date d1 = utcFormat.parse("2019-10-27T00:30:00.000Z");
Date d2 = utcFormat.parse("2019-10-27T01:30:00.000Z");
//Date 2 is before date 2
Assert.assertTrue(d1.getTime() < d2.getTime());
// And there is 1 hour difference between the 2 dates
Assert.assertEquals(1000*60*60, d2.getTime() - d1.getTime());
//Print the dates in local time
SimpleDateFormat localFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm z Z", Locale.forLanguageTag("fr_CH"));
localFormat.setTimeZone(TimeZone.getTimeZone("Europe/Zurich"));
//Both dates are at 02h30 local time (because of DST), but one is CEST +0200 and the other CET +0100 (clock goes backwards)
Assert.assertEquals("2019-10-27 02:30 CEST +0200", localFormat.format(d1));
Assert.assertEquals("2019-10-27 02:30 CET +0100", localFormat.format(d2));
//Small test that shows that LocalDateTime does not handle DST (and should not be used for storing timeseries data)
LocalDateTime ld1 = LocalDateTime.ofInstant(d1.toInstant(), ZoneId.of("Europe/Zurich"));
LocalDateTime ld2 = LocalDateTime.ofInstant(d2.toInstant(), ZoneId.of("Europe/Zurich"));
//Note that a localdatetime does not handle DST, therefore the 2 dates are the same
Assert.assertEquals(ld1, ld2);
//They both have the following local values
Assert.assertEquals(2019, ld1.getYear());
Assert.assertEquals(27, ld1.getDayOfMonth());
Assert.assertEquals(10, ld1.getMonthValue());
Assert.assertEquals(2, ld1.getHour());
Assert.assertEquals(30, ld1.getMinute());
Assert.assertEquals(0, ld1.getSecond());
}
When want to convert from UTC to format that we want. It will change depend on the zone/location we stay
//utcDate = "2021-06-05T02:46:29Z"
fun converterUtcToReadableDateTime(utcDate: String): String {
val offsetDateTime = OffsetDateTime.ofInstant(Instant.parse(utcDate), ZoneId.systemDefault())
val patternDate = "dd MMM yyyy h:mm a"
return DateTimeFormatter.ofPattern(patternDate).format(offsetDateTime)
}
fun converterUtcToReadableDate(utcDate: String): String {
val offsetDateTime = OffsetDateTime.ofInstant(Instant.parse(utcDate), ZoneId.systemDefault())
val patternDate = "d MMM yyyy"
return DateTimeFormatter.ofPattern(patternDate).format(offsetDateTime)
}
fun converterUtcToReadableTime(utcDate: String): String {
val offsetDateTime = OffsetDateTime.ofInstant(Instant.parse(utcDate), ZoneId.systemDefault())
val patternDate = "h:mm a"
return DateTimeFormatter.ofPattern(patternDate).format(offsetDateTime)
}
I had a similar need: I needed to be able to parse any date ISO8601 compliant without knowing the exact format in advance, and I wanted a lightweight solution which would also work on Android.
When I googled my needs I stumbled upon this question, and noticed that AFAIU, no answer completely fit my needs. So I developed jISO8601 and pushed it on maven central.
Just add in you pom.xml:
<dependency>
<groupId>fr.turri</groupId>
<artifactId>jISO8601</artifactId>
<version>0.2</version>
</dependency>
and then you're good to go:
import fr.turri.jiso8601.*;
...
Calendar cal = Iso8601Deserializer.toCalendar("1985-03-04");
Date date = Iso8601Deserializer.toDate("1985-03-04T12:34:56Z");
Hopes it help.
To just format a date like this the following worked for me in a Java 6 based application. There is a DateFormat class JacksonThymeleafISO8601DateFormat in the thymeleaf project which inserts the missing colon:
https://github.com/thymeleaf/thymeleaf/blob/40d27f44df7b52eda47d1bc6f1b3012add6098b3/src/main/java/org/thymeleaf/standard/serializer/StandardJavaScriptSerializer.java
I used it for ECMAScript date format compatibilty.
I couldn't use Java 8 features, so only java.util.Date was available. I already had a dependency on gson library but didn't want to use ISO8601Utils directly. ISO8601Utils is an internal API, gson's authors warns not to use it.
I parsed a ISO8601 date using gson's public API:
fun parseISO8601DateToLocalTimeOrNull(date: String): Date? {
return try {
GsonBuilder()
.create()
.getAdapter(Date::class.java)
.fromJson("\"$date\"")
} catch (t: Throwable) {
null
}
}
Under the hood, the adapter still uses ISO8601Utils. But if you're using the adapter you can be sure that a different compatible version of gson won't break your project.
I worried that creation of adapter may be slow so I measured execution time on Pixel 3a with debuggable=false.parseISO8601DateToLocalTimeOrNull took ~0.5 milliseconds to parse a date.
Base Function Courtesy : #wrygiel.
This function can convert ISO8601 format to Java Date which can handle the offset values. As per the definition of ISO 8601 the offset can be mentioned in different formats.
±[hh]:[mm]
±[hh][mm]
±[hh]
Eg: "18:30Z", "22:30+04", "1130-0700", and "15:00-03:30" all mean the same time. - 06:30PM UTC
This class has static methods to convert
ISO8601 string to Date(Local TimeZone) object
Date to ISO8601 string
Daylight Saving is automatically calc
Sample ISO8601 Strings
/* "2013-06-25T14:00:00Z";
"2013-06-25T140000Z";
"2013-06-25T14:00:00+04";
"2013-06-25T14:00:00+0400";
"2013-06-25T140000+0400";
"2013-06-25T14:00:00-04";
"2013-06-25T14:00:00-0400";
"2013-06-25T140000-0400";*/
public class ISO8601DateFormatter {
private static final DateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
private static final DateFormat DATE_FORMAT_2 = new SimpleDateFormat("yyyy-MM-dd'T'HHmmssZ");
private static final String UTC_PLUS = "+";
private static final String UTC_MINUS = "-";
public static Date toDate(String iso8601string) throws ParseException {
iso8601string = iso8601string.trim();
if(iso8601string.toUpperCase().indexOf("Z")>0){
iso8601string = iso8601string.toUpperCase().replace("Z", "+0000");
}else if(((iso8601string.indexOf(UTC_PLUS))>0)){
iso8601string = replaceColon(iso8601string, iso8601string.indexOf(UTC_PLUS));
iso8601string = appendZeros(iso8601string, iso8601string.indexOf(UTC_PLUS), UTC_PLUS);
}else if(((iso8601string.indexOf(UTC_MINUS))>0)){
iso8601string = replaceColon(iso8601string, iso8601string.indexOf(UTC_MINUS));
iso8601string = appendZeros(iso8601string, iso8601string.indexOf(UTC_MINUS), UTC_MINUS);
}
Date date = null;
if(iso8601string.contains(":"))
date = DATE_FORMAT_1.parse(iso8601string);
else{
date = DATE_FORMAT_2.parse(iso8601string);
}
return date;
}
public static String toISO8601String(Date date){
return DATE_FORMAT_1.format(date);
}
private static String replaceColon(String sourceStr, int offsetIndex){
if(sourceStr.substring(offsetIndex).contains(":"))
return sourceStr.substring(0, offsetIndex) + sourceStr.substring(offsetIndex).replace(":", "");
return sourceStr;
}
private static String appendZeros(String sourceStr, int offsetIndex, String offsetChar){
if((sourceStr.length()-1)-sourceStr.indexOf(offsetChar,offsetIndex)<=2)
return sourceStr + "00";
return sourceStr;
}
}

How to solve Unparseable date:error in spring-boot

I having difficulty trying to solve an error with my date in Spring. I think i have exhausted almost all the solution on stack overflow an i still do not have a solution. I have implement a customDateEditor and i am still get the same error.
I am using the datepicker to select the date on the form.
Error
Failed to convert property value of type java.lang.String to required type java.util.Date for property date; nested exception is
java.lang.IllegalArgumentException: Could not parse date: Unparseable date: "1 March, 2017"
Model
#NotNull(message = "Date field must not be blank.")
#DateTimeFormat(pattern = "dd-MM-yyyy")
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
Controller
#InitBinder
public void allowEmptyDateBinding( WebDataBinder binder )
{
// Custom String Editor. tell spring to set empty values as null instead of empty string.
binder.registerCustomEditor( String.class, new StringTrimmerEditor( true ));
//Custom Date Editor
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("dd-MM-yyyy");
simpleDateFormat.setLenient(false);
binder.registerCustomEditor( Date.class, new CustomDateEditor( simpleDateFormat,true));
}
Form
<input th:type="date" class="form-control input-sm datepicker" th:field="*{date}"
placeholder="Date Of Birth"/>
JS Script
$('.datepicker').pickadate({
autoclose: true,
Format: 'dd-MM-yyyy'
});
You are actually using pickadate.js rather than jquery-ui datepicker.
And according to documentation, you are supposed to give 'formatSubmit' property to ensure format of submitted date.
Another issue is your date format for javascript and Java code does not match.
Format 'dd-MM-yyyy' for SimpleDateFormat class will give 01-03-2017,
while for pickatedate.js you need to give 'dd-mm-yyyy' as format.
Therefore, for your "JS script" you should use
$('.datepicker').pickadate({
autoclose: true,
format: 'dd-MM-yyyy',
formatSubmit: 'dd-mm-yyyy'
});

Grails - Domain object doesn't validate correctly

I'm trying to set the date of birth of a person using jQuery Datepicker. However, all I get is that the Property dateOfBirth must be a valid Date.
So, originally, my controller looks like this:
def update(Person personInstance) {
if (personInstance == null) {
// do Something
return
}
if (personInstance.hasErrors()) {
respond personInstance.errors, view: 'edit'
return
}
// do the rest
}
I figured out, that with jQuery I should use a SimpleDateFormat object in order to generate a proper Date object. Nevertheless, even if I directly assign a new Date object to dateOfBirth and subsequently validating the personInstance domain object - like in the following code segment - I still get the Property dateOfBirth must be a valid Date error.
def update(Person personInstance) {
if (personInstance == null) {
// do Something
return
}
// added code
personInstance.dateOfBirth = new Date()
personInstance.validate()
// added code
if (personInstance.hasErrors()) {
respond personInstance.errors, view: 'edit'
return
}
// do the rest
}
Thank you for any help :)
The reason why you are still seeing errors is because validation is automatically called after binding your command/domain object when the method is called.
Use personInstance.clearErrors() before calling personInstance.validate() manually to clear out any existing binding/validation errors. You can see more about this in the documentation.

How to add ajax to dynamically created primefaces input fields

I want to create a page to allow users to update a mysql table. This table can be changed by client admins so I have to read the table schema and create fields on the fly. I got the basic code for doing that from How to create dynamic JSF form fields and the ajax code from How to add ajax validation to programmatically generated primefaces component.
To create a proof of concept page I use the following code (note, I'm using primefaces):
for (int idx = 1; idx < 3; idx++) {
UIInput input = new InputText();
input.setId("text" + idx);
ValueExpression targetExpression = facesContext.getApplication().getExpressionFactory().createValueExpression(facesContext.getELContext(), "#{viewMakeFields.value}", String.class);
input.setValueExpression("value", targetExpression);
AjaxBehavior ab = new AjaxBehavior();
ab.setAsync(true);
ab.setProcess("text"+idx);
input.addClientBehavior("blur", ab); // "change" doesn't work either
form.getChildren().add(input);
}
Then, in the getter and setter, I'm getting the component ID to identify which field is changed:
public static String getCallingComponentID() {
UIComponent component = UIComponent.getCurrentComponent(FacesContext.getCurrentInstance());
return component.getId();
}
public String getValue() {
String id = getCallingComponentID();
System.out.println("getValue " + id);
return text1;
}
public void setValue(String text1) {
String id = getCallingComponentID();
System.out.println("setValue " + id);
this.text1 = text1;
}
The ajax isn't firing and when I click the submit button I get (and I know mixing partial and full submits isn't good):
INFO: getValue text1
INFO: getValue text2
INFO: getValue text1
INFO: getValue text2
INFO: setValue j_id1
INFO: setValue j_id1
INFO: getValue text1
INFO: getValue text2
I see two possible solutions: get ajax working so that the component calling the setter has the correct id or get the form submit to identify which child is calling the setter. The former is preferable since I want to disable the save button until something has changed but I'm willing to accept the latter at this point. Any help would be much appreciated.
Turns out this code does work. Somewhere in my meandering the error changed and I didn't understand the significance of that. Replacing head with h:head in the xhtml file that I was adding the fields to makes ajax work.

LocalDateTime format after form Submit

I use the following jQuery function to format my datepicker
$(".datepicker").datepicker({
dateFormat : "MM yy"
}).attr('readonly', true);
Upon selection I can see the text field is set correctly to November 2013. Before form is submitted I am using Spring validation to validate the date with
public class LocalMonthEditor extends PropertyEditorSupport {
#Override
public void setAsText(final String text) throws IllegalArgumentException {
if (!StringUtils.hasText(text)) {
// Treat empty String as null value.
setValue(null);
} else {
LocalDateTime localDateTime = LocalDateTime.parse(text,
DateTimeFormat.forPattern("MMMM yyyy"));
setValue(localDateTime);
}
}
#Override
public void setValue(final Object value) {
super.setValue(value == null || value instanceof LocalDateTime ? value
: new LocalDateTime(value));
}
#Override
public LocalDateTime getValue() {
return (LocalDateTime) super.getValue();
}
#Override
public String getAsText() {
return getValue() != null ? getValue().toString() : "";
}
}
However after form being submitted the text field is changed to 2013-11-01T00:00:00.000. How can I maintain the field to November 2013 ?
First of all, if the data you need is simply a month and a year, why are you using Joda-Time at all? That's like getting in your car to drive to your mailbox at the end of the driveway: extra effort and complexity for no benefit.
Instead, I suggest you choose between:
Track a pair of variables (month, year)
Define your own class with a pair of members (month, year), and track an instance.
Use a String as seems to be your intention: "November 2013" as you seem to be thinking, or a simpler schemes such as "2013-11".
Secondly, because you created an instance of LocalDatetime, at some point toString seems to be called. The default output of toString on a LocalDateTime is output in the standard ISO 8601 format you saw: 2013-11-01T00:00:00.000. A LocalDateTime has a date value and a time value (hence the name), even if the time value may be set to zeros (meaning start of day). So this is a feature, not a bug.
I don't know Spring Validatation nor the rest of your class structure. I'm guessing you are storing a LocalDateTime instance where instead you meant to be (or should be) storing a String instance. You may need to read up on the subject of "model" versus "view". Often we track data behind the scenes differently than we present data to the user. In this case, you probably should be holding a pair of ints or Integers (one for month, one for year) in your model with a String in your view ("November 2013").
You are setting your local time object directly to text field, thats why your getting full date string.convert your date object by using parse() method and set it. Do not create new object for date set your value directly.

Resources