Adding a ZonedDateTime propery to a BSON Document

I’m having trouble finding documentation on how I can convert a ZonedDateTime object to a BSON property on a BSON Document. I’m using driver version 4.5.0.

My code is like such:

// JsonObject json
// ZonedDateTime date

StringWriter sw = new StringWriter();

try (JsonWriter writer = Json.createWriter(sw)) {
      writer.writeObject(json);
      writer.close();
} catch (Exception ex) {
      return null;
}

Document document = new Document(BasicDBObject.parse(sw.toString()).toMap());
document.append("entryDate",  date)
collection.insertOne(document); // <-- ERROR

Error:

Encoding a ZonedDateTime: '2022-12-16T19:35:59.038918-05:00[America/New_York]' failed with the following exception:
Failed to encode 'ZonedDateTime'. Encoding 'zone' errored with: An exception occurred when encoding using the AutomaticPojoCodec.
Encoding a ZoneRegion: 'America/New_York' failed with the following exception:
Unable to get value for property 'id' in ZoneRegion
A custom Codec or PojoCodec may need to be explicitly configured and registered to handle this type.
A custom Codec or PojoCodec may need to be explicitly configured and registered to handle this type.

I’ve seen anything like this before. ZonedDateTime is not handled by the driver? If I were to write a PojoCodec, what format would I be converting that for MongoDB to see it as a date?

Hi @John_Manko,

How do you want a ZonedDateTime serialized into BSON?

Regards,
Jeff

Well, I guess that what I’m trying to figure out. I need to retain time zone information, but also would like it if it’s a mongo date. Does mongo have any such type?

MongoDB does not support any such type directly. The closed thing is the BSON date type, which does not have time zone info. You might want to represent it as some sort of nested document that contains all the information you need. Something like:

{
   "date" : <BSON Date value>,   //  assuming you only need millisecond precision>
   "zoneId" : <BSON String value representing the zone>
}

If something like that works, I can show you how to write a custom Codec to accomplish the goal.

I would appreciate the guidance on writing a codec. Your solution seems to be the only way.

Sure, no problem:

import org.bson.BsonDocument;
import org.bson.BsonDocumentReader;
import org.bson.BsonDocumentWriter;
import org.bson.BsonReader;
import org.bson.BsonWriter;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class ZonedDateTimeCodec implements Codec<ZonedDateTime> {

    @Override
    public ZonedDateTime decode(BsonReader reader, DecoderContext decoderContext) {
        reader.readStartDocument();

        // There is some risk in assuming that the order of these fields in the document never changes.  
        // The order _could_ change in some circumstances, like if you update one of the fields independently 
        // of the other, or if you use some other code in some cases to create these documents.  If you're
        // concerned about the risk, then it's not that hard to re-write this code to remove the assumption
        // that the "date" field is always first
        long date = reader.readDateTime("date");
        String zoneId = reader.readString("zoneId");

        reader.readEndDocument();

        return ZonedDateTime.ofInstant(Instant.ofEpochMilli(date), ZoneId.of(zoneId));
    }

    @Override
    public void encode(BsonWriter writer, ZonedDateTime value, EncoderContext encoderContext) {
        writer.writeStartDocument();

        writer.writeDateTime("date",value.toInstant().toEpochMilli());
        writer.writeString("zoneId", value.getZone().getId());

        writer.writeEndDocument();
    }

    @Override
    public Class<ZonedDateTime> getEncoderClass() {
        return ZonedDateTime.class;
    }

    // Refactor this into some actual unit tests :)
    public static void main(String[] args) {
        var subject = ZonedDateTime.now();

        System.out.println(subject);

        var doc = new BsonDocument();
        var writer = new BsonDocumentWriter(doc);
        var codec = new ZonedDateTimeCodec();

        codec.encode(writer, subject, EncoderContext.builder().build());

        System.out.println(doc.toJson());

        var reader = new BsonDocumentReader(doc);

        var decoded = codec.decode(reader, DecoderContext.builder().build());

        System.out.println(decoded);
    }
}