Skip to content

Commit a87a2e7

Browse files
authored
Merge pull request #15121 from codeconsole/7.0.x-json-dates
Use consistent ISO-8601 formatting for rendering JSON Date, Calendar, LocalDateTime, and Instant properties
2 parents 2924ab3 + 9cb1a0f commit a87a2e7

File tree

13 files changed

+781
-4
lines changed

13 files changed

+781
-4
lines changed

grails-converters/src/main/groovy/org/grails/web/converters/configuration/ConvertersConfigurationInitializer.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@ private void initJSONConfiguration() {
112112
}
113113
marshallers.add(new org.grails.web.converters.marshaller.json.DateMarshaller());
114114
}
115+
marshallers.add(new org.grails.web.converters.marshaller.json.CalendarMarshaller());
116+
marshallers.add(new org.grails.web.converters.marshaller.json.InstantMarshaller());
117+
marshallers.add(new org.grails.web.converters.marshaller.json.LocalDateMarshaller());
118+
marshallers.add(new org.grails.web.converters.marshaller.json.LocalDateTimeMarshaller());
119+
marshallers.add(new org.grails.web.converters.marshaller.json.OffsetDateTimeMarshaller());
120+
marshallers.add(new org.grails.web.converters.marshaller.json.ZonedDateTimeMarshaller());
115121
marshallers.add(new org.grails.web.converters.marshaller.json.ToStringBeanMarshaller());
116122

117123
boolean includeDomainVersion = includeDomainVersionProperty(grailsConfig, "json");
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.grails.web.converters.marshaller.json;
20+
21+
import java.text.Format;
22+
import java.util.Calendar;
23+
import java.util.Locale;
24+
import java.util.TimeZone;
25+
26+
import org.apache.commons.lang3.time.FastDateFormat;
27+
28+
import grails.converters.JSON;
29+
import org.grails.web.converters.exceptions.ConverterException;
30+
import org.grails.web.converters.marshaller.ObjectMarshaller;
31+
import org.grails.web.json.JSONException;
32+
33+
/**
34+
* JSON ObjectMarshaller which converts a Calendar Object to ISO-8601 format with Z suffix.
35+
*
36+
* @since 7.0
37+
*/
38+
public class CalendarMarshaller implements ObjectMarshaller<JSON> {
39+
40+
private final Format formatter;
41+
42+
/**
43+
* Constructor with a custom formatter.
44+
* @param formatter the formatter
45+
*/
46+
public CalendarMarshaller(Format formatter) {
47+
this.formatter = formatter;
48+
}
49+
50+
/**
51+
* Default constructor.
52+
*/
53+
public CalendarMarshaller() {
54+
this(FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("GMT"), Locale.US));
55+
}
56+
57+
public boolean supports(Object object) {
58+
return object instanceof Calendar;
59+
}
60+
61+
public void marshalObject(Object object, JSON converter) throws ConverterException {
62+
try {
63+
Calendar calendar = (Calendar) object;
64+
converter.getWriter().value(formatter.format(calendar.getTime()));
65+
}
66+
catch (JSONException e) {
67+
throw new ConverterException(e);
68+
}
69+
}
70+
}

grails-converters/src/main/groovy/org/grails/web/converters/marshaller/json/DateMarshaller.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public DateMarshaller(Format formatter) {
5353
* Default constructor.
5454
*/
5555
public DateMarshaller() {
56-
this(FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("GMT"), Locale.US));
56+
this(FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("GMT"), Locale.US));
5757
}
5858

