Skip to content

Time Control

A decorator to run a method based on a time condition.

Parameters:

Name Type Description Default
condition dict

A dictionary containing conditions to check against the time attribute. The keys can be ['year', 'month', 'weekday', 'freqstr'].

required
when_run bool

If True, the decorated method will run when the condition is met. If False, the decorated method will not run when the condition is met.

True
Example

class TestActor(Actor):
    @time_condition(condition={"month": 1, "day": 1}, when_run=True)
    def happy_new_year(self):
        print("Today is 1th, January, Happy new year!")


parameters = {"time": {"start": "1996-12-24", "days": 1}}


model = MainModel(parameters=parameters)
agent = model.agents.new(TestActor, 1, singleton=True)

for _ in range(10):
    print(f"Time now is {model.time}")
    model.time.go()
    agent.happy_new_year()
It should be called again in the next year beginning (i.e., 1998-01-01) if we run this model longer... It means, the function will be called when the condition is fully satisfied.

Source code in abses/time.py
def time_condition(condition: dict, when_run: bool = True) -> Callable:
    """
    A decorator to run a method based on a time condition.

    Parameters:
        condition:
            A dictionary containing conditions to check against the `time` attribute.
            The keys can be ['year', 'month', 'weekday', 'freqstr'].
        when_run:
            If True, the decorated method will run when the condition is met.
            If False, the decorated method will not run when the condition is met.

    Example:
        ```
        class TestActor(Actor):
            @time_condition(condition={"month": 1, "day": 1}, when_run=True)
            def happy_new_year(self):
                print("Today is 1th, January, Happy new year!")


        parameters = {"time": {"start": "1996-12-24", "days": 1}}


        model = MainModel(parameters=parameters)
        agent = model.agents.new(TestActor, 1, singleton=True)

        for _ in range(10):
            print(f"Time now is {model.time}")
            model.time.go()
            agent.happy_new_year()
        ```
        It should be called again in the next year beginning (i.e., `1998-01-01`) if we run this model longer... It means, the function will be called when the condition is fully satisfied.
    """

    def decorator(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            if not hasattr(self, "time"):
                raise AttributeError(
                    "The object doesn't have a TimeDriver object as `time` attribute."
                )
            time = self.time
            if not isinstance(time, TimeDriver):
                raise TypeError("The `TimeDriver` must be existing.")

            satisfied = all(
                getattr(time.dt, key, None) == value
                for key, value in condition.items()
            )

            if (satisfied and when_run) or (not satisfied and not when_run):
                return func(self, *args, **kwargs)

        return wrapper

    return decorator

Bases: _Component

TimeDriver provides the functionality to manage time.

This class is responsible for managing the time of a simulation model. It keeps track of the current time period, updates it according to a given frequency, and provides properties to access different components of the current time period (e.g., day, hour, etc.). The TimeDriver class is a singleton, meaning that there can be only one instance of it per simulation model.

When init a TimeDriver, it accepts below parameters:

Parameter Name Expected Data Type Default Value Description
start str, None None If None: use the current time, else: should be a string which can be parsed by pendulum.parse().
end str, int, None None If it's a string that can be parsed into datetime the model should end until achieving this time; if int: the model should end in that tick; if None no auto-end.
irregular bool False If False: not dive into an irregular mode (tick-mode); if True, the model will solve as an irregular mode.
years int 0 Time duration in years for the duration mode.
months int 0 Time duration in months for the duration mode.
weeks int 0 Time duration in weeks for the duration mode.
days int 0 Time duration in days for the duration mode.
hours int 0 Time duration in hours for the duration mode.
minutes int 0 Time duration in minutes for the duration mode.
seconds int 0 Time duration in seconds for the duration mode.

See tutorial to see more.

Source code in abses/time.py
def __init__(self, model: MainModel):
    super().__init__(model=model, name="time")
    self._model: MainModel[Any, Any] = model
    self._tick: int = 0
    self._start_dt: DateTime = pendulum.instance(datetime.now(), tz=None)
    self._end_dt: Optional[Union[DateTime, int]] = None
    self._duration: Optional[Duration] = None
    self._history: deque = deque()
    self._parse_time_settings()
    self._logging_setup()

fmt property

fmt

String format of datetime.

expected_ticks property

expected_ticks

Returns the expected ticks.

should_end property

should_end

Should the model end or not.

tick property

tick

Returns the current tick.

ticking_mode property

ticking_mode

Returns the ticking mode of the time driver.

history property

history

Returns the history of the time driver.

irregular property writable

irregular

Returns the irregular mode of the time driver.

duration property

duration

Returns the duration of the time driver.

start_dt property writable

start_dt

Returns the starting time for the model.

end_dt property writable

end_dt

The real-world time or the ticks when the model should be end.

dt property writable

dt

The current real-world time for the model without timezone information.

day property

day

Returns the current day for the model.

day_of_week property

day_of_week

Returns the number for the day of the week for the model.

day_of_year property

day_of_year

Returns the day of the year for the model.

days_in_month property

days_in_month

Get the total number of days of the month that this period falls on for the model.

hour property

hour

Get the hour of the day component of the Period.

minute property

minute

Get minute of the hour component of the Period.

month property

month

Return the month the current model's Period falls on.

quarter property

quarter

Return the quarter the current model's Period falls on.

second property

second

Get the second component of a model's Period.

is_leap_year property

is_leap_year

Return True if the period's year is in a leap year.

week_of_year property

week_of_year

Get the week of the year on the given Period.

week_of_month property

week_of_month

Get the week of the month on the given Period.

weekday property

weekday

Day of the week the period lies in, with Monday=0 and Sunday=6.

year property

year

Return the year this Period falls on.

go

go(ticks=1, **kwargs)

Increments the tick.

Parameters:

Name Type Description Default
ticks int

How many ticks to increase.

1
Source code in abses/time.py
def go(self, ticks: int = 1, **kwargs) -> None:
    """Increments the tick.

    Parameters:
        ticks:
            How many ticks to increase.
    """
    if ticks < 0:
        raise ValueError("Ticks cannot be negative.")
    if ticks == 0 and self.ticking_mode != "irregular":
        raise ValueError(
            "Ticks cannot be zero unless the ticking mode is 'irregular'."
        )
    if ticks > 1:
        for _ in range(ticks):
            self.go(ticks=1, **kwargs)
        return
    # tick = 1
    dt_msg = f" {self.strftime()}" if self.duration else ""
    tick_msg = f" [tick {self.tick}] "
    logger.bind(no_format=True).info(
        "\n" + f"{dt_msg}{tick_msg}".center(30, "-")
    )
    self._tick += ticks
    if self.ticking_mode == "duration":
        self.dt += self.duration
        self._history.append(self.dt)
    elif self.ticking_mode == "irregular":
        self.dt += pendulum.duration(**kwargs)
        self._history.append(self.dt)
    elif self.ticking_mode != "tick":
        raise ValueError(f"Invalid ticking mode: {self.ticking_mode}")
    # end going
    if self.should_end:
        self._model.running = False

parse_duration

parse_duration(duration)

Set the duration of the time driver.

Source code in abses/time.py
def parse_duration(self, duration: DictConfig) -> None:
    """Set the duration of the time driver."""
    valid_attributes = VALID_DT_ATTRS
    valid_dict = {}
    for attribute in valid_attributes:
        value = duration.get(attribute, 0)
        if not isinstance(value, int):
            raise TypeError(f"{attribute} must be an integer.")
        valid_dict[attribute] = value
    if all(value == 0 for value in valid_dict.values()):
        self._duration = None
    else:
        self._duration = pendulum.duration(**valid_dict)

strftime

strftime(fmt=None)

Returns a string representing the current time.

Parameters:

Name Type Description Default
fmt Optional[str]

An explicit format string of datetime.

None
Source code in abses/time.py
def strftime(self, fmt: Optional[str] = None) -> str:
    """Returns a string representing the current time.

    Parameters:
        fmt:
            An explicit format string of datetime.
    """
    return (
        self.dt.strftime(self.fmt)
        if fmt is None
        else self.dt.strftime(fmt)
    )