domainic-type 0.1.0.alpha.3.2.0 → 0.1.0.alpha.3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +11 -0
  3. data/README.md +66 -10
  4. data/docs/USAGE.md +787 -0
  5. data/lib/domainic/type/accessors.rb +3 -2
  6. data/lib/domainic/type/behavior/date_time_behavior.rb +121 -37
  7. data/lib/domainic/type/behavior.rb +16 -0
  8. data/lib/domainic/type/config/registry.yml +24 -0
  9. data/lib/domainic/type/constraint/constraints/nor_constraint.rb +1 -1
  10. data/lib/domainic/type/constraint/constraints/predicate_constraint.rb +76 -0
  11. data/lib/domainic/type/definitions.rb +212 -0
  12. data/lib/domainic/type/types/core/complex_type.rb +122 -0
  13. data/lib/domainic/type/types/core/range_type.rb +47 -0
  14. data/lib/domainic/type/types/core/rational_type.rb +38 -0
  15. data/lib/domainic/type/types/core_extended/big_decimal_type.rb +34 -0
  16. data/lib/domainic/type/types/core_extended/set_type.rb +34 -0
  17. data/lib/domainic/type/types/datetime/date_time_string_type.rb +156 -0
  18. data/lib/domainic/type/types/datetime/timestamp_type.rb +50 -0
  19. data/sig/domainic/type/accessors.rbs +2 -2
  20. data/sig/domainic/type/behavior/date_time_behavior.rbs +35 -23
  21. data/sig/domainic/type/behavior.rbs +9 -0
  22. data/sig/domainic/type/constraint/constraints/predicate_constraint.rbs +56 -0
  23. data/sig/domainic/type/definitions.rbs +165 -0
  24. data/sig/domainic/type/types/core/complex_type.rbs +96 -0
  25. data/sig/domainic/type/types/core/range_type.rbs +41 -0
  26. data/sig/domainic/type/types/core/rational_type.rbs +32 -0
  27. data/sig/domainic/type/types/core_extended/big_decimal_type.rbs +27 -0
  28. data/sig/domainic/type/types/core_extended/set_type.rbs +27 -0
  29. data/sig/domainic/type/types/datetime/date_time_string_type.rbs +124 -0
  30. data/sig/domainic/type/types/datetime/timestamp_type.rbs +44 -0
  31. metadata +25 -6
@@ -77,6 +77,36 @@ module Domainic
77
77
  end
78
78
  alias _List? _Array?
79
79
 
80
+ # Creates a BigDecimalType instance.
81
+ #
82
+ # @example
83
+ # type = _BigDecimal
84
+ # type.validate(BigDecimal('123.45')) # => true
85
+ #
86
+ # @param options [Hash] additional configuration options
87
+ #
88
+ # @return [Domainic::Type::BigDecimalType] the created type
89
+ # @rbs (**__todo__ options) -> BigDecimalType
90
+ def _BigDecimal(**options)
91
+ require 'domainic/type/types/core_extended/big_decimal_type'
92
+ Domainic::Type::BigDecimalType.new(**options)
93
+ end
94
+
95
+ # Creates a nilable BigDecimalType instance.
96
+ #
97
+ # @example
98
+ # type = _BigDecimal?
99
+ # type.validate(BigDecimal('123.45')) # => true
100
+ # type.validate(nil) # => true
101
+ #
102
+ # @param options [Hash] additional configuration options
103
+ #
104
+ # @return [Domainic::Type::UnionType] the created type (BigDecimal or NilClass)
105
+ # @rbs (**__todo__ options) -> UnionType
106
+ def _BigDecimal?(**options)
107
+ _Nilable(_BigDecimal(**options))
108
+ end
109
+
80
110
  # Creates a Boolean type.
81
111
  #
82
112
  # Represents a union of TrueClass and FalseClass.
@@ -133,6 +163,35 @@ module Domainic
133
163
  end
134
164
  alias _Cuid? _CUID?
135
165
 
