To add to Rob's post, in the context of User Experience, the best you can do is give a user an estimate which is updated block to block.
Most logic on the blockchain is measured out in blocks.
Unfortunately, no consensus system that I know of can provide perfectly accurate time between blocks. Instead, the network has mechanisms to encourage block production within an average window, but can always allow for some error from that average window from block to block.
For example, in Ethereum and Bitcoin, the block difficulty is adjusted to account for having blocks produced at a specific average interval, however lucky (or unlucky) mining could lead to short or long blocks.
For Substrate / Polkadot, we allow for block producers to miss their slot, which can lead to longer time between blocks.
So, if presenting a user with an estimate of how much time before some event on chain happens, I would do the following:
Get the timestamp of the last block produced from the Timestamp
pallet.
Calculate the estimated future time as a best effort.
time_at_last_block + average_block_time * number_of_blocks
Listen for a new block to be produced, and repeat this process.
This means that a user may see a countdown time go higher after going down, but practically speaking, this is an accurate representation of what is happening on chain.
You can query the average_block_time
from the BABE constants: