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.wyrand | ||||
| 
 | ||||
| import time | ||||
| 
 | ||||
| // Configuration struct for creating a new instance of the default RNG.
 | ||||
| pub struct PRNGConfigStruct { | ||||
| 	seed []u32 = util.time_seed_array(2) | ||||
|  | @ -182,3 +184,51 @@ pub fn uuid_v4() string { | |||
| 	} | ||||
| 	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 | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 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.
 | ||||
| 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 { | ||||
| 		return int(t.unix) | ||||
| 		return t | ||||
| 	} | ||||
| 	tt := C.tm{ | ||||
| 		tm_sec: t.second | ||||
|  | @ -168,7 +149,20 @@ pub fn (t Time) unix_time() int { | |||
| 		tm_mon: t.month - 1 | ||||
| 		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.
 | ||||
|  |  | |||
|  | @ -192,3 +192,21 @@ fn test_utc() { | |||
| 	assert now.microsecond 		>= 0 | ||||
| 	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