Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deserialize zone offset with or without colon #38

Closed
brenuart opened this issue Aug 21, 2017 · 10 comments
Closed

Deserialize zone offset with or without colon #38

brenuart opened this issue Aug 21, 2017 · 10 comments
Milestone

Comments

@brenuart
Copy link

brenuart commented Aug 21, 2017

Is there a way to configure the module to accept zone offset expressed with and without colon ?

In other words we'd like the parser to read the following two forms:

  • 1970-01-01T01:00:00.000+01:00
  • 1970-01-01T01:00:00.000+0100

Note: it already accepts these two forms but ONLY if offset is zero (+00:00 or +0000)...

@cowtowncoder
Copy link
Member

And this is for which datatype(s)?

All parsing for this module uses JDK-provided parsing functionality, for that is worth.

@brenuart
Copy link
Author

brenuart commented Aug 22, 2017

For OffsetDateTime and ZonedDateTime...

Regarding JDK-provided parsing stuff, I could not find a (easy) way to build a parser that would accept both formats. Something a bit more tolerant like Jackson's StdDateFormat for java.util.Date...

Before digging deeper I thought it was better to ask here in case I missed something obvious ;-)

@brenuart
Copy link
Author

Any idea ?
The only solution I found so far is to register custom deserializers as follows:

public class MyInstantDeserializers<T extends Temporal> extends InstantDeserializer<T> {

	private static final DateTimeFormatter ISO_OFFSET_DATE_TIME;
	private static final DateTimeFormatter ISO_ZONED_DATE_TIME;
	static {
		ISO_OFFSET_DATE_TIME = new DateTimeFormatterBuilder()
		        .parseCaseInsensitive()
		        .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
		        .optionalStart()
		        	.appendOffset("+HH:MM:ss", "Z")
		        .optionalEnd()
		        .optionalStart()
		        	.appendOffset("+HHMMss", "Z")
		        .optionalEnd()
		        .toFormatter();
		
        ISO_ZONED_DATE_TIME = new DateTimeFormatterBuilder()
                .append(ISO_OFFSET_DATE_TIME)
                .optionalStart()
                .appendLiteral('[')
                .parseCaseSensitive()
                .appendZoneRegionId()
                .appendLiteral(']')
                .toFormatter();
	}

	public static final InstantDeserializer<OffsetDateTime> OFFSET_DATE_TIME = new MyInstantDeserializers<>(
            OffsetDateTime.class, ISO_OFFSET_DATE_TIME,
            OffsetDateTime::from,
            a -> OffsetDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId),
            a -> OffsetDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId),
            (d, z) -> d.withOffsetSameInstant(z.getRules().getOffset(d.toLocalDateTime())),
            true // yes, replace zero offset with Z
    );

    public static final InstantDeserializer<ZonedDateTime> ZONED_DATE_TIME = new MyInstantDeserializers<>(
            ZonedDateTime.class, ISO_ZONED_DATE_TIME,
            ZonedDateTime::from,
            a -> ZonedDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId),
            a -> ZonedDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId),
            ZonedDateTime::withZoneSameInstant,
            false // keep zero offset and Z separate since zones explicitly supported
    );
    
    protected MyInstantDeserializers(Class<T> supportedType, DateTimeFormatter formatter,
			Function<TemporalAccessor, T> parsedToValue,
			Function<com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.FromIntegerArguments, T> fromMilliseconds,
			Function<com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.FromDecimalArguments, T> fromNanoseconds,
			BiFunction<T, ZoneId, T> adjust, boolean replaceZeroOffsetAsZ) {
		super(supportedType, formatter, parsedToValue, fromMilliseconds, fromNanoseconds, adjust, replaceZeroOffsetAsZ);
	}
}

Then use a custom module:

SimpleModule module = new SimpleModule();
module.addDeserializer(OffsetDateTime.class, MyInstantDeserializers.OFFSET_DATE_TIME);
module.addDeserializer(ZonedDateTime.class,  MyInstantDeserializers.ZONED_DATE_TIME);

Although it (seems to) work, it has the following drawbacks:

I think it could be made easier if the InstantDEserializer constructors were made public...

@brenuart brenuart changed the title Deserialize zone offset with or without column Deserialize zone offset with or without colon Aug 25, 2017
@cowtowncoder
Copy link
Member

I did not write this package (Nick Williams wrote it), so my understanding of design ideas (as well as Java 8 date/time limitations) is bit partial. I am ok with changing private constructors into protected ones (for example).
If suggested parser would work better by allowing (optional?) colon, that'd be great.

@sparhomenko
Copy link

sparhomenko commented Feb 26, 2018

This seems to be fixed as part of JDK-8032051 but unfortunately the fix will be delivered only in Java 9. Would it be possible to incorporate the workaround from the comment above into InstantDeserializer?

@cowtowncoder
Copy link
Member

If someone has time & itch work-arounds can be integrated. I do not have time to work on this module in near future, most likely, but can help with PRs.

@bric3
Copy link

bric3 commented Feb 12, 2019

For reference, one can write such code to enable the parsing of OffsetDateTime from ISO8601 date time having an offset containing or not a colon, or just the hour part.

new ObjectMapper().registerModules(
        new JavaTimeModule()
                .addDeserializer(OffsetDateTime.class,
                                 new ISO8601OffsetDateTimeDeserializer()))
                .disable(ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
public class ISO8601OffsetDateTimeDeserializer extends InstantDeserializer<OffsetDateTime> {
    private static final long serialVersionUID = -237644245579626895L;

    public ISO8601OffsetDateTimeDeserializer() {
        super(InstantDeserializer.OFFSET_DATE_TIME,
              new DateTimeFormatterBuilder()
                      .parseCaseInsensitive()
                      .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
                      .optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd()
                      .optionalStart().appendOffset("+HHMM", "+0000").optionalEnd()
                      .optionalStart().appendOffset("+HH", "Z").optionalEnd()
                      .toFormatter());
    }

    @Override
    public JsonDeserializer<OffsetDateTime> createContextual(DeserializationContext ctxt, BeanProperty property) {
        // ignore context (i.e. formatting pattern that will be used for serialization)
        return this;
    }
}

@jmayday
Copy link

jmayday commented Jul 12, 2019

Hi. I hope it won't be off-topic question...
I have some issue trying to disable ADJUST_DATES_TO_CONTEXT_TIME_ZONE feature in Spring application.

Here is how I tried to configure mapper:

  @Bean
  @Primary
  public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
    ObjectMapper objectMapper = builder.createXmlMapper(false).build();
    objectMapper.registerModule(new JavaTimeModule());
    objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
    return objectMapper;
  }

I also tried adding extra properties in application.yml:

spring:
  jackson:
    deserialization:
      adjust-dates-to-context-time-zone: false
      ADJUST_DATES_TO_CONTEXT_TIME_ZONE: false

But every time deserialization has to kick in, I see under debugger that features is still not disabled (com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer):

    protected boolean shouldAdjustToContextTimezone(DeserializationContext context) {
        return (_adjustToContextTZOverride != null) ? _adjustToContextTZOverride :
                context.isEnabled(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
    }

_adjustToContextTZOverride is null & context.isEnabled(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE) is true and it's not what I would expect.

How to simply disable ADJUST_DATES_TO_CONTEXT_TIME_ZONE? Deserialization partially works, but result has always "Z" offset instead of precise information (lets have a "2021-04-28T19:58:40+02:00" as example input).

@cowtowncoder
Copy link
Member

@jmayday Please use other forums for usage questions -- issue tracker is specifically for issues, and not (for example) question of how to configure Jackson within specific framework. In this particular case you may want to reach out to users of framework in question (Spring? DropWizard?).

@kupci
Copy link
Member

kupci commented Jun 5, 2021

@cowtowncoder Just going through a few 'todo' items and realized this issue was a duplicate of #131, so was fixed with @oeystein 's fix #208 . I think this can be closed.

Summarizing a few pertinent quotes from above thread:

Is there a way to configure the module to accept zone offset expressed with and without colon ?

And this is for which datatype(s)?

For OffsetDateTime and ZonedDateTime...

@kupci kupci added this to the 2.13.0 milestone Jun 9, 2021
@kupci kupci added the 2.13 label Jun 9, 2021
@kupci kupci closed this as completed Jun 9, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants