Skip to main content

Access Onchain Time

If you need a near real-time measurement (within a few seconds) for your application, use the immutable reference of time that the Clock module in Move provides. The reference value from this module updates with every consensus commit. If you do not need the current time, use the epoch_timestamp_ms function to refer to the timestamp of when the current epoch started.

The sui::clock::Clock module​

To access the current timestamp, you must pass a read-only reference of sui::clock::Clock as an entry function parameter in your transactions. Sui provides an instance of Clock at address 0x6. You cannot create new instances.

Use the timestamp_ms function from the sui::clock module to extract a Unix timestamp in milliseconds.

public fun timestamp_ms(clock: &Clock): u64 {
clock.timestamp_ms
}

The following clock.move example demonstrates an entry function that emits an event containing a timestamp from sui::clock::Clock:

module basics::clock;

use sui::clock::Clock;
use sui::event;

public struct TimeEvent has copy, drop, store {
timestamp_ms: u64,
}

entry fun access(clock: &Clock) {
event::emit(TimeEvent { timestamp_ms: clock.timestamp_ms() });
}

Call the access entry function with the following format, passing 0x6 as the address for the Clock parameter:

tip

Beginning with the Sui v1.24.1 release, the --gas-budget option is no longer required for CLI commands.

$ sui client call --package <EXAMPLE> --module 'clock' --function 'access' --args '0x6'

The sui::clock::Clock timestamp advances with each consensus commit, which occurs approximately every 1/4 second.

Timestamps from sui::clock::Clock follow two rules:

  • Within a transaction: Every successful call to sui::clock::timestamp_ms in the same transaction returns the same value, because transactions take effect instantly.
  • Across transactions: Timestamps are monotonic across transactions that touch the same shared objects. A successful transaction sees a timestamp greater than or equal to the timestamps its predecessors saw.

Any transaction that requires access to sui::clock::Clock must go through consensus because the only available instance is a shared object. As a result, this technique is not suitable for transactions that must use the single-owner fastpath. See Epoch timestamps for a single-owner-compatible source of timestamps.

Transactions that use this workflow must accept it as an immutable reference, not a mutable reference or value. This prevents contention, as transactions that access sui::clock::Clock can only read it, so they do not need sequencing relative to each other. Validators refuse to sign transactions that do not meet this requirement. Packages that include entry functions that accept sui::clock::Clock or &mut Clock fail to publish.

The following functions test sui::clock::Clock-dependent code by manually creating a Clock object and manipulating its timestamp. This is only possible in test code:

public fun create_for_testing(ctx: &mut TxContext): Clock {
Clock {
id: object::new(ctx),
timestamp_ms: borrow().timestamp_ms(),
}
}
public fun share_for_testing(clock: Clock) {
transfer::share_object(clock)
}

#[test_only, deprecated(note = b"Use `sui::test_scenario::increment_clock_timestamp`")]
public fun increment_for_testing(clock: &mut Clock, tick: u64) {
set_for_testing(clock, borrow().timestamp_ms() + tick);
}

#[test_only, deprecated(note = b"Use `sui::test_scenario::set_clock_timestamp`")]
public fun set_for_testing(clock: &mut Clock, timestamp_ms: u64) {
set_timestamp_ms_for_testing(timestamp_ms);
clock.timestamp_ms = timestamp_ms;
}
public fun destroy_for_testing(clock: Clock) {
let Clock { id, timestamp_ms: _ } = clock;
id.delete();
}

The next example presents a basic test that creates a Clock object, increments it, and then checks its value:

#[test_only]
module sui::clock_tests;

use std::unit_test::assert_eq;
use sui::clock;
use sui::test_scenario;

#[test, allow(deprecated_usage)]
fun creating_a_clock_and_incrementing_it() {
let mut ctx = tx_context::dummy();
let mut clock = clock::create_for_testing(&mut ctx);

clock.increment_for_testing(42);
assert!(clock.timestamp_ms() == 42);

clock.set_for_testing(50);
assert!(clock.timestamp_ms() == 50);

clock.destroy_for_testing();
}

#[test, allow(deprecated_usage)]
fun borrowed_clock_follows_the_test_clock() {
assert!(clock::borrow().timestamp_ms() == 0);

let mut ctx = tx_context::dummy();
let mut clock = clock::create_for_testing(&mut ctx);
clock.set_for_testing(50);
assert!(clock::borrow().timestamp_ms() == 50);

clock.increment_for_testing(5);
assert!(clock::borrow().timestamp_ms() == 55);

let fresh = clock::create_for_testing(&mut ctx);
assert!(fresh.timestamp_ms() == 55);
assert!(clock::borrow().timestamp_ms() == 55);

fresh.destroy_for_testing();
clock.destroy_for_testing();
}

#[test, allow(deprecated_usage)]
fun deprecated_setters_update_outstanding_borrows() {
let borrowed = clock::borrow();
let mut ctx = tx_context::dummy();
let mut clock = clock::create_for_testing(&mut ctx);

clock.set_for_testing(50);
assert_eq!(borrowed.timestamp_ms(), 50);

clock.increment_for_testing(5);
assert_eq!(borrowed.timestamp_ms(), 55);
assert_eq!(clock::borrow().timestamp_ms(), 55);

clock.destroy_for_testing();
}

#[test, allow(deprecated_usage)]
fun deprecated_increment_reads_the_current_time() {
let mut ctx = tx_context::dummy();
let mut stale = clock::create_for_testing(&mut ctx);
let mut fresh = clock::create_for_testing(&mut ctx);

fresh.increment_for_testing(10);
stale.increment_for_testing(5);
assert_eq!(stale.timestamp_ms(), 15);
assert_eq!(clock::borrow().timestamp_ms(), 15);

stale.destroy_for_testing();
fresh.destroy_for_testing();
}

#[test, allow(deprecated_usage), expected_failure(abort_code = clock::ETimestampDecreased)]
fun deprecated_set_rejects_an_earlier_time() {
let mut ctx = tx_context::dummy();
let mut stale = clock::create_for_testing(&mut ctx);
let mut fresh = clock::create_for_testing(&mut ctx);

fresh.set_for_testing(100);
stale.set_for_testing(50);
abort
}

#[test, allow(deprecated_usage)]
fun create_for_testing_keeps_the_scenario_time() {
let mut scenario = test_scenario::begin(@0xA);
let builder = scenario.ctx_builder().set_clock_timestamp(100);
scenario.next_with_context(builder);

let clock = clock::create_for_testing(scenario.ctx());
assert_eq!(clock.timestamp_ms(), 100);
assert_eq!(clock::borrow().timestamp_ms(), 100);
clock.destroy_for_testing();

scenario.create_system_objects();
assert_eq!(clock::borrow().timestamp_ms(), 102);
let clock = scenario.take_shared<clock::Clock>();
assert_eq!(clock.timestamp_ms(), 101);
test_scenario::return_shared(clock);
scenario.end();
}

#[test]
fun scenario_clock_starts_at_zero() {
let scenario = test_scenario::begin(@0xA);
assert_eq!(clock::borrow().timestamp_ms(), 0);
scenario.end();
}

#[test]
fun new_transactions_advance_the_clock() {
let mut scenario = test_scenario::begin(@0xA);
let builder = scenario.ctx_builder().set_clock_timestamp(100);
scenario.next_with_context(builder);
scenario.next_tx(@0xB);
assert_eq!(clock::borrow().timestamp_ms(), 101);
scenario.next_epoch(@0xB);
assert_eq!(clock::borrow().timestamp_ms(), 102);
scenario.later_epoch(1000, @0xB);
assert_eq!(clock::borrow().timestamp_ms(), 103);
scenario.skip_to_epoch(5);
assert_eq!(clock::borrow().timestamp_ms(), 104);
scenario.skip_to_epoch(5);
assert_eq!(clock::borrow().timestamp_ms(), 104);
scenario.end();
}

#[test]
fun begin_with_context_keeps_the_clock_by_default() {
let scenario = test_scenario::begin_with_context(test_scenario::ctx_builder_from_sender(@0xA));
assert_eq!(clock::borrow().timestamp_ms(), 0);
scenario.end();
}

#[test]
fun begin_with_context_sets_the_clock() {
let builder = test_scenario::ctx_builder_from_sender(@0xA).set_clock_timestamp(100);
let scenario = test_scenario::begin_with_context(builder);
assert_eq!(clock::borrow().timestamp_ms(), 100);
scenario.end();
}

#[test]
fun next_with_context_sets_the_clock() {
let mut scenario = test_scenario::begin(@0xA);
let builder = scenario.ctx_builder().set_clock_timestamp(100);
scenario.next_with_context(builder);
assert_eq!(clock::borrow().timestamp_ms(), 100);

let builder = scenario.ctx_builder().set_clock_timestamp(250);

scenario.next_with_context(builder);
assert_eq!(clock::borrow().timestamp_ms(), 250);
scenario.end();
}

