Time and Calendars

This section covers working with time and calendars in Deephaven queries. Since Deephaven specializes in real-time operations, understanding how to work with time is crucial to building effective analyses.

Critical concepts covered in this crash course include:

Time data types

Deephaven tables natively support all data types found in the java.time package.

Commonly used types include:

  • Instant: A specific point in time
  • ZonedDateTime: A specific point in time with time zone information
  • LocalDate: A date without time zone information
  • Duration: A length of time; e.g. 5 minutes, 1 hour, etc.
  • Period: A date-based amount of time (like "2 months")

Time constants

Deephaven offers a set of built-in constants for common time periods. All of the following values are expressed in nanoseconds:

from deephaven import empty_table
from deephaven.constants import *

# Several of the constants available
time_constants = empty_table(1).update(
    [
        "Millisecond = MILLI",
        "Second = SECOND",
        "Minute = MINUTE",
        "Hour = HOUR",
        "Day = DAY",
        "Week = WEEK",
        "Year = YEAR_365",
    ]
)

Create temporal data

You can create temporal data with query string methods. For instance, you can construct columns with a single timestamp, sequences of timestamps, and more:

from deephaven import empty_table

temporal_data_table = empty_table(10).update(
    [
        "CurrentTime = now()",
        "SequentialSeconds = now() + ii * SECOND",
        "SequentialMinutes = now() + ii * MINUTE",
    ]
)

Time arithmetic and filtering

Time arithmetic in tables should always be done with built-in methods. See DateTimeUtils for the available constants and methods.

from deephaven import empty_table

time_arithmetic_table = empty_table(10).update(
    [
        "Timestamp = '2025-06-01T09:30Z' + ii * MINUTE",
        "EpochMillis = epochMillis(Timestamp)",
        "TimestampMinusOneHour = minus(Timestamp, 'PT1H')",
        "TimestampPlusThirtyMinutes = plus(Timestamp, 'PT30M')",
        "UpperBin15Minutes = upperBin(Timestamp, 'PT15M')",
        "LowerBin8Minutes = lowerBin(Timestamp, 'PT8M')",
    ]
)

Filtering temporal data isn't much different from filtering numeric data:

times_greater_than = time_arithmetic_table.where("Timestamp >= '2025-06-01T09:34Z'")
times_less_than = time_arithmetic_table.where("Timestamp < '2025-06-01T09:37Z'")
times_in_range = time_arithmetic_table.where(
    ["Timestamp > '2025-06-01T09:34Z'", "Timestamp <= '2025-06-01T09:37Z'"]
)

Time zones

Time zones are a critical part of temporal data. For example, if it's 6 PM in Los Angeles, it's 3 AM the next day in Shanghai. There are many methods that require time zone information because the answer is dependent on the time zone.

from deephaven import empty_table

time_zone_arithmetic_table = empty_table(10).update(
    [
        "Timestamp = '2025-06-01T09:30Z' + 2 * ii * HOUR",
        "SecondOfDayLosAngeles = secondOfDay(Timestamp, 'PT', true)",
        "DayOfYearLosAngeles = dayOfYear(Timestamp, 'PT')",
        "HourOfDayLosAngeles = hourOfDay(Timestamp, 'PT', true)",
        "SecondOfDayShanghai = secondOfDay(Timestamp, 'Asia/Shanghai', true)",
        "DayOfYearShanghai = dayOfYear(Timestamp, 'Asia/Shanghai')",
        "HourOfDayShanghai = hourOfDay(Timestamp, 'Asia/Shanghai', true)",
    ]
)

All of the previous operations use Instant values, which contain no time zone information - hence the need to pass time zones like 'PT' and 'Asia/Shanghai'. Instead, you can bake the time zone information into the data by using ZonedDateTime:

from deephaven import empty_table

time_zone_arithmetic_table = (
    empty_table(10)
    .update(
        [
            "Timestamp = '2025-06-01T09:30Z' + 2 * ii * HOUR",
            "TimestampLosAngeles = toZonedDateTime(Timestamp, 'PT')",
            "TimestampShanghai = toZonedDateTime(Timestamp, 'Asia/Shanghai')",
            "SecondOfDayLosAngeles = secondOfDay(TimestampLosAngeles, true)",
            "DayOfYearLosAngeles = dayOfYear(TimestampLosAngeles)",
            "HourOfDayLosAngeles = hourOfDay(TimestampLosAngeles, true)",
            "SecondOfDayShanghai = secondOfDay(TimestampShanghai, true)",
            "DayOfYearShanghai = dayOfYear(TimestampShanghai)",
            "HourOfDayShanghai = hourOfDay(TimestampShanghai, true)",
        ]
    )
    .drop_columns("Timestamp")
)

