enumdate 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|