Skip to content

Avro: Encode non-zone timestamps with local-timestamp logical types - #16577

Closed
Shekharrajak wants to merge 7 commits into
apache:mainfrom
Shekharrajak:iceberg-12751-avro-local-timestamp
Closed

Avro: Encode non-zone timestamps with local-timestamp logical types#16577
Shekharrajak wants to merge 7 commits into
apache:mainfrom
Shekharrajak:iceberg-12751-avro-local-timestamp

Conversation

@Shekharrajak

Copy link
Copy Markdown

Closes#12751

Switches Iceberg's Avro encoding for non-zone timestamp types
(timestamp, timestamp_ns) to the Avro-spec-correct
local-timestamp-{micros,nanos} logical types, instead of overloading
timestamp-{micros,nanos} with the Iceberg-private
adjust-to-utc=false convention.

The zone-adjusted variants (timestamptz, timestamptz_ns) continue
to use timestamp-{micros,nanos} with adjust-to-utc=true.

Why

Per the Avro spec, timestamp-{millis,micros,nanos} is an instant on
the global timeline (UTC), while local-timestamp-{millis,micros,nanos}
is the timezone-free local timestamp.

Iceberg's writer mapped both the
zone and non-zone Iceberg types to timestamp-* and disambiguated with
the adjust-to-utc property, which the spec footnote already calls
out as an Iceberg-only convention.

@github-actionsgithub-actionsBot added spark core data flink Specification Issues that may introduce spec changes. labels May 27, 2026
private static final Schema TIME_SCHEMA =
LogicalTypes.timeMicros().addToSchema(Schema.create(Schema.Type.LONG));
private static final Schema TIMESTAMP_SCHEMA =
LogicalTypes.timestampMicros().addToSchema(Schema.create(Schema.Type.LONG));

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switch writer to emit local-timestamp-micros for timestamp and local-timestamp-nanos for timestamp_ns,

return Types.TimestampType.withoutZone();
}

} else if (logical instanceof LogicalTypes.LocalTimestampMillis

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LogicalTypes.LocalTimestamp{Millis,Micros} to TimestampType.withoutZone() and LocalTimestampNanos to TimestampNanoType.withoutZone().


public static boolean isTimestamptz(Schema schema) {
LogicalType logicalType = schema.getLogicalType();
if (logicalType instanceof LogicalTypes.LocalTimestampMillis

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isTimestamptz returns false for LocalTimestamp* a

case "timestamp-micros":
case "timestamp-nanos":
// both are handled in memory as long values, using the type to track units
case "local-timestamp-micros":

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decode as long

return GenericReaders.timestamps();

case "local-timestamp-micros":
return GenericReaders.timestamps();

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

select the non-zone

}
return GenericReaders.timestampMillis();

case "local-timestamp-millis":

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

select the non-zone

Comment threadformat/spec.md
Notes:

1. Avro type annotation `adjust-to-utc` is an Iceberg convention; default value is `false` if not present.
1. Non-zone timestamp types are written using Avro's `local-timestamp-{micros,nanos}` logical types. Zone-adjusted timestamp types are written using `timestamp-{micros,nanos}` with `adjust-to-utc: true`. Readers must also accept legacy files that encode non-zone timestamps as `timestamp-{micros,nanos}` with `adjust-to-utc: false`.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the Avro spec without any confusion.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's back this out into a separate PR and do it first. We just had a lot of discussion on a similar issue for "dates" transform and I think we should have a discussion on this to make sure we aren't doing something that will break other implementations.

I would recommend starting a thread on the dev list and point to a specific PR for this change

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


private static final Schema TS_SCHEMA =
new Schema(
Types.NestedField.required(1, "ts", Types.TimestampType.withoutZone()),

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Iceberg schema with all four timestamp variants (ts, tstz, ts_ns, tstz_ns)

File file = temp.resolve("timestamps-" + System.nanoTime() + ".avro").toFile();

try (org.apache.iceberg.io.FileAppender<Record> writer =
Avro.write(Files.localOutput(file))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

writes a single record holding LocalDateTime and OffsetDateTime values for all four fields


List<Record> read;
try (AvroIterable<Record> reader =
Avro.read(Files.localInput(file))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reads them back via Avro.read(...).createResolvingReader(PlannedDataReader::create)

@Shekharrajak
Shekharrajakforce-pushed the iceberg-12751-avro-local-timestamp branch 2 times, most recently from bf9289a to 8c82bc8CompareMay 27, 2026 10:07
@Shekharrajak
Shekharrajakforce-pushed the iceberg-12751-avro-local-timestamp branch from 8c82bc8 to 9248748CompareMay 27, 2026 10:11
@Shekharrajak
Shekharrajakforce-pushed the iceberg-12751-avro-local-timestamp branch from 9248748 to 98aa875CompareMay 27, 2026 10:18

private static org.apache.avro.Schema rewriteLocalTimestampForFlink(
org.apache.avro.Schema schema) {
switch (schema.getType()) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avro schemas are nested: RECORD → FIELD → SCHEMA → UNION → ARRAY/MAP element → primitive.

@github-actions

Copy link
Copy Markdown

This pull request has been marked as stale due to 30 days of inactivity. It will be closed in 1 week if no further activity occurs. If you think that’s incorrect or this pull request requires a review, please simply write any comment. If closed, you can revive the PR at any time and @mention a reviewer or discuss it on the dev@iceberg.apache.org list. Thank you for your contributions.

@github-actions

Copy link
Copy Markdown

This pull request has been closed due to lack of activity. This is not a judgement on the merit of the PR in any way. It is just a way of keeping the PR queue manageable. If you think that is incorrect, or the pull request requires review, you can revive the PR at any time.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

coredataflinksparkSpecificationIssues that may introduce spec changes.stale

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Spark, Avro and Iceberg Timestamp, Timestamp NTZ definition need more clarifications

2 participants

@Shekharrajak@RussellSpitzer