An alternative to @Krut's implementation that is quite a bit faster based in our testing:
CREATE OR REPLACE FUNCTION uuid_timestamp(uuid UUID) RETURNS TIMESTAMPTZ AS $$
DECLARE
bytes bytea;
BEGIN
bytes := uuid_send(uuid);
RETURN to_timestamp(
(
(
(get_byte(bytes, 0)::bigint << 24) |
(get_byte(bytes, 1)::bigint << 16) |
(get_byte(bytes, 2)::bigint << 8) |
(get_byte(bytes, 3)::bigint << 0)
) + (
((get_byte(bytes, 4)::bigint << 8 |
get_byte(bytes, 5)::bigint)) << 32
) + (
(((get_byte(bytes, 6)::bigint & 15) << 8 | get_byte(bytes, 7)::bigint) & 4095) << 48
) - 122192928000000000
) / 10000 / 1000::double precision
);
END
$$ LANGUAGE plpgsql
IMMUTABLE PARALLEL SAFE
RETURNS NULL ON NULL INPUT;
Note, it will only do millisecond precision so you may want to tweak the function's "/ 10000 / 1000::double precision" bit to just "/ 10000000::double precision" instead.
Example:
=> select uuid_timestamp(uuid_generate_v1()), now();
uuid_timestamp | now
----------------------------+-------------------------------
2020-04-29 17:40:54.519+00 | 2020-04-29 17:40:54.518204+00
(1 row)
Also, it is assuming the input is a v1. If you attempt to give it something like a v4, expect to get weird answers or alter the function to RAISE if it isn't v1.
=> select uuid_timestamp(uuid_generate_v4());
uuid_timestamp
----------------------------
4251-12-19 17:38:34.866+00
(1 row)