enumdate 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 +7 -0
- data/.github/workflows/main.yml +16 -0
- data/.gitignore +65 -0
- data/.rubocop.yml +32 -0
- data/.solargraph.yml +21 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +21 -0
- data/README.org +147 -0
- data/Rakefile +21 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/enumdate.gemspec +28 -0
- data/lib/enumdate.rb +85 -0
- data/lib/enumdate/date_enumerator.rb +272 -0
- data/lib/enumdate/date_frame.rb +193 -0
- data/lib/enumdate/date_helper.rb +50 -0
- data/lib/enumdate/enum_merger.rb +57 -0
- data/lib/enumdate/version.rb +5 -0
- metadata +62 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c7941bd64bd2ffee84449e892558f43592866010fcd92d8809be7df94ec80f3a
|
4
|
+
data.tar.gz: f9ae67f04f0e5134700c4ad535d69f94f7e685607bb3fa8d9e69946c6610104b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0a2c4d506c93db39dd5e56ab5a562294d747e47977133191fe9682879dd1cfafb146214812a698da2837443e0a0aa32805e25c1991ebb0881b825eb596fc5929
|
7
|
+
data.tar.gz: eb6686a639cd867b90a938425abdb90f2465b2dc1fdefe79fb3d20a9ca1e66f477056a3a0b85d469e1e12794d94eec4771e63fcd781cb6e312d6169fd8cb8648
|
@@ -0,0 +1,16 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: [push,pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
steps:
|
9
|
+
- uses: actions/checkout@v2
|
10
|
+
- name: Set up Ruby
|
11
|
+
uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
ruby-version: 3.0.2
|
14
|
+
bundler-cache: true
|
15
|
+
- name: Run the default task
|
16
|
+
run: bundle exec rake
|
data/.gitignore
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
################################################################
|
2
|
+
# Added from https://raw.github.com/github/gitignore/master/Ruby.gitignore
|
3
|
+
#
|
4
|
+
|
5
|
+
*.gem
|
6
|
+
*.rbc
|
7
|
+
/.config
|
8
|
+
/coverage/
|
9
|
+
/InstalledFiles
|
10
|
+
/pkg/
|
11
|
+
/spec/reports/
|
12
|
+
/spec/examples.txt
|
13
|
+
/test/tmp/
|
14
|
+
/test/version_tmp/
|
15
|
+
/tmp/
|
16
|
+
|
17
|
+
# Used by dotenv library to load environment variables.
|
18
|
+
# .env
|
19
|
+
|
20
|
+
# Ignore Byebug command history file.
|
21
|
+
.byebug_history
|
22
|
+
|
23
|
+
## Specific to RubyMotion:
|
24
|
+
.dat*
|
25
|
+
.repl_history
|
26
|
+
build/
|
27
|
+
*.bridgesupport
|
28
|
+
build-iPhoneOS/
|
29
|
+
build-iPhoneSimulator/
|
30
|
+
|
31
|
+
## Specific to RubyMotion (use of CocoaPods):
|
32
|
+
#
|
33
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
34
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
35
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
36
|
+
#
|
37
|
+
# vendor/Pods/
|
38
|
+
|
39
|
+
## Documentation cache and generated files:
|
40
|
+
/.yardoc/
|
41
|
+
/_yardoc/
|
42
|
+
/doc/
|
43
|
+
/rdoc/
|
44
|
+
|
45
|
+
## Environment normalization:
|
46
|
+
/.bundle/
|
47
|
+
/vendor/bundle
|
48
|
+
/lib/bundler/man/
|
49
|
+
|
50
|
+
# for a library or gem, you might want to ignore these files since the code is
|
51
|
+
# intended to run in multiple environments; otherwise, check them in:
|
52
|
+
Gemfile.lock
|
53
|
+
.ruby-version
|
54
|
+
.ruby-gemset
|
55
|
+
|
56
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
57
|
+
.rvmrc
|
58
|
+
|
59
|
+
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
|
60
|
+
.rubocop-https?--*
|
61
|
+
|
62
|
+
################################################################
|
63
|
+
# Site-locals
|
64
|
+
|
65
|
+
attic/
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.5
|
3
|
+
NewCops: enable
|
4
|
+
|
5
|
+
Layout/LineLength:
|
6
|
+
Max: 120
|
7
|
+
|
8
|
+
Layout/MultilineMethodCallIndentation:
|
9
|
+
EnforcedStyle: indented_relative_to_receiver
|
10
|
+
|
11
|
+
Layout/SpaceInsideBlockBraces:
|
12
|
+
SpaceBeforeBlockParameters: false
|
13
|
+
|
14
|
+
Metrics/AbcSize:
|
15
|
+
Max: 25
|
16
|
+
|
17
|
+
Metrics/ParameterLists:
|
18
|
+
CountKeywordArgs: false
|
19
|
+
|
20
|
+
Style/Alias:
|
21
|
+
Enabled: true
|
22
|
+
EnforcedStyle: prefer_alias_method
|
23
|
+
|
24
|
+
Style/FormatStringToken:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
Style/ParallelAssignment:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
Style/StringLiterals:
|
31
|
+
Enabled: true
|
32
|
+
EnforcedStyle: double_quotes
|
data/.solargraph.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
---
|
2
|
+
include:
|
3
|
+
- "**/*.rb"
|
4
|
+
exclude:
|
5
|
+
- spec/**/*
|
6
|
+
- test/**/*
|
7
|
+
- vendor/**/*
|
8
|
+
- ".bundle/**/*"
|
9
|
+
require: []
|
10
|
+
domains: []
|
11
|
+
reporters:
|
12
|
+
- rubocop
|
13
|
+
formatter:
|
14
|
+
rubocop:
|
15
|
+
cops: safe
|
16
|
+
except: []
|
17
|
+
only: []
|
18
|
+
extra_args: []
|
19
|
+
require_paths: []
|
20
|
+
plugins: []
|
21
|
+
max_files: 5000
|
data/Gemfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in enumdate.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem "rake", "~> 13.0"
|
9
|
+
|
10
|
+
gem "minitest", "~> 5.0"
|
11
|
+
|
12
|
+
gem "rubocop", "~> 1.7"
|
13
|
+
|
14
|
+
gem "solargraph"
|
15
|
+
|
16
|
+
gem "org-ruby"
|
17
|
+
gem "yard"
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 Yoshinari Nomura
|
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.org
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
#+TITLE: enumdate -- a small enumerator library to expand recurring dates
|
2
|
+
#+AUTHOR: Yoshinari Nomura
|
3
|
+
#+EMAIL:
|
4
|
+
#+DATE: 2021-08-21
|
5
|
+
#+OPTIONS: H:3 num:2 toc:nil
|
6
|
+
#+OPTIONS: ^:nil @:t \n:nil ::t |:t f:t TeX:t
|
7
|
+
#+OPTIONS: skip:nil
|
8
|
+
#+OPTIONS: author:t
|
9
|
+
#+OPTIONS: email:nil
|
10
|
+
#+OPTIONS: creator:nil
|
11
|
+
#+OPTIONS: timestamp:nil
|
12
|
+
#+OPTIONS: timestamps:nil
|
13
|
+
#+OPTIONS: d:nil
|
14
|
+
#+OPTIONS: tags:t
|
15
|
+
#+LANGUAGE: ja
|
16
|
+
|
17
|
+
[[https://badge.fury.io/rb/enumdate.svg]]
|
18
|
+
[[https://github.com/yoshinari-nomura/enumdate/actions/workflows/main.yml/badge.svg]]
|
19
|
+
|
20
|
+
** Description
|
21
|
+
Enumdate is a small enumerator library to expand recurring dates.
|
22
|
+
|
23
|
+
You can get the latest version from:
|
24
|
+
+ https://github.com/yoshinari-nomura/enumdate
|
25
|
+
|
26
|
+
** How to use
|
27
|
+
*** Create enumerables
|
28
|
+
Thease a simple example to create enumerables:
|
29
|
+
#+begin_src ruby
|
30
|
+
# June 2021
|
31
|
+
# Su Mo Tu We Th Fr Sa
|
32
|
+
# 1 2 3 4 5
|
33
|
+
# 6 7 8 9 10 11 12
|
34
|
+
# 13 14 15 16 17 18 19
|
35
|
+
# 20 21 22 23 24 25 26
|
36
|
+
# 27 28 29 30
|
37
|
+
#
|
38
|
+
start = Date.new(2021, 6, 1) # first Tuesday June
|
39
|
+
|
40
|
+
# June 1st every year:
|
41
|
+
Enumdate.yearly_by_monthday(start).lazy.map(&:to_s).take(3).force
|
42
|
+
# => ["2021-06-01", "2022-06-01", "2023-06-01"]
|
43
|
+
|
44
|
+
# First Tuesday June every year:
|
45
|
+
Enumdate.yearly_by_day(start).lazy.map(&:to_s).take(3).force
|
46
|
+
# => ["2021-06-01", "2022-06-07", "2023-06-06"]
|
47
|
+
|
48
|
+
# 1st monthday every month:
|
49
|
+
Enumdate.monthly_by_monthday(start).lazy.map(&:to_s).take(3).force
|
50
|
+
# => ["2021-06-01", "2021-07-01", "2021-08-01"]
|
51
|
+
|
52
|
+
# First Tuesday every month:
|
53
|
+
Enumdate.monthly_by_day(start).lazy.map(&:to_s).take(3).force
|
54
|
+
# => ["2021-06-01", "2021-07-06", "2021-08-03"]
|
55
|
+
|
56
|
+
# every Tuesday:
|
57
|
+
Enumdate.weekly(start).lazy.map(&:to_s).take(3).force
|
58
|
+
# => ["2021-06-01", "2021-06-08", "2021-06-15"]
|
59
|
+
|
60
|
+
# Everyday:
|
61
|
+
Enumdate.daily(start).lazy.map(&:to_s).take(3).force
|
62
|
+
# => ["2021-06-01", "2021-06-02", "2021-06-03"]
|
63
|
+
#+end_src
|
64
|
+
|
65
|
+
These constructor methods can take more complex parameters
|
66
|
+
such as ~month:~, ~mday:~, ~wday:~, ~nth:~, ~wkst:~, ~interval:~.
|
67
|
+
See the gem document for details.
|
68
|
+
|
69
|
+
*** Make finit durations
|
70
|
+
This code makes infinit loop (every two years forever):
|
71
|
+
#+begin_src ruby
|
72
|
+
start = Date.new(2021, 6, 1)
|
73
|
+
Enumdate.yearly_by_monthday(start, interval: 2).each do |date|
|
74
|
+
puts date
|
75
|
+
end
|
76
|
+
#+end_src
|
77
|
+
Results:
|
78
|
+
: 2021-06-01
|
79
|
+
: 2023-06-01
|
80
|
+
: 2025-06-01
|
81
|
+
: :
|
82
|
+
|
83
|
+
To clip the duration, you can use ~forward_to~ and ~until~:
|
84
|
+
#+begin_src ruby
|
85
|
+
start = Date.new(2021, 6, 1)
|
86
|
+
Enumdate.yearly_by_monthday(start, interval: 2)
|
87
|
+
.forward_to(Date.new(2022, 1, 1))
|
88
|
+
.until(Date.new(2025, 12, 31))
|
89
|
+
.each do |date|
|
90
|
+
puts date
|
91
|
+
end
|
92
|
+
#+end_src
|
93
|
+
Rssults:
|
94
|
+
: 2023-06-01
|
95
|
+
: 2025-06-01
|
96
|
+
|
97
|
+
Note that the meaning of ~forward_to~ is different from that of
|
98
|
+
changing the ~start~ parameter.
|
99
|
+
#+begin_src ruby
|
100
|
+
start = Date.new(2022, 1, 1) # changed as if set forward_to
|
101
|
+
Enumdate.yearly_by_monthday(start, interval: 2)
|
102
|
+
.until(Date.new(2025, 12, 31))
|
103
|
+
.each do |date|
|
104
|
+
puts date
|
105
|
+
end
|
106
|
+
#+end_src
|
107
|
+
Rssults:
|
108
|
+
: 2022-01-01
|
109
|
+
: 2024-01-01
|
110
|
+
|
111
|
+
~forward_to~ and ~until~ clip concrete occurrences without changing
|
112
|
+
the recurring pattern.
|
113
|
+
|
114
|
+
*** Merge multiple enumerables
|
115
|
+
Sometimes, you may need to compose more complex recurring patterns.
|
116
|
+
In this case, you can merge multiple enumerables:
|
117
|
+
#+begin_src ruby
|
118
|
+
mon = Date.new(2021, 8, 2) # = 1st Monday
|
119
|
+
wed = Date.new(2021, 8, 4) # = 1st Wednesday
|
120
|
+
|
121
|
+
# Every Monday and Wednesday:
|
122
|
+
(Enumdate::EnumMerger.new << Enumdate.weekly(mon) << Enumdate.weekly(wed))
|
123
|
+
.lazy.map(&:to_s).take(4).force
|
124
|
+
# => ["2021-08-02", "2021-08-04", "2021-08-09", "2021-08-11"]
|
125
|
+
#+end_src
|
126
|
+
|
127
|
+
** Installation
|
128
|
+
Add this line to your application's Gemfile:
|
129
|
+
#+begin_src ruby
|
130
|
+
gem "enumdate"
|
131
|
+
#+end_src
|
132
|
+
|
133
|
+
And then execute:
|
134
|
+
#+begin_src shell-script
|
135
|
+
$ bundle install
|
136
|
+
#+end_src
|
137
|
+
|
138
|
+
Or install it yourself as:
|
139
|
+
#+begin_src shell-script
|
140
|
+
$ gem install enumdate
|
141
|
+
#+end_src
|
142
|
+
|
143
|
+
** Contributing
|
144
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/yoshinari-nomura/enumdate.
|
145
|
+
|
146
|
+
** License
|
147
|
+
The gem is available as open source under the terms of the [[https://opensource.org/licenses/MIT][MIT License]].
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
require "rubocop/rake_task"
|
6
|
+
require "yard"
|
7
|
+
|
8
|
+
Rake::TestTask.new(:test) do |t|
|
9
|
+
t.libs << "test"
|
10
|
+
t.libs << "lib"
|
11
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
12
|
+
end
|
13
|
+
|
14
|
+
RuboCop::RakeTask.new
|
15
|
+
|
16
|
+
YARD::Rake::YardocTask.new do |t|
|
17
|
+
t.options = ["-m", "org"]
|
18
|
+
end
|
19
|
+
|
20
|
+
task doc: %i[yard]
|
21
|
+
task default: %i[test rubocop]
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "enumdate"
|
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
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/enumdate.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/enumdate/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "enumdate"
|
7
|
+
spec.version = Enumdate::VERSION
|
8
|
+
spec.authors = ["Yoshinari Nomura"]
|
9
|
+
spec.email = ["nom@quickhack.net"]
|
10
|
+
|
11
|
+
spec.summary = "Enumerator for recurring dates."
|
12
|
+
spec.homepage = "https://github.com/yoshinari-nomura/enumdate"
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.required_ruby_version = ">= 2.5.0"
|
15
|
+
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["source_code_uri"] = "https://github.com/yoshinari-nomura/enumdate"
|
18
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
23
|
+
`git ls-files -z`.split("\x0").reject {|f| f.match(%r{\A(?:test|spec|features)/}) }
|
24
|
+
end
|
25
|
+
spec.bindir = "exe"
|
26
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) {|f| File.basename(f) }
|
27
|
+
spec.require_paths = ["lib"]
|
28
|
+
end
|
data/lib/enumdate.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Enumerator for recurring dates
|
4
|
+
module Enumdate
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
require "date"
|
8
|
+
|
9
|
+
dir = File.expand_path("enumdate", File.dirname(__FILE__))
|
10
|
+
|
11
|
+
autoload :EnumMerger, "#{dir}/enum_merger.rb"
|
12
|
+
autoload :DateEnumerator, "#{dir}/date_enumerator.rb"
|
13
|
+
autoload :DateFrame, "#{dir}/date_frame.rb"
|
14
|
+
autoload :DateHelper, "#{dir}/date_helper.rb"
|
15
|
+
autoload :VERSION, "#{dir}/version.rb"
|
16
|
+
|
17
|
+
class << self
|
18
|
+
# @return [DateEnumerator::YearlyByMonthday]
|
19
|
+
def yearly_by_monthday(first_date, month: nil, mday: nil, interval: 1)
|
20
|
+
month ||= first_date.month
|
21
|
+
mday ||= first_date.mday
|
22
|
+
DateEnumerator::YearlyByMonthday.new(
|
23
|
+
first_date: first_date,
|
24
|
+
month: month,
|
25
|
+
mday: mday,
|
26
|
+
interval: interval
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [DateEnumerator::YearlyByDay]
|
31
|
+
def yearly_by_day(first_date, month: nil, nth: nil, wday: nil, interval: 1)
|
32
|
+
month ||= first_date.month
|
33
|
+
nth ||= (first_date.mday + 6) / 7
|
34
|
+
wday ||= first_date.wday
|
35
|
+
DateEnumerator::YearlyByDay.new(
|
36
|
+
first_date: first_date,
|
37
|
+
month: month,
|
38
|
+
nth: nth,
|
39
|
+
wday: wday,
|
40
|
+
interval: interval
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [DateEnumerator::MonthlyByMonthday]
|
45
|
+
def monthly_by_monthday(first_date, mday: nil, interval: 1)
|
46
|
+
mday ||= first_date.mday
|
47
|
+
DateEnumerator::MonthlyByMonthday.new(
|
48
|
+
first_date: first_date,
|
49
|
+
mday: mday,
|
50
|
+
interval: interval
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [DateEnumerator::MonthlyByDay]
|
55
|
+
def monthly_by_day(first_date, nth: nil, wday: nil, interval: 1)
|
56
|
+
nth ||= (first_date.mday + 6) / 7
|
57
|
+
wday ||= first_date.wday
|
58
|
+
DateEnumerator::MonthlyByDay.new(
|
59
|
+
first_date: first_date,
|
60
|
+
nth: nth,
|
61
|
+
wday: wday,
|
62
|
+
interval: interval
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [DateEnumerator::Weekly]
|
67
|
+
def weekly(first_date, wday: nil, wkst: 1, interval: 1)
|
68
|
+
wday ||= first_date.wday
|
69
|
+
DateEnumerator::Weekly.new(
|
70
|
+
first_date: first_date,
|
71
|
+
wday: wday,
|
72
|
+
wkst: wkst,
|
73
|
+
interval: interval
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [DateEnumerator::Daily]
|
78
|
+
def daily(first_date, interval: 1)
|
79
|
+
DateEnumerator::Daily.new(
|
80
|
+
first_date: first_date,
|
81
|
+
interval: interval
|
82
|
+
)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Enumdate
|
4
|
+
module DateEnumerator
|
5
|
+
# Base class for DateEnumerator
|
6
|
+
class Base
|
7
|
+
include DateHelper
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
def initialize(first_date:, interval: 1, wkst: 1)
|
11
|
+
@first_date, @interval, @wkst = first_date, interval, wkst
|
12
|
+
@frame_manager = frame_manager.new(first_date, interval, wkst)
|
13
|
+
@duration_begin = first_date
|
14
|
+
@duration_until = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
18
|
+
def each
|
19
|
+
return enum_for(:each) unless block_given?
|
20
|
+
|
21
|
+
# ~first_date~ value always counts as the first occurrence
|
22
|
+
# even if it does not match the specified rule.
|
23
|
+
# cf. DTSTART vs RRULE in RFC5445.
|
24
|
+
yield @first_date if between_duration?(@first_date)
|
25
|
+
|
26
|
+
@frame_manager.each do |frame|
|
27
|
+
# Avoid inifinit loops even if the rule emits no occurrences
|
28
|
+
# such as "31st April in every year".
|
29
|
+
# (Every ~occurrence_in_frame(frame)~ returns nil)
|
30
|
+
break if @duration_until && @duration_until < frame
|
31
|
+
|
32
|
+
# In some cases, ~occurrence_in_frame~ returns nil.
|
33
|
+
# For example, a recurrence that returns 31st of each month
|
34
|
+
# will return nil for short months such as April and June.
|
35
|
+
next unless (date = occurrence_in_frame(frame))
|
36
|
+
|
37
|
+
break if @duration_until && @duration_until < date
|
38
|
+
|
39
|
+
# ~occurrence_in_frame~ may return a date earlier than
|
40
|
+
# ~first_date~ in the first iteration. This is because
|
41
|
+
# ~first_date~ does not necessarily follow the repetetion
|
42
|
+
# rule. For example, if the rule is "every August 1st" and
|
43
|
+
# ~first_date~ is August 15th, The first occurrence calculated
|
44
|
+
# by the rule returns "August 1st", which is earlier than
|
45
|
+
# August 15th. In this context, ~@duration_begin~ is the matter.
|
46
|
+
next if date < @duration_begin
|
47
|
+
|
48
|
+
# Ignore ~first_date~ not to yield twice.
|
49
|
+
next if date == @first_date
|
50
|
+
|
51
|
+
yield date
|
52
|
+
end
|
53
|
+
end
|
54
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
55
|
+
|
56
|
+
# Set the new beginning of duration for the recurrence rule.
|
57
|
+
#
|
58
|
+
# It also controls the underlying frame manager. Since the
|
59
|
+
# frame manager is enough smart to avoid unnecessary repetition,
|
60
|
+
# there is no problem in setting the date hundred years later.
|
61
|
+
#
|
62
|
+
# Note that the meaning of calling ~forward_to~ is different
|
63
|
+
# from that of setting the ~first_date~ parameter on creation.
|
64
|
+
# For example, if a yearly event has *two-years* ~interval~:
|
65
|
+
#
|
66
|
+
# 1) if first_date is 2021-08-01 and forward_to 2022-08-01,
|
67
|
+
# it will create [2021-08-01 2023-08-01 ...]
|
68
|
+
#
|
69
|
+
# 2) if first_date is 2022-08-01,
|
70
|
+
# it will create [2022-08-01 2024-08-01 ...]
|
71
|
+
#
|
72
|
+
def forward_to(date)
|
73
|
+
@frame_manager.forward_to(date)
|
74
|
+
@duration_begin = date
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
# Imprement rewind for Enumrator class
|
79
|
+
def rewind
|
80
|
+
@frame_manager.rewind
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
# Set the new end of duration for the recurrence rule.
|
85
|
+
def until(date)
|
86
|
+
@duration_until = date
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def between_duration?(date)
|
93
|
+
(!@duration_begin || @duration_begin <= date) &&
|
94
|
+
(!@duration_until || date <= @duration_until)
|
95
|
+
end
|
96
|
+
|
97
|
+
def frame_manager
|
98
|
+
raise NotImplementedError
|
99
|
+
end
|
100
|
+
|
101
|
+
def occurrence_in_frame(date)
|
102
|
+
raise NotImplementedError
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
################################################################
|
107
|
+
# Enumerate yealy dates by day like: Apr 4th Tue
|
108
|
+
class YearlyByDay < Base
|
109
|
+
def initialize(first_date:, month:, nth:, wday:, interval: 1)
|
110
|
+
super(first_date: first_date, interval: interval)
|
111
|
+
@month, @nth, @wday = month, nth, wday
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def frame_manager
|
117
|
+
DateFrame::Yearly
|
118
|
+
end
|
119
|
+
|
120
|
+
def occurrence_in_frame(date)
|
121
|
+
make_date_by_day(year: date.year, month: @month, nth: @nth, wday: @wday)
|
122
|
+
rescue ArgumentError
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
################################################################
|
128
|
+
# Enumerate yealy dates by month-day like: Apr 22
|
129
|
+
# s, e = Date.new(2021, 1, 1), Date.new(20100, 12, 31)
|
130
|
+
# Enumdate::YearlyByMonthday(start_date: s, end_date: e, month: 4, mday: 22, interval: 2).map(&:to_s)
|
131
|
+
# : => [2021-04-22, 2023-04-22, ..., 2099-04-22]
|
132
|
+
class YearlyByMonthday < Base
|
133
|
+
def initialize(first_date:, month:, mday:, interval: 1)
|
134
|
+
super(first_date: first_date, interval: interval)
|
135
|
+
@month, @mday = month, mday
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def frame_manager
|
141
|
+
DateFrame::Yearly
|
142
|
+
end
|
143
|
+
|
144
|
+
def occurrence_in_frame(date)
|
145
|
+
Date.new(date.year, @month, @mday)
|
146
|
+
rescue Date::Error
|
147
|
+
nil
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
################################################################
|
152
|
+
# Enumerate monthly dates by day like: 4th Tue
|
153
|
+
class MonthlyByDay < Base
|
154
|
+
def initialize(first_date:, nth:, wday:, interval: 1)
|
155
|
+
super(first_date: first_date, interval: interval)
|
156
|
+
@nth, @wday = nth, wday
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def frame_manager
|
162
|
+
DateFrame::Monthly
|
163
|
+
end
|
164
|
+
|
165
|
+
def occurrence_in_frame(date)
|
166
|
+
make_date_by_day(year: date.year, month: date.month, nth: @nth, wday: @wday)
|
167
|
+
rescue Date::Error
|
168
|
+
nil
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
################################################################
|
173
|
+
# Enumerate monthly dates by month-day like: 22
|
174
|
+
class MonthlyByMonthday < Base
|
175
|
+
def initialize(first_date:, mday:, interval: 1)
|
176
|
+
super(first_date: first_date, interval: interval)
|
177
|
+
@mday = mday
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
def frame_manager
|
183
|
+
DateFrame::Monthly
|
184
|
+
end
|
185
|
+
|
186
|
+
def occurrence_in_frame(date)
|
187
|
+
Date.new(date.year, date.month, @mday)
|
188
|
+
rescue Date::Error
|
189
|
+
nil
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
################################################################
|
194
|
+
# Enumerate weekly dates like: Tue
|
195
|
+
class Weekly < Base
|
196
|
+
def initialize(first_date:, wday:, interval: 1, wkst: 1)
|
197
|
+
super(first_date: first_date, interval: interval, wkst: wkst)
|
198
|
+
@wday = wday
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
def frame_manager
|
204
|
+
DateFrame::Weekly
|
205
|
+
end
|
206
|
+
|
207
|
+
# Sun Mon Tue Wed Thu Fri Sat Sun Mon Tue ...
|
208
|
+
# 0 1 2 3 4 5 6 0 1 2 ...
|
209
|
+
def occurrence_in_frame(date)
|
210
|
+
bof = date - ((date.wday - @wkst) % 7)
|
211
|
+
candidate = bof + (@wday - bof.wday) % 7
|
212
|
+
return candidate if date <= candidate
|
213
|
+
|
214
|
+
nil
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
################################################################
|
219
|
+
# Enumerate every n days
|
220
|
+
class Daily < Base
|
221
|
+
def initialize(first_date:, interval: 1)
|
222
|
+
super(first_date: first_date, interval: interval)
|
223
|
+
end
|
224
|
+
|
225
|
+
private
|
226
|
+
|
227
|
+
def frame_manager
|
228
|
+
DateFrame::Daily
|
229
|
+
end
|
230
|
+
|
231
|
+
def occurrence_in_frame(date)
|
232
|
+
date
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
################################################################
|
237
|
+
# Enumerate dates from list.
|
238
|
+
class ByDateList
|
239
|
+
include Enumerable
|
240
|
+
|
241
|
+
def initialize(date_list: [])
|
242
|
+
@date_list = date_list
|
243
|
+
@duration_until = nil
|
244
|
+
end
|
245
|
+
|
246
|
+
def <<(date)
|
247
|
+
@date_list << date
|
248
|
+
end
|
249
|
+
|
250
|
+
def rewind; end
|
251
|
+
|
252
|
+
def until(date)
|
253
|
+
@duration_until = date
|
254
|
+
end
|
255
|
+
|
256
|
+
def forward_to(date)
|
257
|
+
@first_date = date
|
258
|
+
end
|
259
|
+
|
260
|
+
def each
|
261
|
+
return enum_for(:each) unless block_given?
|
262
|
+
|
263
|
+
@date_list.sort.each do |date|
|
264
|
+
next if @fist_date && date < @first_date
|
265
|
+
break if @duration_until && date > @duration_until
|
266
|
+
|
267
|
+
yield date
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Enumdate
|
4
|
+
# SYNOPSIS:
|
5
|
+
# + Enumdate::DateFrame::Yearly.new(first_date, interval)
|
6
|
+
# + Enumdate::DateFrame::Monthly.new(first_date, interval)
|
7
|
+
# + Enumdate::DateFrame::Weekly.new(first_date, interval, wkst)
|
8
|
+
# + Enumdate::DateFrame::Daily.new(first_date, interval)
|
9
|
+
#
|
10
|
+
# Iterate the date in frames of year, month, week, or day,
|
11
|
+
# and enumerate the first date of each frame.
|
12
|
+
#
|
13
|
+
# ~FIRST_DATE~ is an arbitrary date of the first frame.
|
14
|
+
#
|
15
|
+
# #+begin_src ruby
|
16
|
+
# Enumdate::DateFrame::Yearly.new(Date.new(2021, 1, 1), 2).lazy.map(&:to_s).take(3).force
|
17
|
+
# # => ["2021-01-01", "2023-01-01", "2025-01-01"]
|
18
|
+
#
|
19
|
+
# Enumdate::DateFrame::Yearly.new(Date.new(2021, 6, 1), 2).lazy.map(&:to_s).take(3).force
|
20
|
+
# # => ["2021-01-01", "2023-01-01", "2025-01-01"]
|
21
|
+
#
|
22
|
+
# Enumdate::DateFrame::Monthly.new(Date.new(2021, 6, 10), 2).lazy.map(&:to_s).take(3).force
|
23
|
+
# # => ["2021-06-01", "2021-08-01", "2021-10-01"]
|
24
|
+
# #+end_src
|
25
|
+
#
|
26
|
+
# ~Enumdate::DateFrame::Weekly~ is sensitive to ~WKST~ param:
|
27
|
+
#
|
28
|
+
# 2021-06-08 is Tuesday:
|
29
|
+
# #+begin_example
|
30
|
+
# June 2021
|
31
|
+
# Su Mo Tu We Th Fr Sa
|
32
|
+
# 30 31 1 2 3 4 5
|
33
|
+
# 6 7 8 9 10 11 12
|
34
|
+
# 13 14 15 16 17 18 19
|
35
|
+
# 20 21 22 23 24 25 26
|
36
|
+
# 27 28 29 30 1 2 3
|
37
|
+
# #+end_example
|
38
|
+
#
|
39
|
+
# #+begin_src ruby
|
40
|
+
# sun, mon, tue, wed = 0, 1, 2, 3
|
41
|
+
#
|
42
|
+
# # If week start is Sunday:
|
43
|
+
# Enumdate::DateFrame::Weekly.new(Date.new(2021, 6, 8), 2, sun).lazy.map(&:to_s).take(3).force
|
44
|
+
# # => ["2021-06-06", "2021-06-20", "2021-07-04"]
|
45
|
+
#
|
46
|
+
# # Monday (default):
|
47
|
+
# Enumdate::DateFrame::Weekly.new(Date.new(2021, 6, 8), 2, mon).lazy.map(&:to_s).take(3).force
|
48
|
+
# # => ["2021-06-07", "2021-06-21", "2021-07-05"]
|
49
|
+
#
|
50
|
+
# # Tuesday:
|
51
|
+
# Enumdate::DateFrame::Weekly.new(Date.new(2021, 6, 8), 2, tue).lazy.map(&:to_s).take(3).force
|
52
|
+
# # => ["2021-06-08", "2021-06-22", "2021-07-06"]
|
53
|
+
#
|
54
|
+
# # Wednesday:
|
55
|
+
# Enumdate::DateFrame::Weekly.new(Date.new(2021, 6, 8), 2, wed).lazy.map(&:to_s).take(3).force
|
56
|
+
# # => ["2021-06-02", "2021-06-16", "2021-06-30"]
|
57
|
+
#
|
58
|
+
# Enumdate::DateFrame::Daily.new(Date.new(2021, 5, 15), 10).lazy.map(&:to_s).take(3).force
|
59
|
+
# # => ["2021-05-15", "2021-05-25", "2021-06-04"]
|
60
|
+
#
|
61
|
+
# # `forward_to` method is helpful to jump to some specific date before the iteration.
|
62
|
+
# Enumdate::DateFrame::Yearly.new(Date.new(2021, 1, 1), 2).forward_to(Date.new(2100, 1, 1))
|
63
|
+
# .lazy.map(&:to_s).take(3).force
|
64
|
+
# # => ["2101-01-01", "2103-01-01", "2105-01-01"]
|
65
|
+
#
|
66
|
+
# # Let's see how it differs from simply changing FIRST_DATE:
|
67
|
+
# Enumdate::DateFrame::Yearly.new(Date.new(2100, 1, 1), 2).lazy.map(&:to_s).take(3).force
|
68
|
+
# # => ["2100-01-01", "2102-01-01", "2104-01-01"]
|
69
|
+
# #+end_src
|
70
|
+
#
|
71
|
+
module DateFrame
|
72
|
+
# DateFrame: yearly, monthly, weekly, and daily
|
73
|
+
class Base
|
74
|
+
include DateHelper
|
75
|
+
include Enumerable
|
76
|
+
|
77
|
+
def initialize(first_date, interval = 1, wkst = 1)
|
78
|
+
@first_date, @interval, @wkst = first_date, interval, wkst
|
79
|
+
rewind
|
80
|
+
end
|
81
|
+
|
82
|
+
def each
|
83
|
+
return enum_for(:each) unless block_given?
|
84
|
+
|
85
|
+
loop do
|
86
|
+
yield @current_frame_date
|
87
|
+
@current_frame_date = next_frame_start(@current_frame_date)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Imprement rewind for Enumrator class
|
92
|
+
def rewind
|
93
|
+
@current_frame_date = beginning_of_frame(@first_date)
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
# Go forward to the frame in which DATE is involved
|
98
|
+
def forward_to(date)
|
99
|
+
rewind # reset @current_frame_date
|
100
|
+
frames = frames_between(@current_frame_date, date)
|
101
|
+
cycles = (frames + (@interval - 1)) / @interval
|
102
|
+
@current_frame_date = next_frame_start(@current_frame_date, cycles) if cycles.positive?
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def next_frame_start(current_frame_date, cycles = 1)
|
109
|
+
raise NotImplementedError
|
110
|
+
end
|
111
|
+
|
112
|
+
def beginning_of_frame(date)
|
113
|
+
raise NotImplementedError
|
114
|
+
end
|
115
|
+
|
116
|
+
def frames_between(date1, date2)
|
117
|
+
raise NotImplementedError
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Dummy date frame
|
122
|
+
class Dummy < Base
|
123
|
+
end
|
124
|
+
|
125
|
+
# Yearly date frame
|
126
|
+
class Yearly < Base
|
127
|
+
private
|
128
|
+
|
129
|
+
def next_frame_start(current_frame_date, cycles = 1)
|
130
|
+
current_frame_date >> (@interval * 12 * cycles)
|
131
|
+
end
|
132
|
+
|
133
|
+
def beginning_of_frame(date)
|
134
|
+
beginning_of_year(date)
|
135
|
+
end
|
136
|
+
|
137
|
+
def frames_between(date1, date2)
|
138
|
+
years_between(date1, date2)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Monthly date frame
|
143
|
+
class Monthly < Base
|
144
|
+
private
|
145
|
+
|
146
|
+
def next_frame_start(current_frame_date, cycles = 1)
|
147
|
+
current_frame_date >> (@interval * cycles)
|
148
|
+
end
|
149
|
+
|
150
|
+
def beginning_of_frame(date)
|
151
|
+
beginning_of_month(date)
|
152
|
+
end
|
153
|
+
|
154
|
+
def frames_between(date1, date2)
|
155
|
+
months_between(date1, date2)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Weekly date frame
|
160
|
+
class Weekly < Base
|
161
|
+
private
|
162
|
+
|
163
|
+
def next_frame_start(current_frame_date, cycles = 1)
|
164
|
+
current_frame_date + (@interval * 7 * cycles)
|
165
|
+
end
|
166
|
+
|
167
|
+
def beginning_of_frame(date)
|
168
|
+
beginning_of_week(date, @wkst)
|
169
|
+
end
|
170
|
+
|
171
|
+
def frames_between(date1, date2)
|
172
|
+
(beginning_of_frame(date2) - beginning_of_frame(date1)) / 7
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Daily date frame
|
177
|
+
class Daily < Base
|
178
|
+
private
|
179
|
+
|
180
|
+
def next_frame_start(current_frame_date, cycles = 1)
|
181
|
+
current_frame_date + (@interval * cycles)
|
182
|
+
end
|
183
|
+
|
184
|
+
def beginning_of_frame(date)
|
185
|
+
date
|
186
|
+
end
|
187
|
+
|
188
|
+
def frames_between(date1, date2)
|
189
|
+
date2 - date1
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Enumdate
|
4
|
+
# Helper for date-calculation.
|
5
|
+
module DateHelper
|
6
|
+
private
|
7
|
+
|
8
|
+
# Make a date by DAY like ``1st Wed of Nov, 1999''.
|
9
|
+
# caller must make sure:
|
10
|
+
# YEAR and MONTH must be valid
|
11
|
+
# NTH must be < 0 or > 0
|
12
|
+
# WDAY must be 0:Sun .. 6:Sat
|
13
|
+
#
|
14
|
+
# raise ArgumentError if no date matches. for example:
|
15
|
+
# no 5th Saturday exists on April 2010.
|
16
|
+
#
|
17
|
+
def make_date_by_day(year:, month:, nth:, wday:)
|
18
|
+
direction = nth.positive? ? 1 : -1
|
19
|
+
|
20
|
+
edge = Date.new(year, month, direction)
|
21
|
+
ydiff = nth - direction
|
22
|
+
xdiff = direction * ((direction * (wday - edge.wday)) % 7)
|
23
|
+
mday = edge.mday + ydiff * 7 + xdiff
|
24
|
+
|
25
|
+
raise ArgumentError if mday < 1
|
26
|
+
|
27
|
+
Date.new(year, month, mday)
|
28
|
+
end
|
29
|
+
|
30
|
+
def beginning_of_year(date)
|
31
|
+
date.class.new(date.year, 1, 1)
|
32
|
+
end
|
33
|
+
|
34
|
+
def beginning_of_month(date)
|
35
|
+
date.class.new(date.year, date.month, 1)
|
36
|
+
end
|
37
|
+
|
38
|
+
def beginning_of_week(date, wkst = 1)
|
39
|
+
date - ((date.wday - wkst) % 7)
|
40
|
+
end
|
41
|
+
|
42
|
+
def years_between(date1, date2)
|
43
|
+
date2.year - date1.year
|
44
|
+
end
|
45
|
+
|
46
|
+
def months_between(date1, date2)
|
47
|
+
(date2.year * 12 + date2.month) - (date1.year * 12 + date1.month)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Enumdate
|
4
|
+
# Create new Enumerator merging multiple enumerators.
|
5
|
+
#
|
6
|
+
# All enumerators should yield objects that respond to `<=>` method.
|
7
|
+
# enums = (EnumMerger.new << enum1 << enum2).to_enum
|
8
|
+
#
|
9
|
+
class EnumMerger
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@enumerators = []
|
14
|
+
end
|
15
|
+
|
16
|
+
# Imprement each for Enumrator class
|
17
|
+
def each
|
18
|
+
return enum_for(:each) unless block_given?
|
19
|
+
|
20
|
+
loop do
|
21
|
+
yield next_minimum(@enumerators)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Imprement rewind for Enumrator class
|
26
|
+
def rewind
|
27
|
+
@enumerators.map(&:rewind)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Add enumerator
|
31
|
+
def <<(enumerator)
|
32
|
+
@enumerators << enumerator.to_enum
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Yield next minimum value
|
39
|
+
def next_minimum(enumerators)
|
40
|
+
raise StopIteration if enumerators.empty?
|
41
|
+
|
42
|
+
# Could raise StopIteration
|
43
|
+
minimum_enumrator(enumerators).next
|
44
|
+
end
|
45
|
+
|
46
|
+
def minimum_enumrator(enumerators)
|
47
|
+
min_e, min_v = enumerators.first, nil
|
48
|
+
enumerators.each do |e|
|
49
|
+
v = e.peek
|
50
|
+
min_e, min_v = e, v if min_v.nil? || v < min_v
|
51
|
+
rescue StopIteration
|
52
|
+
# do nothing
|
53
|
+
end
|
54
|
+
min_e
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: enumdate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yoshinari Nomura
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-08-22 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
- nom@quickhack.net
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".github/workflows/main.yml"
|
21
|
+
- ".gitignore"
|
22
|
+
- ".rubocop.yml"
|
23
|
+
- ".solargraph.yml"
|
24
|
+
- Gemfile
|
25
|
+
- LICENSE.txt
|
26
|
+
- README.org
|
27
|
+
- Rakefile
|
28
|
+
- bin/console
|
29
|
+
- bin/setup
|
30
|
+
- enumdate.gemspec
|
31
|
+
- lib/enumdate.rb
|
32
|
+
- lib/enumdate/date_enumerator.rb
|
33
|
+
- lib/enumdate/date_frame.rb
|
34
|
+
- lib/enumdate/date_helper.rb
|
35
|
+
- lib/enumdate/enum_merger.rb
|
36
|
+
- lib/enumdate/version.rb
|
37
|
+
homepage: https://github.com/yoshinari-nomura/enumdate
|
38
|
+
licenses:
|
39
|
+
- MIT
|
40
|
+
metadata:
|
41
|
+
homepage_uri: https://github.com/yoshinari-nomura/enumdate
|
42
|
+
source_code_uri: https://github.com/yoshinari-nomura/enumdate
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: 2.5.0
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
requirements: []
|
58
|
+
rubygems_version: 3.2.22
|
59
|
+
signing_key:
|
60
|
+
specification_version: 4
|
61
|
+
summary: Enumerator for recurring dates.
|
62
|
+
test_files: []
|