Time

March 30, 2026 ยท View on GitHub

Bonsai.Clock contains some utils for dealing with time. You should use this over any other time sources, because it is designed for the browser, and can be controlled in tests.

Accessing Time

We can access the current time as a Time_ns.t Bonsai.t:

let approx_current_time (local_ graph) =
  let%arr now = Bonsai.Clock.approx_now ~tick_every:(Time_ns.Span.of_sec 1.) graph in
  Vdom.Node.text (Time_ns.to_string_utc now)
;;

There's also a Bonsai.Clock.Expert.now, which will update once every frame:

let current_time (local_ graph) =
  let%arr now = Bonsai.Clock.Expert.now graph in
  Vdom.Node.text (Time_ns.to_string_utc now)
;;

You should be very careful with Clock.Expert.now, because any computations depending on it will have to recompute, and possibly re-patch the DOM, every frame.

If you just want to display how long has passed since some time, you can also use [vdom_time_ago], which is implemented entirely in vdom, and won't impact your Bonsai computations:

let vdom_time_ago (local_ graph) =
  let time_since, set_time_since = Bonsai.state (Time_ns.now ()) graph in
  let%arr time_since and set_time_since in
  Vdom.Node.div
    [ Vdom.Node.button
        ~attrs:[ Vdom.Attr.on_click (fun _ -> set_time_since (Time_ns.now ())) ]
        [ Vdom.Node.text "Update base time" ]
    ; Vdom_time_ago.view time_since
    ]
;;

If we want to access the current time in effects without it becoming stale, we can use Bonsai.Clock.get_current_time:

let measure_time (local_ graph) =
  let%arr get_time = Bonsai.Clock.get_current_time graph in
  Vdom.Node.button
    ~attrs:
      [ Vdom.Attr.on_click (fun _ ->
          let%bind.Effect start = get_time in
          let%bind.Effect () = long_effect in
          let%bind.Effect end_ = get_time in
          let diff = Time_ns.diff end_ start |> Time_ns.Span.to_string_hum in
          Effect.alert [%string "that took: %{diff}"])
      ]
    [ Vdom.Node.text "Click to measure a long effect." ]
;;

Delaying Effects

Bonsai.Clock.sleep can be used to delay an effect (like setTimeout):

let clock_sleep_demo (local_ graph) =
  let%arr sleep = Bonsai.Clock.sleep graph in
  Vdom.Node.button
    ~attrs:
      [ Vdom.Attr.on_click (fun _ ->
          let%bind.Effect () = sleep (Time_ns.Span.of_sec 2.) in
          Effect.alert "... 2 seconds later...")
      ]
    [ Vdom.Node.text "delayed alert" ]
;;

Scheduling Effects

And Bonsai.Clock.every runs an effect repeatedly:

let clock_every_demo (local_ graph) =
  let count, set_count = Bonsai.state 0 graph in
  Bonsai.Clock.every
    ~when_to_start_next_effect:`Every_multiple_of_period_blocking
    ~trigger_on_activate:false
    (Bonsai.return (Time_ns.Span.of_sec 1.0))
    (let%arr count and set_count in
     set_count (count + 1))
    graph;
  let%arr count in
  Vdom.Node.text [%string "Seconds since you opened the page: %{count#Int}"]
;;