166
+ # Creates a ComplexType instance.
167
+ #
168
+ # @example
169
+ # type = _Complex
170
+ # type.validate(Complex(1, 2)) # => true
171
+ #
172
+ # @param options [Hash] additional configuration options
173
+ #
174
+ # @return [Domainic::Type::ComplexType] the created type
175
+ # @rbs (**__todo__ options) -> ComplexType
176
+ def _Complex(**options)
177
+ require 'domainic/type/types/core/complex_type'
178
+ Domainic::Type::ComplexType.new(**options)
179
+ end
180
+
181
+ # Creates a nilable ComplexType instance.
182
+ #
183
+ # @example
184
+ # type = _Complex?
185
+ # type.validate(Complex(1, 2)) # => true
186
+ #
187
+ # @param options [Hash] additional configuration options
188
+ #
189
+ # @return [Domainic::Type::UnionType] the created type (Complex or NilClass)
190
+ # @rbs (**__todo__ options) -> UnionType
191
+ def _Complex?(**options)
192
+ _Nilable(_Complex(**options))
193
+ end
194
+
136
195
  # Creates a DateType instance.
137
196
  #
138
197
  # DateType restricts values to valid `Date` objects.
@@ -195,6 +254,39 @@ module Domainic
195
254
  _Nilable(_DateTime(**options))
196
255
  end
197
256
 
257
+ # Creates a DateTimeStringType instance.
258
+ #
259
+ # DateTimeStringType restricts values to valid date-time strings.
260
+ #
261
+ # @example
262
+ # type = _DateTimeString
263
+ # type.validate('2024-01-01T12:00:00Z') # => true
264
+ #
265
+ # @param options [Hash] additional configuration options
266
+ #
267
+ # @return [Domainic::Type::DateTimeStringType] the created type
268
+ # @rbs (**__todo__ options) -> DateTimeStringType
269
+ def _DateTimeString(**options)
270
+ require 'domainic/type/types/datetime/date_time_string_type'
271
+ Domainic::Type::DateTimeStringType.new(**options)
272
+ end
273
+ alias _DateString _DateTimeString
274
+
275
+ # Creates a nilable DateTimeStringType instance.
276
+ #
277
+ # @example
278
+ # _DateTimeString? === '2024-01-01T12:00:00Z' # => true
279
+ # _DateTimeString? === nil # => true
280
+ #
281
+ # @param options [Hash] additional configuration options
282
+ #
283
+ # @return [Domainic::Type::UnionType] the created type (DateTimeStringType or NilClass)
284
+ # @rbs (**__todo__ options) -> UnionType
285
+ def _DateTimeString?(**options)
286
+ _Nilable(_DateTimeString(**options))
287
+ end
288
+ alias _DateString? _DateTimeString?
289
+
198
290
  # Creates a DuckType instance.
199
291
  #
200
292
  # DuckType allows specifying behavior based on method availability.
@@ -477,6 +569,95 @@ module Domainic
477
569
  end
478
570
  alias _Nullable _Nilable
479
571
 
572
+ # Creates a RangeType instance.
573
+ #
574
+ # @example
575
+ # type = _Range
576
+ # type.validate(1..10) # => true
577
+ #
578
+ # @param options [Hash] additional configuration options
579
+ #
580
+ # @return [Domainic::Type::RangeType] the created type
581
+ # @rbs (**__todo__ options) -> RangeType
582
+ def _Range(**options)
583
+ require 'domainic/type/types/core/range_type'
584
+ Domainic::Type::RangeType.new(**options)
585
+ end
586
+
587
+ # Creates a nilable RangeType instance.
588
+ #
589
+ # @example
590
+ # type = _Range?
591
+ # type.validate(1..10) # => true
592
+ #
593
+ # @param options [Hash] additional configuration options
594
+ #
595
+ # @return [Domainic::Type::UnionType] the created type (Range or NilClass)
596
+ # @rbs (**__todo__ options) -> UnionType
597
+ def _Range?(**options)
598
+ _Nilable(_Range(**options))
599
+ end
600
+
601
+ # Creates a RationalType instance.
602
+ #
603
+ # @example
604
+ # type = _Rational
605
+ # type.validate(Rational(1, 2)) # => true
606
+ #
607
+ # @param options [Hash] additional configuration options
608
+ #
609
+ # @return [Domainic::Type::RationalType] the created type
610
+ # @rbs (**__todo__ options) -> RationalType
611
+ def _Rational(**options)
612
+ require 'domainic/type/types/core/rational_type'
613
+ Domainic::Type::RationalType.new(**options)
614
+ end
615
+
616
+ # Creates a nilable RationalType instance.
617
+ #
618
+ # @example
619
+ # type = _Rational?
620
+ # type.validate(Rational(1, 2)) # => true
621
+ # type.validate(nil) # => true
622
+ #
623
+ # @param options [Hash] additional configuration options
624
+ #
625
+ # @return [Domainic::Type::UnionType] the created type (Rational or NilClass)
626
+ # @rbs (**__todo__ options) -> UnionType
627
+ def _Rational?(**options)
628
+ _Nilable(_Rational(**options))
629
+ end
630
+
631
+ # Creates a SetType instance.
632
+ #
633
+ # @example
634
+ # type = _Set
635
+ # type.validate(Set[1, 2, 3]) # => true
636
+ #
637
+ # @param options [Hash] additional configuration options
638
+ #
639
+ # @return [Domainic::Type::SetType] the created type
640
+ # @rbs (**__todo__ options) -> SetType
641
+ def _Set(**options)
642
+ require 'domainic/type/types/core_extended/set_type'
643
+ Domainic::Type::SetType.new(**options)
644
+ end
645
+
646
+ # Creates a nilable SetType instance.
647
+ #
648
+ # @example
649
+ # type = _Set?
650
+ # type.validate(Set[1, 2, 3]) # => true
651
+ # type.validate(nil) # => true
652
+ #
653
+ # @param options [Hash] additional configuration options
654
+ #
655
+ # @return [Domainic::Type::UnionType] the created type (Set or NilClass)
656
+ # @rbs (**__todo__ options) -> UnionType
657
+ def _Set?(**options)
658
+ _Nilable(_Set(**options))
659
+ end
660
+
480
661
  # Creates a StringType instance.