Business calendars

Business calendars help you work with trading days, business hours, and holidays. Deephaven comes with built-in calendars for major exchanges.

from deephaven import empty_table

# The table will have 5 days' worth of trading data, one every 30 seconds
num_trades = 5 * 24 * 60 * 2

trading_data = empty_table(num_trades).update(
    [
        "Timestamp = '2025-07-14T09:30 ET' + ii * 30 * SECOND",
        "Ticker = (ii % 2 == 0) ? `A` : `B`",
        "Price = (ii % 2 == 0) ? randomDouble(50, 75) : randomDouble(120, 160)",
        "Size = randomDouble(0, 10)",
    ]
)

Built-in calendars

Deephaven comes with a few built-in calendars.

Note

The built-in calendars are examples. For any production system, a custom or other pre-defined calendar should be used.

from deephaven.calendar import calendar_names, calendar

print(calendar_names())
nyse_cal = calendar("USNYSE_EXAMPLE")

Filter by business days and hours

A common use of calendars is to filter data based on whether or not it falls within business hours or on business days.

business_days_only = trading_data.where("nyse_cal.isBusinessDay(Timestamp)")
business_hours_only = trading_data.where("nyse_cal.isBusinessTime(Timestamp)")

business_day_counts = trading_data.update(
    [
        "BizDaysFromStart = nyse_cal.numberBusinessDates('2024-01-01T00:00:00 ET', Timestamp)"
    ]
)

Common time operations

Aggregations over windows of time are essential for time-series analysis.

Daily summaries

Combine time operations with aggregations for powerful analysis. For example, the following query calculates the daily sum of trades for each ticker:

daily_summary = (
    trading_data.update(["Date = toLocalDate(Timestamp, 'America/New_York')"])
    .drop_columns(["Timestamp"])
    .sum_by(by=["Date", "Ticker"])
)

Aggregations over time buckets

Data is commonly placed into temporal buckets, allowing for analysis of trends over specific intervals. The following query places each trade into a 15-minute bucket, then calculates the average trade price and size for each bucket:

from deephaven import agg

avg_by_time_bucket = trading_data.update(
    ["TimeBucket = lowerBin(Timestamp, 15 * MINUTE)"]
).agg_by(
    [agg.avg("AvgPrice = Price"), agg.avg("AvgSize = Size")],
    by=["TimeBucket", "Ticker"],
)

Combine calendar and time methods

Here's a practical example that combines multiple time concepts:

from deephaven import empty_table
from deephaven.calendar import calendar

# Get NYSE calendar
nyse = calendar("USNYSE_EXAMPLE")

# Create realistic market data
market_data = empty_table(1000).update(
    [
        "Timestamp = '2024-01-01T09:00:00 ET' + 'PT5m' * ii",
        "Symbol = i % 3 == 0 ? `AAPL` : (i % 3 == 1 ? `GOOGL` : `MSFT`)",
        "Price = 150 + randomDouble(-50, 50)",
        "Volume = randomInt(100, 10000)",
    ]
)

# Filter to business hours and add time-based features
processed_data = market_data.where(["nyse.isBusinessTime(Timestamp)"]).update(
    [
        "Date = toLocalDate(Timestamp, 'America/New_York')",
        "MinutesSinceOpen = diffMinutes('2024-01-01T09:30:00 ET', Timestamp)",
        "IsEarlyTrading = MinutesSinceOpen <= 60",
    ]
)

# Calculate daily summary statistics
daily_summary = processed_data.drop_columns(["Timestamp"]).sum_by(by=["Symbol", "Date"])

This example demonstrates filtering by business hours, extracting time components, and performing time-based aggregations - all common patterns in financial data analysis.

Next steps

Time handling is crucial for real-time data processing. The concepts covered here form the foundation for more advanced time-series analysis, including: