Hot questions for Using Joda-Time in spring

Question:


Answer:

Update: Using DateTimeFormat, introduced in java 8:

The idea is to define two formats: one for the input format, and one for the output format. Parse with the input formatter, then format with the output formatter.

Your input format looks quite standard, except the trailing Z. Anyway, let's deal with this: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'". The trailing Z is the interesting part. Usually there's time zone data here, like -0700. So the pattern would be ...Z, i.e. without apostrophes.

The output format is way more simple: "dd-MM-yyyy". Mind the small y -s.

Here is the example code:

DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH);
DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern("dd-MM-yyy", Locale.ENGLISH);
LocalDate date = LocalDate.parse("2018-04-10T04:00:00.000Z", inputFormatter);
String formattedDate = outputFormatter.format(date);
System.out.println(formattedDate); // prints 10-04-2018

Original answer - with old API SimpleDateFormat

SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
SimpleDateFormat outputFormat = new SimpleDateFormat("dd-MM-yyyy");
Date date = inputFormat.parse("2018-04-10T04:00:00.000Z");
String formattedDate = outputFormat.format(date);
System.out.println(formattedDate); // prints 10-04-2018

Question:

I'm using Spring to retrieve JSON data from a Web API rest API, with the Jackson Joda Module to deserialize an ISO date, and convert it into a Joda DateTime.

Spring request:

HttpEntity<?> httpEntity = new HttpEntity<>(headers);
    final ResponseEntity<returnedEntities> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, EntitiesList.class);

The Entities have a property that is a Joda DateTime

/**
 *
 * @return
 * The departureDate
 */
@JsonProperty("departureDate")
public DateTime getDepartureDate() {
    return departureDate;
}

/**
 *
 * @param departureDate
 * The departureDate
 */
@JsonProperty("departureDate")
public void setDepartureDate(DateTime departureDate) {
    this.departureDate = departureDate;
}

My problem is the servers returning JSON with a local time like so:

"departureDate": "2017-10-03T00:00:00+01:00",

So the above represents 3rd of October the +01 representing that we're in British Summer time 1 hour ahead of GMT.

Now Jackson JSON Module appears to be stripping the timezone information effectively converting the time into a GMT datetime. And so the Joda DateTime returned is:

2017-10-02T23:00:00.000Z

The 2nd of October at 11pm, which is the wrong date.

What do I need to do to have the Jackson Joda Module store the +1:00 timezone when converting the Json to the Joda DateTime?

At present I have a vanilla Joda Module:

JodaModule jodaModule = new JodaModule();
jsonConverter.getObjectMapper().registerModule(jodaModule);
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = mappingJackson2HttpMessageConverter.getObjectMapper();
objectMapper.registerModule(jodaModule);

MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = mappingJackson2HttpMessageConverter.getObjectMapper();
objectMapper.registerModule(jodaModule);

objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);           
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
listHttpMessageConverters.add(mappingJackson2HttpMessageConverter);
restTemplate.setMessageConverters(listHttpMessageConverters);

Answer:

When deserializing, by default, Jackson adjusts the dates to the context timezone. In your case, it's adjusting it to UTC (2017-10-03T00:00:00+01:00 is equivalent to 2017-10-02T23:00:00.000Z - the Z in the end of the String is the UTC designator).

To avoid this and preserve the input's timezone, you can disable the ADJUST_DATES_TO_CONTEXT_TIME_ZONE feature:

objectMapper.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false);

With this, an input String such as 2017-10-03T00:00:00+01:00 will be deserialized to a Joda's DateTime with the value 2017-10-03T00:00:00.000+01:00.

Question:

I have a DateTimeXmlAdapter that I'm using to convert Joda dates to Strings. It looks as follows:

public class DateTimeXmlAdapter extends XmlAdapter<String, DateTime> {
    private static final String PATTERN = "yyyy-MM-dd'T'HH:mm:ssZ";
    private static final DateTimeFormatter formatter = DateTimeFormat.forPattern(PATTERN);

    @Override
    public DateTime unmarshal(String value) {
        return formatter.parseDateTime(value);
    }

    @Override
    public String marshal(DateTime value) {
        return formatter.print(value);
    }
}

This results in the following string in XML:

2014-10-16T18:31:57-0400

However, the endpoint I'm sending this to expects a 'Z' at the end instead of -0400. They recommend converting to UTC so timezone information doesn't need to be sent. Makes sense to me. However, I can't seem to get a 'Z' to be at the end unless I state it literally. As in:

private static final String PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'";

The discussion below seems to indicate that adding the Z is not recommended.

Validating Timestamp format yyyy-MM-dd'T'HH:mm:ssZ in java?

It tried changing it to the following, but this results in a date w/o the 'Z'.

public class DateTimeXmlAdapter extends XmlAdapter<String, DateTime> {
    private static final DateTimeFormatter formatter = ISODateTimeFormat.dateHourMinuteSecond().withZoneUTC();

    @Override
    public DateTime unmarshal(String value) {
        return formatter.parseDateTime(value);
    }

    @Override
    public String marshal(DateTime value) {
        return formatter.print(value);
    }
}

Output:

2014-10-17T18:50:43

