date_values 0.2.0 → 0.2.1

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: f294856b7f5149ded52770b4d43d003b5cac6e8ee533f81ec003ac04caf9988b
4
- data.tar.gz: 8c1f6046926d15cda1a01e55f89e2975b92e6cf515bbc21cb0b6892d816bf93b
3
+ metadata.gz: 2fcd39df5c13a6c209d893be43a2be980a20219c5e02c5cc1283a67ba84cf07e
4
+ data.tar.gz: 16ddf29780a17802e64bef957362fe4588fe4e1c9bd67af9a95798d4c65f1379
5
5
  SHA512:
6
- metadata.gz: 05e23c3cd91ddda9dcdf5ae63e81dd89c7a6bfd5150dab2893231ebe63c12bc64023cae0667c296e4e88db2dd5869ebdab39245370291cb29cbb9b2d906ad3e3
7
- data.tar.gz: 28e82e5dc12f33c6e8af8b546338636ef2f6cda3273a2957bf7f86dcb4bb4a6074a4e2376c1c0ed0600f19e98b2783661710c8882b066eb05d3d9c1aa359aea8
6
+ metadata.gz: b0611c9270e1237268eb2a1d17cca321455bebca402afc5ce4730922171364948498759deba7ba471474657aa2e445b066d7ab26b33e23318f6848bb95363e6b
7
+ data.tar.gz: 664a70b1c75f04341ba490823ae76073bc7d5618493497d7e64c513e3de8c198ae67b0c8ce84549fca3100608922fc79f8b63592180c529be468e45300082e1f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.1] - 2026-03-21
4
+
5
+ - Add `TimeOfDay` arithmetic (`+`, `-`), `advance`, `change`, `to_seconds`, `.from_seconds`
6
+ - Add `YearMonth#advance`, `YearMonth#change`, `YearMonth#days`
7
+ - Add `MonthDay#change`
8
+ - Add `#as_json` to all value classes
9
+ - Add `DateValues.config.month_day_order` for locale-dependent `MonthDay.parse`
10
+
3
11
  ## [0.2.0] - 2026-03-20
4
12
 
5
13
  - **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.
data/README.md CHANGED
@@ -23,6 +23,7 @@ include DateValues
23
23
  ym = YearMonth.new(2026, 3)
24
24
  ym.to_s # => "2026-03"
25
25
  ym.to_date # => #<Date: 2026-03-01>
26
+ ym.days # => 31
26
27
 
27
28
  YearMonth.from(Date.today) # => #<DateValues::YearMonth 2026-03>
28
29
  YearMonth.parse('2026-03') # => #<DateValues::YearMonth 2026-03>
@@ -32,6 +33,9 @@ ym + 1 # => #<DateValues::YearMonth 2026-04>
32
33
  ym - 1 # => #<DateValues::YearMonth 2026-02>
33
34
  YearMonth.new(2026, 3) - YearMonth.new(2025, 1) # => 14
34
35
 
36
+ ym.advance(years: 1, months: 2) # => #<DateValues::YearMonth 2027-05>
37
+ ym.change(year: 2025) # => #<DateValues::YearMonth 2025-03>
38
+
35
39
  # Range support
36
40
  (YearMonth.new(2026, 1)..YearMonth.new(2026, 3)).to_a
37
41
  # => [#<DateValues::YearMonth 2026-01>, #<DateValues::YearMonth 2026-02>, #<DateValues::YearMonth 2026-03>]
@@ -48,7 +52,9 @@ md.to_date(2026) # => #<Date: 2026-03-19>
48
52
 
49
53
  MonthDay.from(Date.today) # => #<DateValues::MonthDay --03-20>
50
54
  MonthDay.parse('--03-19') # => #<DateValues::MonthDay --03-19>
51
- MonthDay.parse('3/19') # also works
55
+ MonthDay.parse('3/19') # also works (month/day order by default)
56
+
57
+ md.change(month: 12) # => #<DateValues::MonthDay --12-19>
52
58
 
53
59
  # Range membership
54
60
  summer = MonthDay.new(6, 1)..MonthDay.new(8, 31)
@@ -66,6 +72,16 @@ TimeOfDay.new(14, 30, 45).to_s # => "14:30:45"
66
72
  TimeOfDay.from(Time.now) # => #<DateValues::TimeOfDay 14:30>
67
73
  TimeOfDay.parse('14:30') # => #<DateValues::TimeOfDay 14:30>
68
74
 