#[test]
fun next_with_context_increments_the_clock() {
let mut scenario = test_scenario::begin(@0xA);
let builder = scenario.ctx_builder().increment_clock_timestamp(10);
scenario.next_with_context(builder);
assert_eq!(clock::borrow().timestamp_ms(), 10);

let builder = scenario.ctx_builder().increment_clock_timestamp(5).increment_clock_timestamp(7);
scenario.next_with_context(builder);
assert_eq!(clock::borrow().timestamp_ms(), 22);

let builder = scenario.ctx_builder().set_clock_timestamp(100).increment_clock_timestamp(1);
scenario.next_with_context(builder);
assert_eq!(clock::borrow().timestamp_ms(), 101);
scenario.end();
}

#[test]
fun clock_setters_combine_on_one_builder() {
let mut scenario = test_scenario::begin(@0xA);
let builder = scenario.ctx_builder().set_clock_timestamp(200).set_clock_timestamp(100);
scenario.next_with_context(builder);
assert_eq!(clock::borrow().timestamp_ms(), 100);

let builder = scenario.ctx_builder().increment_clock_timestamp(5).increment_clock_timestamp(7);
scenario.next_with_context(builder);
assert_eq!(clock::borrow().timestamp_ms(), 112);

let builder = scenario.ctx_builder().increment_clock_timestamp(1000).set_clock_timestamp(150);
scenario.next_with_context(builder);
assert_eq!(clock::borrow().timestamp_ms(), 150);

let builder = scenario.ctx_builder().set_clock_timestamp(200).increment_clock_timestamp(10);
scenario.next_with_context(builder);
assert_eq!(clock::borrow().timestamp_ms(), 210);

let builder = scenario
.ctx_builder()
.increment_clock_timestamp(10)
.set_clock_timestamp(300)
.increment_clock_timestamp(5)
.set_clock_timestamp(400)
.increment_clock_timestamp(1);
scenario.next_with_context(builder);
assert_eq!(clock::borrow().timestamp_ms(), 401);
scenario.end();
}

#[test, expected_failure(abort_code = test_scenario::EClockTimestampNotIncreased)]
fun second_set_clock_timestamp_rejects_the_current_timestamp() {
let mut scenario = test_scenario::begin(@0xA);
let builder = scenario.ctx_builder().set_clock_timestamp(100);
scenario.next_with_context(builder);
let _builder = scenario.ctx_builder().set_clock_timestamp(200).set_clock_timestamp(100);
abort
}

#[test]
fun next_with_context_advances_the_clock_by_default() {
let mut scenario = test_scenario::begin(@0xA);
let builder = scenario.ctx_builder().set_clock_timestamp(100);
scenario.next_with_context(builder);
let builder = scenario.ctx_builder();
scenario.next_with_context(builder);
assert_eq!(clock::borrow().timestamp_ms(), 101);
let rgp = scenario.ctx().reference_gas_price();
let builder = test_scenario::ctx_builder_from_sender(@0xB).set_reference_gas_price(rgp);
scenario.next_with_context(builder);
assert_eq!(clock::borrow().timestamp_ms(), 102);
scenario.end();
}

#[test]
fun next_with_context_advances_the_clock_once_across_epochs() {
let mut scenario = test_scenario::begin(@0xA);
let builder = scenario.ctx_builder().set_epoch(3);
scenario.next_with_context(builder);
assert_eq!(scenario.ctx().epoch(), 3);
assert_eq!(clock::borrow().timestamp_ms(), 1);
scenario.end();
}

#[test, expected_failure(abort_code = test_scenario::EClockTimestampNotIncreased)]
fun set_clock_timestamp_rejects_the_current_timestamp() {
let mut scenario = test_scenario::begin(@0xA);
let builder = scenario.ctx_builder().set_clock_timestamp(100);
scenario.next_with_context(builder);
let _builder = scenario.ctx_builder().set_clock_timestamp(100);
abort
}

#[test, expected_failure(abort_code = test_scenario::EClockTimestampNotIncreased)]
fun increment_clock_timestamp_rejects_zero() {
let scenario = test_scenario::begin(@0xA);
let _builder = scenario.ctx_builder().increment_clock_timestamp(0);
abort
}

