I want to keep some sort of reference for my future self when I eventually need to do any time aware programming. To keep it as language agnostic as possible, I'll only cover the basics.
Why we need to be careful with time in the first place
Every time we look at a watch piece you can see time increase steadily (if you're not near any black holes). This creates an implicit assumption in your subconscient that time always moves forward, despite also grasping notions like daylight savings, leap seconds, and other things humans do to synchronise their time with what seems to be happening in the solar system.
While a regular old watch will not likely move backwards one hour, the clock on your smartphone or computer will certainly do it at some point. Any systems you build will need to be extra careful to avoid making calculations based on observed time, as it can lead to some weird things.
So here are a couple of useful things you might need to do every now and then in any language:
How long does this bit of code take to run?
The naive approach would be to look at the clock, write it down, perform the action, look at the clock a second time and write the time again, and subtract the 2 times to calculate the time the action took. Well, what happens if time adjustments are performed while the action is being performed? This will ruin your readings and may impact more complex systems in severe ways.
As it turns out, Operating Systems have known this to be a problem for quite some time and modern OSes will provide an API to obtain what is called a monotonic time source. Monotonic is a fancy term for meaning that readings will always indicate that time is moving forwards, but may be equal (meaning time is "frozen"). If you can get a strictly monotonic time source, then that means time will always seem to move forward. The implementation details are interesting, but left as homework for the reader. What matters is that you have access to a time source that has little to no volatility in the event of a time warp.
To summarise, here is how you'd do it the wrong way in Elixir:
The right way to do it:
The BEAM VM can be configured to correct time warps over time, which means that from your program's perspective time is always passing smoothly. This is exactly what you need when measuring the time it takes some code to run.
What time is it?
Sometimes you just can't escape looking at the clock. For example, you might need to log what time it is in the context of a crash, and in this case the monotonic time source just isn't useful. But herein lies a secret: the Erlang/Elixir virtual machine has a second layer of defense in front of the operating system time, which it refers to as system time. This time source may be out of sync with the operating system (in the event of a time warp), but the BEAM will be working to synchronise the 2 clocks by speeding up or slowing down the system time. This is a pretty nifty trick.
This is how you'd go about getting the system time in Elixir:
Everything in order, chief?
Let's say that you're building a distributed queue. You want to ensure that there is an absolute order among all requests received, so that they are processed in the right order. Your first approach could be based on the previous techniques I've showed, but that wouldn't be enough: when multiple processes are handling user requests concurrently, a timestamp based approach is bound to have collisions (i.e. two requests will have the same timestamp). If you need an absolute order, the BEAM has got you covered too. There is a built in function that returns a unique integer number and you can use this function to build a sequence of integers, effectively solving this need:
iex(1)> System.unique_integer() -576460752303423485
The reason the VM starts with a negative number is to increase the range of numbers you can get. If you'd rather just work with positive numbers, pass in an additional parameter:
iex(2)> System.unique_integer([:positive]) 68
And now you can define an absolute order to whatever you need (system events, etc).
There's also another useful purpose for the
System.unique_integer function, since it guarantees that no 2 returned numbers are the same: making unique names! So if you're starting many instances of the same process and you'd like them to have unique names, now you know how you can do that easily.
This post was a sort of a reboot of my previous cringy version from 2017. Thanks to ferd for writing this, which is the basis for this post. I've read it many times trying to figure out what it all means.