ZonedDateTimeSerializer.java

package tools.jackson.databind.ext.javatime.ser;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

import com.fasterxml.jackson.annotation.JsonFormat;

import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.core.JsonToken;

import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.cfg.DateTimeFeature;

public class ZonedDateTimeSerializer extends InstantSerializerBase<ZonedDateTime> {
    public static final ZonedDateTimeSerializer INSTANCE = new ZonedDateTimeSerializer();

    /**
     * Flag for <code>JsonFormat.Feature.WRITE_DATES_WITH_ZONE_ID</code>
     */
    protected final Boolean _writeZoneId;
    
    protected ZonedDateTimeSerializer() {
        // ISO_ZONED_DATE_TIME is an extended version of ISO compliant format
        // ISO_OFFSET_DATE_TIME with additional information :Zone Id
        // (This is not part of the ISO-8601 standard)
        this(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
    }

    public ZonedDateTimeSerializer(DateTimeFormatter formatter) {
        super(ZonedDateTime.class, dt -> dt.toInstant().toEpochMilli(),
              ZonedDateTime::toEpochSecond, ZonedDateTime::getNano,
              formatter);
        _writeZoneId = null;
    }

    protected ZonedDateTimeSerializer(ZonedDateTimeSerializer base,
            DateTimeFormatter formatter,
            Boolean useTimestamp, Boolean useNanoseconds,
            Boolean writeZoneId,
            JsonFormat.Shape shape)
    {
        super(base, formatter, useTimestamp, useNanoseconds, shape);
        _writeZoneId = writeZoneId;
    }

    @Override
    protected JSR310FormattedSerializerBase<?> withFormat(DateTimeFormatter formatter, 
            Boolean useTimestamp,
            JsonFormat.Shape shape)
    {
        return new ZonedDateTimeSerializer(this, formatter,
                useTimestamp, _useNanoseconds, _writeZoneId,
                shape);
    }

    @Override
    protected JSR310FormattedSerializerBase<?> withFeatures(Boolean writeZoneId,
            Boolean useNanoseconds)
    {
        return new ZonedDateTimeSerializer(this, _formatter,
                _useTimestamp, useNanoseconds, writeZoneId, _shape);
    }

    @Override
    public void serialize(ZonedDateTime value, JsonGenerator g, SerializationContext ctxt)
        throws JacksonException
    {
        if (!useTimestamp(ctxt)) {
            // [modules-java8#333]: `@JsonFormat` with pattern should override
            //   `SerializationFeature.WRITE_DATES_WITH_ZONE_ID`
            if ((_formatter != null) && (_shape == JsonFormat.Shape.STRING)) {
                ; // use default handling
            } else if (shouldWriteWithZoneId(ctxt)) {
                // Apply millisecond truncation if enabled
                if (ctxt.isEnabled(DateTimeFeature.TRUNCATE_TO_MSECS_ON_WRITE)) {
                    value = value.truncatedTo(ChronoUnit.MILLIS);
                }
                // write with zone
                g.writeString(DateTimeFormatter.ISO_ZONED_DATE_TIME.format(value));
                return;
            }
        }
        super.serialize(value, g, ctxt);
    }

    @Override
    protected String formatValue(ZonedDateTime value, SerializationContext ctxt) {
        String formatted = super.formatValue(value, ctxt);
        // [modules-java8#333]: `@JsonFormat` with pattern should override
        //   `SerializationFeature.WRITE_DATES_WITH_ZONE_ID`
        if (_formatter != null && _shape == JsonFormat.Shape.STRING) {
            // Why not `if (shouldWriteWithZoneId(provider))` ?
            if (Boolean.TRUE.equals(_writeZoneId)) {
                formatted += "[" + value.getZone().getId() + "]";
            }
        }
        return formatted;
    }    
    public boolean shouldWriteWithZoneId(SerializationContext ctxt) {
        return (_writeZoneId != null)
                ? _writeZoneId
                : ctxt.isEnabled(DateTimeFeature.WRITE_DATES_WITH_ZONE_ID);
    }

    @Override
    protected JsonToken serializationShape(SerializationContext ctxt) {
        if (!useTimestamp(ctxt) && shouldWriteWithZoneId(ctxt)) {
            return JsonToken.VALUE_STRING;
        }
        return super.serializationShape(ctxt);
    }
}