ruby-ulid 0.8.0 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 323b750a4e11157bd492b31d74833df2042192775c8627917880ef386dee4c1c
4
- data.tar.gz: 2dc8a91cbed7b473d6f9e28480dd227ab3469828ab0833f48a754111bd6e926c
3
+ metadata.gz: 3b0c26b06cd815f96d9ffc78836ceb69ed4677a4f5acfac6e15c016e093e0d18
4
+ data.tar.gz: '09acd9841d6ee33b87687dd12a7912ef262c9cad3d85b37db6215912048b36e1'
5
5
  SHA512:
6
- metadata.gz: e78a5289863c1300a3f4c186b72a0f1d97cd709ce6043b69729d851f5e6ffcedac001e3bb691397d761ee497d609b5292c300db7f2ec3f8cf5be0ee5c095af80
7
- data.tar.gz: e19197709d7a896a79c6ebdc40fa919c2e7876284fa1dbf8895498ad7b80e989634cdd3e651362db5cf9f7c627fe17439ccc21e75c8e6598b002da6b19858aa0
6
+ metadata.gz: bd7f5fe5b75a1d8f91d5a016a81a6a132debaf4247f075918ad4a52dfc96a6bef937ef433663d71610a008621eac3e5c997f35f15249c8030bbc095357882a78
7
+ data.tar.gz: d444cfac8c9b66ce72850236a7cd7367873a3ea8c51a6c67bc5d34bb0a549fb4192c120cbaa13ba92b4e4c49ccc45fe2cf7a3a6a01882daee287428ca29dd597
data/README.md CHANGED
@@ -3,6 +3,9 @@
3
3
  [![Build Status](https://github.com/kachick/ruby-ulid/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/kachick/ruby-ulid/actions/workflows/ci.yml?query=branch%3Amain)
4
4
  [![Gem Version](https://badge.fury.io/rb/ruby-ulid.svg)](http://badge.fury.io/rb/ruby-ulid)
5
5
 
6
+ This gem is in maintenance mode, I have no plan to add new features.\
7
+ The reason is UUID v7 has been accepted in [IETF](https://www.rfc-editor.org/rfc/rfc9562.html) and [ruby's securerandom](https://github.com/ruby/securerandom/pull/19). See [UUID section](#uuid) for detail.
8
+
6
9
  ## Overview
7
10
 
8
11
  [ulid/spec](https://github.com/ulid/spec) defines some useful features.\
@@ -38,12 +41,12 @@ Instead, herein is proposed ULID:
38
41
 
39
42
  ### Install
40
43
 
41
- Tested only in the last 2 Rubies. So you need Ruby 3.1 or higher.
44
+ Tested only in the Ruby 4.
42
45
 
43
46
  Add this line to your `Gemfile`.
44
47
 
45
48
  ```ruby
46
- gem('ruby-ulid', '~> 0.8.0')
49
+ gem('ruby-ulid', '~> 1.0.0')
47
50
  ```
48
51
 
49
52
  And load it.
@@ -53,7 +56,18 @@ require 'ulid'
53
56
  ```
54
57
 
55
58
  NOTE: This README contains information about the development version.\
56
- If you would like to see released version's one. [Look at the ref](https://github.com/kachick/ruby-ulid/tree/v0.8.0).
59
+ If you would like to see released version's one. [Look at the ref](https://github.com/kachick/ruby-ulid/tree/v1.0.0).
60
+
61
+ In [Nix](https://nixos.org/), you can skip the installation steps for both ruby and ruby-ulid to try.
62
+
63
+ ```console
64
+ > nix run github:kachick/ruby-ulid#ruby -- -e 'p ULID.generate'
65
+ ULID(2024-03-03 18:37:06.152 UTC: 01HR2SNY789ZZ027EDJEHAGQ62)
66
+
67
+ > nix run github:kachick/ruby-ulid#irb
68
+ irb(main):001:0> ULID.parse('01H66XG2A9WWYRCYGPA62T4AZA')
69
+ => ULID(2023-07-25 16:18:12.937 UTC: 01H66XG2A9WWYRCYGPA62T4AZA)
70
+ ```
57
71
 
58
72
  ### Generator and Parser
59
73
 
@@ -269,7 +283,7 @@ ULID.min(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3V0000000000000000)
269
283
  ULID.max(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
270
284
  ```
271
285
 
272
- #### As element in Enumerable
286
+ #### As an element in Enumerable and Range
273
287
 
274
288
  `ULID#next` and `ULID#succ` returns next(successor) ULID.\
275
289
  Especially `ULID#succ` makes it possible `Range[ULID]#each`.
@@ -290,6 +304,17 @@ ULID.parse('01BX5ZZKBK0000000000000000').pred.to_s #=> "01BX5ZZKBJZZZZZZZZZZZZZZ
290
304
  ULID.parse('00000000000000000000000000').pred #=> nil
291
305
  ```
292
306
 
307
+ `ULID#+` is also provided to realize `Range#step` since [ruby-3.4.0 spec changes](https://bugs.ruby-lang.org/issues/18368).
308
+
309
+ ```ruby
310
+ # This code works only in ruby-3.4.0dev or later
311
+ (ULID.min...).step(42).take(3)
312
+ # =>
313
+ [ULID(1970-01-01 00:00:00.000 UTC: 00000000000000000000000000),
314
+ ULID(1970-01-01 00:00:00.000 UTC: 0000000000000000000000001A),
315
+ ULID(1970-01-01 00:00:00.000 UTC: 0000000000000000000000002M)]
316
+ ```
317
+
293
318
  #### Test helpers
294
319
 
295
320
  `ULID.sample` returns random ULIDs.
@@ -356,38 +381,47 @@ ULID.parse_variant_format('01G70Y0Y7G-ZLXWDIREXERGSDoD') #=> ULID(2022-07-03 02:
356
381
 
357
382
  #### UUID
358
383
 
359
- Both ULID and UUID are 128-bit IDs. But with different specs. Especially UUID has some versions probably UUIDv4.
384
+ Both ULID and UUID are 128-bit IDs. But with different specs. Especially, UUID has some versions, for example, UUIDv4 and UUIDv7.
360
385
 
361
- All UUIDv4s can be converted to ULID, but this will not have the correct "timestamp".\
362
- Most ULIDs cannot be converted to UUIDv4 while maintaining reversibility, because UUIDv4 requires version and variants in the fields.
386
+ All UUIDs can be converted to ULID, but only [new versions](https://datatracker.ietf.org/doc/rfc9562/) have a correct "timestamp".\
387
+ Most ULIDs cannot be converted to UUID while maintaining reversibility, because UUID requires version and variants in the fields.
363
388
 
364
389
  See also [ulid/spec#64](https://github.com/ulid/spec/issues/64) for further detail.
365
390
 
366
- For now, this gem provides 4 methods for UUIDs.
391
+ For now, this gem provides some methods for UUIDs.
367
392
 
368
393
  - Reversibility is preferred: `ULID.from_uuidish`, `ULID.to_uuidish`
369
- - Prefer UUIDv4 specification: `ULID.from_uuidv4`, `ULID.to_uuidv4`
394
+ - Prefer variants specification: `ULID.from_uuid_v4`, `ULID.from_uuid_v7`, `ULID.to_uuid_v4`, `ULID.to_uuid_v7`
370
395
 
371
396
  ```ruby
372
- # All UUIDv4 IDs can be reversible even if converted to ULID
373
- uuid = SecureRandom.uuid
374
- ULID.from_uuidish(uuid) == ULID.from_uuidv4(uuid) #=> true
375
- ULID.from_uuidish(uuid).to_uuidish == ULID.from_uuidv4(uuid).to_uuidv4 #=> true
397
+ # All UUIDv4 and UUIDv7 IDs can be reversible even if converted to ULID
398
+ uuid_v4 = SecureRandom.uuid_v4
399
+ ULID.from_uuidish(uuid_v4) == ULID.from_uuid_v4(uuid_v4) #=> true
400
+ ULID.from_uuidish(uuid_v4).to_uuidish == ULID.from_uuid_v4(uuid_v4).to_uuid_v4 #=> true
401
+
402
+ # v4 does not have timestamp, v7 has it.
403
+
404
+ ULID.from_uuid_v4(SecureRandom.uuid_v4).to_time
405
+ # 'f80b3f53-043a-4298-a674-cd83a7fd5d22' => 10612-05-19 16:58:53.882 UTC
376
406
 
377
- # But most ULIDs cannot be converted to UUIDv4
407
+ ULID.from_uuid_v7(SecureRandom.uuid_v7).to_time
408
+ # '01946f9e-bf58-7be3-8fd4-4606606b05aa' => 2025-01-16 14:57:42.232 UTC
409
+ # ULID is officially defined milliseconds precision for the spec. So omit the nanoseconds precisions even if the UUID v7 ID was generated with extra_timestamp_bits >= 1.
410
+
411
+ # However most ULIDs cannot be converted to versioned UUID
378
412
  ulid = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA')
379
- ulid.to_uuidv4 #=> ULID::IrreversibleUUIDError
413
+ ulid.to_uuid_v4 #=> ULID::IrreversibleUUIDError
380
414
  # So 2 ways to get substitute strings that might satisfy the use case
381
- ulid.to_uuidv4(force: true) #=> "0179145f-07ca-4b3c-af33-4c3c3149254a" this cannot be reverse to source ULID
382
- ulid == ULID.from_uuidv4(ulid.to_uuidv4(force: true)) #=> false
415
+ ulid.to_uuid_v4(force: true) #=> "0179145f-07ca-4b3c-af33-4c3c3149254a" this cannot be reverse to source ULID
416
+ ulid == ULID.from_uuid_v4(ulid.to_uuid_v4(force: true)) #=> false
383
417
  ulid.to_uuidish #=> "0179145f-07ca-bb3c-af33-4c3c3149254a" does not satisfy UUIDv4 spec
384
418
  ulid == ULID.from_uuidish(ulid.to_uuidish) #=> true
385
419
 
386
420
  # Seeing boundary IDs makes it easier to understand
387
421
  ULID.min.to_uuidish #=> "00000000-0000-0000-0000-000000000000"
388
- ULID.min.to_uuidv4(force: true) #=> "00000000-0000-4000-8000-000000000000"
422
+ ULID.min.to_uuid_v4(force: true) #=> "00000000-0000-4000-8000-000000000000"
389
423
  ULID.max.to_uuidish #=> "ffffffff-ffff-ffff-ffff-ffffffffffff"
390
- ULID.max.to_uuidv4(force: true) #=> "ffffffff-ffff-4fff-bfff-ffffffffffff"
424
+ ULID.max.to_uuid_v4(force: true) #=> "ffffffff-ffff-4fff-bfff-ffffffffffff"
391
425
  ```
392
426
 
393
427
  ## Migration from other gems
@@ -404,9 +438,3 @@ See [wiki page for gem migration](https://github.com/kachick/ruby-ulid/wiki/Gem-
404
438
  - [Repository](https://github.com/kachick/ruby-ulid)
405
439
  - [API documents](https://kachick.github.io/ruby-ulid/)
406
440
  - [ulid/spec](https://github.com/ulid/spec)
407
-
408
- ## Note
409
-
410
- - [UUIDv6, UUIDv7, UUIDv8](https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-02.html) is another choice for sortable and randomness ID.
411
- \
412
- However they remain in draft state. Our tracker is: [ruby-ulid#37](https://github.com/kachick/ruby-ulid/issues/37)
@@ -10,7 +10,7 @@ require_relative('utils')
10
10
  class ULID
11
11
  class MonotonicGenerator
12
12
  # @note When use https://github.com/ko1/ractor-tvar might realize Ractor based thread safe monotonic generator.
13
- # However it is a C extention, I'm pending to use it for now.
13
+ # However it is a C extension, I'm pending to use it for now.
14
14
  include(MonitorMixin)
15
15
 
16
16
  # @return [ULID, nil]
data/lib/ulid/utils.rb CHANGED
@@ -7,20 +7,16 @@
7
7
  require('securerandom')
8
8
 
9
9
  class ULID
10
- # @note I don't have confidence for the naming of `Utils`. However some standard libraries have same name.
11
- # https://github.com/ruby/webrick/blob/14612a7540fdd7373344461851c4bfff64985b3e/lib/webrick/utils.rb#L17
12
- # https://docs.ruby-lang.org/ja/latest/class/ERB=3a=3aUtil.html
13
- # https://github.com/ruby/rss/blob/af1c3c9c9630ec0a48abec48ed1ef348ba82aa13/lib/rss/utils.rb#L9
14
10
  module Utils
15
11
  # @return [Integer]
16
12
  def self.current_milliseconds
17
- milliseconds_from_time(Time.now)
18
- end
19
-
20
- # @param [Time] time
21
- # @return [Integer]
22
- def self.milliseconds_from_time(time)
23
- (time.to_r * 1000).to_i
13
+ # There are different recommendations for this featrure with the accuracy and other context
14
+ # At here, I prefer to adjust with Ruby UUID v7 imeplementation and respect monotonicity use-case
15
+ # https://github.com/ruby/securerandom/pull/19/files#diff-cad52e37612706fe31d85599bb8bc789e90fd382f091ed31fdd036119af3e5cdR252
16
+ # Other resources
17
+ # - https://blog.dnsimple.com/2018/03/elapsed-time-with-ruby-the-right-way/
18
+ # - https://github.com/ruby/ruby/blob/5df20ab0b49b55c9cf858879f3e6e30cc3dcd803/process.c#L8131
19
+ Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
24
20
  end
25
21
 
26
22
  # @param [Time, Integer] moment
@@ -30,7 +26,7 @@ class ULID
30
26
  when Integer
31
27
  moment
32
28
  when Time
33
- milliseconds_from_time(moment)
29
+ (moment.to_r * 1000).to_i
34
30
  else
35
31
  raise(ArgumentError, '`moment` should be a `Time` or `Integer as milliseconds`')
36
32
  end
@@ -0,0 +1,41 @@
1
+ # coding: us-ascii
2
+ # frozen_string_literal: true
3
+ # shareable_constant_value: literal
4
+
5
+ # Copyright (C) 2021 Kenichi Kamiya
6
+
7
+ class ULID
8
+ module UUID
9
+ # @see https://www.rfc-editor.org/rfc/rfc4122#section-4.1.2
10
+ # @note
11
+ # - Using `Fields = Data.define do; end` syntax made https://github.com/kachick/ruby-ulid/issues/233 again. So use class syntax instead
12
+ # - This file is extracted to avoid YARD warnings "Ruby::ClassHandler: Undocumentable superclass" https://github.com/lsegal/yard/issues/737
13
+ # Partially avoiding is hard in YARD, so extracting the code and using exclude filter in yardopts...
14
+ class Fields < Data.define(:time_low, :time_mid, :time_hi_and_version, :clock_seq_hi_and_res, :clk_seq_low, :node)
15
+ def self.raw_from_octets(octets)
16
+ case octets.pack('C*').unpack('NnnnnN')
17
+ in [Integer => time_low, Integer => time_mid, Integer => time_hi_and_version, Integer => clock_seq_hi_and_res, Integer => clk_seq_low, Integer => node]
18
+ new(time_low:, time_mid:, time_hi_and_version:, clock_seq_hi_and_res:, clk_seq_low:, node:).freeze
19
+ end
20
+ end
21
+
22
+ def self.forced_version_from_octets(octets, mask:)
23
+ case octets.pack('C*').unpack('NnnnnN')
24
+ in [Integer => time_low, Integer => time_mid, Integer => time_hi_and_version, Integer => clock_seq_hi_and_res, Integer => clk_seq_low, Integer => node]
25
+ new(
26
+ time_low:,
27
+ time_mid:,
28
+ time_hi_and_version: (time_hi_and_version & 0x0fff) | mask,
29
+ clock_seq_hi_and_res: (clock_seq_hi_and_res & 0x3fff) | 0x8000,
30
+ clk_seq_low:,
31
+ node:
32
+ ).freeze
33
+ end
34
+ end
35
+
36
+ def to_s
37
+ '%08x-%04x-%04x-%04x-%04x%08x' % deconstruct
38
+ end
39
+ end
40
+ end
41
+ end
data/lib/ulid/uuid.rb CHANGED
@@ -6,12 +6,14 @@
6
6
 
7
7
  require_relative('errors')
8
8
  require_relative('utils')
9
+ require_relative('uuid/fields')
9
10
 
10
11
  class ULID
11
12
  module UUID
12
13
  BASE_PATTERN = /\A[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\z/i
13
14
  # Imported from https://stackoverflow.com/a/38191104/1212807, thank you!
14
15
  V4_PATTERN = /\A[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\z/i
16
+ V7_PATTERN = /\A[0-9A-F]{8}-[0-9A-F]{4}-7[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\z/i
15
17
 
16
18
  def self.parse_any_to_int(uuidish)
17
19
  encoded = String.try_convert(uuidish)
@@ -38,34 +40,16 @@ class ULID
38
40
  parse_any_to_int(encoded)
39
41
  end
40
42
 
41
- # @see https://www.rfc-editor.org/rfc/rfc4122#section-4.1.2
42
- # @todo Replace to Data class after dropped Ruby 3.1
43
- # @note Using `Fields = Struct.new` syntax made https://github.com/kachick/ruby-ulid/issues/233 again. So use class syntax instead
44
- class Fields < Struct.new(:time_low, :time_mid, :time_hi_and_version, :clock_seq_hi_and_res, :clk_seq_low, :node, keyword_init: true)
45
- def self.raw_from_octets(octets)
46
- case octets.pack('C*').unpack('NnnnnN')
47
- in [Integer => time_low, Integer => time_mid, Integer => time_hi_and_version, Integer => clock_seq_hi_and_res, Integer => clk_seq_low, Integer => node]
48
- new(time_low:, time_mid:, time_hi_and_version:, clock_seq_hi_and_res:, clk_seq_low:, node:).freeze
49
- end
50
- end
43
+ def self.parse_v7_to_int(uuid)
44
+ encoded = String.try_convert(uuid)
45
+ raise(ArgumentError, 'should pass a string for UUID parser') unless encoded
51
46
 
52
- def self.forced_v4_from_octets(octets)
53
- case octets.pack('C*').unpack('NnnnnN')
54
- in [Integer => time_low, Integer => time_mid, Integer => time_hi_and_version, Integer => clock_seq_hi_and_res, Integer => clk_seq_low, Integer => node]
55
- new(
56
- time_low:,
57
- time_mid:,
58
- time_hi_and_version: (time_hi_and_version & 0x0fff) | 0x4000,
59
- clock_seq_hi_and_res: (clock_seq_hi_and_res & 0x3fff) | 0x8000,
60
- clk_seq_low:,
61
- node:
62
- ).freeze
63
- end
47
+ prefix_trimmed = encoded.delete_prefix('urn:uuid:')
48
+ unless V7_PATTERN.match?(prefix_trimmed)
49
+ raise(ParserError, "given `#{encoded}` does not match to `#{V7_PATTERN.inspect}`")
64
50
  end
65
51
 
66
- def to_s
67
- '%08x-%04x-%04x-%04x-%04x%08x' % values
68
- end
52
+ parse_any_to_int(encoded)
69
53
  end
70
54
  end
71
55
 
data/lib/ulid/version.rb CHANGED
@@ -3,5 +3,5 @@
3
3
  # shareable_constant_value: literal
4
4
 
5
5
  class ULID
6
- VERSION = '0.8.0'
6
+ VERSION = '1.0.0'
7
7
  end
data/lib/ulid.rb CHANGED
@@ -40,7 +40,7 @@ class ULID
40
40
  SCANNING_PATTERN = /\b[0-7][#{CrockfordBase32::ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}}[#{CrockfordBase32::ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}}\b/i
41
41
 
42
42
  # Similar as Time#inspect since Ruby 2.7, however it is NOT same.
43
- # Time#inspect trancates needless digits. Keeping full milliseconds with "%3N" will fit for ULID.
43
+ # Time#inspect truncates needless digits. Keeping full milliseconds with "%3N" will fit for ULID.
44
44
  # @see https://bugs.ruby-lang.org/issues/15958
45
45
  # @see https://github.com/ruby/ruby/blob/744d17ff6c33b09334508e8110007ea2a82252f5/time.c#L4026-L4078
46
46
  TIME_FORMAT_IN_INSPECT = '%Y-%m-%d %H:%M:%S.%3N %Z'
@@ -351,10 +351,17 @@ class ULID
351
351
  # @param [String, #to_str] uuid
352
352
  # @return [ULID]
353
353
  # @raise [ParserError] if the given format is not correct for UUIDv4 specs
354
- def self.from_uuidv4(uuid)
354
+ def self.from_uuid_v4(uuid)
355
355
  from_integer(UUID.parse_v4_to_int(uuid))
356
356
  end
357
357
 
358
+ # @param [String, #to_str] uuid
359
+ # @return [ULID]
360
+ # @raise [ParserError] if the given format is not correct for UUIDv4 specs
361
+ def self.from_uuid_v7(uuid)
362
+ from_integer(UUID.parse_v7_to_int(uuid))
363
+ end
364
+
358
365
  attr_reader(:milliseconds, :entropy, :encoded)
359
366
  protected(:encoded)
360
367
 
@@ -449,33 +456,43 @@ class ULID
449
456
  @encoded.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH) || raise(UnexpectedError)
450
457
  end
451
458
 
452
- # @return [ULID, nil] when called on ULID as `7ZZZZZZZZZZZZZZZZZZZZZZZZZ`, returns `nil` instead of ULID
453
- def succ
454
- succ_int = @integer.succ
455
- if succ_int >= MAX_INTEGER
456
- if succ_int == MAX_INTEGER
457
- MAX
458
- else
459
+ # @param [Integer] other
460
+ # @return [ULID, nil] when returning URID might be greater than `7ZZZZZZZZZZZZZZZZZZZZZZZZZ`, returns `nil` instead of ULID
461
+ def +(other)
462
+ raise(ArgumentError, 'ULID#+ takes only integers') unless Integer === other
463
+
464
+ new_int = @integer + other
465
+ case new_int
466
+ when MAX_INTEGER
467
+ MAX
468
+ when 0
469
+ MIN
470
+ else
471
+ if new_int > MAX_INTEGER || new_int < 0
459
472
  nil
473
+ else
474
+ ULID.from_integer(new_int)
460
475
  end
461
- else
462
- ULID.from_integer(succ_int)
463
476
  end
464
477
  end
478
+
479
+ # @param [Integer] other
480
+ # @return [ULID, nil] when returning URID might be less than `00000000000000000000000000`, returns `nil` instead of ULID
481
+ def -(other)
482
+ raise(ArgumentError, 'ULID#- takes only integers') unless Integer === other
483
+
484
+ self + -other
485
+ end
486
+
487
+ # @return [ULID, nil] when called on ULID as `7ZZZZZZZZZZZZZZZZZZZZZZZZZ`, returns `nil` instead of ULID
488
+ def succ
489
+ self + 1
490
+ end
465
491
  alias_method(:next, :succ)
466
492
 
467
493
  # @return [ULID, nil] when called on ULID as `00000000000000000000000000`, returns `nil` instead of ULID
468
494
  def pred
469
- pred_int = @integer.pred
470
- if pred_int <= 0
471
- if pred_int == 0
472
- MIN
473
- else
474
- nil
475
- end
476
- else
477
- ULID.from_integer(pred_int)
478
- end
495
+ self - 1
479
496
  end
480
497
 
481
498
  # @return [Integer]
@@ -500,8 +517,8 @@ class ULID
500
517
  self
501
518
  end
502
519
 
503
- # Generate a UUID-like string that does not set the version and variants field.
504
- # It means wrong in UUIDv4 spec, but reversible
520
+ # Generate a UUID-like string that does not touch the version and variants field.
521
+ # It means basically wrong in UUID specs, but reversible
505
522
  #
506
523
  # @return [String]
507
524
  def to_uuidish
@@ -516,8 +533,8 @@ class ULID
516
533
  # @see https://github.com/kachick/ruby-ulid/issues/76
517
534
  # @param [bool] force
518
535
  # @return [String]
519
- def to_uuidv4(force: false)
520
- v4 = UUID::Fields.forced_v4_from_octets(octets)
536
+ def to_uuid_v4(force: false)
537
+ v4 = UUID::Fields.forced_version_from_octets(octets, mask: 0x4000)
521
538
  unless force
522
539
  uuidish = UUID::Fields.raw_from_octets(octets)
523
540
  raise(IrreversibleUUIDError) unless uuidish == v4
@@ -526,6 +543,19 @@ class ULID
526
543
  v4.to_s.freeze
527
544
  end
528
545
 
546
+ # @see [#to_uuid_v4] and https://datatracker.ietf.org/doc/rfc9562/
547
+ # @param [bool] force
548
+ # @return [String]
549
+ def to_uuid_v7(force: false)
550
+ v7 = UUID::Fields.forced_version_from_octets(octets, mask: 0x7000)
551
+ unless force
552
+ uuidish = UUID::Fields.raw_from_octets(octets)
553
+ raise(IrreversibleUUIDError) unless uuidish == v7
554
+ end
555
+
556
+ v7.to_s.freeze
557
+ end
558
+
529
559
  # @return [ULID]
530
560
  def dup
531
561
  super.freeze
data/sig/ulid.rbs CHANGED
@@ -37,21 +37,23 @@ class ULID < Object
37
37
  module UUID
38
38
  BASE_PATTERN: Regexp
39
39
  V4_PATTERN: Regexp
40
+ V7_PATTERN: Regexp
40
41
 
41
42
  def self.parse_any_to_int: (String) -> Integer
42
43
  def self.parse_v4_to_int: (String) -> Integer
43
-
44
- class Fields < Struct[Integer]
45
- attr_accessor time_low: Integer
46
- attr_accessor time_mid: Integer
47
- attr_accessor time_hi_and_version: Integer
48
- attr_accessor clock_seq_hi_and_res: Integer
49
- attr_accessor clk_seq_low: Integer
50
- attr_accessor node: Integer
44
+ def self.parse_v7_to_int: (String) -> Integer
45
+
46
+ class Fields
47
+ attr_reader time_low: Integer
48
+ attr_reader time_mid: Integer
49
+ attr_reader time_hi_and_version: Integer
50
+ attr_reader clock_seq_hi_and_res: Integer
51
+ attr_reader clk_seq_low: Integer
52
+ attr_reader node: Integer
51
53
  def self.raw_from_octets: (octets) -> Fields
52
- def self.forced_v4_from_octets: (octets) -> Fields
54
+ def self.forced_version_from_octets: (octets, mask: Integer) -> Fields
53
55
 
54
- def values: -> Array[Integer]
56
+ def deconstruct: -> Array[Integer]
55
57
 
56
58
  private
57
59
 
@@ -69,8 +71,6 @@ class ULID < Object
69
71
 
70
72
  def self.reasonable_entropy: -> Integer
71
73
 
72
- def self.milliseconds_from_time: (Time time) -> milliseconds
73
-
74
74
  def self.safe_get_class_name: (untyped object) -> String
75
75
 
76
76
  def self.make_sharable_constants: (Module) -> void
@@ -147,7 +147,7 @@ class ULID < Object
147
147
  @integer: Integer
148
148
  @encoded: String
149
149
 
150
- # Retuns a ULID
150
+ # Returns a ULID
151
151
  #
152
152
  # They are sortable when generated in different timestamp with milliseconds precision
153
153
  #
@@ -183,7 +183,7 @@ class ULID < Object
183
183
  #
184
184
  def self.generate: (?moment: moment, ?entropy: Integer) -> ULID
185
185
 
186
- # Retuns encoded and normalzied String.\
186
+ # Returns encoded and normalzied String.\
187
187
  # It has same arguments signatures as `.generate`\
188
188
  # So can be used for just ID creation usecases without needless object creation.
189
189
  #
@@ -287,20 +287,23 @@ class ULID < Object
287
287
  # #=> ULID(2605-08-20 10:28:29.979 UTC: 0J7S2PFT4V2B9T8NJ2CRA1EG00)
288
288
  # ```
289
289
  #
290
- # See also [ULID.from_uuidv4]
290
+ # See also [ULID.from_uuid_v4]
291
291
  def self.from_uuidish: (String uuidish) -> ULID
292
292
 
293
293
  # Load a UUIDv4 string with checking version and variants.
294
294
  #
295
295
  # ```ruby
296
- # ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39')
296
+ # ULID.from_uuid_v4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39')
297
297
  # #=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
298
- # ULID.from_uuidv4('123e4567-e89b-12d3-a456-426614174000')
298
+ # ULID.from_uuid_v4('123e4567-e89b-12d3-a456-426614174000')
299
299
  # #=> ULID::ParserError
300
300
  # ```
301
301
  #
302
302
  # See also [ULID.from_uuidish]
303
- def self.from_uuidv4: (String uuid) -> ULID
303
+ def self.from_uuid_v4: (String uuid) -> ULID
304
+
305
+ # See also [ULID.from_uuid_v4]
306
+ def self.from_uuid_v7: (String uuid) -> ULID
304
307
 
305
308
  # Load integer as ULID
306
309
  #
@@ -594,6 +597,18 @@ class ULID < Object
594
597
  # ```
595
598
  def octets: -> octets
596
599
 
600
+ # Returns incremented ULID.\
601
+ # Especially providing for Range#step since ruby-3.4.0 spec changes
602
+ #
603
+ # See also [ruby-lang#18368](https://bugs.ruby-lang.org/issues/18368)
604
+ # ```
605
+ def +: (Integer other) -> ULID?
606
+
607
+ # Returns decremented ULID.\
608
+ # Providing for realizing natural API convention with the `ULID#+`
609
+ # ```
610
+ def -: (Integer other) -> ULID?
611
+
597
612
  # Returns next(successor) ULID.\
598
613
  # Especially `ULID#succ` makes it possible `Range[ULID]#each`.
599
614
  #
@@ -636,7 +651,7 @@ class ULID < Object
636
651
  # ULID.from_uuidish(ulid.to_uuidish) #=> ULID(2023-03-07 11:48:07.469 UTC: 01GTXYCWNDKRYH14DBZ77TRSD7)
637
652
  # ```
638
653
  #
639
- # See also [ULID.from_uuidish], [ULID#to_uuidv4], [ulid/spec#64](https://github.com/ulid/spec/issues/64)
654
+ # See also [ULID.from_uuidish], [ULID#to_uuid_v4], [ulid/spec#64](https://github.com/ulid/spec/issues/64)
640
655
  def to_uuidish: -> String
641
656
 
642
657
  # Generate a UUIDv4-like string that sets the version and variants field.\
@@ -645,18 +660,21 @@ class ULID < Object
645
660
  #
646
661
  # ```ruby
647
662
  # uuid = '0983d0a2-ff15-4d83-8f37-7dd945b5aa39'
648
- # ulid = ULID.from_uuidv4(uuid)
649
- # ulid.to_uuidv4 #=> 0983d0a2-ff15-4d83-8f37-7dd945b5aa39
663
+ # ulid = ULID.from_uuid_v4(uuid)
664
+ # ulid.to_uuid_v4 #=> 0983d0a2-ff15-4d83-8f37-7dd945b5aa39
650
665
  # ```
651
666
  #
652
667
  # ```ruby
653
668
  # ulid = ULID.from_uuidish('0186bbe6-72ad-9e3d-1091-abf9cfac65a7')
654
- # ulid.to_uuidv4 #=> ULID::IrreversibleUUIDError
655
- # ulid.to_uuidv4(force: true) #=> '0186bbe6-72ad-4e3d-9091-abf9cfac65a7'
669
+ # ulid.to_uuid_v4 #=> ULID::IrreversibleUUIDError
670
+ # ulid.to_uuid_v4(force: true) #=> '0186bbe6-72ad-4e3d-9091-abf9cfac65a7'
656
671
  # ```
657
672
  #
658
- # See also [ULID.from_uuidv4], [ULID#to_uuidish], [ulid/spec#64](https://github.com/ulid/spec/issues/64)
659
- def to_uuidv4: (?force: boolish) -> String
673
+ # See also [ULID.from_uuid_v4], [ULID#to_uuidish], [ulid/spec#64](https://github.com/ulid/spec/issues/64)
674
+ def to_uuid_v4: (?force: boolish) -> String
675
+
676
+ # See also [ULID.from_uuid_v7], [ULID#to_uuidish]
677
+ def to_uuid_v7: (?force: boolish) -> String
660
678
 
661
679
  # Returns same ID with different Ruby object.
662
680
  def dup: -> ULID
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-ulid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenichi Kamiya
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2023-03-08 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: " generator, optional monotonicity, parser and tools for ULID (RBS
14
13
  included)\n"
@@ -27,6 +26,7 @@ files:
27
26
  - lib/ulid/monotonic_generator.rb
28
27
  - lib/ulid/utils.rb
29
28
  - lib/ulid/uuid.rb
29
+ - lib/ulid/uuid/fields.rb
30
30
  - lib/ulid/version.rb
31
31
  - sig/ulid.rbs
32
32
  homepage: https://github.com/kachick/ruby-ulid
@@ -34,11 +34,9 @@ licenses:
34
34
  - MIT
35
35
  metadata:
36
36
  documentation_uri: https://kachick.github.io/ruby-ulid/
37
- homepage_uri: https://github.com/kachick/ruby-ulid
38
37
  source_code_uri: https://github.com/kachick/ruby-ulid
39
38
  bug_tracker_uri: https://github.com/kachick/ruby-ulid/issues
40
39
  rubygems_mfa_required: 'true'
41
- post_install_message:
42
40
  rdoc_options: []
43
41
  require_paths:
44
42
  - lib
@@ -46,15 +44,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
46
44
  requirements:
47
45
  - - ">="
48
46
  - !ruby/object:Gem::Version
49
- version: '3.1'
47
+ version: '4.0'
50
48
  required_rubygems_version: !ruby/object:Gem::Requirement
51
49
  requirements:
52
50
  - - ">="
53
51
  - !ruby/object:Gem::Version
54
52
  version: '0'
55
53
  requirements: []
56
- rubygems_version: 3.4.6
57
- signing_key:
54
+ rubygems_version: 4.0.3
58
55
  specification_version: 4
59
56
  summary: ULID manipulation library
60
57
  test_files: []