Skip to content

capturegraph.scheduling.distance.time #

Time Distance - Circular Time-of-Day Distance#

Measures distance between times accounting for the circular nature of the 24-hour clock. Midnight is close to 11:59 PM.

time_of_day(sigma=timedelta(hours=1)) #

Create a circular time-of-day distance function.

Measures the angular distance on the 24-hour clock, treating time as circular (so 11:59 PM is close to 12:01 AM).

Parameters:

Name Type Description Default
sigma timedelta

Normalization factor as timedelta. The returned distance is normalized so times within sigma apart have distance < 1.0. Default is 1 hour.

timedelta(hours=1)

Returns:

Type Description
Callable[[datetime, datetime], float]

A distance function (time_a, time_b) -> float.

Example
import capturegraph.scheduling as cgsh
from datetime import timedelta

# Sessions within 2 hours are "close"
dist_fn = cgsh.distance.time_of_day(sigma=timedelta(hours=2))

# Use with combine for custom attribute
combined = cgsh.distance.combine(
    time_of_day=cgsh.distance.time_of_day(sigma=timedelta(hours=1))
)
Source code in capturegraph-lib/capturegraph/scheduling/distance/time.py
def time_of_day(
    sigma: timedelta = timedelta(hours=1),
) -> Callable[[datetime, datetime], float]:
    """Create a circular time-of-day distance function.

    Measures the angular distance on the 24-hour clock, treating time
    as circular (so 11:59 PM is close to 12:01 AM).

    Args:
        sigma: Normalization factor as timedelta. The returned distance is
            normalized so times within `sigma` apart have distance < 1.0.
            Default is 1 hour.

    Returns:
        A distance function `(time_a, time_b) -> float`.

    Example:
        ```python
        import capturegraph.scheduling as cgsh
        from datetime import timedelta

        # Sessions within 2 hours are "close"
        dist_fn = cgsh.distance.time_of_day(sigma=timedelta(hours=2))

        # Use with combine for custom attribute
        combined = cgsh.distance.combine(
            time_of_day=cgsh.distance.time_of_day(sigma=timedelta(hours=1))
        )
        ```
    """
    sigma_hours = sigma.total_seconds() / 3600.0

    def distance_fn(time_a: datetime, time_b: datetime) -> float:
        # Extract time of day as hours (0-24)
        hours_a = (
            time_a.hour
            + time_a.minute / 60.0
            + time_a.second / 3600.0
            + time_a.microsecond / 3600000.0
        )
        hours_b = (
            time_b.hour
            + time_b.minute / 60.0
            + time_b.second / 3600.0
            + time_b.microsecond / 3600000.0
        )

        # Circular distance on 24-hour clock
        diff = np.abs(hours_a - hours_b)
        circular_diff = np.minimum(diff, 24.0 - diff)

        return circular_diff / sigma_hours

    return distance_fn