├── .gitignore
├── lib
├── black_friday
│ └── version.rb
└── black_friday.rb
├── .standard.yml
├── sig
└── black_friday.rbs
├── bin
├── setup
└── console
├── test
├── test_helper.rb
└── test_black_friday.rb
├── Rakefile
├── Gemfile
├── .github
├── dependabot.yml
├── workflows
│ └── main.yml
├── FUNDING.yml
├── pull_request_template.md
└── ISSUE_TEMPLATE
│ ├── config.yml
│ └── issue_template.md
├── CHANGELOG.md
├── LICENSE.txt
├── black_friday.gemspec
├── Gemfile.lock
├── README.md
└── CODE_OF_CONDUCT.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /_yardoc/
4 | /coverage/
5 | /doc/
6 | /pkg/
7 | /spec/reports/
8 | /tmp/
9 |
--------------------------------------------------------------------------------
/lib/black_friday/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module BlackFriday
4 | VERSION = "1.0.4"
5 | end
6 |
--------------------------------------------------------------------------------
/.standard.yml:
--------------------------------------------------------------------------------
1 | # For available configuration options, see:
2 | # https://github.com/standardrb/standard
3 | ruby_version: 3.0
4 |
--------------------------------------------------------------------------------
/sig/black_friday.rbs:
--------------------------------------------------------------------------------
1 | module BlackFriday
2 | VERSION: String
3 | # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4 | end
5 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 |
6 | bundle install
7 |
8 | # Do any other automated setup that you need to do here
9 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
4 | require "black_friday"
5 |
6 | require "minitest/autorun"
7 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "bundler/gem_tasks"
4 | require "minitest/test_task"
5 |
6 | Minitest::TestTask.create
7 |
8 | require "standard/rake"
9 |
10 | task default: %i[test standard]
11 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 |
5 | # Specify your gem's dependencies in black_friday.gemspec
6 | gemspec
7 |
8 | gem "rake", "~> 13.0"
9 |
10 | gem "minitest", "~> 5.16"
11 |
12 | gem "standard", "~> 1.3"
13 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: bundler
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 | - package-ecosystem: github-actions
9 | directory: "/"
10 | schedule:
11 | interval: daily
12 | open-pull-requests-limit: 10
13 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require "bundler/setup"
5 | require "black_friday"
6 |
7 | # You can add fixtures and/or initialization code here to make experimenting
8 | # with your gem easier. You can also use a different console, if you like.
9 |
10 | require "irb"
11 | IRB.start(__FILE__)
12 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [Unreleased]
2 |
3 | ## [1.0.4] - 2024-11-27
4 |
5 | - Fix `current_sales` to return an array of names, not a hash.
6 |
7 | ## [1.0.3] - 2024-11-22
8 |
9 | - Add Canadian Thanksgiving
10 | - Add `range_for(name)` to retrieve a range easily
11 |
12 | ## [1.0.2] - 2024-11-22
13 |
14 | - Add `BlackFriday.range_for(:name)` to retrieve a range
15 |
16 | ## [1.0.1] - 2024-11-22
17 |
18 | - Add support for `ActiveSupport::TimeWithZone` by switching to `cover?`
19 |
20 | ## [1.0.0] - 2024-11-22
21 |
22 | - Initial release
23 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Ruby
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | pull_request:
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | name: Ruby ${{ matrix.ruby }}
14 | strategy:
15 | matrix:
16 | ruby:
17 | - '3.3.6'
18 |
19 | steps:
20 | - uses: actions/checkout@v4
21 | - name: Set up Ruby
22 | uses: ruby/setup-ruby@v1
23 | with:
24 | ruby-version: ${{ matrix.ruby }}
25 | bundler-cache: true
26 | - name: Run the default task
27 | run: bundle exec rake
28 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [excid3] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Pull Request
2 |
3 | **Summary:**
4 |
5 |
6 | **Related Issue:**
7 |
8 |
9 | **Description:**
10 |
11 |
12 | **Testing:**
13 |
14 |
15 | **Screenshots (if applicable):**
16 |
17 |
18 | **Checklist:**
19 |
20 |
21 | - [ ] Code follows the project's coding standards
22 | - [ ] Tests have been added or updated to cover the changes
23 | - [ ] Documentation has been updated (if applicable)
24 | - [ ] All existing tests pass
25 | - [ ] Conforms to the contributing guidelines
26 |
27 | **Additional Notes:**
28 |
29 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2024 Chris Oliver
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.
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Get Help
4 | url: https://github.com/excid3/refer/discussions/new?category=help
5 | about: If you can't get something to work the way you expect, open a question in our discussion forums.
6 | - name: Feature Request
7 | url: https://github.com/excid3/refer/discussions/new?category=ideas
8 | about: 'Suggest any ideas you have using our discussion forums.'
9 | - name: Bug Report
10 | url: https://github.com/excid3/refer/issues/new?body=%3C%21--%20Please%20provide%20all%20of%20the%20information%20requested%20below.%20We%27re%20a%20small%20team%20and%20without%20all%20of%20this%20information%20it%27s%20not%20possible%20for%20us%20to%20help%20and%20your%20bug%20report%20will%20be%20closed.%20--%3E%0A%0A%2A%2AWhat%20version%20of%20Noticed%20are%20you%20using%3F%2A%2A%0A%0AFor%20example%3A%20v2.0.4%0A%0A%2A%2AWhat%20version%20of%20Rails%20are%20you%20using%3F%2A%2A%0A%0AFor%20example%3A%20v7.1.1%0A%0A%2A%2ADescribe%20your%20issue%2A%2A%0A%0ADescribe%20the%20problem%20you%27re%20seeing%2C%20any%20important%20steps%20to%20reproduce%20and%20what%20behavior%20you%20expect%20instead.
11 | about: If you've already asked for help with a problem and confirmed something is broken with Noticed itself, create a bug report.
12 |
--------------------------------------------------------------------------------
/black_friday.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative "lib/black_friday/version"
4 |
5 | Gem::Specification.new do |spec|
6 | spec.name = "black_friday"
7 | spec.version = BlackFriday::VERSION
8 | spec.authors = ["Chris Oliver"]
9 | spec.email = ["excid3@gmail.com"]
10 |
11 | spec.summary = "Black Friday sales in your Rails apps"
12 | spec.description = "Black Friday sales in your Rails apps"
13 | spec.homepage = "https://github.com/excid3/black_friday"
14 | spec.license = "MIT"
15 | spec.required_ruby_version = ">= 3.0.0"
16 |
17 | spec.metadata["homepage_uri"] = spec.homepage
18 | spec.metadata["source_code_uri"] = spec.homepage
19 | spec.metadata["changelog_uri"] = spec.homepage + "/blob/main/CHANGELOG.md"
20 |
21 | # Specify which files should be added to the gem when it is released.
22 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23 | gemspec = File.basename(__FILE__)
24 | spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls|
25 | ls.readlines("\x0", chomp: true).reject do |f|
26 | (f == gemspec) ||
27 | f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
28 | end
29 | end
30 | spec.bindir = "exe"
31 | spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32 | spec.require_paths = ["lib"]
33 |
34 | spec.add_dependency "activesupport"
35 | end
36 |
--------------------------------------------------------------------------------
/lib/black_friday.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative "black_friday/version"
4 | require "active_support/isolated_execution_state"
5 | require "active_support/core_ext/array/wrap"
6 | require "active_support/core_ext/numeric/time"
7 |
8 | module BlackFriday
9 | extend self
10 |
11 | class Error < StandardError; end
12 |
13 | mattr_accessor :sales, default: {}
14 |
15 | def add_sale(name = :black_friday, &block)
16 | sales[name] = block
17 | end
18 |
19 | def active?(*sale_names)
20 | blocks = sale_names.empty? ? sales.values : sales.values_at(*sale_names).compact
21 | Array.wrap(blocks).any? do
22 | in_range? instance_eval(&_1)
23 | end
24 | end
25 |
26 | def current_sales
27 | sales.filter_map do |name, block|
28 | name if in_range? instance_eval(&block)
29 | end
30 | end
31 |
32 | def current_sale
33 | current_sales.first
34 | end
35 |
36 | def in_range?(range)
37 | range.cover?(range.first.is_a?(Date) ? Date.today : Time.current)
38 | end
39 |
40 | def range_for(sale_name)
41 | instance_eval(&sales.fetch(sale_name))
42 | end
43 |
44 | # Date helpers
45 |
46 | def thanksgiving(year = Date.today.year)
47 | nov_1st = Date.new(year, 11, 1)
48 | first_thursday = nov_1st.thursday? ? nov_1st : nov_1st.next_occurring(:thursday)
49 | first_thursday + 3.weeks
50 | end
51 |
52 | def canadian_thanksgiving(year = Date.today.year)
53 | oct_1st = Date.new(year, 10, 1)
54 | first_monday = oct_1st.monday? ? oct_1st : oct_1st.next_occurring(:monday)
55 | first_monday + 1.weeks
56 | end
57 |
58 | def black_friday(year = Date.today.year)
59 | thanksgiving(year) + 1.day
60 | end
61 |
62 | def cyber_monday(year = Date.today.year)
63 | thanksgiving(year) + 4.days
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_template.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🐞 Bug
3 | about: File a bug/issue
4 | title: '[BUG]
'
5 | labels: Bug, Needs Triage
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Bug Report
11 |
12 | **Describe the Bug:**
13 |
14 |
15 | **To Reproduce:**
16 |
17 |
18 | 1. Step 1
19 | 2. Step 2
20 | 3. ...
21 |
22 | **Expected Behavior:**
23 |
24 |
25 | **Actual Behavior:**
26 |
27 |
28 | **Screenshots (if applicable):**
29 |
30 |
31 | **Environment:**
32 | - Gem version:
33 | - Ruby version:
34 | - Rails version:
35 | - Operating System:
36 |
37 | **Additional Context:**
38 |
39 |
40 | **Possible Fix:**
41 |
42 |
43 | **Steps to Reproduce with Fix (if available):**
44 |
45 |
46 | **Related Issues:**
47 |
48 |
49 | **Labels to Apply:**
50 |
51 |
52 | **Checklist:**
53 |
54 |
55 | - [ ] I have searched for similar issues and couldn't find any
56 | - [ ] I have checked the documentation for relevant information
57 | - [ ] I have included all the required information
58 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | black_friday (1.0.4)
5 | activesupport
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | activesupport (8.1.1)
11 | base64
12 | bigdecimal
13 | concurrent-ruby (~> 1.0, >= 1.3.1)
14 | connection_pool (>= 2.2.5)
15 | drb
16 | i18n (>= 1.6, < 2)
17 | json
18 | logger (>= 1.4.2)
19 | minitest (>= 5.1)
20 | securerandom (>= 0.3)
21 | tzinfo (~> 2.0, >= 2.0.5)
22 | uri (>= 0.13.1)
23 | ast (2.4.3)
24 | base64 (0.3.0)
25 | bigdecimal (3.3.1)
26 | concurrent-ruby (1.3.5)
27 | connection_pool (2.5.5)
28 | drb (2.2.3)
29 | i18n (1.14.7)
30 | concurrent-ruby (~> 1.0)
31 | json (2.16.0)
32 | language_server-protocol (3.17.0.5)
33 | lint_roller (1.1.0)
34 | logger (1.7.0)
35 | minitest (5.26.2)
36 | parallel (1.27.0)
37 | parser (3.3.10.0)
38 | ast (~> 2.4.1)
39 | racc
40 | prism (1.6.0)
41 | racc (1.8.1)
42 | rainbow (3.1.1)
43 | rake (13.3.1)
44 | regexp_parser (2.11.3)
45 | rubocop (1.81.7)
46 | json (~> 2.3)
47 | language_server-protocol (~> 3.17.0.2)
48 | lint_roller (~> 1.1.0)
49 | parallel (~> 1.10)
50 | parser (>= 3.3.0.2)
51 | rainbow (>= 2.2.2, < 4.0)
52 | regexp_parser (>= 2.9.3, < 3.0)
53 | rubocop-ast (>= 1.47.1, < 2.0)
54 | ruby-progressbar (~> 1.7)
55 | unicode-display_width (>= 2.4.0, < 4.0)
56 | rubocop-ast (1.48.0)
57 | parser (>= 3.3.7.2)
58 | prism (~> 1.4)
59 | rubocop-performance (1.25.0)
60 | lint_roller (~> 1.1)
61 | rubocop (>= 1.75.0, < 2.0)
62 | rubocop-ast (>= 1.38.0, < 2.0)
63 | ruby-progressbar (1.13.0)
64 | securerandom (0.4.1)
65 | standard (1.52.0)
66 | language_server-protocol (~> 3.17.0.2)
67 | lint_roller (~> 1.0)
68 | rubocop (~> 1.81.7)
69 | standard-custom (~> 1.0.0)
70 | standard-performance (~> 1.8)
71 | standard-custom (1.0.2)
72 | lint_roller (~> 1.0)
73 | rubocop (~> 1.50)
74 | standard-performance (1.8.0)
75 | lint_roller (~> 1.1)
76 | rubocop-performance (~> 1.25.0)
77 | tzinfo (2.0.6)
78 | concurrent-ruby (~> 1.0)
79 | unicode-display_width (3.2.0)
80 | unicode-emoji (~> 4.1)
81 | unicode-emoji (4.1.0)
82 | uri (1.1.1)
83 |
84 | PLATFORMS
85 | arm64-darwin-24
86 | ruby
87 |
88 | DEPENDENCIES
89 | black_friday!
90 | minitest (~> 5.16)
91 | rake (~> 13.0)
92 | standard (~> 1.3)
93 |
94 | BUNDLED WITH
95 | 2.7.2
96 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BlackFriday
2 |
3 | Add Black Friday sales to your Rails app.
4 |
5 | [](https://github.com/excid3/black_friday/actions/workflows/main.yml)
6 |
7 | ## Installation
8 |
9 | Install the gem and add to the application's Gemfile by executing:
10 |
11 | ```bash
12 | bundle add black_friday
13 | ```
14 |
15 | ## Usage
16 |
17 | ### Date helpers
18 |
19 | Black Friday provides some helpers for dates. You can also pass in the year which can be helpful for making reports for previous years.
20 |
21 | ```ruby
22 | BlackFriday.thanksgiving #=> Thu, 28 Nov 2024
23 | BlackFriday.black_friday #=> Fri, 29 Nov 2024
24 | BlackFriday.cyber_monday #=> Mon, 2 Dec 2024
25 |
26 | BlackFriday.thanksgiving(2029) #=> Thu, 22 Nov 2029
27 | ```
28 |
29 | ### Adding Sales
30 |
31 | Sales are handy to set the date/time range for a sale. The `add_sale` block should return a `Range`.
32 |
33 | ```ruby
34 | BlackFriday.add_sale do
35 | thanksgiving.monday.beginning_of_day..cyber_monday.end_of_day
36 | end
37 |
38 | BlackFriday.add_sale :labor_day do
39 | # First Monday in September
40 | sep_1st = Date.new(Date.today.year, 9, 1)
41 | labor_day = sep_1st.monday? ? sep_1st : sep_1st.next_occurring(:monday)
42 | start_day = labor_day - 3.days
43 |
44 | start_day.beginning_of_day..labor_day.end_of_day
45 | end
46 | ```
47 |
48 | You can then check if a sale is active:
49 |
50 | ```ruby
51 | BlackFriday.active?
52 | #=> true/false
53 |
54 | # Or specific sale(s)
55 | BlackFriday.active?(:black_friday, :labor_day)
56 | #=> true/false
57 | ```
58 |
59 | Render a banner when a sale is active:
60 |
61 | ```erb
62 | <%= render "banners/black_friday" if BlackFriday.active?(:black_friday) %>
63 | ```
64 |
65 | To check the active sales:
66 |
67 | ```ruby
68 | BlackFriday.current_sale #=> :black_friday
69 |
70 | # For multiple sales at once
71 | BlackFriday.current_sales #=> [:black_friday]
72 | ```
73 |
74 | ## Development
75 |
76 | 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.
77 |
78 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
79 |
80 | ## Contributing
81 |
82 | Bug reports and pull requests are welcome on GitHub at https://github.com/excid3/black_friday. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/excid3/black_friday/blob/main/CODE_OF_CONDUCT.md).
83 |
84 | ## License
85 |
86 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
87 |
88 | ## Code of Conduct
89 |
90 | Everyone interacting in the BlackFriday project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/excid3/black_friday/blob/main/CODE_OF_CONDUCT.md).
91 |
--------------------------------------------------------------------------------
/test/test_black_friday.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "test_helper"
4 |
5 | class TestBlackFriday < Minitest::Test
6 | def setup
7 | BlackFriday.sales.clear
8 | end
9 |
10 | def test_that_it_has_a_version_number
11 | refute_nil ::BlackFriday::VERSION
12 | end
13 |
14 | def test_thanksgiving
15 | assert_equal Date.new(2024, 11, 28), BlackFriday.thanksgiving(2024)
16 | assert_equal Date.new(2025, 11, 27), BlackFriday.thanksgiving(2025)
17 | assert_equal Date.new(2026, 11, 26), BlackFriday.thanksgiving(2026)
18 | assert_equal Date.new(2029, 11, 22), BlackFriday.thanksgiving(2029)
19 | end
20 |
21 | def test_canadian_thanksgiving
22 | assert_equal Date.new(2024, 10, 14), BlackFriday.canadian_thanksgiving(2024)
23 | assert_equal Date.new(2025, 10, 13), BlackFriday.canadian_thanksgiving(2025)
24 | assert_equal Date.new(2026, 10, 12), BlackFriday.canadian_thanksgiving(2026)
25 | assert_equal Date.new(2029, 10, 8), BlackFriday.canadian_thanksgiving(2029)
26 | end
27 |
28 | def test_black_friday
29 | assert_equal Date.new(2024, 11, 29), BlackFriday.black_friday(2024)
30 | assert_equal Date.new(2025, 11, 28), BlackFriday.black_friday(2025)
31 | assert_equal Date.new(2026, 11, 27), BlackFriday.black_friday(2026)
32 | assert_equal Date.new(2029, 11, 23), BlackFriday.black_friday(2029)
33 | end
34 |
35 | def test_cyber_monday
36 | assert_equal Date.new(2024, 12, 2), BlackFriday.cyber_monday(2024)
37 | assert_equal Date.new(2025, 12, 1), BlackFriday.cyber_monday(2025)
38 | assert_equal Date.new(2026, 11, 30), BlackFriday.cyber_monday(2026)
39 | assert_equal Date.new(2029, 11, 26), BlackFriday.cyber_monday(2029)
40 | end
41 |
42 | def test_add_sale
43 | block = -> {}
44 | BlackFriday.add_sale(&block)
45 | assert_equal block, BlackFriday.sales[:black_friday]
46 | end
47 |
48 | def test_active_sale
49 | BlackFriday.add_sale { Date.yesterday..Date.tomorrow }
50 | assert BlackFriday.active?
51 | end
52 |
53 | def test_active_sale_by_name
54 | BlackFriday.add_sale { Date.yesterday..Date.tomorrow }
55 | assert BlackFriday.active?(:black_friday)
56 | refute BlackFriday.active?(:invalid)
57 | end
58 |
59 | def test_inactive_sale
60 | BlackFriday.add_sale { Date.yesterday..Date.yesterday }
61 | refute BlackFriday.active?
62 | refute BlackFriday.active?(:black_friday)
63 | refute BlackFriday.active?(:invalid)
64 | end
65 |
66 | def test_current_sales
67 | BlackFriday.add_sale { Date.yesterday..Date.tomorrow }
68 | BlackFriday.add_sale(:extra) { Date.yesterday..Date.tomorrow }
69 | BlackFriday.add_sale(:tomorrow) { Date.tomorrow..Date.tomorrow }
70 | assert_equal [:black_friday, :extra], BlackFriday.current_sales
71 | end
72 |
73 | def test_current_sale
74 | BlackFriday.add_sale { Date.yesterday..Date.tomorrow }
75 | assert_equal :black_friday, BlackFriday.current_sale
76 | end
77 |
78 | def test_in_range_with_time_with_zone
79 | Time.zone = "Central Time (US & Canada)"
80 | BlackFriday.add_sale { Time.current.yesterday..Time.current.tomorrow }
81 | assert BlackFriday.active?
82 | ensure
83 | Time.zone = nil
84 | end
85 |
86 | def test_range_for
87 | range = Date.yesterday..Date.tomorrow
88 | BlackFriday.add_sale { range }
89 | assert_equal range, BlackFriday.range_for(:black_friday)
90 | end
91 | end
92 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, caste, color, religion, or sexual
10 | identity and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the overall
26 | community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or advances of
31 | any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email address,
35 | without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official email address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | [INSERT CONTACT METHOD].
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series of
86 | actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or permanent
93 | ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within the
113 | community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.1, available at
119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120 |
121 | Community Impact Guidelines were inspired by
122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123 |
124 | For answers to common questions about this code of conduct, see the FAQ at
125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126 | [https://www.contributor-covenant.org/translations][translations].
127 |
128 | [homepage]: https://www.contributor-covenant.org
129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130 | [Mozilla CoC]: https://github.com/mozilla/diversity
131 | [FAQ]: https://www.contributor-covenant.org/faq
132 | [translations]: https://www.contributor-covenant.org/translations
133 |
--------------------------------------------------------------------------------