5959
public boolean supports(Object object) {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.grails.web.converters.marshaller.json;
20+
21+
import java.time.Instant;
22+
import java.time.format.DateTimeFormatter;
23+
24+
import grails.converters.JSON;
25+
import org.grails.web.converters.exceptions.ConverterException;
26+
import org.grails.web.converters.marshaller.ObjectMarshaller;
27+
import org.grails.web.json.JSONException;
28+
29+
/**
30+
* JSON ObjectMarshaller which converts an Instant to ISO-8601 format with Z suffix.
31+
*
32+
* @since 7.0
33+
*/
34+
public class InstantMarshaller implements ObjectMarshaller<JSON> {
35+
36+
public boolean supports(Object object) {
37+
return object instanceof Instant;
38+
}
39+
40+
public void marshalObject(Object object, JSON converter) throws ConverterException {
41+
try {
42+
Instant instant = (Instant) object;
43+
converter.getWriter().value(DateTimeFormatter.ISO_INSTANT.format(instant));
44+
}
45+
catch (JSONException e) {
46+
throw new ConverterException(e);
47+
}
48+
}
49+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.grails.web.converters.marshaller.json;
20+
21+
import java.time.LocalDate;
22+
import java.time.format.DateTimeFormatter;
23+
24+
import grails.converters.JSON;
25+
import org.grails.web.converters.exceptions.ConverterException;
26+
import org.grails.web.converters.marshaller.ObjectMarshaller;
27+
import org.grails.web.json.JSONException;
28+
29+
/**
30+
* JSON ObjectMarshaller which converts a LocalDate to ISO-8601 date format (YYYY-MM-DD).
31+
*
32+
* @since 7.0
33+
*/
34+
public class LocalDateMarshaller implements ObjectMarshaller<JSON> {
35+
36+
public boolean supports(Object object) {
37+
return object instanceof LocalDate;
38+
}
39+
40+
public void marshalObject(Object object, JSON converter) throws ConverterException {
41+
try {
42+
LocalDate localDate = (LocalDate) object;
43+
converter.getWriter().value(DateTimeFormatter.ISO_LOCAL_DATE.format(localDate));
44+
}
45+
catch (JSONException e) {
46+
throw new ConverterException(e);
47+
}
48+
}
49+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.grails.web.converters.marshaller.json;
20+
21+
import java.time.LocalDateTime;
22+
import java.time.format.DateTimeFormatter;
23+
24+
import grails.converters.JSON;
25+
import org.grails.web.converters.exceptions.ConverterException;
26+
import org.grails.web.converters.marshaller.ObjectMarshaller;
27+
import org.grails.web.json.JSONException;
28+
29+
/**
30+
* JSON ObjectMarshaller which converts a LocalDateTime to ISO-8601 format (without timezone).
31+
*
32+
* @since 7.0
33+
*/
34+
public class LocalDateTimeMarshaller implements ObjectMarshaller<JSON> {
35+
36+
public boolean supports(Object object) {
37+
return object instanceof LocalDateTime;
38+
}
39+
40+
public void marshalObject(Object object, JSON converter) throws ConverterException {
41+
try {
42+
LocalDateTime localDateTime = (LocalDateTime) object;
43+
converter.getWriter().value(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(localDateTime));
44+
}
45+
catch (JSONException e) {
46+
throw new ConverterException(e);
47+
}
48+
}
49+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.grails.web.converters.marshaller.json;
20+
21+
import java.time.OffsetDateTime;
22+
import java.time.format.DateTimeFormatter;
23+
24+
import grails.converters.JSON;
25+
import org.grails.web.converters.exceptions.ConverterException;
26+
import org.grails.web.converters.marshaller.ObjectMarshaller;
27+
import org.grails.web.json.JSONException;
28+
29+
/**
30+
* JSON ObjectMarshaller which converts an OffsetDateTime to ISO-8601 format with timezone offset.
31+
*
32+
* @since 7.0
33+
*/
34+
public class OffsetDateTimeMarshaller implements ObjectMarshaller<JSON> {
35+
36+
public boolean supports(Object object) {
37+
return object instanceof OffsetDateTime;
38+
}
39+
40+
public void marshalObject(Object object, JSON converter) throws ConverterException {
41+
try {
42+
OffsetDateTime offsetDateTime = (OffsetDateTime) object;
43+
converter.getWriter().value(DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(offsetDateTime));
44+
}
45+
catch (JSONException e) {
46+
throw new ConverterException(e);
47+
}
48+
}
49+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.grails.web.converters.marshaller.json;
20+
21+
import java.time.ZonedDateTime;
22+
import java.time.format.DateTimeFormatter;
23+
24+
import grails.converters.JSON;
25+
import org.grails.web.converters.exceptions.ConverterException;
26+
import org.grails.web.converters.marshaller.ObjectMarshaller;
27+
import org.grails.web.json.JSONException;
28+
29+
/**
30+
* JSON ObjectMarshaller which converts a ZonedDateTime to ISO-8601 format with timezone offset.
31+
*
32+
* @since 7.0
33+
*/
34+
public class ZonedDateTimeMarshaller implements ObjectMarshaller<JSON> {
35+
36+
public boolean supports(Object object) {
37+
return object instanceof ZonedDateTime;
38+
}
39+
40+
public void marshalObject(Object object, JSON converter) throws ConverterException {
41+
try {
42+
ZonedDateTime zonedDateTime = (ZonedDateTime) object;
43+
converter.getWriter().value(DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(zonedDateTime));
44+
}
45+
catch (JSONException e) {
46+
throw new ConverterException(e);
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)