The endpoint I'm talking to says they need the 'Z' because it indicates zero offset from UTC. What's the proper way to add a 'Z' at the end when using Joda Time and JAXB?

Thanks,

Matt


Answer:

The use of method ISODateTimeFormat.dateHourMinuteSecond() is not appropriate because it does not output the offset (in your case this is Z = UTC+00:00). Instead you can use the method dateTimeNoMillis(), and of course you need to change the DateTime-object to UTC-offset:

public class DateTimeXmlAdapter extends XmlAdapter<String, DateTime> {
    private static final DateTimeFormatter formatter = 
        ISODateTimeFormat.dateTimeNoMillis().withZoneUTC();

    @Override
    public DateTime unmarshal(String value) {
        return formatter.parseDateTime(value);
    }

    @Override
    public String marshal(DateTime value) {
        return formatter.print(value);
    }
}

From the documentation:

Returns a formatter that combines a full date and time without millis, separated by a 'T' (yyyy-MM-dd'T'HH:mm:ssZZ). The time zone offset is 'Z' for zero, and of the form '±HH:mm' for non-zero.

Question:

I am injecting constructor for org.joda.time.DateTime using spring as

<bean id="myDateTime" class="org.joda.time.DateTime">
    <constructor-arg type="java.lang.Long" value="${startDateTime:#{null}}" />
</bean>

The startDateTime is resolved as 1341571102000. But I'm getting error regarding unable to resolve constructor

Cannot resolve reference to bean 'myDateTime' while setting constructor argument; 
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myDateTime' defined in URL [file:/path/to/spring-configuration/application-config.xml]: Could not resolve matching constructor (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)

Answer:

The org.joda.time.DateTime class does not have a constructor that accepts a java.lang.Long. You probably want to use the one that accepts a primitive long. To do that, try specifying type="long" for the constructor-arg.

However, the fallback to null in case startDateTime is not set will not work in that case. I'm not sure what your intention with that fallback is, but you need to solve it some other way if you are to use the long constructor.

Question:

Here are the configurations that I've made:

My config file:

<mvc:annotation-driven>
    <mvc:message-converters>
        <!-- Support for Joda Time -->
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper" ref="customJacksonMapper" />
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven> 

My class that extends Object

@Component("customJacksonMapper")
public class CustomJacksonMapper extends ObjectMapper {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;

/**
 * Instantiates a new custom jackson mapper.
 * 
 * RegisterModule = Registar o módulo do JodaTime.
 * Locale = Padrão portugues Brasil.
 * TimeZone = Converte para o timezone de São Paulo.
 * 
 */
public CustomJacksonMapper() {
    this.registerModule(new JodaModule());
    this.setLocale(new Locale("pt_BR"));
    this.setTimeZone(TimeZone.getTimeZone("America/Sao_Paulo"));
    this.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS , false);
    this.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}

}

With this configuration, as I've read on a lot of posts, the date should be returned on the correct format in Json, but what I got is the following:

{"date": 1467920285301}

What am I doing wrong?


Answer:

Try to add this line to constructor:

this.setDateFormat(new ISO8601DateFormat());

Question:

Am unable to convert string to Joda LocalTime with DefaultFormattingConversionService.

If I pass time as "12:00" it says time is too short, but if I pass it as "12:00:00", it says it is malformed.

import org.joda.time.LocalTime;
import org.springframework.format.support.DefaultFormattingConversionService;

public class SpringLocalTimeFormatterTry {

   public static void main(String[] args) {

      DefaultFormattingConversionService service = new DefaultFormattingConversionService();
      try {
         System.out.println(service.convert("12:00", LocalTime.class));
      }
      catch (Exception e) {
         System.out.println(e.getMessage());
      }
      try {
         System.out.println(service.convert("12:00:00", LocalTime.class));
      }
      catch (Exception e) {
         System.out.println(e.getMessage());
      }


   }
}

How to use it correctly or fix?


Answer:

The vanilla settings of DefaultFormattingConversionService use platform default locale, which, I assume from the error, are the same as mine, ie. English. That means, that for time you need to add the AM/PM indicator. This works for me:

System.out.println(service.convert("10:12 am", LocalTime.class));

>> 10:12:00.000

To handle your desired time format, you can add an extra converter:

service.addConverter(new Converter<String, LocalTime>() {
    @Override
    public LocalTime convert(String source) {
        return LocalTime.parse(source);
    }
});

Then, both examples pass:

System.out.println(service.convert("12:00", LocalTime.class));
>> 12:00:00.000
System.out.println(service.convert("12:00:00", LocalTime.class));
>> 12:00:00.000

You can skip registering the default converters by creating the service with

new DefaultFormattingConversionService(false);

Finally, I assume in the production code you are actually getting the ConversionService from the ConversionServiceFactoryBean, so you can configure that as follows:

@Bean
public ConversionServiceFactoryBean conversionService() {
    ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
    Set<Converter<?, ?>> myConverters = new HashSet<>();
    myConverters.add(new Converter<String, LocalTime>() {
        @Override
        public LocalTime convert(String source) {
            return LocalTime.parse(source);
        }
    });
    conversionServiceFactoryBean.setConverters(myConverters);
    return conversionServiceFactoryBean;
}