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
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
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.
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:
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.
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
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
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")