75
+ tod + 3600 # => #<DateValues::TimeOfDay 15:30>
76
+ tod - 1800 # => #<DateValues::TimeOfDay 14:00>
77
+ tod.advance(hours: 2, minutes: 15) # => #<DateValues::TimeOfDay 16:45>
78
+ tod.change(minute: 0) # => #<DateValues::TimeOfDay 14:00>
79
+ tod.to_seconds # => 52200
80
+ TimeOfDay.from_seconds(52200) # => #<DateValues::TimeOfDay 14:30>
81
+
82
+ # Wraps at 24h boundaries
83
+ TimeOfDay.new(23, 30) + 3600 # => #<DateValues::TimeOfDay 00:30>
84
+
69
85
  # Range membership
70
86
  business_hours = TimeOfDay.new(9, 0)..TimeOfDay.new(17, 0)
71
87
  business_hours.cover?(TimeOfDay.new(12, 30)) # => true
@@ -92,9 +108,33 @@ in { hour: (9..17) }
92
108
  end
93
109
  ```
94
110
 
111
+ ### JSON
112
+
113
+ All classes implement `#as_json`, returning the same string as `#to_s`:
114
+
115
+ ```ruby
116
+ YearMonth.new(2026, 3).as_json # => "2026-03"
117
+ MonthDay.new(3, 19).as_json # => "--03-19"
118
+ TimeOfDay.new(14, 30).as_json # => "14:30"
119
+ ```
120
+
121
+ ## Configuration
122
+
123
+ ### MonthDay parse order
124
+
125
+ By default, `MonthDay.parse` interprets ambiguous formats like `"3/19"` as month/day. For day/month (European convention):
126
+
127
+ ```ruby
128
+ DateValues.config.month_day_order = :day_first
129
+
130
+ MonthDay.parse('19/3') # => #<DateValues::MonthDay --03-19>
131
+ ```
132
+
133
+ ISO 8601 format (`--MM-DD`) is always month/day regardless of this setting.
134
+
95
135
  ## Rails Integration
96
136
 
