rand: add rand.ulid() (#5979)
* removed debug println * added newline to the end of the file * time: add .unix_time_milli() method; rand,time: add tests * rand: add more ulid tests; move tests to a separate file random_identifiers_test.v * run vfmt over vlib/rand/random_identifiers_test.v * speed up time.unix_time_milli * simplify and speedup time.unix_time/0 and time.new_time/1 * update comment about rand.ulid() * fix terminating 0 off by 1 issue in rand.ulid() * optimize time.new_time() * restore the master version of vlib/time/parse.v * make test_unix_time more robust Co-authored-by: Delyan Angelov <delian66@gmail.com>pull/5983/head
parent
9e652c4f40
commit
7d52d612ce
|
@ -6,6 +6,8 @@ module rand
|
||||||
import rand.util
|
import rand.util
|
||||||
import rand.wyrand
|
import rand.wyrand
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
// Configuration struct for creating a new instance of the default RNG.
|
// Configuration struct for creating a new instance of the default RNG.
|
||||||
pub struct PRNGConfigStruct {
|
pub struct PRNGConfigStruct {
|
||||||
seed []u32 = util.time_seed_array(2)
|
seed []u32 = util.time_seed_array(2)
|
||||||
|
@ -182,3 +184,51 @@ pub fn uuid_v4() string {
|
||||||
}
|
}
|
||||||
return string(buf, buflen)
|
return string(buf, buflen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const(
|
||||||
|
ulid_encoding = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
|
||||||
|
)
|
||||||
|
|
||||||
|
// rand.ulid generates an Unique Lexicographically sortable IDentifier.
|
||||||
|
// See https://github.com/ulid/spec .
|
||||||
|
// NB: ULIDs can leak timing information, if you make them public, because
|
||||||
|
// you can infer the rate at which some resource is being created, like
|
||||||
|
// users or business transactions.
|
||||||
|
// (https://news.ycombinator.com/item?id=14526173)
|
||||||
|
pub fn ulid() string {
|
||||||
|
buflen := 26
|
||||||
|
mut buf := malloc(27)
|
||||||
|
// time section
|
||||||
|
mut t := time.utc().unix_time_milli()
|
||||||
|
mut i := 9
|
||||||
|
for i >= 0 {
|
||||||
|
unsafe{
|
||||||
|
buf[i] = ulid_encoding[t & 0x1F]
|
||||||
|
}
|
||||||
|
t = t >> 5
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
// first rand set
|
||||||
|
mut x := default_rng.u64()
|
||||||
|
i = 10
|
||||||
|
for i < 19 {
|
||||||
|
unsafe{
|
||||||
|
buf[i] = ulid_encoding[x & 0x1F]
|
||||||
|
}
|
||||||
|
x = x >> 5
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
// second rand set
|
||||||
|
x = default_rng.u64()
|
||||||
|
for i < 26 {
|
||||||
|
unsafe{
|
||||||
|
buf[i] = ulid_encoding[x & 0x1F]
|
||||||
|
}
|
||||||
|
x = x >> 5
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
unsafe{
|
||||||
|
buf[26] = 0
|
||||||
|
}
|
||||||
|
return string(buf,buflen)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
import time
|
||||||
|
import rand
|
||||||
|
|
||||||
|
// uuid_v4:
|
||||||
|
fn test_rand_uuid_v4() {
|
||||||
|
uuid1 := rand.uuid_v4()
|
||||||
|
uuid2 := rand.uuid_v4()
|
||||||
|
uuid3 := rand.uuid_v4()
|
||||||
|
assert uuid1 != uuid2
|
||||||
|
assert uuid1 != uuid3
|
||||||
|
assert uuid2 != uuid3
|
||||||
|
assert uuid1.len == 36
|
||||||
|
assert uuid2.len == 36
|
||||||
|
assert uuid3.len == 36
|
||||||
|
assert uuid1[14] == `4`
|
||||||
|
assert uuid2[14] == `4`
|
||||||
|
assert uuid3[14] == `4`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ulids:
|
||||||
|
fn test_ulids_are_unique() {
|
||||||
|
ulid1 := rand.ulid()
|
||||||
|
ulid2 := rand.ulid()
|
||||||
|
ulid3 := rand.ulid()
|
||||||
|
assert ulid1.len == 26
|
||||||
|
assert ulid2.len == 26
|
||||||
|
assert ulid3.len == 26
|
||||||
|
assert ulid1 != ulid2
|
||||||
|
assert ulid1 != ulid3
|
||||||
|
assert ulid2 != ulid3
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_ulids_max_start_character_is_ok() {
|
||||||
|
ulid1 := rand.ulid()
|
||||||
|
// the largest valid ULID encoded in Base32 is 7ZZZZZZZZZZZZZZZZZZZZZZZZZ
|
||||||
|
assert (int(ulid1[0]) - 48) <= 7
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_ulids_generated_in_the_same_millisecond_have_the_same_prefix() {
|
||||||
|
mut t1 := time.utc()
|
||||||
|
mut t2 := time.utc()
|
||||||
|
mut ulid1 := ''
|
||||||
|
mut ulid2 := ''
|
||||||
|
mut ulid3 := ''
|
||||||
|
for {
|
||||||
|
t1 = time.utc()
|
||||||
|
ulid1 = rand.ulid()
|
||||||
|
ulid2 = rand.ulid()
|
||||||
|
ulid3 = rand.ulid()
|
||||||
|
t2 = time.utc()
|
||||||
|
if t1.unix_time_milli() == t2.unix_time_milli() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ulid1_prefix := ulid1[0..10]
|
||||||
|
ulid2_prefix := ulid2[0..10]
|
||||||
|
ulid3_prefix := ulid3[0..10]
|
||||||
|
assert ulid1_prefix == ulid2_prefix
|
||||||
|
assert ulid1_prefix == ulid3_prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_ulids_should_be_lexicographically_ordered_when_not_in_same_millisecond() {
|
||||||
|
ulid1 := rand.ulid()
|
||||||
|
time.sleep_ms(1)
|
||||||
|
ulid2 := rand.ulid()
|
||||||
|
time.sleep_ms(1)
|
||||||
|
ulid3 := rand.ulid()
|
||||||
|
mut all := [ulid3, ulid2, ulid1]
|
||||||
|
// eprintln('all before: $all')
|
||||||
|
all.sort()
|
||||||
|
// eprintln('all after: $all')
|
||||||
|
s1 := all[0]
|
||||||
|
s2 := all[1]
|
||||||
|
s3 := all[2]
|
||||||
|
assert s1 == ulid1
|
||||||
|
assert s2 == ulid2
|
||||||
|
assert s3 == ulid3
|
||||||
|
}
|
|
@ -175,18 +175,3 @@ fn test_rand_f64_in_range() {
|
||||||
assert value < max
|
assert value < max
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_rand_uuid_v4() {
|
|
||||||
uuid1 := rand.uuid_v4()
|
|
||||||
uuid2 := rand.uuid_v4()
|
|
||||||
uuid3 := rand.uuid_v4()
|
|
||||||
assert uuid1 != uuid2
|
|
||||||
assert uuid1 != uuid3
|
|
||||||
assert uuid2 != uuid3
|
|
||||||
assert uuid1.len == 36
|
|
||||||
assert uuid2.len == 36
|
|
||||||
assert uuid3.len == 36
|
|
||||||
assert uuid1[14] == `4`
|
|
||||||
assert uuid2[14] == `4`
|
|
||||||
assert uuid3[14] == `4`
|
|
||||||
}
|
|
||||||
|
|
|
@ -138,27 +138,8 @@ pub fn (t Time) smonth() string {
|
||||||
|
|
||||||
// new_time returns a time struct with calculated Unix time.
|
// new_time returns a time struct with calculated Unix time.
|
||||||
pub fn new_time(t Time) Time {
|
pub fn new_time(t Time) Time {
|
||||||
return Time{
|
|
||||||
year: t.year
|
|
||||||
month: t.month
|
|
||||||
day: t.day
|
|
||||||
hour: t.hour
|
|
||||||
minute: t.minute
|
|
||||||
second: t.second
|
|
||||||
unix: u64(t.unix_time())
|
|
||||||
microsecond: t.microsecond
|
|
||||||
}
|
|
||||||
// TODO Use the syntax below when it works with reserved keywords like `unix`
|
|
||||||
// return {
|
|
||||||
// t |
|
|
||||||
// unix:t.unix_time()
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// unix_time returns Unix time.
|
|
||||||
pub fn (t Time) unix_time() int {
|
|
||||||
if t.unix != 0 {
|
if t.unix != 0 {
|
||||||
return int(t.unix)
|
return t
|
||||||
}
|
}
|
||||||
tt := C.tm{
|
tt := C.tm{
|
||||||
tm_sec: t.second
|
tm_sec: t.second
|
||||||
|
@ -168,7 +149,20 @@ pub fn (t Time) unix_time() int {
|
||||||
tm_mon: t.month - 1
|
tm_mon: t.month - 1
|
||||||
tm_year: t.year - 1900
|
tm_year: t.year - 1900
|
||||||
}
|
}
|
||||||
return make_unix_time(tt)
|
utime := u64(make_unix_time(tt))
|
||||||
|
return { t | unix: utime }
|
||||||
|
}
|
||||||
|
|
||||||
|
// unix_time returns Unix time.
|
||||||
|
[inline]
|
||||||
|
pub fn (t Time) unix_time() int {
|
||||||
|
return int(t.unix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unix_time_milli returns Unix time with millisecond resolution.
|
||||||
|
[inline]
|
||||||
|
pub fn (t Time) unix_time_milli() u64 {
|
||||||
|
return t.unix * 1000 + u64(t.microsecond/1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
// add_seconds returns a new time struct with an added number of seconds.
|
// add_seconds returns a new time struct with an added number of seconds.
|
||||||
|
|
|
@ -192,3 +192,21 @@ fn test_utc() {
|
||||||
assert now.microsecond >= 0
|
assert now.microsecond >= 0
|
||||||
assert now.microsecond < 1000000
|
assert now.microsecond < 1000000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_unix_time() {
|
||||||
|
t1 := time.utc()
|
||||||
|
time.sleep_ms(50)
|
||||||
|
t2 := time.utc()
|
||||||
|
ut1 := t1.unix_time()
|
||||||
|
ut2 := t2.unix_time()
|
||||||
|
assert ut2 - ut1 < 2
|
||||||
|
//
|
||||||
|
utm1 := t1.unix_time_milli()
|
||||||
|
utm2 := t2.unix_time_milli()
|
||||||
|
assert (utm1 - u64(ut1)*1000) < 1000
|
||||||
|
assert (utm2 - u64(ut2)*1000) < 1000
|
||||||
|
//
|
||||||
|
//println('utm1: $utm1 | utm2: $utm2')
|
||||||
|
assert utm2 - utm1 > 2
|
||||||
|
assert utm2 - utm1 < 999
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue