Skip to content

feat: convert and replace timezone#6106

Open
aaron-ang wants to merge 1 commit intoEventual-Inc:mainfrom
aaron-ang:tz-convert
Open

feat: convert and replace timezone#6106
aaron-ang wants to merge 1 commit intoEventual-Inc:mainfrom
aaron-ang:tz-convert

Conversation

@aaron-ang
Copy link
Contributor

@aaron-ang aaron-ang commented Jan 31, 2026

Changes Made

  • When converting time zone:
    • Output error when from_timezone is not provided for timestamp without tz info.
    • Ignore from_timezone if timestamp already has tz info.
  • When replacing time zone, drop timezone in timestamp if timezone is not provided.
  • Create new ParsedTimezone enum to handle different timezone types (offset and tz name).
  • Use new Daft array building semantics.

Related Issues

Closes #4096.

@aaron-ang aaron-ang changed the title Main feat: convert and replace timezone Jan 31, 2026
@github-actions github-actions bot added the feat label Jan 31, 2026
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 31, 2026

Greptile Overview

Greptile Summary

This PR implements timezone conversion and replacement functionality for timestamp types. The implementation introduces a new ParsedTimezone enum in src/daft-schema/src/time_unit.rs that unifies handling of both fixed offsets (e.g., +02:00) and timezone database names (e.g., America/New_York), eliminating code duplication across the codebase.

Key Changes:

  • Added convert_time_zone() function that preserves the instant in time while changing timezone representation; requires from_timezone parameter for timestamps without timezone info
  • Added replace_time_zone() function that preserves local time while changing timezone; supports removing timezone by passing None
  • Refactored existing temporal methods (date(), time(), truncate()) to use the new ParsedTimezone abstraction
  • Properly handles DST transitions by returning errors for ambiguous or nonexistent local times
  • Consistent API across Python functions, expressions, and series methods

Test Coverage:

  • Comprehensive test coverage for both naive and timezone-aware timestamps
  • Tests verify that from_timezone is required for naive timestamps in convert_time_zone()
  • Tests verify that from_timezone is properly ignored when timestamp already has timezone info
  • Tests cover timezone removal via replace_time_zone(None)

The implementation follows Daft's new array building semantics and properly validates inputs at both the type-checking and runtime levels.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The implementation is well-structured with proper error handling for edge cases (ambiguous/nonexistent times during DST transitions). All timezone parsing is validated upfront, the new ParsedTimezone enum eliminates previous code duplication, and comprehensive test coverage validates both happy paths and error conditions. The refactoring of existing methods maintains backward compatibility while improving code quality.
  • No files require special attention

Important Files Changed

Filename Overview
src/daft-schema/src/time_unit.rs Introduced ParsedTimezone enum to unify handling of fixed offsets and timezone names, with helper functions for conversions
src/daft-core/src/array/ops/time.rs Added convert_time_zone and replace_time_zone methods; refactored existing methods to use new ParsedTimezone enum
src/daft-functions-temporal/src/time.rs Added ConvertTimeZone and ReplaceTimeZone scalar UDFs with proper type checking and return field handling
daft/functions/datetime.py Added Python API functions convert_time_zone and replace_time_zone with clear docstrings
tests/series/test_temporal_ops.py Added comprehensive tests for timezone conversion and replacement; moved imports to top of file

Sequence Diagram

sequenceDiagram
    participant User
    participant Python API
    participant Series
    participant UDF
    participant Array
    participant TimeUnit

    User->>Python API: convert_time_zone(expr, to_tz, from_tz?)
    Python API->>Series: dt_convert_time_zone(to_tz, from_tz?)
    Series->>UDF: ConvertTimeZone::call(...)
    UDF->>Array: convert_time_zone(to_tz, from_tz?)
    Array->>TimeUnit: parse_timezone(to_tz)
    TimeUnit-->>Array: ParsedTimezone
    
    alt timestamp has timezone
        Array->>Array: Return with updated timezone metadata
    else timestamp without timezone
        alt from_timezone provided
            Array->>TimeUnit: parse_timezone(from_tz)
            TimeUnit-->>Array: ParsedTimezone
            Array->>TimeUnit: naive_local_to_timestamp(...)
            TimeUnit-->>Array: converted timestamp
        else from_timezone missing
            Array-->>User: Error: from_timezone required
        end
    end
    
    Array-->>UDF: TimestampArray
    UDF-->>Series: Series
    Series-->>User: Result

    User->>Python API: replace_time_zone(expr, tz?)
    Python API->>Series: dt_replace_time_zone(tz?)
    Series->>UDF: ReplaceTimeZone::call(...)
    UDF->>Array: replace_time_zone(tz?)
    Array->>TimeUnit: timestamp_to_naive_local(...)
    TimeUnit-->>Array: naive datetime
    
    alt new timezone provided
        Array->>TimeUnit: naive_local_to_timestamp(...)
        TimeUnit-->>Array: new timestamp
    else timezone = None
        Array->>TimeUnit: naive_datetime_to_timestamp(...)
        TimeUnit-->>Array: timestamp as UTC
    end
    
    Array-->>UDF: TimestampArray
    UDF-->>Series: Series
    Series-->>User: Result
Loading

Last reviewed commit: 75d6e27

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

4 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

aaron-ang

This comment was marked as resolved.

@aaron-ang
Copy link
Contributor Author

@greptile-apps re-review

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

4 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@codecov
Copy link

codecov bot commented Jan 31, 2026

Codecov Report

❌ Patch coverage is 4.63215% with 350 lines in your changes missing coverage. Please review.
✅ Project coverage is 43.12%. Comparing base (e7adf06) to head (bff0bd1).
⚠️ Report is 11 commits behind head on main.

Files with missing lines Patch % Lines
src/daft-core/src/array/ops/time.rs 0.61% 162 Missing ⚠️
src/daft-schema/src/time_unit.rs 5.00% 76 Missing ⚠️
src/daft-functions-temporal/src/time.rs 0.00% 61 Missing ⚠️
src/daft-core/src/series/ops/time.rs 0.00% 22 Missing ⚠️
src/daft-core/src/array/ops/cast.rs 0.00% 18 Missing ⚠️
src/daft-core/src/utils/display.rs 0.00% 7 Missing ⚠️
daft/functions/datetime.py 50.00% 2 Missing ⚠️
src/daft-functions-temporal/src/lib.rs 0.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #6106      +/-   ##
==========================================
- Coverage   43.34%   43.12%   -0.23%     
==========================================
  Files         917      920       +3     
  Lines      112995   113929     +934     
==========================================
+ Hits        48978    49131     +153     
- Misses      64017    64798     +781     
Files with missing lines Coverage Δ
daft/expressions/expressions.py 94.93% <100.00%> (-0.28%) ⬇️
daft/functions/__init__.py 100.00% <ø> (ø)
daft/series.py 92.21% <100.00%> (+0.24%) ⬆️
daft/functions/datetime.py 96.92% <50.00%> (-3.08%) ⬇️
src/daft-functions-temporal/src/lib.rs 0.00% <0.00%> (ø)
src/daft-core/src/utils/display.rs 0.00% <0.00%> (ø)
src/daft-core/src/array/ops/cast.rs 40.23% <0.00%> (+0.13%) ⬆️
src/daft-core/src/series/ops/time.rs 0.00% <0.00%> (ø)
src/daft-functions-temporal/src/time.rs 0.00% <0.00%> (ø)
src/daft-schema/src/time_unit.rs 33.33% <5.00%> (-17.03%) ⬇️
... and 1 more

... and 44 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aaron-ang aaron-ang force-pushed the tz-convert branch 2 times, most recently from 8863882 to a735dce Compare February 1, 2026 16:07
@aaron-ang
Copy link
Contributor Author

@greptile-apps re-review

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

5 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

.unwrap_or_else(|_| panic!("Unable to parse timezone string {tz}"))
});
let str_array: daft_arrow::array::Utf8Array<i64> = self
.as_arrow2()
Copy link
Member

Choose a reason for hiding this comment

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

nit: We'd prefer to not introduce any new arrow2 based code. Could you update this to use arrow-rs instead?

@aaron-ang
Copy link
Contributor Author

rebased

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

13 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

time zone conversion features

2 participants