Skip to content

Logical Types

The following list represent the avro logical types mapped to python types:

Avro Type Logical Type Python Type
int date datetime.date
int time-millis datetime.time
long time-micros types.TimeMicro
long timestamp-millis datetime.datetime
long timestamp-micros types.DateTimeMicro
double timedelta datetime.timedelta
long local-timestamp-millis types.LocalDateTime
string uuid uuid.uuid4
string uuid uuid.UUID
bytes decimal types.condecimal

Note

Custom logicalTypes are not supported

Date

Date example
import datetime
import dataclasses
import typing

from dataclasses_avroschema import AvroModel

a_datetime = datetime.datetime(2019, 10, 12, 17, 57, 42)


@dataclasses.dataclass
class DateLogicalType(AvroModel):
    "Date type"
    birthday: datetime.date
    meeting_date: typing.Optional[datetime.date] = None
    release_datetime: datetime.date = a_datetime.date()


DateLogicalType.avro_schema()

'{
  "type": "record",
  "name": "DateLogicalType",
  "fields": [
    {
      "name": "birthday",
      "type": {"type": "int", "logicalType": "date"}},
    {
      "name": "meeting_date",
      "type": ["null", {"type": "int", "logicalType": "date"}],
      "default": null
    },
    {
      "name": "release_datetime",
      "type": {"type": "int", "logicalType": "date"},
      "default": 18181
    }
  ],
  "doc": "Date type"
}'

(This script is complete, it should run "as is")

Time

Time example
import datetime
import dataclasses
import typing

from dataclasses_avroschema import AvroModel, TimeMicro

a_datetime = datetime.datetime(2019, 10, 12, 17, 57, 42)


@dataclasses.dataclass
class TimeLogicalTypes(AvroModel):
    "Time logical types"
    birthday_time: datetime.time
    meeting_time: typing.Optional[datetime.time] = None
    release_time: datetime.time = a_datetime.time()
    release_time_micro: TimeMicro = a_datetime.time()

TimeLogicalTypes.avro_schema()

'{
  "type": "record",
  "name": "TimeLogicalTypes",
  "fields": [
    {
      "name": "birthday_time",
      "type": {"type": "int", "logicalType": "time-millis"}
    },
    {
      "name": "meeting_time",
      "type": ["null", {"type": "int", "logicalType": "time-millis"}],
      "default": null
    },
    {
      "name": "release_time",
      "type": {"type": "int", "logicalType": "time-millis"},
      "default": 64662000
    },
    {
      "name": "release_time_micro",
      "type": {"type": "long", "logicalType": "time-micros"},
      "default": 64662000000
    }
  ],
  "doc": "Time logical types"
}'

(This script is complete, it should run "as is")

To use time-micros in avro schemas you need to use types.TimeMicro

Datetime

In avro aware datetime objects can be represented in two flavours: timestamp-millis and timestamp-micros

The timestamp-millis logical type represents an instant on the global timeline, independent of a particular time zone or calendar, with a precision of one millisecond. A timestamp-millis logical type annotates an Avro long, where the long stores the number of milliseconds from the unix epoch, 1 January 1970 00:00:00.000 UTC. We represent timestamp-millis with datetime.datetime python objects.

The timestamp-micros represents the same as timestamp-millis but with microsecond precision.

In python we do not have a native type for it, then we will use DateTimeMicro, which is an annotation of datetime.datetime: Annotated[datetime.datetime, DateTimeMicroFieldInfo()].

Use timestamp-millis and timestamp-micros when:

  • You care about the exact instant in time globally.
  • Producers/consumers run in different time zones.
  • You need reliable ordering, deduplication, TTL/retention windows, or auditing.
  • You want round-trip safety for aware datetimes.
DateTime example
import datetime
import dataclasses
import typing

from dataclasses_avroschema import AvroModel, DateTimeMicro

a_datetime = datetime.datetime(2019, 10, 12, 17, 57, 42)


@dataclasses.dataclass
class DatetimeLogicalType(AvroModel):
    "Datetime logical types"
    birthday: datetime.datetime
    meeting_time: typing.Optional[datetime.datetime] = None
    release_datetime: datetime.datetime = a_datetime
    release_datetime_micro: DateTimeMicro = a_datetime