#[test]
fun next_with_context_sets_the_clock_across_epochs() {
let mut scenario = test_scenario::begin(@0xA);
let builder = scenario.ctx_builder().set_epoch(3).set_clock_timestamp(100);
scenario.next_with_context(builder);
assert_eq!(scenario.ctx().epoch(), 3);
assert_eq!(clock::borrow().timestamp_ms(), 100);
scenario.end();
}

#[test]
fun next_with_context_updates_outstanding_borrows() {
let mut scenario = test_scenario::begin(@0xA);
let borrowed = clock::borrow();
assert_eq!(borrowed.timestamp_ms(), 0);

let builder = scenario.ctx_builder().set_clock_timestamp(100);

scenario.next_with_context(builder);
assert_eq!(borrowed.timestamp_ms(), 100);

let builder = scenario.ctx_builder().increment_clock_timestamp(5);

scenario.next_with_context(builder);
assert_eq!(borrowed.timestamp_ms(), 105);
scenario.end();
}

#[test, expected_failure(abort_code = test_scenario::EClockTimestampNotIncreased)]
fun next_with_context_rejects_an_earlier_timestamp() {
let mut scenario = test_scenario::begin(@0xA);
let builder = scenario.ctx_builder().set_clock_timestamp(100);
scenario.next_with_context(builder);
let builder = scenario.ctx_builder().set_clock_timestamp(99);
scenario.next_with_context(builder);
abort
}

#[test, expected_failure(abort_code = test_scenario::EClockTimestampNotIncreased)]
fun next_with_context_rejects_a_stale_builder() {
let mut scenario = test_scenario::begin(@0xA);
let stale = scenario.ctx_builder().increment_clock_timestamp(10);
let builder = scenario.ctx_builder().set_clock_timestamp(100);
scenario.next_with_context(builder);
scenario.next_with_context(stale);
abort
}

#[test, expected_failure(abort_code = test_scenario::EClockTimestampNotIncreased)]
fun next_with_context_rejects_an_earlier_timestamp_across_epochs() {
let mut scenario = test_scenario::begin(@0xA);
let builder = scenario.ctx_builder().set_clock_timestamp(100);
scenario.next_with_context(builder);
let builder = scenario.ctx_builder().set_epoch(2).set_clock_timestamp(99);
scenario.next_with_context(builder);
abort
}

#[
test,
allow(
deprecated_usage,
),
expected_failure(
abort_code = test_scenario::EClockTimestampNotIncreased,
),
]
fun begin_with_context_rejects_an_earlier_timestamp() {
let mut ctx = tx_context::dummy();
let mut clock = clock::create_for_testing(&mut ctx);
clock.set_for_testing(100);
clock.destroy_for_testing();
let builder = test_scenario::ctx_builder_from_sender(@0xA).set_clock_timestamp(99);
let _scenario = test_scenario::begin_with_context(builder);
abort
}

How do I read the time without a Clock parameter?​

From protocol version 141, the Sui framework provides sui::clock::borrow, which returns a &Clock without the Clock being a transaction input. It is the same Clock, at the same timestamp, as one passed into the same transaction, so timestamp_ms returns the same value either way.

public fun borrow(): &Clock {
native_borrow()
}

The framework recommends this for new code: a function that reads the time through borrow keeps its signature, so its callers do not change when it starts to need the time. To call an existing function that takes a &Clock argument, pass it clock::borrow() instead of threading a Clock through every caller.

Epoch timestamps​

Use the following function from the sui::tx_context module to access the timestamp for the start of the current epoch for all transactions, including ones that do not go through consensus:

public fun epoch_timestamp_ms(_self: &TxContext): u64 {
native_epoch_timestamp_ms()
}

This function returns the point in time when the current epoch started as a millisecond granularity Unix timestamp in a u64. This value changes roughly once every 24 hours when the epoch changes.

Tests based on sui::test_scenario can use later_epoch to exercise time-sensitive code that uses epoch_timestamp_ms:

public fun later_epoch(
scenario: &mut Scenario,
delta_ms: u64,
sender: address,
): TransactionEffects {
scenario.ctx.increment_epoch_timestamp(delta_ms);
next_epoch(scenario, sender)
}

later_epoch behaves like sui::test_scenario::next_epoch as it finishes the current transaction and epoch in the test scenario, but also increments the timestamp by delta_ms milliseconds to simulate the progress of time.