ruby-ulid 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +48 -28
- data/lib/ulid/crockford_base32.rb +52 -46
- data/lib/ulid/errors.rb +10 -0
- data/lib/ulid/monotonic_generator.rb +6 -2
- data/lib/ulid/version.rb +1 -1
- data/lib/ulid.rb +73 -41
- data/sig/ulid.rbs +40 -15
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82f48c45c124521403b04938ec4498a85bf292645357d75f32173cc2b9613618
|
4
|
+
data.tar.gz: 9c6269cbef649b57df980d120116687cdcc33e505cb45585b4566f4b16f3cdf2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 817d5589c2967d8d1b787ce20c147483cd12769eba22a0489ade4002285e1edc31e06f9e5d8dd590d3cf2342d2875c388818e55b95cbd1d72921b1848287483f
|
7
|
+
data.tar.gz: 6bcacebe7955bb247127ddc64df1fec2cc776954e7c86530750c200da5420fd4cbb73d5fd2f0888cb37ff5f097987dd12f372ee591adccb8b35f35e03e4f0538
|
data/README.md
CHANGED
@@ -49,7 +49,7 @@ Should be installed!
|
|
49
49
|
Add this line in your Gemfile.
|
50
50
|
|
51
51
|
```ruby
|
52
|
-
gem('ruby-ulid', '~> 0.
|
52
|
+
gem('ruby-ulid', '~> 0.6.0')
|
53
53
|
```
|
54
54
|
|
55
55
|
### How to use
|
@@ -58,33 +58,31 @@ gem('ruby-ulid', '~> 0.5.0')
|
|
58
58
|
require 'ulid'
|
59
59
|
|
60
60
|
ULID::VERSION
|
61
|
-
# => "0.
|
61
|
+
# => "0.6.0"
|
62
62
|
```
|
63
63
|
|
64
|
-
|
64
|
+
NOTE: This README includes info about development version. If you would see released version's one. [Look at the ref](https://github.com/kachick/ruby-ulid/tree/v0.6.0).
|
65
65
|
|
66
|
-
|
66
|
+
### Generator and Parser
|
67
|
+
|
68
|
+
`ULID.generate` returns `ULID` instance. It is not just a string.
|
67
69
|
|
68
70
|
```ruby
|
69
71
|
ulid = ULID.generate #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
|
70
72
|
```
|
71
73
|
|
72
|
-
|
73
|
-
|
74
|
-
Get the objects from exists encoded ULIDs.
|
74
|
+
`ULID.parse` returns `ULID` instance from exists encoded ULIDs.
|
75
75
|
|
76
76
|
```ruby
|
77
|
-
ulid = ULID.parse('
|
77
|
+
ulid = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
|
78
78
|
```
|
79
79
|
|
80
|
-
|
81
|
-
|
82
|
-
Extract timestamps and binary formats.
|
80
|
+
It is helpful to inspect.
|
83
81
|
|
84
82
|
```ruby
|
85
|
-
ulid = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
|
86
83
|
ulid.to_time #=> 2021-04-27 17:27:22.826 UTC
|
87
84
|
ulid.milliseconds #=> 1619544442826
|
85
|
+
ulid.encode #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
|
88
86
|
ulid.to_s #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
|
89
87
|
ulid.timestamp #=> "01F4A5Y1YA"
|
90
88
|
ulid.randomness #=> "QCYAYCTC7GRMJ9AA"
|
@@ -92,6 +90,42 @@ ulid.to_i #=> 1957909092946624190749577070267409738
|
|
92
90
|
ulid.octets #=> [1, 121, 20, 95, 7, 202, 187, 60, 175, 51, 76, 60, 49, 73, 37, 74]
|
93
91
|
```
|
94
92
|
|
93
|
+
`ULID.generate` can take fixed `Time` instance. `ULID.at` is the shorthand.
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
time = Time.at(946684800).utc #=> 2000-01-01 00:00:00 UTC
|
97
|
+
ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB00N018DCPJA4H9379P)
|
98
|
+
ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB006WQT3JTMN0T14EBP)
|
99
|
+
ULID.at(time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB002W5BGWWKN76N22H6)
|
100
|
+
```
|
101
|
+
|
102
|
+
Also `ULID.encode` and `ULID.decode_time` can be used to get primitive values for most usecases.
|
103
|
+
|
104
|
+
`ULID.encode` returns [normalized](#variants-of-format) String without ULID object creation.
|
105
|
+
It can take same arguments as `ULID.generate`.
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
ULID.encode #=> "01G86M42Q6SJ9XQM2ZRM6JRDSF"
|
109
|
+
ULID.encode(moment: Time.at(946684800).utc) #=> "00VHNCZB00SYG7RCEXZC9DA4E1"
|
110
|
+
```
|
111
|
+
|
112
|
+
`ULID.decode_time` returns Time. It can take `in` keyarg as same as `Time.at`.
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
ULID.decode_time('00VHNCZB00SYG7RCEXZC9DA4E1') #=> 2000-01-01 00:00:00 UTC
|
116
|
+
ULID.decode_time('00VHNCZB00SYG7RCEXZC9DA4E1', in: '+09:00') #=> 2000-01-01 09:00:00 +0900
|
117
|
+
```
|
118
|
+
|
119
|
+
This project does not prioritize the speed. However it actually works faster than others! :zap:
|
120
|
+
|
121
|
+
Snapshot on 0.6.0 is below
|
122
|
+
|
123
|
+
* Generator is 1.4x faster than - [ulid gem](https://github.com/rafaelsales/ulid)
|
124
|
+
* Generator is 1.7x faster than - [ulid-ruby gem](https://github.com/abachman/ulid-ruby)
|
125
|
+
* Parser is 2.6x faster than - [ulid-ruby gem](https://github.com/abachman/ulid-ruby)
|
126
|
+
|
127
|
+
You can see further detail at [Benchmark](https://github.com/kachick/ruby-ulid/wiki/Benchmark).
|
128
|
+
|
95
129
|
### Sortable with the timestamp
|
96
130
|
|
97
131
|
ULIDs are sortable when they are generated in different timestamp with milliseconds precision.
|
@@ -105,20 +139,6 @@ ulids.uniq(&:to_time).size #=> 1000
|
|
105
139
|
ulids.sort == ulids #=> true
|
106
140
|
```
|
107
141
|
|
108
|
-
`ULID.generate` can take fixed `Time` instance. The shorthand is `ULID.at`.
|
109
|
-
|
110
|
-
```ruby
|
111
|
-
time = Time.at(946684800).utc #=> 2000-01-01 00:00:00 UTC
|
112
|
-
ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB00N018DCPJA4H9379P)
|
113
|
-
ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB006WQT3JTMN0T14EBP)
|
114
|
-
ULID.at(time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB002W5BGWWKN76N22H6)
|
115
|
-
|
116
|
-
ulids = 1000.times.map do |n|
|
117
|
-
ULID.at(time + n)
|
118
|
-
end
|
119
|
-
ulids.sort == ulids #=> true
|
120
|
-
```
|
121
|
-
|
122
142
|
Basic generator prefers `randomness`, it does not guarantee `sortable` for same milliseconds ULIDs.
|
123
143
|
|
124
144
|
```ruby
|
@@ -161,7 +181,7 @@ sample_ulids_by_the_time.take(5) #=>
|
|
161
181
|
ulids.sort == ulids #=> true
|
162
182
|
```
|
163
183
|
|
164
|
-
Same
|
184
|
+
Same instance of `ULID::MonotonicGenerator` does not generate duplicated ULIDs even in multi threads environment. It is implemented with [Monitor](https://bugs.ruby-lang.org/issues/16255).
|
165
185
|
|
166
186
|
### Filtering IDs with `Time`
|
167
187
|
|
@@ -277,7 +297,7 @@ ULID.max(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
|
|
277
297
|
`ULID#next` and `ULID#succ` returns next(successor) ULID.
|
278
298
|
Especially `ULID#succ` makes it possible `Range[ULID]#each`.
|
279
299
|
|
280
|
-
NOTE: However basically `Range[ULID]#each` should not be used.
|
300
|
+
NOTE: However basically `Range[ULID]#each` should not be used. Incrementing 128 bits IDs are not reasonable operation in most cases.
|
281
301
|
|
282
302
|
```ruby
|
283
303
|
ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZY').next.to_s #=> "01BX5ZZKBKZZZZZZZZZZZZZZZZ"
|
@@ -15,63 +15,62 @@ class ULID
|
|
15
15
|
# * https://github.com/kachick/ruby-ulid/issues/57
|
16
16
|
# * https://github.com/kachick/ruby-ulid/issues/78
|
17
17
|
module CrockfordBase32
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
'
|
31
|
-
'
|
32
|
-
'
|
33
|
-
'
|
34
|
-
'
|
35
|
-
'
|
36
|
-
'
|
37
|
-
'
|
38
|
-
'
|
39
|
-
'
|
40
|
-
'
|
41
|
-
'
|
42
|
-
'
|
43
|
-
'
|
18
|
+
# Excluded I, L, O, U, - from Base32
|
19
|
+
base32_to_crockford = {
|
20
|
+
'0' => '0',
|
21
|
+
'1' => '1',
|
22
|
+
'2' => '2',
|
23
|
+
'3' => '3',
|
24
|
+
'4' => '4',
|
25
|
+
'5' => '5',
|
26
|
+
'6' => '6',
|
27
|
+
'7' => '7',
|
28
|
+
'8' => '8',
|
29
|
+
'9' => '9',
|
30
|
+
'A' => 'A',
|
31
|
+
'B' => 'B',
|
32
|
+
'C' => 'C',
|
33
|
+
'D' => 'D',
|
34
|
+
'E' => 'E',
|
35
|
+
'F' => 'F',
|
36
|
+
'G' => 'G',
|
37
|
+
'H' => 'H',
|
38
|
+
'I' => 'J',
|
39
|
+
'J' => 'K',
|
40
|
+
'K' => 'M',
|
41
|
+
'L' => 'N',
|
42
|
+
'M' => 'P',
|
43
|
+
'N' => 'Q',
|
44
|
+
'O' => 'R',
|
45
|
+
'P' => 'S',
|
46
|
+
'Q' => 'T',
|
47
|
+
'R' => 'V',
|
48
|
+
'S' => 'W',
|
49
|
+
'T' => 'X',
|
50
|
+
'U' => 'Y',
|
51
|
+
'V' => 'Z'
|
44
52
|
}.freeze
|
53
|
+
BASE32_TR_PATTERN = base32_to_crockford.keys.join.freeze
|
54
|
+
ENCODING_STRING = CROCKFORD_BASE32_TR_PATTERN = base32_to_crockford.values.freeze.join.freeze
|
45
55
|
|
46
|
-
|
47
|
-
if n = crockford_base32_mappings[encoding_char]
|
48
|
-
char_32 = n32_char_by_number.fetch(n)
|
49
|
-
map[encoding_char] = char_32
|
50
|
-
end
|
51
|
-
end.freeze
|
52
|
-
raise(SetupError, 'obvious bug exists in the mapping algorithm') unless N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys == crockford_base32_mappings.keys
|
53
|
-
|
54
|
-
CROCKFORD_BASE32_CHAR_PATTERN = /[#{N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys.join}]/.freeze
|
55
|
-
|
56
|
-
CROCKFORD_BASE32_CHAR_BY_N32_CHAR = N32_CHAR_BY_CROCKFORD_BASE32_CHAR.invert.freeze
|
57
|
-
N32_CHAR_PATTERN = /[#{CROCKFORD_BASE32_CHAR_BY_N32_CHAR.keys.join}]/.freeze
|
58
|
-
|
59
|
-
STANDARD_BY_VARIANT = {
|
56
|
+
normarized_by_variant = {
|
60
57
|
'L' => '1',
|
61
58
|
'l' => '1',
|
62
59
|
'I' => '1',
|
63
60
|
'i' => '1',
|
64
61
|
'O' => '0',
|
65
|
-
'o' => '0'
|
66
|
-
'-' => ''
|
62
|
+
'o' => '0'
|
67
63
|
}.freeze
|
68
|
-
|
64
|
+
VARIANT_TR_PATTERN = normarized_by_variant.keys.join.freeze
|
65
|
+
NORMALIZED_TR_PATTERN = normarized_by_variant.values.join.freeze
|
66
|
+
|
67
|
+
# @note Avoid to depend regex as possible. `tr(string, string)` is almost 2x Faster than `gsub(regex, hash)` in Ruby 3.1
|
69
68
|
|
70
69
|
# @api private
|
71
70
|
# @param [String] string
|
72
71
|
# @return [Integer]
|
73
72
|
def self.decode(string)
|
74
|
-
n32encoded = string.upcase.
|
73
|
+
n32encoded = string.upcase.tr(CROCKFORD_BASE32_TR_PATTERN, BASE32_TR_PATTERN)
|
75
74
|
n32encoded.to_i(32)
|
76
75
|
end
|
77
76
|
|
@@ -80,14 +79,21 @@ class ULID
|
|
80
79
|
# @return [String]
|
81
80
|
def self.encode(integer)
|
82
81
|
n32encoded = integer.to_s(32)
|
83
|
-
n32encoded
|
82
|
+
from_n32(n32encoded).rjust(ENCODED_LENGTH, '0')
|
84
83
|
end
|
85
84
|
|
86
85
|
# @api private
|
87
86
|
# @param [String] string
|
88
87
|
# @return [String]
|
89
88
|
def self.normalize(string)
|
90
|
-
string.
|
89
|
+
string.delete('-').tr(VARIANT_TR_PATTERN, NORMALIZED_TR_PATTERN)
|
90
|
+
end
|
91
|
+
|
92
|
+
# @api private
|
93
|
+
# @param [String] n32encoded
|
94
|
+
# @return [String]
|
95
|
+
def self.from_n32(n32encoded)
|
96
|
+
n32encoded.upcase.tr(BASE32_TR_PATTERN, CROCKFORD_BASE32_TR_PATTERN)
|
91
97
|
end
|
92
98
|
end
|
93
99
|
end
|
data/lib/ulid/errors.rb
ADDED
@@ -10,7 +10,6 @@ class ULID
|
|
10
10
|
# However it is a C extention, I'm pending to use it for now.
|
11
11
|
include(MonitorMixin)
|
12
12
|
|
13
|
-
# @dynamic prev
|
14
13
|
# @return [ULID, nil]
|
15
14
|
attr_reader(:prev)
|
16
15
|
|
@@ -25,7 +24,6 @@ class ULID
|
|
25
24
|
def inspect
|
26
25
|
"ULID::MonotonicGenerator(prev: #{@prev.inspect})"
|
27
26
|
end
|
28
|
-
# @dynamic to_s
|
29
27
|
alias_method(:to_s, :inspect)
|
30
28
|
|
31
29
|
# @param [Time, Integer] moment
|
@@ -70,6 +68,12 @@ class ULID
|
|
70
68
|
end
|
71
69
|
end
|
72
70
|
|
71
|
+
# @param [Time, Integer] moment
|
72
|
+
# @return [String]
|
73
|
+
def encode(moment: ULID.current_milliseconds)
|
74
|
+
generate(moment: moment).encode
|
75
|
+
end
|
76
|
+
|
73
77
|
undef_method(:freeze)
|
74
78
|
|
75
79
|
# @raise [TypeError] always raises exception and does not freeze self
|
data/lib/ulid/version.rb
CHANGED
data/lib/ulid.rb
CHANGED
@@ -6,6 +6,11 @@
|
|
6
6
|
|
7
7
|
require('securerandom')
|
8
8
|
|
9
|
+
require_relative('ulid/version')
|
10
|
+
require_relative('ulid/errors')
|
11
|
+
require_relative('ulid/crockford_base32')
|
12
|
+
require_relative('ulid/monotonic_generator')
|
13
|
+
|
9
14
|
# @see https://github.com/ulid/spec
|
10
15
|
# @!attribute [r] milliseconds
|
11
16
|
# @return [Integer]
|
@@ -14,16 +19,6 @@ require('securerandom')
|
|
14
19
|
class ULID
|
15
20
|
include(Comparable)
|
16
21
|
|
17
|
-
class Error < StandardError; end
|
18
|
-
class OverflowError < Error; end
|
19
|
-
class ParserError < Error; end
|
20
|
-
class UnexpectedError < Error; end
|
21
|
-
|
22
|
-
# Excluded I, L, O, U, -.
|
23
|
-
# This is the encoding patterns.
|
24
|
-
# The decoding issue is written in ULID::CrockfordBase32
|
25
|
-
CROCKFORD_BASE32_ENCODING_STRING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
|
26
|
-
|
27
22
|
TIMESTAMP_ENCODED_LENGTH = 10
|
28
23
|
RANDOMNESS_ENCODED_LENGTH = 16
|
29
24
|
ENCODED_LENGTH = 26
|
@@ -38,12 +33,12 @@ class ULID
|
|
38
33
|
|
39
34
|
# @see https://github.com/ulid/spec/pull/57
|
40
35
|
# Currently not used as a constant, but kept as a reference for now.
|
41
|
-
PATTERN_WITH_CROCKFORD_BASE32_SUBSET = /(?<timestamp>[0-7][#{
|
36
|
+
PATTERN_WITH_CROCKFORD_BASE32_SUBSET = /(?<timestamp>[0-7][#{CrockfordBase32::ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}})(?<randomness>[#{CrockfordBase32::ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}})/i.freeze
|
42
37
|
|
43
38
|
STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET = /\A#{PATTERN_WITH_CROCKFORD_BASE32_SUBSET.source}\z/i.freeze
|
44
39
|
|
45
40
|
# Optimized for `ULID.scan`, might be changed the definition with gathered `ULID.scan` spec changed.
|
46
|
-
SCANNING_PATTERN = /\b[0-7][#{
|
41
|
+
SCANNING_PATTERN = /\b[0-7][#{CrockfordBase32::ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}}[#{CrockfordBase32::ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}}\b/i.freeze
|
47
42
|
|
48
43
|
# Similar as Time#inspect since Ruby 2.7, however it is NOT same.
|
49
44
|
# Time#inspect trancates needless digits. Keeping full milliseconds with "%3N" will fit for ULID.
|
@@ -60,6 +55,16 @@ class ULID
|
|
60
55
|
from_milliseconds_and_entropy(milliseconds: milliseconds_from_moment(moment), entropy: entropy)
|
61
56
|
end
|
62
57
|
|
58
|
+
# Almost same as [.generate] except directly returning String without needless object creation
|
59
|
+
#
|
60
|
+
# @param [Integer, Time] moment
|
61
|
+
# @param [Integer] entropy
|
62
|
+
# @return [String]
|
63
|
+
def self.encode(moment: current_milliseconds, entropy: reasonable_entropy)
|
64
|
+
n32_encoded = encode_n32(milliseconds: milliseconds_from_moment(moment), entropy: entropy)
|
65
|
+
CrockfordBase32.from_n32(n32_encoded)
|
66
|
+
end
|
67
|
+
|
63
68
|
# Short hand of `ULID.generate(moment: time)`
|
64
69
|
# @param [Time] time
|
65
70
|
# @return [ULID]
|
@@ -166,7 +171,12 @@ class ULID
|
|
166
171
|
milliseconds = n32encoded_timestamp.to_i(32)
|
167
172
|
entropy = n32encoded_randomness.to_i(32)
|
168
173
|
|
169
|
-
new(
|
174
|
+
new(
|
175
|
+
milliseconds: milliseconds,
|
176
|
+
entropy: entropy,
|
177
|
+
integer: integer,
|
178
|
+
encoded: CrockfordBase32.from_n32(n32encoded).freeze
|
179
|
+
)
|
170
180
|
end
|
171
181
|
|
172
182
|
# @param [Range<Time>, Range<nil>, Range[ULID]] period
|
@@ -254,6 +264,17 @@ class ULID
|
|
254
264
|
SecureRandom.random_number(MAX_ENTROPY)
|
255
265
|
end
|
256
266
|
|
267
|
+
private_class_method def self.encode_n32(milliseconds:, entropy:)
|
268
|
+
raise(ArgumentError, 'milliseconds and entropy should be an `Integer`') unless Integer === milliseconds && Integer === entropy
|
269
|
+
raise(OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}") unless milliseconds <= MAX_MILLISECONDS
|
270
|
+
raise(OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}") unless entropy <= MAX_ENTROPY
|
271
|
+
raise(ArgumentError, 'milliseconds and entropy should not be negative') if milliseconds.negative? || entropy.negative?
|
272
|
+
|
273
|
+
n32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
|
274
|
+
n32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
|
275
|
+
"#{n32encoded_timestamp}#{n32encoded_randomness}"
|
276
|
+
end
|
277
|
+
|
257
278
|
# @param [String, #to_str] string
|
258
279
|
# @return [ULID]
|
259
280
|
# @raise [ParserError] if the given format is not correct for ULID specs
|
@@ -279,6 +300,25 @@ class ULID
|
|
279
300
|
parse(normalized_in_crockford)
|
280
301
|
end
|
281
302
|
|
303
|
+
# Almost same as `ULID.parse(string).to_time` except directly returning Time instance without needless object creation
|
304
|
+
#
|
305
|
+
# @param [String, #to_str] string
|
306
|
+
# @return [Time]
|
307
|
+
# @raise [ParserError] if the given format is not correct for ULID specs
|
308
|
+
def self.decode_time(string, in: 'UTC')
|
309
|
+
in_for_time_at = binding.local_variable_get(:in) # Needed because `in` is a reserved word.
|
310
|
+
string = String.try_convert(string)
|
311
|
+
raise(ArgumentError, 'ULID.decode_time takes only strings') unless string
|
312
|
+
|
313
|
+
unless STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.match?(string)
|
314
|
+
raise(ParserError, "given `#{string}` does not match to `#{STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.inspect}`")
|
315
|
+
end
|
316
|
+
|
317
|
+
timestamp = string.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze || raise(UnexpectedError)
|
318
|
+
|
319
|
+
Time.at(0, CrockfordBase32.decode(timestamp), :millisecond, in: in_for_time_at)
|
320
|
+
end
|
321
|
+
|
282
322
|
# @param [String, #to_str] string
|
283
323
|
# @return [String]
|
284
324
|
# @raise [ParserError] if the given format is not correct for ULID specs, even if ignored `orthographical variants of the format`
|
@@ -303,7 +343,7 @@ class ULID
|
|
303
343
|
# @param [String, #to_str] string
|
304
344
|
# @return [Boolean]
|
305
345
|
def self.valid_as_variant_format?(string)
|
306
|
-
|
346
|
+
parse_variant_format(string)
|
307
347
|
rescue Exception
|
308
348
|
false
|
309
349
|
else
|
@@ -378,37 +418,36 @@ class ULID
|
|
378
418
|
# @raise [OverflowError] if the given value is larger than the ULID limit
|
379
419
|
# @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
|
380
420
|
def self.from_milliseconds_and_entropy(milliseconds:, entropy:)
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
integer = (n32encoded_timestamp + n32encoded_randomness).to_i(32)
|
389
|
-
|
390
|
-
new(milliseconds: milliseconds, entropy: entropy, integer: integer)
|
421
|
+
n32_encoded = encode_n32(milliseconds: milliseconds, entropy: entropy)
|
422
|
+
new(
|
423
|
+
milliseconds: milliseconds,
|
424
|
+
entropy: entropy,
|
425
|
+
integer: n32_encoded.to_i(32),
|
426
|
+
encoded: CrockfordBase32.from_n32(n32_encoded).upcase.freeze
|
427
|
+
)
|
391
428
|
end
|
392
429
|
|
393
|
-
# @dynamic milliseconds, entropy
|
394
430
|
attr_reader(:milliseconds, :entropy)
|
395
431
|
|
396
432
|
# @api private
|
397
433
|
# @param [Integer] milliseconds
|
398
434
|
# @param [Integer] entropy
|
399
435
|
# @param [Integer] integer
|
436
|
+
# @param [String] encoded
|
400
437
|
# @return [void]
|
401
|
-
def initialize(milliseconds:, entropy:, integer:)
|
438
|
+
def initialize(milliseconds:, entropy:, integer:, encoded:)
|
402
439
|
# All arguments check should be done with each constructors, not here
|
403
440
|
@integer = integer
|
441
|
+
@encoded = encoded
|
404
442
|
@milliseconds = milliseconds
|
405
443
|
@entropy = entropy
|
406
444
|
end
|
407
445
|
|
408
446
|
# @return [String]
|
409
|
-
def
|
410
|
-
@
|
447
|
+
def encode
|
448
|
+
@encoded
|
411
449
|
end
|
450
|
+
alias_method(:to_s, :encode)
|
412
451
|
|
413
452
|
# @return [Integer]
|
414
453
|
def to_i
|
@@ -427,14 +466,13 @@ class ULID
|
|
427
466
|
|
428
467
|
# @return [String]
|
429
468
|
def inspect
|
430
|
-
@inspect ||= "ULID(#{to_time.strftime(TIME_FORMAT_IN_INSPECT)}: #{
|
469
|
+
@inspect ||= "ULID(#{to_time.strftime(TIME_FORMAT_IN_INSPECT)}: #{@encoded})".freeze
|
431
470
|
end
|
432
471
|
|
433
472
|
# @return [Boolean]
|
434
473
|
def eql?(other)
|
435
474
|
equal?(other) || (ULID === other && @integer == other.to_i)
|
436
475
|
end
|
437
|
-
# @dynamic ==
|
438
476
|
alias_method(:==, :eql?)
|
439
477
|
|
440
478
|
# Return `true` for same value of ULID, variant formats of strings, same Time in ULID precision(msec).
|
@@ -449,11 +487,9 @@ class ULID
|
|
449
487
|
@integer == other.to_i
|
450
488
|
when String
|
451
489
|
begin
|
452
|
-
|
490
|
+
to_i == ULID.parse_variant_format(other).to_i
|
453
491
|
rescue Exception
|
454
492
|
false
|
455
|
-
else
|
456
|
-
to_s == normalized
|
457
493
|
end
|
458
494
|
when Time
|
459
495
|
to_time == ULID.floor(other)
|
@@ -488,12 +524,12 @@ class ULID
|
|
488
524
|
|
489
525
|
# @return [String]
|
490
526
|
def timestamp
|
491
|
-
@timestamp ||= (
|
527
|
+
@timestamp ||= (@encoded.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze || raise(UnexpectedError))
|
492
528
|
end
|
493
529
|
|
494
530
|
# @return [String]
|
495
531
|
def randomness
|
496
|
-
@randomness ||= (
|
532
|
+
@randomness ||= (@encoded.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH).freeze || raise(UnexpectedError))
|
497
533
|
end
|
498
534
|
|
499
535
|
# @note Providing for rough operations. The keys and values is not fixed.
|
@@ -519,7 +555,6 @@ class ULID
|
|
519
555
|
ULID.from_integer(succ_int)
|
520
556
|
end
|
521
557
|
end
|
522
|
-
# @dynamic next
|
523
558
|
alias_method(:next, :succ)
|
524
559
|
|
525
560
|
# @return [ULID, nil] when called on ULID as `00000000000000000000000000`, returns `nil` instead of ULID
|
@@ -554,7 +589,7 @@ class ULID
|
|
554
589
|
# @return [void]
|
555
590
|
def marshal_load(integer)
|
556
591
|
unmarshaled = ULID.from_integer(integer)
|
557
|
-
initialize(integer: unmarshaled.to_i, milliseconds: unmarshaled.milliseconds, entropy: unmarshaled.entropy)
|
592
|
+
initialize(integer: unmarshaled.to_i, milliseconds: unmarshaled.milliseconds, entropy: unmarshaled.entropy, encoded: unmarshaled.to_s)
|
558
593
|
end
|
559
594
|
|
560
595
|
# @return [self]
|
@@ -584,12 +619,9 @@ class ULID
|
|
584
619
|
end
|
585
620
|
end
|
586
621
|
|
587
|
-
require_relative('ulid/version')
|
588
|
-
require_relative('ulid/crockford_base32')
|
589
|
-
require_relative('ulid/monotonic_generator')
|
590
622
|
require_relative('ulid/ractor_unshareable_constants')
|
591
623
|
|
592
624
|
class ULID
|
593
625
|
# Do not write as `ULID.private_constant` for avoiding YARD warnings `[warn]: in YARD::Handlers::Ruby::PrivateConstantHandler: Undocumentable private constants:`
|
594
|
-
private_constant(:TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR
|
626
|
+
private_constant(:TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR)
|
595
627
|
end
|
data/sig/ulid.rbs
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
class ULID < Object
|
2
2
|
VERSION: String
|
3
|
-
CROCKFORD_BASE32_ENCODING_STRING: String
|
4
3
|
TIMESTAMP_ENCODED_LENGTH: 10
|
5
4
|
RANDOMNESS_ENCODED_LENGTH: 16
|
6
5
|
ENCODED_LENGTH: 26
|
@@ -39,12 +38,11 @@ class ULID < Object
|
|
39
38
|
class SetupError < UnexpectedError
|
40
39
|
end
|
41
40
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
VARIANT_PATTERN: Regexp
|
41
|
+
ENCODING_STRING: String
|
42
|
+
CROCKFORD_BASE32_TR_PATTERN: String
|
43
|
+
BASE32_TR_PATTERN: String
|
44
|
+
VARIANT_TR_PATTERN: String
|
45
|
+
NORMALIZED_TR_PATTERN: String
|
48
46
|
|
49
47
|
# A private API. Should not be used in your code.
|
50
48
|
def self.encode: (Integer integer) -> String
|
@@ -54,6 +52,9 @@ class ULID < Object
|
|
54
52
|
|
55
53
|
# A private API. Should not be used in your code.
|
56
54
|
def self.normalize: (String string) -> String
|
55
|
+
|
56
|
+
# A private API. Should not be used in your code.
|
57
|
+
def self.from_n32: (String n32encoded) -> String
|
57
58
|
end
|
58
59
|
|
59
60
|
class MonotonicGenerator
|
@@ -78,6 +79,9 @@ class ULID < Object
|
|
78
79
|
# The `Thread-safety` is implemented with [Monitor](https://bugs.ruby-lang.org/issues/16255)
|
79
80
|
def generate: (?moment: moment) -> ULID
|
80
81
|
|
82
|
+
# Just providing similar api as `ULID.generate` and `ULID.encode` relation. No performance benefit exists in monotonic generator's one.
|
83
|
+
def encode: (?moment: moment) -> String
|
84
|
+
|
81
85
|
# Returned value is `basically not` Thread-safety
|
82
86
|
# If you want to keep Thread-safety, keep to call {#generate} only in same {#synchronize} block
|
83
87
|
#
|
@@ -102,8 +106,8 @@ class ULID < Object
|
|
102
106
|
type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer] | Array[Integer]
|
103
107
|
type period = Range[Time] | Range[nil] | Range[ULID]
|
104
108
|
|
105
|
-
@string: String?
|
106
109
|
@integer: Integer
|
110
|
+
@encoded: String
|
107
111
|
@timestamp: String?
|
108
112
|
@randomness: String?
|
109
113
|
@inspect: String?
|
@@ -145,6 +149,16 @@ class ULID < Object
|
|
145
149
|
#
|
146
150
|
def self.generate: (?moment: moment, ?entropy: Integer) -> ULID
|
147
151
|
|
152
|
+
# Retuns encoded and normalzied String.
|
153
|
+
# It has same arguments signatures as `.generate`. So can be used for just ID creation usecases without needless object creation.
|
154
|
+
#
|
155
|
+
# NOTE: Difference of ULID#encode, returned String is NOT frozen.
|
156
|
+
#
|
157
|
+
def self.encode: (?moment: moment, ?entropy: Integer) -> String
|
158
|
+
|
159
|
+
# A private API. Should not be used in your code.
|
160
|
+
def self.encode_n32: (milliseconds: Integer, entropy: Integer) -> String
|
161
|
+
|
148
162
|
# Shorthand of `ULID.generate(moment: Time)`
|
149
163
|
# See also [ULID.generate](https://kachick.github.io/ruby-ulid/ULID.html#generate-class_method)
|
150
164
|
#
|
@@ -205,7 +219,7 @@ class ULID < Object
|
|
205
219
|
# ```
|
206
220
|
def self.floor: (Time time) -> Time
|
207
221
|
|
208
|
-
#
|
222
|
+
# Return ULID instance from encoded String.
|
209
223
|
#
|
210
224
|
# ```ruby
|
211
225
|
# ulid = ULID.parse('01ARZ3NDEKTSV4RRFFQ69G5FAV')
|
@@ -213,6 +227,17 @@ class ULID < Object
|
|
213
227
|
# ```
|
214
228
|
def self.parse: (_ToStr string) -> ULID
|
215
229
|
|
230
|
+
# Return Time instance from encoded String.
|
231
|
+
# See also `ULID.encode` for similar purpose.
|
232
|
+
#
|
233
|
+
# NOTE: Difference of ULID#to_time, returned Time is NOT frozen.
|
234
|
+
#
|
235
|
+
# ```ruby
|
236
|
+
# time = ULID.decode_time('01ARZ3NDEKTSV4RRFFQ69G5FAV')
|
237
|
+
# #=> 2016-07-30 23:54:10.259 UTC
|
238
|
+
# ```
|
239
|
+
def self.decode_time: (_ToStr string, ?in: String | Integer | nil) -> Time
|
240
|
+
|
216
241
|
# Get ULID instance from unnormalized String that encoded in Crockford's base32.
|
217
242
|
#
|
218
243
|
# http://www.crockford.com/base32.html
|
@@ -430,9 +455,12 @@ class ULID < Object
|
|
430
455
|
# ```ruby
|
431
456
|
# ulid = ULID.generate
|
432
457
|
# #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
|
433
|
-
# ulid.
|
458
|
+
# ulid.encode #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
|
459
|
+
# ulid.encode.frozen? #=> true
|
460
|
+
# ulid.encode.equal?(ulid.to_s) #=> true
|
434
461
|
# ```
|
435
|
-
def
|
462
|
+
def encode: -> String
|
463
|
+
alias to_s encode
|
436
464
|
|
437
465
|
# ```ruby
|
438
466
|
# ulid = ULID.generate
|
@@ -581,9 +609,6 @@ class ULID < Object
|
|
581
609
|
|
582
610
|
private
|
583
611
|
|
584
|
-
# A private API. Should not be used in your code.
|
585
|
-
def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) -> ULID
|
586
|
-
|
587
612
|
# A private API. Should not be used in your code.
|
588
613
|
def self.reasonable_entropy: -> Integer
|
589
614
|
|
@@ -594,7 +619,7 @@ class ULID < Object
|
|
594
619
|
def self.safe_get_class_name: (untyped object) -> String
|
595
620
|
|
596
621
|
# A private API. Should not be used in your code.
|
597
|
-
def initialize: (milliseconds: Integer, entropy: Integer, integer: Integer) -> void
|
622
|
+
def initialize: (milliseconds: Integer, entropy: Integer, integer: Integer, encoded: String) -> void
|
598
623
|
|
599
624
|
# A private API. Should not be used in your code.
|
600
625
|
def cache_all_instance_variables: -> void
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-ulid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kenichi Kamiya
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-07-
|
11
|
+
date: 2022-07-18 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: " generator, optional monotonicity, parser and tools for ULID (RBS
|
14
14
|
included)\n"
|
@@ -23,6 +23,7 @@ files:
|
|
23
23
|
- lib/ruby-ulid.rb
|
24
24
|
- lib/ulid.rb
|
25
25
|
- lib/ulid/crockford_base32.rb
|
26
|
+
- lib/ulid/errors.rb
|
26
27
|
- lib/ulid/monotonic_generator.rb
|
27
28
|
- lib/ulid/ractor_unshareable_constants.rb
|
28
29
|
- lib/ulid/uuid.rb
|