DatetimeLogicalType.avro_schema()

'{
  "type": "record",
  "name": "DatetimeLogicalType",
  "fields": [
    {
      "name": "birthday",
      "type": {"type": "long", "logicalType": "timestamp-millis"}
    },
    {
      "name": "meeting_time",
      "type": ["nul", {"type": "long", "logicalType": "timestamp-millis"}],
      "default": null
    },
    {
      "name": "release_datetime",
      "type": {"type": "long", "logicalType": "timestamp-millis"},
      "default": 1570903062000
    },
    {
      "name": "release_datetime_micro",
      "type": {"type": "long", "logicalType": "timestamp-micros"},
      "default": 1570903062000000
    }
  ],
  "doc": "Datetime logical types"
}'

(This script is complete, it should run "as is")

Note

To use timestamp-micros in avro schemas you need to use DateTimeMicro

Note

timestamp-millis and timestamp-micros maintain timezone info

LocalDateTime

In avro naive datetime objects can be represented in two flavours: local-timestamp-millis and local-timestamp-micros

The local-timestamp-millis logical type represents a timestamp in a local timezone, regardless of what specific time zone is considered local, with a precision of one millisecond. A local-timestamp-millis logical type annotates an Avro long, where the long stores the number of milliseconds, from 1 January 1970 00:00:00.000.

The local-timestamp-micros represents the same as timestamp-millis but with a microsecond precision.

In python we do not have a native type for them, then we will use types.LocalDateTime and types.LocalDateTimeMicro, which are annotations for datetime.datetime.

Warning

When using local-timestamp with aware datetime objects, the timezone info is lost!!

Use local-timestamp only when:

  • You care about wall-clock local time semantics, not a global instant.
  • The value is tied to human local schedules, like “store opens at 09:00 local”.
  • You intentionally do not want timezone conversion behavior.

In the following example, we ilustrate the usage of local-timestamp and the difference between timestamp:

Local datetime
import datetime
from dataclasses import dataclass

from dataclasses_avroschema import AvroModel, ModelGenerator, types

# aware datetime object in CEST (+7200). In UTC (+0000) the datetime is datetime.datetime(2026, 5, 11, 15, 33, 56)
dt = datetime.datetime(2026, 5, 11, 17, 33, 56, tzinfo=datetime.timezone(datetime.timedelta(hours=2)))

@dataclass
class LogicalTypesMillis(AvroModel):
    release_datetime: datetime.datetime = dt
    local_datetime: types.LocalDateTime = dt
    local_datetime_micro: types.LocalDateTimeMicro = dt


LogicalTypesMillis.avro_schema()

Given as output:

{
  "type": "record",
  "name": "LogicalTypesMillis",
  "fields": [
    {"name": "release_datetime", "type": {"type": "long", "logicalType": "timestamp-millis"}, "default": 1778513636000},
    {"name": "local_datetime", "type": {"type": "long", "logicalType": "local-timestamp-millis"}, "default": 1778513636000},
    {"name": "local_datetime_micro", "type": {"type": "long", "logicalType": "local-timestamp-micros"}, "default": 1778513636000000},
  ]
}

We can see that release_datetime and local_datetime have the same default 1778513636000. when using local timestamp because we set the same default in the class. Now, if we serialize and deserialize an instance, we don't get the same original object as the timezone info is lost when using local-timestamp:

import datetime
from dataclasses import dataclass

from dataclasses_avroschema import AvroModel, ModelGenerator, types

# aware datetime object in CEST (+02:00). In UTC (+00:00) the datetime is datetime.datetime(2026, 5, 11, 15, 33, 56)
dt = datetime.datetime(2026, 5, 11, 17, 33, 56, tzinfo=datetime.timezone(datetime.timedelta(hours=2)))

@dataclass
class LogicalTypesMillis(AvroModel):
    release_datetime: datetime.datetime = dt
    local_datetime: types.LocalDateTime = dt
    local_datetime_micro: types.LocalDateTimeMicro = dt


instance = LogicalTypesMillis()
new_instance = LogicalTypesMillis.deserialize(instance.serialize())

assert instance != new_instance