97
- See [date_values-rails](https://github.com/ursm/date_values-rails) for ActiveModel/ActiveRecord type casting, validation, and I18n support.
137
+ See [date_values-rails](https://github.com/ursm/date_values-rails) for ActiveModel/ActiveRecord type casting, validation, I18n, and ActiveJob support.
98
138
 
99
139
  ## License
100
140
 
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DateValues
4
+ class Configuration
5
+ attr_accessor :month_day_order
6
+
7
+ def initialize
8
+ @month_day_order = :month_first
9
+ end
10
+ end
11
+
12
+ def self.config
13
+ @config ||= Configuration.new
14
+ end
15
+
16
+ def self.configure
17
+ yield config
18
+ end
19
+ end
@@ -19,9 +19,18 @@ module DateValues
19
19
 
20
20
  def self.parse(str)
21
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}"
22
+ when /\A--(\d{1,2})-(\d{1,2})\z/
23
+ new($1.to_i, $2.to_i)
24
+ when /\A(\d{1,2})[\/\-](\d{1,2})\z/
25
+ a, b = $1.to_i, $2.to_i
26
+
27
+ if DateValues.config.month_day_order == :day_first
28
+ new(b, a)
29
+ else
30
+ new(a, b)
31
+ end
32
+ else
33
+ raise ArgumentError, "invalid MonthDay: #{str}"
25
34
  end
26
35
  end
27
36
 
@@ -31,6 +40,10 @@ module DateValues
31
40
  [month, day] <=> [other.month, other.day]
32
41
  end
33
42
 
43
+ def change(month: self.month, day: self.day)
44
+ self.class.new(month, day)
45
+ end
46
+
34
47
  def strftime(format)
35
48
  # 2000 is a leap year, so Feb 29 works
36
49
  Date.new(2000, month, day).strftime(format)
@@ -40,6 +53,10 @@ module DateValues
40
53
  Date.new(year, month, day)
41
54
  end
42
55
 
56
+ def as_json(*)
57
+ to_s
58
+ end
59
+
43
60
  def to_s
44
61
  format('--%02d-%02d', month, day)
45
62
  end
@@ -29,10 +29,41 @@ module DateValues
29
29
  [hour, minute, second] <=> [other.hour, other.minute, other.second]
30
30
  end
31
31
 
32
+ def +(seconds)
33
+ self.class.from_seconds((to_seconds + seconds) % 86_400)
34
+ end
35
+
36
+ def -(seconds)
37
+ self + (-seconds)
38
+ end
39
+
40
+ def advance(hours: 0, minutes: 0, seconds: 0)
41
+ self + (hours * 3600 + minutes * 60 + seconds)
42
+ end
43
+
44
+ def change(hour: self.hour, minute: self.minute, second: self.second)
45
+ self.class.new(hour, minute, second)
46
+ end
47
+
48
+ def to_seconds
49
+ hour * 3600 + minute * 60 + second
50
+ end
51
+
52
+ def self.from_seconds(total)
53
+ total = total % 86_400
54
+ h, rest = total.divmod(3600)
55
+ m, s = rest.divmod(60)
56
+ new(h, m, s)
57
+ end
58
+
32
59
  def strftime(format)
33
60
  Time.new(2000, 1, 1, hour, minute, second).strftime(format)
34
61
  end
35
62
 
63
+ def as_json(*)
64
+ to_s
65
+ end
66
+
36
67
  def to_s
37
68
  if second.zero?
38
69
  format('%02d:%02d', hour, minute)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DateValues
4
- VERSION = '0.2.0'
4
+ VERSION = '0.2.1'
5
5
  end
@@ -49,6 +49,18 @@ module DateValues
49
49
  self + 1
50
50
  end
51
51
 
52
+ def advance(years: 0, months: 0)
53
+ self + (years * 12 + months)
54
+ end
55
+
56
+ def change(year: self.year, month: self.month)
57
+ self.class.new(year, month)
58
+ end
59
+
60
+ def days
61
+ Date.new(year, month, -1).day
62
+ end
63
+
52
64
  def strftime(format)
53
65
  to_date.strftime(format)
54
66
  end
@@ -57,6 +69,10 @@ module DateValues
57
69
  Date.new(year, month, 1)
58
70
  end
59
71
 
72
+ def as_json(*)
73
+ to_s
74
+ end
75
+
60
76
  def to_s
61
77
  format('%04d-%02d', year, month)
62
78
  end
data/lib/date_values.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'date_values/version'
4
+ require_relative 'date_values/configuration'
4
5
  require_relative 'date_values/year_month'
5
6
  require_relative 'date_values/month_day'
6
7
  require_relative 'date_values/time_of_day'
data/sig/date_values.rbs CHANGED
@@ -1,6 +1,15 @@
1
1
  module DateValues
2
2
  VERSION: String
3
3
 
4
+ def self.config: () -> Configuration
5
+ def self.configure: () { (Configuration) -> void } -> void
6
+
7
+ class Configuration
8
+ attr_accessor month_day_order: :month_first | :day_first
9
+
10
+ def initialize: () -> void
11
+ end
12
+
4
13
  class YearMonth
5
14
  include Comparable
6
15
 
@@ -23,10 +32,18 @@ module DateValues
23
32
 
24
33
  def succ: () -> YearMonth
25
34
 
35
+ def advance: (?years: Integer, ?months: Integer) -> YearMonth
36
+
37
+ def change: (?year: Integer, ?month: Integer) -> YearMonth
38
+
39
+ def days: () -> Integer
40
+
26
41
  def strftime: (String format) -> String
27
42
 
28
43
  def to_date: () -> Date
29
44
 
45
+ def as_json: (*untyped) -> String
46
+
30
47
  def to_s: () -> String
31
48
 
32
49
  def inspect: () -> String
@@ -47,10 +64,14 @@ module DateValues
47
64
  def <=>: (MonthDay other) -> Integer
48
65
  | (untyped other) -> nil
49
66
 
67
+ def change: (?month: Integer, ?day: Integer) -> MonthDay
68
+
50
69
  def strftime: (String format) -> String
51
70
 
52
71
  def to_date: (Integer year) -> Date
53
72
 
73
+ def as_json: (*untyped) -> String
74
+
54
75
  def to_s: () -> String
55
76
 
56
77
  def inspect: () -> String
@@ -71,13 +92,27 @@ module DateValues
71
92
 
72
93
  def self.from: (Time time) -> TimeOfDay
73
94
 
95
+ def self.from_seconds: (Integer total) -> TimeOfDay
96
+
74
97
  def self.parse: (String str) -> TimeOfDay
75
98
 
76
99
  def <=>: (TimeOfDay other) -> Integer
77
100
  | (untyped other) -> nil
78
101
 
102
+ def +: (Integer seconds) -> TimeOfDay
103
+
104
+ def -: (Integer seconds) -> TimeOfDay
105
+
106
+ def advance: (?hours: Integer, ?minutes: Integer, ?seconds: Integer) -> TimeOfDay
107
+
108
+ def change: (?hour: Integer, ?minute: Integer, ?second: Integer) -> TimeOfDay
109
+
110
+ def to_seconds: () -> Integer
111
+
79
112
  def strftime: (String format) -> String
80
113
 
114
+ def as_json: (*untyped) -> String
115
+
81
116
  def to_s: () -> String
82
117
 
83
118
  def inspect: () -> String
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.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keita Urashima
@@ -20,6 +20,7 @@ files:
20
20
  - README.md
21
21
  - Rakefile
22
22
  - lib/date_values.rb
23
+ - lib/date_values/configuration.rb
23
24
  - lib/date_values/month_day.rb
24
25
  - lib/date_values/time_of_day.rb
25
26
  - lib/date_values/version.rb