Skip to the content.

Date and Time

We make extensive use of java.time types. But Avro has no support for dates and times.

Schema

For all except ZonedDateTime it is possible to use a Long with milliseconds. Although precision could be greater for some of the types, millis throughout provided greater consistency and is adequent for my needs.

  implicit val instantSchema: SchemaGenerator[Instant] = new SchemaGenerator[Instant] {
    override def generate: JValue = longAvroSchema.generate
  }

  implicit val localDateSchema: SchemaGenerator[LocalDate] = new SchemaGenerator[LocalDate] {
    override def generate: JValue = longAvroSchema.generate
  }

  implicit val localDateTimeSchema: SchemaGenerator[LocalDateTime] = new SchemaGenerator[LocalDateTime] {
    override def generate: JValue = longAvroSchema.generate
  }

  implicit val localTimeSchema: SchemaGenerator[LocalTime] = new SchemaGenerator[LocalTime] {
    override def generate: JValue = longAvroSchema.generate
  }  

For ZonedDateTime it seemed easiest to split into a ZoneId plus Instant

  case class ZoneInstant(zoneIdS: String, instant: Instant) {
    def toZonedDateTime: ZonedDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of(zoneIdS))
  }

  implicit val zonedDateTimeSchema: SchemaGenerator[ZonedDateTime] = new SchemaGenerator[ZonedDateTime] {
    private val zoneInstantSchema = AvroSchemaDerivation.avroSchema[ZoneInstant]
    override def generate: JValue = zoneInstantSchema.generate
  }
  
    lazy val utcId = ZoneId.of("UTC")

The utcId declaration is used for localDateTime - see below

Writer

  implicit val instantWriter: AvroWriter[Instant] = { (schema, value) =>
    longAvroWriter.write(schema, value.toEpochMilli)
  }

  implicit val localDateWriter: AvroWriter[LocalDate] = { (schema, value) =>
    longAvroWriter.write(schema, value.toEpochDay)
  }
  implicit val localDateTimeWriter: AvroWriter[LocalDateTime] = { (schema, value) =>
    val inst = value.atZone(Utils.utcId).toInstant
    longAvroWriter.write(schema, inst.toEpochMilli)
  }

  implicit val localTimeWriter: AvroWriter[LocalTime] ={ (schema, value) =>
    val asMillis = value.toNanoOfDay / 1000000L
    longAvroWriter.write(schema, asMillis)
  }

  implicit val ZoneIdWriter: AvroWriter[ZoneId] = { (schema, value) =>
    stringAvroWriter.write(schema, value.getId)
  }

  private val zoneInstantWriter = AvroWriterDerivation.avroWriter[ZoneInstant]

  implicit val zonedDateTimeWriter: AvroWriter[ZonedDateTime] = { (schema, value) =>
    val zii = ZoneInstant(value.getZone.getId, value.toInstant)
    zoneInstantWriter.write(schema, zii)
  }

Reader

  implicit val instantReader: AvroReader[Instant] = { (schema, data) =>
    val instantL = longAvroReader.read(schema, data)
    Instant.ofEpochMilli(instantL)
  }

  implicit val localDateReader: AvroReader[LocalDate] = { (schema, data) =>
    val localDateL = longAvroReader.read(schema, data)
    LocalDate.ofEpochDay(localDateL)
  }
  implicit val localDateTimeReader: AvroReader[LocalDateTime] = { (schema, data) =>
    val instantL = longAvroReader.read(schema, data)
    val inst = Instant.ofEpochMilli(instantL)
    LocalDateTime.ofInstant(inst, Utils.utcId)
  }
  implicit val localTimeReader: AvroReader[LocalTime] = { (schema, data) =>
    val localTimeL = longAvroReader.read(schema, data)
    LocalTime.ofNanoOfDay(localTimeL * 1000000L)
  }

  implicit val zonedDateTimeReader: AvroReader[ZonedDateTime] = new AvroReader[ZonedDateTime] {
    private val zoneInstantReader = AvroReaderDerivation.avroReader[ZoneInstant]

    override def read(schema: Schema, data: AnyRef): ZonedDateTime =
      zoneInstantReader.read(schema, data).toZonedDateTime
  }