date_values 0.1.2 → 0.1.4
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/CHANGELOG.md +12 -0
- data/README.md +63 -12
- data/lib/date_values/month_day.rb +1 -1
- data/lib/date_values/rails/date_value_validator.rb +13 -0
- data/lib/date_values/rails/month_day_type.rb +2 -1
- data/lib/date_values/rails/time_of_day_type.rb +2 -1
- data/lib/date_values/rails/year_month_type.rb +2 -1
- data/lib/date_values/rails.rb +7 -0
- data/lib/date_values/time_of_day.rb +1 -1
- data/lib/date_values/version.rb +1 -1
- data/lib/date_values/year_month.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3ed7149400551e703761bd9ea324f55decb43cfe14e11d36d8e6e22f1fcb21a3
|
|
4
|
+
data.tar.gz: d809fce0b0215b62891a453b4e7e4278ad7a600ffea25cc045662f4a4ae1425b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 821ca618c27d3b17583c8420697ad11cbb3b9ee34dc44d7ea4d938ffd4500a4795b8aacd6b41df9864c982877d201c0dc616fecbca6342945c671b8b1f410679
|
|
7
|
+
data.tar.gz: f69de80391b67de78fafca31368c4f54683299c94740eba4047d0dff800ecb0647bcf208039473911fbf0fb60cbbb5595ef3c322d79154b72bc39e47056361a5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.1.4] - 2026-03-20
|
|
4
|
+
|
|
5
|
+
- Cast returns `nil` for invalid input instead of raising, following Rails convention
|
|
6
|
+
- Add `date_value` validator to distinguish "invalid input" from "no input"
|
|
7
|
+
- `#inspect` now uses `#<ClassName value>` format following Ruby convention
|
|
8
|
+
- Works with Rails standard validators (`comparison`, `inclusion`, `exclusion`)
|
|
9
|
+
|
|
10
|
+
## [0.1.3] - 2026-03-20
|
|
11
|
+
|
|
12
|
+
- Fix ActiveRecord type registration — use `ActiveSupport.on_load(:active_record)` to register types regardless of load order
|
|
13
|
+
- Values now work directly in ActiveRecord queries (`Shop.where(billing_month: YearMonth.new(2026, 3))`)
|
|
14
|
+
|
|
3
15
|
## [0.1.2] - 2026-03-19
|
|
4
16
|
|
|
5
17
|
- Add `#strftime` to `YearMonth`, `MonthDay`, and `TimeOfDay`
|
data/README.md
CHANGED
|
@@ -24,27 +24,33 @@ ym = YearMonth.new(2026, 3)
|
|
|
24
24
|
ym.to_s # => "2026-03"
|
|
25
25
|
ym.to_date # => #<Date: 2026-03-01>
|
|
26
26
|
|
|
27
|
-
YearMonth.from(Date.today) # => YearMonth
|
|
28
|
-
YearMonth.parse('2026-03') # => YearMonth
|
|
27
|
+
YearMonth.from(Date.today) # => #<DateValues::YearMonth 2026-03>
|
|
28
|
+
YearMonth.parse('2026-03') # => #<DateValues::YearMonth 2026-03>
|
|
29
29
|
|
|
30
|
-
ym + 1 # => YearMonth
|
|
31
|
-
ym - 1 # => YearMonth
|
|
30
|
+
ym + 1 # => #<DateValues::YearMonth 2026-04>
|
|
31
|
+
ym - 1 # => #<DateValues::YearMonth 2026-02>
|
|
32
32
|
YearMonth.new(2026, 3) - YearMonth.new(2025, 1) # => 14
|
|
33
33
|
|
|
34
34
|
# Range support
|
|
35
35
|
(YearMonth.new(2026, 1)..YearMonth.new(2026, 3)).to_a
|
|
36
|
-
# => [YearMonth
|
|
36
|
+
# => [#<DateValues::YearMonth 2026-01>, #<DateValues::YearMonth 2026-02>, #<DateValues::YearMonth 2026-03>]
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
### MonthDay
|
|
40
40
|
|
|
41
|
+
String representation uses ISO 8601 `--MM-DD` format (year omitted):
|
|
42
|
+
|
|
41
43
|
```ruby
|
|
42
44
|
md = MonthDay.new(3, 19)
|
|
43
45
|
md.to_s # => "--03-19"
|
|
44
46
|
md.to_date(2026) # => #<Date: 2026-03-19>
|
|
45
47
|
|
|
46
|
-
MonthDay.from(Date.today) # => MonthDay
|
|
47
|
-
MonthDay.parse('--03-19') # => MonthDay
|
|
48
|
+
MonthDay.from(Date.today) # => #<DateValues::MonthDay --03-20>
|
|
49
|
+
MonthDay.parse('--03-19') # => #<DateValues::MonthDay --03-19>
|
|
50
|
+
|
|
51
|
+
# Range membership
|
|
52
|
+
summer = MonthDay.new(6, 1)..MonthDay.new(8, 31)
|
|
53
|
+
summer.cover?(MonthDay.new(7, 15)) # => true
|
|
48
54
|
```
|
|
49
55
|
|
|
50
56
|
### TimeOfDay
|
|
@@ -55,8 +61,12 @@ tod.to_s # => "14:30"
|
|
|
55
61
|
|
|
56
62
|
TimeOfDay.new(14, 30, 45).to_s # => "14:30:45"
|
|
57
63
|
|
|
58
|
-
TimeOfDay.from(Time.now) # => TimeOfDay
|
|
59
|
-
TimeOfDay.parse('14:30') # => TimeOfDay
|
|
64
|
+
TimeOfDay.from(Time.now) # => #<DateValues::TimeOfDay 14:30>
|
|
65
|
+
TimeOfDay.parse('14:30') # => #<DateValues::TimeOfDay 14:30>
|
|
66
|
+
|
|
67
|
+
# Range membership
|
|
68
|
+
business_hours = TimeOfDay.new(9, 0)..TimeOfDay.new(17, 0)
|
|
69
|
+
business_hours.cover?(TimeOfDay.new(12, 30)) # => true
|
|
60
70
|
```
|
|
61
71
|
|
|
62
72
|
### Pattern Matching
|
|
@@ -94,6 +104,44 @@ class Shop < ApplicationRecord
|
|
|
94
104
|
end
|
|
95
105
|
```
|
|
96
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
|
+
|
|
97
145
|
### I18n / `l` Helper
|
|
98
146
|
|
|
99
147
|
All classes implement `#strftime`, and the Rails integration extends `I18n.l` to support them. Define formats in your locale files:
|
|
@@ -110,6 +158,7 @@ en:
|
|
|
110
158
|
time_of_day:
|
|
111
159
|
formats:
|
|
112
160
|
default: '%-I:%M %p'
|
|
161
|
+
long: '%-I:%M:%S %p'
|
|
113
162
|
```
|
|
114
163
|
|
|
115
164
|
```yaml
|
|
@@ -123,12 +172,14 @@ ja:
|
|
|
123
172
|
default: '%-m月%-d日'
|
|
124
173
|
time_of_day:
|
|
125
174
|
formats:
|
|
126
|
-
default: '%-H
|
|
175
|
+
default: '%-H時%-M分'
|
|
176
|
+
long: '%-H時%-M分%-S秒'
|
|
127
177
|
```
|
|
128
178
|
|
|
129
179
|
```ruby
|
|
130
|
-
I18n.l YearMonth.new(2026, 3), locale: :en
|
|
131
|
-
I18n.l YearMonth.new(2026, 3), locale: :ja
|
|
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"
|
|
132
183
|
```
|
|
133
184
|
|
|
134
185
|
## License
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
|
@@ -13,8 +13,9 @@ module DateValues
|
|
|
13
13
|
when Time then TimeOfDay.new(value.hour, value.min, value.sec)
|
|
14
14
|
when String then TimeOfDay.parse(value)
|
|
15
15
|
when nil then nil
|
|
16
|
-
else raise ArgumentError, "can't cast #{value.class} to TimeOfDay"
|
|
17
16
|
end
|
|
17
|
+
rescue ArgumentError
|
|
18
|
+
nil
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def serialize(value)
|
data/lib/date_values/rails.rb
CHANGED
|
@@ -7,9 +7,16 @@ require_relative 'rails/year_month_type'
|
|
|
7
7
|
require_relative 'rails/month_day_type'
|
|
8
8
|
require_relative 'rails/time_of_day_type'
|
|
9
9
|
require_relative 'rails/i18n_backend'
|
|
10
|
+
require_relative 'rails/date_value_validator'
|
|
10
11
|
|
|
11
12
|
ActiveModel::Type.register(:year_month, DateValues::Rails::YearMonthType)
|
|
12
13
|
ActiveModel::Type.register(:month_day, DateValues::Rails::MonthDayType)
|
|
13
14
|
ActiveModel::Type.register(:time_of_day, DateValues::Rails::TimeOfDayType)
|
|
14
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
|
+
|
|
15
22
|
I18n::Backend::Base.prepend(DateValues::Rails::I18nBackend)
|
data/lib/date_values/version.rb
CHANGED
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
|
+
version: 0.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Keita Urashima
|
|
@@ -22,6 +22,7 @@ files:
|
|
|
22
22
|
- lib/date_values.rb
|
|
23
23
|
- lib/date_values/month_day.rb
|
|
24
24
|
- lib/date_values/rails.rb
|
|
25
|
+
- lib/date_values/rails/date_value_validator.rb
|
|
25
26
|
- lib/date_values/rails/i18n_backend.rb
|
|
26
27
|
- lib/date_values/rails/month_day_type.rb
|
|
27
28
|
- lib/date_values/rails/time_of_day_type.rb
|