481
662
  #
482
663
  # @example
@@ -568,6 +749,37 @@ module Domainic
568
749
  _Nilable(_Time(**options))
569
750
  end
570
751
 
752
+ # Creates a TimestampType instance.
753
+ #
754
+ # TimestampType restricts values to valid timestamps.
755
+ #
756
+ # @example
757
+ # type = _Timestamp
758
+ # type.validate(1640995200) # => true
759
+ #
760
+ # @param options [Hash] additional configuration options
761
+ #
762
+ # @return [Domainic::Type::TimestampType] the created type
763
+ # @rbs (**__todo__ options) -> TimestampType
764
+ def _Timestamp(**options)
765
+ require 'domainic/type/types/datetime/timestamp_type'
766
+ Domainic::Type::TimestampType.new(**options)
767
+ end
768
+
769
+ # Creates a nilable TimestampType instance.
770
+ #
771
+ # @example
772
+ # _Timestamp? === 1640995200 # => true
773
+ # _Timestamp? === nil # => true
774
+ #
775
+ # @param options [Hash] additional configuration options
776
+ #
777
+ # @return [Domainic::Type::UnionType] the created type (TimestampType or NilClass)
778
+ # @rbs (**__todo__ options) -> UnionType
779
+ def _Timestamp?(**options)
780
+ _Nilable(_Timestamp(**options))
781
+ end
782
+
571
783
  # Creates a URIType instance.
572
784
  #