print(f"instance.local_datetime {instance.local_datetime} != new_instance.local_datetime. {new_instance.local_datetime}")
# instance.local_datetime 2026-05-11 17:33:56+02:00 != new_instance.local_datetime 2026-05-11 17:33:56


print(f"instance.local_datetime_micro {instance.local_datetime_micro} != new_instance.local_datetime_micro. {new_instance.local_datetime_micro}")
# instance.local_datetime_micro 2026-05-11 17:33:56+02:00 != new_instance.local_datetime_micro. 2026-05-11 17:33:56

Timedelta

timedelta fields are serialized to a double number of seconds.

Timedelta example
import datetime
import dataclasses

from dataclasses_avroschema import AvroModel

delta = datetime.timedelta(weeks=1, days=2, hours=3, minutes=4, seconds=5, milliseconds=6, microseconds=7)

@dataclasses.dataclass
class TimedeltaLogicalType(AvroModel):
    "Timedelta logical type"
    time_elapsed: datetime.timedelta = delta

DatetimeLogicalType.avro_schema()

'{
  "type": "record",
  "name": "DatetimeLogicalType",
  "fields": [
    {
      "name": "time_elapsed",
      "type": {
        "type": "double",
        "logicalType": "dataclasses-avroschema-timedelta"
      },
      "default": 788645.006007
    }
  ],
  "doc": "Timedelta logical type"
}'

(This script is complete, it should run "as is")

UUID

UUID example
import uuid
import dataclasses
import typing

from dataclasses_avroschema import AvroModel


@dataclasses.dataclass
class UUIDLogicalTypes(AvroModel):
    "UUID logical types"
    uuid_1: uuid.uuid4
    uuid_2: typing.Optional[uuid.uuid4] = None
    event_uuid: uuid.uuid4 = uuid.uuid4()

UUIDLogicalTypes.avro_schema()

'{
  "type": "record",
  "name": "UUIDLogicalTypes",
  "fields": [
    {
      "name": "uuid_1",
      "type": {"type": "string", "logicalType": "uuid"}
    },
    {
      "name": "uuid_2",
      "type": ["null", {"type": "string", "logicalType": "uuid"}],
      "default": null
    },
    {
      "name": "event_uuid",
      "type": {"type": "string", "logicalType": "uuid"},
      "default": "ad0677ab-bd1c-4383-9d45-e46c56bcc5c9"
    }
  ],
  "doc": "UUID logical types"
}'

(This script is complete, it should run "as is")

Decimal

Decimal types in avro must specify two required attributes: precision and scale. Precision represents the amount of digits and scale the amount of decimal places. Because with the python type decimal.Decimal is not possible to supply the required arguments, dataclasses-avroschema provides a funtion to create the decimals. The function types.condecimal annotates the decimal.Decimal type and it adds the required attibutes.

Arguments to condecimal

The following arguments are available when using the condecimal type function

  • max_digits (int): total number digits
  • decimal_places (int): total decimal places
Decimal example
import decimal
import dataclasses
import typing

from dataclasses_avroschema import AvroModel, types


@dataclasses.dataclass
class DecimalLogicalTypes(AvroModel):
    "Decimal logical types"
    money: types.condecimal(max_digits=3, decimal_places=2)
    decimal_with_default: types.condecimal(max_digits=3, decimal_places=2) = decimal.Decimal('3.14')
    optional_decimal: typing.Optional[types.condecimal(max_digits=3, decimal_places=2)] = None

DecimalLogicalTypes.avro_schema()

'{
  "type": "record",
  "name": "DecimalLogicalTypes",
  "fields": [
    {
      "name": "money",
      "type": {
        "type": "bytes",
        "logicalType": "decimal",
        "precision": 3,
        "scale": 2
      }
    },
    {
      "name": "decimal_with_default",
      "type": {
        "type": "bytes",
        "logicalType": "decimal",
        "precision": 3,
        "scale": 2
      },
      "default": "\\u013a"
    },
    {
      "name": "optional_decimal",
      "type": [
        "null",
        {
          "type": "bytes",
          "logicalType": "decimal",
          "precision": 3,
          "scale": 2
        }
      ]
      "default": "null"
    }
  ],
  "doc": "Decimal logical types"
}'

(This script is complete, it should run "as is")