date_values 0.1.4 → 0.2.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: 3ed7149400551e703761bd9ea324f55decb43cfe14e11d36d8e6e22f1fcb21a3
4
- data.tar.gz: d809fce0b0215b62891a453b4e7e4278ad7a600ffea25cc045662f4a4ae1425b
3
+ metadata.gz: f294856b7f5149ded52770b4d43d003b5cac6e8ee533f81ec003ac04caf9988b
4
+ data.tar.gz: 8c1f6046926d15cda1a01e55f89e2975b92e6cf515bbc21cb0b6892d816bf93b
5
5
  SHA512:
6
- metadata.gz: 821ca618c27d3b17583c8420697ad11cbb3b9ee34dc44d7ea4d938ffd4500a4795b8aacd6b41df9864c982877d201c0dc616fecbca6342945c671b8b1f410679
7
- data.tar.gz: f69de80391b67de78fafca31368c4f54683299c94740eba4047d0dff800ecb0647bcf208039473911fbf0fb60cbbb5595ef3c322d79154b72bc39e47056361a5
6
+ metadata.gz: 05e23c3cd91ddda9dcdf5ae63e81dd89c7a6bfd5150dab2893231ebe63c12bc64023cae0667c296e4e88db2dd5869ebdab39245370291cb29cbb9b2d906ad3e3
7
+ data.tar.gz: 28e82e5dc12f33c6e8af8b546338636ef2f6cda3273a2957bf7f86dcb4bb4a6074a4e2376c1c0ed0600f19e98b2783661710c8882b066eb05d3d9c1aa359aea8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2026-03-20
4
+
5
+ - **Breaking**: Rails integration extracted to [date_values-rails](https://github.com/ursm/date_values-rails). `require 'date_values/rails'` now requires installing `date_values-rails` gem.
6
+
3
7
  ## [0.1.4] - 2026-03-20
4
8
 
5
9
  - Cast returns `nil` for invalid input instead of raising, following Rails convention
data/README.md CHANGED
@@ -26,6 +26,7 @@ ym.to_date # => #<Date: 2026-03-01>
26
26
 
27
27
  YearMonth.from(Date.today) # => #<DateValues::YearMonth 2026-03>
28
28
  YearMonth.parse('2026-03') # => #<DateValues::YearMonth 2026-03>
29
+ YearMonth.parse('2026/3') # also works
29
30
 
30
31
  ym + 1 # => #<DateValues::YearMonth 2026-04>
31
32
  ym - 1 # => #<DateValues::YearMonth 2026-02>
@@ -47,6 +48,7 @@ md.to_date(2026) # => #<Date: 2026-03-19>
47
48
 
48
49
  MonthDay.from(Date.today) # => #<DateValues::MonthDay --03-20>
49
50
  MonthDay.parse('--03-19') # => #<DateValues::MonthDay --03-19>
51
+ MonthDay.parse('3/19') # also works
50
52
 
51
53
  # Range membership
52
54
  summer = MonthDay.new(6, 1)..MonthDay.new(8, 31)
@@ -92,95 +94,7 @@ end
92
94
 
93
95
  ## Rails Integration
94
96
 
95
- Opt-in ActiveModel type casting for ActiveRecord attributes:
96
-
97
- ```ruby
98
- require 'date_values/rails'
99
-
100
- class Shop < ApplicationRecord
101
- attribute :billing_month, :year_month # string column "2026-03"
102
- attribute :anniversary, :month_day # string column "--03-19"
103
- attribute :opens_at, :time_of_day # string or time column
104
- end
105
- ```
106
-
107
- Values are automatically serialized in queries:
108
-
109
- ```ruby
110
- Shop.where(billing_month: YearMonth.new(2026, 3))
111
- # SELECT * FROM shops WHERE billing_month = '2026-03'
112
- ```
113
-
114
- ### Validation
115
-
116
- All classes are `Comparable` and value-equal, so standard Rails validators work as-is:
117
-
118
- ```ruby
119
- class Contract < ApplicationRecord
120
- attribute :start_month, :year_month
121
- attribute :opens_at, :time_of_day
122
-
123
- validates :start_month, comparison: {greater_than: -> { YearMonth.from(Date.current) }}
124
- validates :opens_at, comparison: {
125
- greater_than_or_equal_to: TimeOfDay.new(9, 0),
126
- less_than_or_equal_to: TimeOfDay.new(17, 0)
127
- }
128
- end
129
- ```
130
-
131
- Invalid input (e.g. `"25:00"`) is cast to `nil` rather than raising, following the same convention as Rails' built-in types. The `date_value` validator detects this and gives a meaningful error message:
132
-
133
- ```ruby
134
- class Shop < ApplicationRecord
135
- attribute :opens_at, :time_of_day
136
-
137
- validates :opens_at, presence: true, date_value: true
138
- end
139
-
140
- Shop.new(opens_at: '25:00').errors[:opens_at] # => ["is invalid"]
141
- Shop.new(opens_at: '').errors[:opens_at] # => ["can't be blank"]
142
- ```
143
-
144
-
145
- ### I18n / `l` Helper
146
-
147
- All classes implement `#strftime`, and the Rails integration extends `I18n.l` to support them. Define formats in your locale files:
148
-
149
- ```yaml
150
- # config/locales/en.yml
151
- en:
152
- year_month:
153
- formats:
154
- default: '%B %Y'
155
- month_day:
156
- formats:
157
- default: '%B %-d'
158
- time_of_day:
159
- formats:
160
- default: '%-I:%M %p'
161
- long: '%-I:%M:%S %p'
162
- ```
163
-
164
- ```yaml
165
- # config/locales/ja.yml
166
- ja:
167
- year_month:
168
- formats:
169
- default: '%Y年%-m月'
170
- month_day:
171
- formats:
172
- default: '%-m月%-d日'
173
- time_of_day:
174
- formats:
175
- default: '%-H時%-M分'
176
- long: '%-H時%-M分%-S秒'
177
- ```
178
-
179
- ```ruby
180
- I18n.l YearMonth.new(2026, 3), locale: :en # => "March 2026"
181
- I18n.l YearMonth.new(2026, 3), locale: :ja # => "2026年3月"
182
- I18n.l TimeOfDay.new(14, 30), format: :long # => "2:30:00 PM"
183
- ```
97
+ See [date_values-rails](https://github.com/ursm/date_values-rails) for ActiveModel/ActiveRecord type casting, validation, and I18n support.
184
98
 
185
99
  ## License
186
100
 
@@ -18,10 +18,11 @@ module DateValues
18
18
  end
19
19
 
20
20
  def self.parse(str)
21
- raise ArgumentError, "invalid MonthDay: #{str}" unless str.match?(/\A--\d{2}-\d{2}\z/)
22
-
23
- month, day = str[2..].split('-').map(&:to_i)
24
- new(month, day)
21
+ case str
22
+ when /\A--(\d{1,2})-(\d{1,2})\z/ then new($1.to_i, $2.to_i)
23
+ when /\A(\d{1,2})[\/\-](\d{1,2})\z/ then new($1.to_i, $2.to_i)
24
+ else raise ArgumentError, "invalid MonthDay: #{str}"
25
+ end
25
26
  end
26
27
 
27
28
  def <=>(other)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DateValues
4
- VERSION = '0.1.4'
4
+ VERSION = '0.2.0'
5
5
  end
@@ -17,10 +17,10 @@ module DateValues
17
17
  end
18
18
 
19
19
  def self.parse(str)
20
- raise ArgumentError, "invalid YearMonth: #{str}" unless str.match?(/\A\d{4}-\d{2}\z/)
21
-
22
- year, month = str.split('-').map(&:to_i)
23
- new(year, month)
20
+ case str
21
+ when /\A(\d{4})[\/\-](\d{1,2})\z/ then new($1.to_i, $2.to_i)
22
+ else raise ArgumentError, "invalid YearMonth: #{str}"
23
+ end
24
24
  end
25
25
 
26
26
  def <=>(other)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: date_values
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keita Urashima
@@ -21,17 +21,10 @@ files:
21
21
  - Rakefile
22
22
  - lib/date_values.rb
23
23
  - lib/date_values/month_day.rb
24
- - lib/date_values/rails.rb
25
- - lib/date_values/rails/date_value_validator.rb
26
- - lib/date_values/rails/i18n_backend.rb
27
- - lib/date_values/rails/month_day_type.rb
28
- - lib/date_values/rails/time_of_day_type.rb
29
- - lib/date_values/rails/year_month_type.rb
30
24
  - lib/date_values/time_of_day.rb
31
25
  - lib/date_values/version.rb
32
26
  - lib/date_values/year_month.rb
33
27
  - sig/date_values.rbs
34
- - sig/date_values/rails.rbs
35
28
  homepage: https://github.com/ursm/date_values
36
29
  licenses:
37
30
  - MIT
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_model/validator'
4
-
5
- class DateValueValidator < ActiveModel::EachValidator
6
- def validate_each(record, attribute, value)
7
- raw = record.read_attribute_before_type_cast(attribute)
8
- return if raw.blank?
9
- return unless value.nil?
10
-
11
- record.errors.add(attribute, :invalid, **options)
12
- end
13
- end
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DateValues
4
- module Rails
5
- module I18nBackend
6
- TYPES = {
7
- DateValues::YearMonth => :year_month,
8
- DateValues::MonthDay => :month_day,
9
- DateValues::TimeOfDay => :time_of_day
10
- }.freeze
11
-
12
- def localize(locale, object, format = :default, options = EMPTY_HASH)
13
- type = TYPES[object.class]
14
- return super unless type
15
-
16
- format_key = format.is_a?(Symbol) ? format : nil
17
-
18
- if format_key
19
- entry = I18n.t("#{type}.formats.#{format_key}", locale: locale, default: nil)
20
- raise I18n::MissingTranslationData.new(locale, "#{type}.formats.#{format_key}") unless entry
21
-
22
- format = entry
23
- end
24
-
25
- object.strftime(format)
26
- end
27
- end
28
- end
29
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DateValues
4
- module Rails
5
- class MonthDayType < ActiveModel::Type::Value
6
- def type
7
- :month_day
8
- end
9
-
10
- def cast(value)
11
- case value
12
- when MonthDay then value
13
- when String then MonthDay.parse(value)
14
- when nil then nil
15
- end
16
- rescue ArgumentError
17
- nil
18
- end
19
-
20
- def serialize(value)
21
- value&.to_s
22
- end
23
-
24
- def deserialize(value)
25
- return nil if value.nil?
26
-
27
- MonthDay.parse(value)
28
- end
29
- end
30
- end
31
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DateValues
4
- module Rails
5
- class TimeOfDayType < ActiveModel::Type::Value
6
- def type
7
- :time_of_day
8
- end
9
-
10
- def cast(value)
11
- case value
12
- when TimeOfDay then value
13
- when Time then TimeOfDay.new(value.hour, value.min, value.sec)
14
- when String then TimeOfDay.parse(value)
15
- when nil then nil
16
- end
17
- rescue ArgumentError
18
- nil
19
- end
20
-
21
- def serialize(value)
22
- value&.to_s
23
- end
24
-
25
- def deserialize(value)
26
- case value
27
- when nil then nil
28
- when Time then TimeOfDay.new(value.hour, value.min, value.sec)
29
- when String then TimeOfDay.parse(value)
30
- end
31
- end
32
- end
33
- end
34
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DateValues
4
- module Rails
5
- class YearMonthType < ActiveModel::Type::Value
6
- def type
7
- :year_month
8
- end
9
-
10
- def cast(value)
11
- case value
12
- when YearMonth then value
13
- when String then YearMonth.parse(value)
14
- when nil then nil
15
- end
16
- rescue ArgumentError
17
- nil
18
- end
19
-
20
- def serialize(value)
21
- value&.to_s
22
- end
23
-
24
- def deserialize(value)
25
- return nil if value.nil?
26
-
27
- YearMonth.parse(value)
28
- end
29
- end
30
- end
31
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'date_values'
4
- require 'active_support'
5
- require 'active_model/type'
6
- require_relative 'rails/year_month_type'
7
- require_relative 'rails/month_day_type'
8
- require_relative 'rails/time_of_day_type'
9
- require_relative 'rails/i18n_backend'
10
- require_relative 'rails/date_value_validator'
11
-
12
- ActiveModel::Type.register(:year_month, DateValues::Rails::YearMonthType)
13
- ActiveModel::Type.register(:month_day, DateValues::Rails::MonthDayType)
14
- ActiveModel::Type.register(:time_of_day, DateValues::Rails::TimeOfDayType)
15
-
16
- ActiveSupport.on_load(:active_record) do
17
- ActiveRecord::Type.register(:year_month, DateValues::Rails::YearMonthType)
18
- ActiveRecord::Type.register(:month_day, DateValues::Rails::MonthDayType)
19
- ActiveRecord::Type.register(:time_of_day, DateValues::Rails::TimeOfDayType)
20
- end
21
-
22
- I18n::Backend::Base.prepend(DateValues::Rails::I18nBackend)
@@ -1,44 +0,0 @@
1
- module DateValues
2
- module Rails
3
- class YearMonthType < ActiveModel::Type::Value
4
- def type: () -> :year_month
5
-
6
- def cast: (YearMonth value) -> YearMonth
7
- | (String value) -> YearMonth
8
- | (nil value) -> nil
9
-
10
- def serialize: (YearMonth? value) -> String?
11
-
12
- def deserialize: (String value) -> YearMonth
13
- | (nil value) -> nil
14
- end
15
-
16
- class MonthDayType < ActiveModel::Type::Value
17
- def type: () -> :month_day
18
-
19
- def cast: (MonthDay value) -> MonthDay
20
- | (String value) -> MonthDay
21
- | (nil value) -> nil
22
-
23
- def serialize: (MonthDay? value) -> String?
24
-
25
- def deserialize: (String value) -> MonthDay
26
- | (nil value) -> nil
27
- end
28
-
29
- class TimeOfDayType < ActiveModel::Type::Value
30
- def type: () -> :time_of_day
31
-
32
- def cast: (TimeOfDay value) -> TimeOfDay
33
- | (Time value) -> TimeOfDay
34
- | (String value) -> TimeOfDay
35
- | (nil value) -> nil
36
-
37
- def serialize: (TimeOfDay? value) -> String?
38
-
39
- def deserialize: (Time value) -> TimeOfDay
40
- | (String value) -> TimeOfDay
41
- | (nil value) -> nil
42
- end
43
- end
44
- end