commercial 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1625934e30d3bbff95fac940c7a12766c5c43a665ef88280f5d0eab1998cadea
4
+ data.tar.gz: 98e5ee5230a3b0ad15feb98ab38b4c3d4c2a4dffd9c273acdc07482610032340
5
+ SHA512:
6
+ metadata.gz: 4975c5b58483ada85a2a7e07b0ce3462a8360b34ff39022c9c2c96071da89acbed29e1be028d33976492cd5a179615a3ce3b8c870b631f7677f4e0feae3e1424
7
+ data.tar.gz: 93105d5fee8884185aa7f2438ff43c956a4f46613fda199409f2864572228feeb6ce80733983bab6946f84910690de5d9ad802b339d17ab62097f9e1dcc37fd0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 William T. Nelson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # Commercial
2
+
3
+ Ruby gem for checking business days based on the United States [federal holiday](https://www.opm.gov/policy-data-oversight/pay-leave/federal-holidays/) schedule.
4
+
5
+ Dates from 1971 onwards are supported.
6
+
7
+ ## Usage
8
+
9
+ Check if a date is a commercial day (not a weekend or federal holiday):
10
+
11
+ ```ruby
12
+ require "commercial"
13
+
14
+ date = Date.new(2025, 7, 4) # Independence Day
15
+ Commercial::US.commercial?(date) # => false
16
+
17
+ date = Date.new(2025, 7, 5) # Saturday
18
+ Commercial::US.commercial?(date) # => false
19
+
20
+ date = Date.new(2025, 7, 7) # Monday
21
+ Commercial::US.commercial?(date) # => true
22
+
23
+ # Defaults to `Date.today`
24
+ Commercial::US.commercial? # => true/false
25
+ ```
26
+
27
+ ### Including the module
28
+
29
+ Include the module to call methods directly:
30
+
31
+ ```ruby
32
+ include Commercial::US
33
+ commercial?(Date.today) # => true/false
34
+ ```
35
+
36
+ Or in a more functional style:
37
+
38
+ ```ruby
39
+ class HolidayChecker
40
+ extend Commercial::US
41
+ end
42
+ HolidayChecker.commercial?(Date.today) # => true/false
43
+ ```
44
+
45
+ ### Checking holidays
46
+
47
+ You can also check for specific holidays or use the general `.federal_holiday?` method:
48
+
49
+ ```ruby
50
+ # Check if a date is a weekend
51
+ Commercial::US.weekend?(Date.new(2025, 7, 5)) # => true (Saturday)
52
+
53
+ # Check if a date is any federal holiday
54
+ Commercial::US.federal_holiday?(Date.new(2025, 7, 4)) # => true
55
+
56
+ # Check for specific holidays
57
+ Commercial::US.independence_day?(Date.new(2025, 7, 4)) # => true
58
+ Commercial::US.christmas_day?(Date.new(2025, 12, 25)) # => true
59
+ Commercial::US.thanksgiving_day?(Date.new(2025, 11, 27)) # => true
60
+ ```
61
+
62
+ ### Public methods
63
+
64
+ All methods accept an optional `Date` parameter, which defaults to `Date.today` if not specified:
65
+
66
+ - `.commercial?(date)` returns true if date is a business day (not weekend or holiday)
67
+ - `.weekend?(date)` returns true if date is Saturday or Sunday
68
+ - `.federal_holiday?(date)` returns true if date is any federal holiday
69
+ - `.new_years_day?(date)` January 1st (or observed)
70
+ - `.martin_luther_king_day?(date)` third Monday of January (observed since 1986)
71
+ - `.washingtons_birthday?(date)` third Monday of February
72
+ - `.memorial_day?(date)` last Monday of May
73
+ - `.juneteenth?(date)` June 19 (or observed, federal holiday since 2021)
74
+ - `.independence_day?(date)` July 4 (or observed)
75
+ - `.labor_day?(date)` first Monday of September
76
+ - `.columbus_day?(date)` second Monday of October
77
+ - `.thanksgiving_day?(date)` fourth Thursday of November
78
+ - `.christmas_day?(date)` December 25 (or observed)
79
+
80
+ ## Type signatures
81
+
82
+ This gem includes [RBS](https://github.com/ruby/rbs) type signatures in the `sig/` directory. These provide static type information for type checkers like [Steep](https://github.com/soutaro/steep) and enable better IDE support.
83
+
84
+ To validate the signatures:
85
+ ```bash
86
+ rbs -I sig -r date validate
87
+ ```
88
+
89
+ ## Development
90
+
91
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
92
+
93
+ ## Contributing
94
+
95
+ Bug reports and pull requests are welcome on GitHub at [github.com/wtn/commercial](https://github.com/wtn/commercial).
96
+
97
+ ## License
98
+
99
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ task default: :test
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module Commercial
6
+ module US
7
+ module_function
8
+
9
+ # Check if a given date is a commercial banking day (not a weekend or federal holiday)
10
+ # @param date [Date] the date to check
11
+ # @return [Boolean] true if the date is a commercial banking day
12
+ def commercial?(date = Date.today)
13
+ !weekend?(date) && !federal_holiday?(date)
14
+ end
15
+
16
+ # Check if date is a US federal holiday
17
+ # @param date [Date] the date to check
18
+ # @return [Boolean] true if the date is a federal holiday
19
+ def federal_holiday?(date)
20
+ date.year < 1971 and raise ArgumentError, "year #{date.year} not supported"
21
+ new_years_day?(date) \
22
+ || martin_luther_king_day?(date) \
23
+ || washingtons_birthday?(date) \
24
+ || memorial_day?(date) \
25
+ || juneteenth?(date) \
26
+ || independence_day?(date) \
27
+ || labor_day?(date) \
28
+ || columbus_day?(date) \
29
+ || thanksgiving_day?(date) \
30
+ || christmas_day?(date)
31
+ end
32
+
33
+ # New Year's Day (January 1st, or first Monday of January if on weekend)
34
+ # @param date [Date] the date to check
35
+ # @return [Boolean] true if the date is New Year's Day or its observance
36
+ def new_years_day?(date)
37
+ return true if date.month == 1 && date.day == 1 && !weekend?(date)
38
+
39
+ # If Jan 1 is on Saturday, observed on previous Friday (Dec 31)
40
+ # If Jan 1 is on Sunday, observed on next Monday (Jan 2)
41
+ if date.month == 1 && date.day == 2 && date.monday?
42
+ jan_1 = Date.new(date.year, 1, 1)
43
+ return jan_1.sunday?
44
+ end
45
+
46
+ # Check if this is Dec 31 and Jan 1 is Saturday
47
+ if date.month == 12 && date.day == 31 && date.friday?
48
+ jan_1_next_year = Date.new(date.year + 1, 1, 1)
49
+ return jan_1_next_year.saturday?
50
+ end
51
+
52
+ false
53
+ end
54
+
55
+ # Martin Luther King Day (third Monday of January)
56
+ # @param date [Date] the date to check
57
+ # @return [Boolean] true if the date is Martin Luther King Day
58
+ def martin_luther_king_day?(date)
59
+ date.year >= 1986 && date.month == 1 && date.monday? && nth_weekday_of_month?(date, 3)
60
+ end
61
+
62
+ # Washington's Birthday (third Monday of February)
63
+ # @param date [Date] the date to check
64
+ # @return [Boolean] true if the date is Washington's Birthday
65
+ def washingtons_birthday?(date)
66
+ date.month == 2 && date.monday? && nth_weekday_of_month?(date, 3)
67
+ end
68
+
69
+ # Memorial Day (last Monday of May)
70
+ # @param date [Date] the date to check
71
+ # @return [Boolean] true if the date is Memorial Day
72
+ def memorial_day?(date)
73
+ date.month == 5 && date.monday? && last_weekday_of_month?(date)
74
+ end
75
+
76
+ # Juneteenth (June 19, or nearest Monday or Friday if on a weekend)
77
+ # @param date [Date] the date to check
78
+ # @return [Boolean] true if the date is Juneteenth or its observance
79
+ def juneteenth?(date)
80
+ return false if date.year < 2021
81
+ return true if date.month == 6 && date.day == 19 && !weekend?(date)
82
+
83
+ # If June 19 is on Saturday, observed on Friday (June 18)
84
+ if date.month == 6 && date.day == 18 && date.friday?
85
+ june_19 = Date.new(date.year, 6, 19)
86
+ return june_19.saturday?
87
+ end
88
+
89
+ # If June 19 is on Sunday, observed on Monday (June 20)
90
+ if date.month == 6 && date.day == 20 && date.monday?
91
+ june_19 = Date.new(date.year, 6, 19)
92
+ return june_19.sunday?
93
+ end
94
+
95
+ false
96
+ end
97
+
98
+ # Independence Day (July 4, or July 3rd if on Saturday, July 5th if on Sunday)
99
+ # @param date [Date] the date to check
100
+ # @return [Boolean] true if the date is Independence Day or its observance
101
+ def independence_day?(date)
102
+ return true if date.month == 7 && date.day == 4 && !weekend?(date)
103
+
104
+ # If July 4 is on Saturday, observed on Friday (July 3)
105
+ if date.month == 7 && date.day == 3 && date.friday?
106
+ july_4 = Date.new(date.year, 7, 4)
107
+ return july_4.saturday?
108
+ end
109
+
110
+ # If July 4 is on Sunday, observed on Monday (July 5)
111
+ if date.month == 7 && date.day == 5 && date.monday?
112
+ july_4 = Date.new(date.year, 7, 4)
113
+ return july_4.sunday?
114
+ end
115
+
116
+ false
117
+ end
118
+
119
+ # Labor Day (First Monday of September)
120
+ # @param date [Date] the date to check
121
+ # @return [Boolean] true if the date is Labor Day
122
+ def labor_day?(date)
123
+ date.month == 9 && date.monday? && nth_weekday_of_month?(date, 1)
124
+ end
125
+
126
+ # Columbus Day (second Monday of October)
127
+ # @param date [Date] the date to check
128
+ # @return [Boolean] true if the date is Columbus Day
129
+ def columbus_day?(date)
130
+ date.month == 10 && date.monday? && nth_weekday_of_month?(date, 2)
131
+ end
132
+
133
+ # Thanksgiving Day (Fourth Thursday of November)
134
+ # @param date [Date] the date to check
135
+ # @return [Boolean] true if the date is Thanksgiving Day
136
+ def thanksgiving_day?(date)
137
+ date.month == 11 && date.thursday? && nth_weekday_of_month?(date, 4)
138
+ end
139
+
140
+ # Christmas Day (December 25th, or December 24th if on Saturday, December 26th if on Sunday)
141
+ # @param date [Date] the date to check
142
+ # @return [Boolean] true if the date is Christmas Day or its observance
143
+ def christmas_day?(date)
144
+ return true if date.month == 12 && date.day == 25 && !weekend?(date)
145
+
146
+ # If Dec 25 is on Saturday, observed on Friday (Dec 24)
147
+ if date.month == 12 && date.day == 24 && date.friday?
148
+ dec_25 = Date.new(date.year, 12, 25)
149
+ return dec_25.saturday?
150
+ end
151
+
152
+ # If Dec 25 is on Sunday, observed on Monday (Dec 26)
153
+ if date.month == 12 && date.day == 26 && date.monday?
154
+ dec_25 = Date.new(date.year, 12, 25)
155
+ return dec_25.sunday?
156
+ end
157
+
158
+ false
159
+ end
160
+
161
+ # Check if date is a weekend (Saturday or Sunday)
162
+ # @param date [Date] the date to check
163
+ # @return [Boolean] true if the date is a weekend
164
+ def weekend?(date)
165
+ date.saturday? || date.sunday?
166
+ end
167
+
168
+ class << self
169
+ private
170
+
171
+ # Check if date is the nth occurrence of its weekday in the month
172
+ # e.g., is this the 3rd Monday of the month?
173
+ def nth_weekday_of_month?(date, n)
174
+ # Calculate which occurrence this is
175
+ # Day 1-7 is 1st, 8-14 is 2nd, 15-21 is 3rd, 22-28 is 4th
176
+ occurrence = ((date.day - 1) / 7) + 1
177
+ occurrence == n
178
+ end
179
+
180
+ # Check if date is the last occurrence of its weekday in the month
181
+ def last_weekday_of_month?(date)
182
+ # Check if adding 7 days would put us in the next month
183
+ next_week = date + 7
184
+ next_week.month != date.month
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Commercial
4
+ VERSION = "0.1.0"
5
+ end
data/lib/commercial.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "commercial/version"
4
+ require_relative "commercial/us"
5
+
6
+ module Commercial
7
+ class Error < StandardError; end
8
+ end
@@ -0,0 +1,31 @@
1
+ module Commercial
2
+ VERSION: String
3
+
4
+ module US
5
+ def self.commercial?: (?Date date) -> bool
6
+
7
+ def self.weekend?: (?Date date) -> bool
8
+
9
+ def self.federal_holiday?: (?Date date) -> bool
10
+
11
+ def self.new_years_day?: (Date date) -> bool
12
+
13
+ def self.martin_luther_king_day?: (Date date) -> bool
14
+
15
+ def self.washingtons_birthday?: (Date date) -> bool
16
+
17
+ def self.memorial_day?: (Date date) -> bool
18
+
19
+ def self.juneteenth?: (Date date) -> bool
20
+
21
+ def self.independence_day?: (Date date) -> bool
22
+
23
+ def self.labor_day?: (Date date) -> bool
24
+
25
+ def self.columbus_day?: (Date date) -> bool
26
+
27
+ def self.thanksgiving_day?: (Date date) -> bool
28
+
29
+ def self.christmas_day?: (Date date) -> bool
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: commercial
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - William T. Nelson
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ email:
13
+ - 35801+wtn@users.noreply.github.com
14
+ executables: []
15
+ extensions: []
16
+ extra_rdoc_files: []
17
+ files:
18
+ - LICENSE.txt
19
+ - README.md
20
+ - Rakefile
21
+ - lib/commercial.rb
22
+ - lib/commercial/us.rb
23
+ - lib/commercial/version.rb
24
+ - sig/commercial.rbs
25
+ homepage: https://github.com/wtn/commercial
26
+ licenses:
27
+ - MIT
28
+ metadata:
29
+ allowed_push_host: https://rubygems.org
30
+ homepage_uri: https://github.com/wtn/commercial
31
+ source_code_uri: https://github.com/wtn/commercial
32
+ rubygems_mfa_required: 'true'
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 3.0.0
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubygems_version: 3.7.2
48
+ specification_version: 4
49
+ summary: US commercial dates
50
+ test_files: []