573
785
  # URIType restricts values to valid URIs.
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/type/behavior'
4
+ require 'domainic/type/behavior/numeric_behavior'
5
+
6
+ module Domainic
7
+ module Type
8
+ # A type for validating and constraining `Complex` numbers.
9
+ #
10
+ # This type extends `NumericBehavior` to provide a fluent interface for numeric-specific validations such as
11
+ # polarity and divisibility checks.
12
+ #
13
+ # @example Validating a `Complex` value
14
+ # type = Domainic::Type::ComplexType.new
15
+ # type.validate!(Complex(3, 4)) # => true
16
+ #
17
+ # @example Enforcing constraints
18
+ # type = Domainic::Type::ComplexType.new
19
+ # type.being_positive.validate!(Complex(42, 0)) # => raises TypeError
20
+ #
21
+ # @author {https://aaronmallen.me Aaron Allen}
22
+ # @since 0.1.0
23
+ class ComplexType
24
+ # @rbs! extend Behavior::ClassMethods
25
+
26
+ include Behavior
27
+ include Behavior::NumericBehavior
28
+
29
+ intrinsically_constrain :self, :type, Complex, abort_on_failure: true, description: :not_described
30
+
31
+ # Constrain the Complex real to be divisible by a given divisor.
32
+ #
33
+ # @param arguments [Array<Numeric>] a list of arguments, typically one divisor
34
+ # @param options [Hash] additional options such as tolerance for floating-point checks
35
+ # @option options [Numeric] :divisor the divisor to check for divisibility
36
+ # @option options [Numeric] :tolerance the tolerance for floating-point checks
37
+ #
38
+ # @return [self] the current instance for chaining
39
+ # @rbs (*Numeric arguments, ?divisor: Numeric, ?tolerance: Numeric) -> Behavior
40
+ def being_divisible_by(*arguments, **options)
41
+ if arguments.size > 1 || (arguments.empty? && !options.key?(:divisor))
42
+ raise ArgumentError, "wrong number of arguments (given #{arguments.size}, expected 1)"
43
+ end
44
+
45
+ divisor = arguments.first || options[:divisor]
46
+ constrain :real, :divisibility, divisor, description: 'being', **options.except(:divisor)
47
+ end
48
+ alias being_multiple_of being_divisible_by
49
+ alias divisible_by being_divisible_by
50
+ alias multiple_of being_divisible_by
51
+
52
+ # Constrain the Complex real to be even.
53
+ #
54
+ # @return [self] the current instance for chaining
55
+ # @rbs () -> Behavior
56
+ def being_even
57
+ # @type self: Object & Behavior
58
+ constrain :real, :parity, :even, description: 'being'
59
+ end
60
+ alias even being_even
61
+ alias not_being_odd being_even
62
+
63
+ # Constrain the Complex real to be negative.
64
+ #
65
+ # @return [self] the current instance for chaining
66
+ # @rbs () -> Behavior
67
+ def being_negative
68
+ # @type self: Object & Behavior
69
+ constrain :real, :polarity, :negative, description: 'being'
70
+ end
71
+ alias negative being_negative
72
+ alias not_being_positive being_negative
73
+
74
+ # Constrain the Complex real value to be odd.
75
+ #
76
+ # @return [self] the current instance for chaining
77
+ # @rbs () -> Behavior
78
+ def being_odd
79
+ # @type self: Object & Behavior
80
+ constrain :real, :parity, :odd, description: 'being'
81
+ end
82
+ alias odd being_odd
83
+ alias not_being_even being_odd
84
+
85
+ # Constrain the Complex real to be positive.
86
+ #
87
+ # @return [self] the current instance for chaining
88
+ # @rbs () -> Behavior
89
+ def being_positive
90
+ # @type self: Object & Behavior
91
+ constrain :real, :polarity, :positive, description: 'being'
92
+ end
93
+ alias positive being_positive
94
+ alias not_being_negative being_positive
95
+
96
+ # Constrain the Complex real to not be divisible by a specific divisor.
97
+ #
98
+ # @note the divisor MUST be provided as an argument or in the options Hash.
99
+ #
100
+ # @param arguments [Array<Numeric>] a list of arguments, typically one divisor
101
+ # @param options [Hash] additional options such as tolerance for floating-point checks
102
+ # @option options [Numeric] :divisor the divisor to check for divisibility
103
+ # @option options [Numeric] :tolerance the tolerance for floating-point checks
104
+ #
105
+ # @return [self] the current instance for chaining
106
+ # @rbs (*Numeric arguments, ?divisor: Numeric, ?tolerance: Numeric) -> Behavior
107
+ def not_being_divisible_by(*arguments, **options)
108
+ if arguments.size > 1 || (arguments.empty? && !options.key?(:divisor))
109
+ raise ArgumentError, "wrong number of arguments (given #{arguments.size}, expected 1)"
110
+ end
111
+
112
+ # @type self: Object & Behavior
113
+ divisor = arguments.first || options[:divisor]
114
+ divisible_by = @constraints.prepare :self, :divisibility, divisor, **options.except(:divisor)
115
+ constrain :real, :not, divisible_by, concerning: :divisibility, description: 'being'
116
+ end
117
+ alias not_being_multiple_of not_being_divisible_by
118
+ alias not_divisible_by not_being_divisible_by
119
+ alias not_multiple_of not_being_divisible_by
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/type/behavior'
4
+ require 'domainic/type/behavior/enumerable_behavior'
5
+
6
+ module Domainic
7
+ module Type
8
+ # A type for validating `Range` objects with comprehensive constraints.
9
+ #
10
+ # This class allows flexible validation of ranges, supporting type checks,
11
+ # inclusion/exclusion of values, and range-specific behaviors such as bounds validation.
12
+ # It integrates with `EnumerableBehavior` for enumerable-style validations
13
+ # (e.g., size constraints, element presence).
14
+ #
15
+ # Key features:
16
+ # - Ensures the value is a `Range`.
17
+ # - Supports constraints for range boundaries (`begin` and `end`).
18
+ # - Provides validations for inclusion and exclusion of values.
19
+ # - Leverages `EnumerableBehavior` for size and collection-style constraints.
20
+ #
21
+ # @example Basic usage
22
+ # type = RangeType.new
23
+ # type.having_bounds(1, 10) # Validates range bounds (inclusive or exclusive).
24
+ # type.containing(5) # Ensures value is within the range.
25
+ # type.not_containing(15) # Ensures value is not within the range.
26
+ #
27
+ # @example Integration with EnumerableBehavior
28
+ # type = RangeType.new
29
+ # type.having_size(10) # Validates size (total elements in the range).
30
+ # type.containing_exactly(3, 7) # Validates specific values within the range.
31
+ #
32
+ # @example Flexible boundaries
33
+ # type = RangeType.new
34
+ # type.having_bounds(1, 10, exclusive: true) # Upper and lower bounds are exclusive.
35
+ #
36
+ # @author {https://aaronmallen.me Aaron Allen}
37
+ # @since 0.1.0
38
+ class RangeType
39
+ # @rbs! extend Behavior::ClassMethods
40
+
41
+ include Behavior
42
+ include Behavior::EnumerableBehavior
43
+
44
+ intrinsically_constrain :self, :type, Range, abort_on_failure: true, description: :not_described
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/type/behavior'
4
+ require 'domainic/type/behavior/numeric_behavior'
5
+
6
+ module Domainic
7
+ module Type
8
+ # A type for validating Rational numbers.
9
+ #
10
+ # This type ensures values are of type `Rational` and supports a range of
11
+ # constraints for numerical validation, such as positivity, negativity,
12
+ # and divisibility. It integrates with `NumericBehavior` to provide a
13
+ # consistent and fluent interface for numeric constraints.
14
+ #
15
+ # @example Validating a positive Rational number
16
+ # type = Domainic::Type::RationalType.new
17
+ # type.being_positive.validate!(Rational(3, 4)) # => true
18
+ #
19
+ # @example Validating divisibility
20
+ # type = Domainic::Type::RationalType.new
21
+ # type.being_divisible_by(1).validate!(Rational(3, 1)) # => true
22
+ #
23
+ # @example Using constraints with chaining
24
+ # type = Domainic::Type::RationalType.new
25
+ # type.being_greater_than(0).being_less_than(1).validate!(Rational(1, 2)) # => true
26
+ #
27
+ # @author {https://aaronmallen.me Aaron Allen}
28
+ # @since 0.1.0
29
+ class RationalType
30
+ # @rbs! extend Behavior::ClassMethods
31
+
32
+ include Behavior
33
+ include Behavior::NumericBehavior
34
+
35
+ intrinsically_constrain :self, :type, Rational, abort_on_failure: true, description: :not_described
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bigdecimal'
4
+ require 'domainic/type/behavior'
5
+ require 'domainic/type/behavior/numeric_behavior'
6
+
7
+ module Domainic
8
+ module Type
9
+ # A type for validating and constraining `BigDecimal` objects.
10
+ #
11
+ # This type extends `NumericBehavior` to provide a fluent interface for numeric-specific validations such as being
12
+ # positive, being within a range, or satisfying divisibility rules.
13
+ #
14
+ # @example Validating a `BigDecimal` value
15
+ # type = Domainic::Type::BigDecimalType.new
16
+ # type.validate!(BigDecimal('3.14')) # => true
17
+ #
18
+ # @example Enforcing constraints
19
+ # type = Domainic::Type::BigDecimalType.new
20
+ # type.being_positive.validate!(BigDecimal('42')) # => true
21
+ # type.being_positive.validate!(BigDecimal('-42')) # raises TypeError
22
+ #
23
+ # @author {https://aaronmallen.me Aaron Allen}
24
+ # @since 0.1.0
25
+ class BigDecimalType
26
+ # @rbs! extend Behavior::ClassMethods
27
+
28
+ include Behavior
29
+ include Behavior::NumericBehavior
30
+
31
+ intrinsically_constrain :self, :type, BigDecimal, abort_on_failure: true, description: :not_described
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/type/behavior'
4
+ require 'domainic/type/behavior/enumerable_behavior'
5
+ require 'set'
6
+
7
+ module Domainic
8
+ module Type
9
+ # A type for validating and constraining `Set` objects.
10
+ #
11
+ # This type extends `EnumerableBehavior` to support validations and constraints
12
+ # such as being empty, containing specific elements, or having a minimum or maximum count.
13
+ #
14
+ # @example Validating a `Set` object
15
+ # type = Domainic::Type::SetType.new
16
+ # type.validate!(Set.new([1, 2, 3])) # => true
17
+ #
18
+ # @example Enforcing constraints
19
+ # type = Domainic::Type::SetType.new
20
+ # type.being_empty.validate!(Set.new) # => true
21
+ # type.being_empty.validate!(Set.new([1])) # raises TypeError
22
+ #
23
+ # @author {https://aaronmallen.me Aaron Allen}
24
+ # @since 0.1.0
25
+ class SetType
26
+ # @rbs! extend Behavior::ClassMethods
27
+
28
+ include Behavior
29
+ include Behavior::EnumerableBehavior
30
+
31
+ intrinsically_constrain :self, :type, Set, abort_on_failure: true, description: :not_described
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/type/behavior'
4
+ require 'domainic/type/behavior/date_time_behavior'
5
+
6
+ module Domainic
7
+ module Type
8
+ # A type for validating and constraining strings to match various datetime formats.
9
+ #
10
+ # This class includes behaviors for handling datetime constraints and ensures that
11
+ # a string matches one of the predefined datetime formats (e.g., ISO 8601, RFC2822).
12
+ #
13
+ # @example Validating American format
14
+ # type = Domainic::Type::DateTimeStringType.new
15
+ # type.having_american_format.validate!("01/31/2024")
16
+ #
17
+ # @example Validating ISO 8601 format
18
+ # type = Domainic::Type::DateTimeStringType.new
19
+ # type.having_iso8601_format.validate!("2024-01-01T12:00:00Z")
20
+ #
21
+ # @example Validating RFC2822 format
22
+ # type = Domainic::Type::DateTimeStringType.new
23
+ # type.having_rfc2822_format.validate!("Thu, 31 Jan 2024 13:30:00 +0000")
24
+ #
25
+ # @author {https://aaronmallen.me Aaron Allen}
26
+ # @since 0.1.0
27
+ class DateTimeStringType
28
+ # @rbs! extend Behavior::ClassMethods
29
+
30
+ include Behavior
31
+ include Behavior::DateTimeBehavior
32
+
33
+ # The `Format` module provides a set of regular expressions for validating
34
+ # various datetime formats, including ISO 8601, RFC2822, American, European,
35
+ # full month names, abbreviated month names, and 12-hour clock formats.
36
+ #
37
+ # @author {https://aaronmallen.me Aaron Allen}
38
+ # @since 0.1.0
39
+ module Format
40
+ # Matches ISO 8601 datetime formats, including:
41
+ # - `2024-01-01T12:00:00.000+00:00`
42
+ # - `2024-01-01T12:00:00+00:00`
43
+ # - `2024-01-01T12:00:00.000`
44
+ # - `2024-01-01T12:00:00`
45
+ # - `20240101T120000+0000` (basic format)
46
+ # @return [Regexp]
47
+ ISO8601 = /\A\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[-+]\d{2}:?\d{2})?)?\z/ #: Regexp
48
+
49
+ # Matches RFC2822 datetime formats, including:
50
+ # - `Thu, 31 Jan 2024 13:30:00 +0000`
51
+ # - `31 Jan 2024 13:30:00 +0000`
52
+ # @return [Regexp]
53
+ RFC2822 = /\A
54
+ (?: # Optional day of the week
55
+ (?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),\s+
56
+ )?
57
+ \d{1,2}\s+ # Day of the month
58
+ (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+ # Month
59
+ \d{2,4}\s+ # Year
60
+ \d{2}:\d{2} # Time in HH:MM
61
+ (?::\d{2})? # Optional seconds
62
+ \s+ # Space
63
+ (?: # Time zone
64
+ [+-]\d{4} | # Offset (e.g., +0000)
65
+ GMT | UTC # Literal GMT or UTC
66
+ )
67
+ \z/x #: Regexp
68
+
69
+ # Matches American-style dates with optional time in 12-hour or 24-hour formats, including:
70
+ # - `01/31/2024`
71
+ # - `01/31/2024 12:30:00`
72
+ # - `01/31/2024 12:30 PM`
73
+ # @return [Regexp]
74
+ AMERICAN = %r{\A\d{1,2}/\d{1,2}/\d{2,4}(?: \d{1,2}:\d{1,2}(?::\d{1,2})?(?:\s*[AaPp][Mm])?)?\z} #: Regexp
75
+
76
+ # Matches European-style dates with optional time in 24-hour format, including:
77
+ # - `31.01.2024`
78
+ # - `31.01.2024 12:30:00`
79
+ # @return [Regexp]
80
+ EUROPEAN = /\A\d{1,2}\.\d{1,2}\.\d{2,4}(?: \d{1,2}:\d{2}(?::\d{2})?)?\z/ #: Regexp
81
+
82
+ # Matches datetime formats with full month names, including:
83
+ # - `January 31, 2024 12:00:00`
84
+ # - `January 31, 2024 12:00`
85
+ # @return [Regexp]
86
+ FULL_MONTH_NAME = /\A
87
+ (?: # Full month name
88
+ (?:January|February|March|April|May|June|July|August|September|October|November|December)
89
+ )\s+\d{1,2},\s+\d{4}(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?
90
+ \z/x #: Regexp
91
+
92
+ # Matches datetime formats with abbreviated month names, including:
93
+ # - `Jan 31, 2024 12:00:00`
94
+ # - `Jan 31, 2024 12:00`
95
+ # @return [Regexp]
96
+ ABBREVIATED_MONTH_NAME = /\A
97
+ \d{1,2}\s+(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec),\s+\d{4}(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?
98
+ \z/x #: Regexp
99
+
100
+ # Matches datetime formats using a 12-hour clock with AM/PM, including:
101
+ # - `2024-01-01 01:30:00 PM`
102
+ # - `2024-01-01 01:30 PM`
103
+ # @return [Regexp]
104
+ TWELVE_HOUR_FORMAT = /\A\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}(?::\d{2})?\s+[APap][Mm]\z/x #: Regexp
105
+ end
106
+ private_constant :Format
107
+
108
+ intrinsically_constrain :self, :type, String, abort_on_failure: true, description: :not_described
109
+ intrinsically_constrain :self, :match_pattern,
110
+ Regexp.union(Format.constants.map { |name| Format.const_get(name) }),
111
+ abort_on_failure: true, concerning: :format, description: :not_described
112
+
113
+ # Constrain the type to match American-style datetime formats.
114
+ #
115
+ # @return [self] self for method chaining
116
+ # @rbs () -> self
117
+ def having_american_format
118
+ constrain :self, :match_pattern, Format::AMERICAN, concerning: :format
119
+ end
120
+ alias american having_american_format
121
+ alias having_us_format having_american_format
122
+ alias us_format having_american_format
123
+
124
+ # Constrain the type to match European-style datetime formats.
125
+ #
126
+ # @return [self] self for method chaining
127
+ # @rbs () -> self
128
+ def having_european_format
129
+ constrain :self, :match_pattern, Format::EUROPEAN, concerning: :format
130
+ end
131
+ alias european having_european_format
132
+ alias having_eu_format having_european_format
133
+ alias eu_format having_european_format
134
+
135
+ # Constrain the type to match ISO 8601 datetime formats.
136
+ #
137
+ # @return [self] self for method chaining
138
+ # @rbs () -> self
139
+ def having_iso8601_format
140
+ constrain :self, :match_pattern, Format::ISO8601, concerning: :format
141
+ end
142
+ alias iso8601 having_iso8601_format
143
+ alias iso8601_format having_iso8601_format
144
+
145
+ # Constrain the type to match RFC2822 datetime formats.
146
+ #
147
+ # @return [self] self for method chaining
148
+ # @rbs () -> self
149
+ def having_rfc2822_format
150
+ constrain :self, :match_pattern, Format::RFC2822, concerning: :format
151
+ end
152
+ alias rfc2822 having_rfc2822_format
153
+ alias rfc2822_format having_rfc2822_format
154
+ end
155
+ end
156
+ end