minutely 3.0.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/.coveralls.yml +1 -0
- data/.gitignore +11 -0
- data/.gitlab-ci.yml +23 -0
- data/.rspec +4 -0
- data/.rubocop.yml +14 -0
- data/.tool-versions +1 -0
- data/.travis.yml +8 -0
- data/.vscode/extensions.json +5 -0
- data/.vscode/settings.json +17 -0
- data/.vscode/tasks.json +43 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +78 -0
- data/LICENSE +21 -0
- data/README.md +134 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/minutely.rb +44 -0
- data/lib/minutely/parser.rb +30 -0
- data/lib/minutely/string_as_json.rb +16 -0
- data/lib/minutely/time.rb +102 -0
- data/lib/minutely/time/parser.rb +42 -0
- data/lib/minutely/time_range.rb +137 -0
- data/lib/minutely/time_range/parser.rb +56 -0
- data/lib/minutely/version.rb +5 -0
- data/minutely.gemspec +34 -0
- metadata +70 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 720e333d931b75150f1e2b9f9854f293e26b7164f2ba6fa483fcab646d2a0aa0
|
|
4
|
+
data.tar.gz: 557aecaa956d88c516d29e2e964314e3ca4600d5b5e5600b9a800e56be3a7c26
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 6afee9948aa220063711a1710d51acd05efbcfd0cea087f43143e5707972486f76b62b02fe2e34666924d188773e9d6575371fb9e366252250f299b276deec18
|
|
7
|
+
data.tar.gz: 2cb497a1ea2d104945886383800657c15db66bc9859771f4d32adbd2c967855ebdac754b6b4ba9c86732659eeb1d6ef0528a1930efcc09d65b8bca714542bb6e
|
data/.coveralls.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
service_name: travis-ci
|
data/.gitignore
ADDED
data/.gitlab-ci.yml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
image: gitlab.i22.de:5001/docker/i22-ubuntu:20.04
|
|
2
|
+
|
|
3
|
+
before_script:
|
|
4
|
+
- gem install bundler -v 2.2.15
|
|
5
|
+
- bundle install -j $(nproc) --path vendor/ruby
|
|
6
|
+
|
|
7
|
+
cache:
|
|
8
|
+
key: ${CI_COMMIT_REF_SLUG}
|
|
9
|
+
paths:
|
|
10
|
+
- vendor/ruby
|
|
11
|
+
|
|
12
|
+
lint:
|
|
13
|
+
script:
|
|
14
|
+
- bundle exec rubocop
|
|
15
|
+
|
|
16
|
+
test:
|
|
17
|
+
script:
|
|
18
|
+
- bundle exec rspec
|
|
19
|
+
coverage: /\(\d+.\d+%\)/
|
|
20
|
+
artifacts:
|
|
21
|
+
reports:
|
|
22
|
+
cobertura: coverage/coverage.xml
|
|
23
|
+
expire_in: 1 day
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.tool-versions
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruby 2.7.0
|
data/.travis.yml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"editor.tabSize": 2,
|
|
3
|
+
"files.insertFinalNewline": true,
|
|
4
|
+
"files.trimFinalNewlines": true,
|
|
5
|
+
"files.trimTrailingWhitespace": true,
|
|
6
|
+
"ruby.format": "rubocop",
|
|
7
|
+
"ruby.lint": {
|
|
8
|
+
"reek": {
|
|
9
|
+
"useBundler": true
|
|
10
|
+
},
|
|
11
|
+
"rubocop": {
|
|
12
|
+
"useBundler": true
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"ruby.useBundler": true,
|
|
16
|
+
"ruby.useLanguageServer": true
|
|
17
|
+
}
|
data/.vscode/tasks.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.0.0",
|
|
3
|
+
"tasks": [{
|
|
4
|
+
"label": "RSpec Test",
|
|
5
|
+
"type": "shell",
|
|
6
|
+
"command": "bundle exec rspec",
|
|
7
|
+
"group": "test",
|
|
8
|
+
"presentation": {
|
|
9
|
+
"echo": true,
|
|
10
|
+
"reveal": "always",
|
|
11
|
+
"focus": false,
|
|
12
|
+
"panel": "shared",
|
|
13
|
+
"showReuseMessage": true,
|
|
14
|
+
"clear": true
|
|
15
|
+
}
|
|
16
|
+
}, {
|
|
17
|
+
"label": "RSpec Test File",
|
|
18
|
+
"type": "shell",
|
|
19
|
+
"command": "bundle exec rspec ${relativeFile}",
|
|
20
|
+
"group": "test",
|
|
21
|
+
"presentation": {
|
|
22
|
+
"echo": true,
|
|
23
|
+
"reveal": "always",
|
|
24
|
+
"focus": false,
|
|
25
|
+
"panel": "shared",
|
|
26
|
+
"showReuseMessage": true,
|
|
27
|
+
"clear": true
|
|
28
|
+
}
|
|
29
|
+
}, {
|
|
30
|
+
"label": "RSpec Test Line",
|
|
31
|
+
"type": "shell",
|
|
32
|
+
"command": "bundle exec rspec ${relativeFile}:${lineNumber}",
|
|
33
|
+
"group": "test",
|
|
34
|
+
"presentation": {
|
|
35
|
+
"echo": true,
|
|
36
|
+
"reveal": "always",
|
|
37
|
+
"focus": false,
|
|
38
|
+
"panel": "shared",
|
|
39
|
+
"showReuseMessage": true,
|
|
40
|
+
"clear": true
|
|
41
|
+
}
|
|
42
|
+
}]
|
|
43
|
+
}
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
minutely (3.0.0)
|
|
5
|
+
|
|
6
|
+
GEM
|
|
7
|
+
remote: https://rubygems.org/
|
|
8
|
+
specs:
|
|
9
|
+
ast (2.4.2)
|
|
10
|
+
coveralls (0.8.23)
|
|
11
|
+
json (>= 1.8, < 3)
|
|
12
|
+
simplecov (~> 0.16.1)
|
|
13
|
+
term-ansicolor (~> 1.3)
|
|
14
|
+
thor (>= 0.19.4, < 2.0)
|
|
15
|
+
tins (~> 1.6)
|
|
16
|
+
diff-lcs (1.4.4)
|
|
17
|
+
docile (1.3.5)
|
|
18
|
+
json (2.5.1)
|
|
19
|
+
parallel (1.20.1)
|
|
20
|
+
parser (3.0.1.0)
|
|
21
|
+
ast (~> 2.4.1)
|
|
22
|
+
rainbow (3.0.0)
|
|
23
|
+
rake (13.0.3)
|
|
24
|
+
regexp_parser (2.1.1)
|
|
25
|
+
rexml (3.2.5)
|
|
26
|
+
rspec (3.10.0)
|
|
27
|
+
rspec-core (~> 3.10.0)
|
|
28
|
+
rspec-expectations (~> 3.10.0)
|
|
29
|
+
rspec-mocks (~> 3.10.0)
|
|
30
|
+
rspec-core (3.10.1)
|
|
31
|
+
rspec-support (~> 3.10.0)
|
|
32
|
+
rspec-expectations (3.10.1)
|
|
33
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
34
|
+
rspec-support (~> 3.10.0)
|
|
35
|
+
rspec-mocks (3.10.2)
|
|
36
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
37
|
+
rspec-support (~> 3.10.0)
|
|
38
|
+
rspec-support (3.10.2)
|
|
39
|
+
rubocop (1.12.1)
|
|
40
|
+
parallel (~> 1.10)
|
|
41
|
+
parser (>= 3.0.0.0)
|
|
42
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
43
|
+
regexp_parser (>= 1.8, < 3.0)
|
|
44
|
+
rexml
|
|
45
|
+
rubocop-ast (>= 1.2.0, < 2.0)
|
|
46
|
+
ruby-progressbar (~> 1.7)
|
|
47
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
|
48
|
+
rubocop-ast (1.4.1)
|
|
49
|
+
parser (>= 2.7.1.5)
|
|
50
|
+
ruby-progressbar (1.11.0)
|
|
51
|
+
simplecov (0.16.1)
|
|
52
|
+
docile (~> 1.1)
|
|
53
|
+
json (>= 1.8, < 3)
|
|
54
|
+
simplecov-html (~> 0.10.0)
|
|
55
|
+
simplecov-html (0.10.2)
|
|
56
|
+
sync (0.5.0)
|
|
57
|
+
term-ansicolor (1.7.1)
|
|
58
|
+
tins (~> 1.0)
|
|
59
|
+
thor (1.1.0)
|
|
60
|
+
tins (1.28.0)
|
|
61
|
+
sync
|
|
62
|
+
unicode-display_width (2.0.0)
|
|
63
|
+
yard (0.9.26)
|
|
64
|
+
|
|
65
|
+
PLATFORMS
|
|
66
|
+
ruby
|
|
67
|
+
x86_64-linux
|
|
68
|
+
|
|
69
|
+
DEPENDENCIES
|
|
70
|
+
coveralls
|
|
71
|
+
minutely!
|
|
72
|
+
rake (~> 13.0)
|
|
73
|
+
rspec (~> 3.0)
|
|
74
|
+
rubocop
|
|
75
|
+
yard
|
|
76
|
+
|
|
77
|
+
BUNDLED WITH
|
|
78
|
+
2.2.15
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Tobias Casper
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Minutely
|
|
2
|
+
|
|
3
|
+
[](https://travis-ci.org/tlux/minutely)
|
|
4
|
+
[](https://coveralls.io/github/tlux/minutely?branch=master)
|
|
5
|
+
[](https://badge.fury.io/rb/minutely)
|
|
6
|
+
|
|
7
|
+
Classes for representing the time of a day by using only hours and minutes.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Add this line to your Gemfile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem 'minutely'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then execute:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
bundle install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Time
|
|
26
|
+
|
|
27
|
+
Create a new time:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
time1 = Minutely::Time.new(21, 42)
|
|
31
|
+
time1.to_s # => "21:42"
|
|
32
|
+
|
|
33
|
+
time2 = Minutely::Time.new(9, 3)
|
|
34
|
+
time2.to_s # => "09:03"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Parse time using `DateTime`, `Time`, `String` or `Integer`:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
Minutely::Time.parse(DateTime.now)
|
|
41
|
+
Minutely::Time.parse(Time.now)
|
|
42
|
+
Minutely::Time.parse('9:03').to_s # => "09:03"
|
|
43
|
+
Minutely::Time.parse(903).to_s # => "09:03"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Get the next minute:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
time = Minutely::Time.parse('9:03')
|
|
50
|
+
time.succ.to_s # => "9:04"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Compare and sort by times:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
time1 = Minutely::Time.parse('9:03')
|
|
57
|
+
time2 = Minutely::Time.parse('21:42')
|
|
58
|
+
|
|
59
|
+
time1 == time2 # => false
|
|
60
|
+
time1 < time2 # => true
|
|
61
|
+
time1 <= time2 # => true
|
|
62
|
+
time1 >= time2 # => false
|
|
63
|
+
time1 > time2 # => false
|
|
64
|
+
|
|
65
|
+
[
|
|
66
|
+
Minutely::Time.parse('21:42'),
|
|
67
|
+
Minutely::Time.parse('9:03'),
|
|
68
|
+
Minutely::Time.parse('15:00')
|
|
69
|
+
].sort.map(&:to_s) # => ["09:03", "15:00", "21:42"]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Native Range support:
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
(Minutely::Time.parse('9:57')..Minutely::Time.parse('10:10'))
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Time Range
|
|
80
|
+
|
|
81
|
+
A special type of time range, that also allows defining ranges spanning over 12
|
|
82
|
+
am (0:00).
|
|
83
|
+
|
|
84
|
+
Create a new time range:
|
|
85
|
+
|
|
86
|
+
time1 = Minutely::Time.new(9, 3)
|
|
87
|
+
```ruby
|
|
88
|
+
time2 = Minutely::Time.new(21, 42)
|
|
89
|
+
|
|
90
|
+
Minutely::TimeRange.new(time1, time2).to_s # => "09:03-21:42"
|
|
91
|
+
Minutely::TimeRange.new(time2, time1).to_s # => "21:42-09:03"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Parse time range using `String`:
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
Minutely::TimeRange.parse('9:03-21:42')
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Parse time range using `Hash`:
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
Minutely::TimeRange.parse(from: '9:03', to: '21:42')
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Check whether time range includes time:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
range = Minutely::TimeRange.new('9:03', '21:42')
|
|
110
|
+
|
|
111
|
+
range.include?('10:00') # => true
|
|
112
|
+
range.include?('22:00') # => false
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Convert to Ruby `Range`:
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
Minutely::TimeRange.new('9:03', '21:42').to_r
|
|
119
|
+
# => #<Minutely::Time @hour=9, @minute=3>..#<Minutely::Time @hour=21, @minute=42>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Convert to Array of `Minutely::Time`s:
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
Minutely::TimeRange.new('23:57', '0:03').to_a.map(&:to_s)
|
|
126
|
+
# => ["23:57", "23:58", "23:59", "00:00", "00:01", "00:02", "00:03"]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Note this is only possible with ranges not spanning midnight.
|
|
130
|
+
|
|
131
|
+
## Contributing
|
|
132
|
+
|
|
133
|
+
Bug reports and pull requests are welcome on [GitHub
|
|
134
|
+
Issues](https://github.com/tlux/minutely/issues)
|
data/Rakefile
ADDED
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 'minutely'
|
|
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/lib/minutely.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'minutely/version'
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
# A library that provides classes for representing the time of a day by using
|
|
7
|
+
# only hours and minutes.
|
|
8
|
+
module Minutely
|
|
9
|
+
autoload :Parser, 'minutely/parser'
|
|
10
|
+
autoload :StringAsJson, 'minutely/string_as_json'
|
|
11
|
+
autoload :Time, 'minutely/time'
|
|
12
|
+
autoload :TimeRange, 'minutely/time_range'
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# Parses the given input and returns a `Minutely::Time` or `nil`,
|
|
18
|
+
# respectively.
|
|
19
|
+
#
|
|
20
|
+
# @param obj [Minutely::Time, #hour, #min, Integer, String, nil]
|
|
21
|
+
#
|
|
22
|
+
# @return [Minutely::Time, nil]
|
|
23
|
+
#
|
|
24
|
+
# @raise [ArgumentError] when the object does not represent a valid time
|
|
25
|
+
def parse(*args)
|
|
26
|
+
Minutely::Time.parse(*args)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Parses the given input and returns a `Minutely::TimeRange` or `nil`,
|
|
31
|
+
# respectively.
|
|
32
|
+
#
|
|
33
|
+
# @param obj [Minutely::TimeRange, Array, Hash, String, nil]
|
|
34
|
+
#
|
|
35
|
+
# @return [Minutely::TimeRange, nil]
|
|
36
|
+
#
|
|
37
|
+
# @raise [ArgumentError] when the object does not represent a valid time range
|
|
38
|
+
#
|
|
39
|
+
# @raise [KeyError] when the given Hash does not contain the required keys
|
|
40
|
+
# (`:from` and `:to`)
|
|
41
|
+
def parse_range(*args)
|
|
42
|
+
Minutely::TimeRange.parse(*args)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Minutely
|
|
4
|
+
##
|
|
5
|
+
# An abstract class for defining custom parsers.
|
|
6
|
+
#
|
|
7
|
+
# @!attribute [r] value
|
|
8
|
+
# @return [Object]
|
|
9
|
+
class Parser
|
|
10
|
+
attr_reader :value
|
|
11
|
+
|
|
12
|
+
##
|
|
13
|
+
# Initialize the parser.
|
|
14
|
+
#
|
|
15
|
+
# @param value [Object]
|
|
16
|
+
def initialize(value)
|
|
17
|
+
@value = value
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# Parse the specified value.
|
|
22
|
+
#
|
|
23
|
+
# @param value [Object]
|
|
24
|
+
# @return [Object]
|
|
25
|
+
# @raise [ArgumentError]
|
|
26
|
+
def self.parse(value)
|
|
27
|
+
new(value).parse
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Minutely
|
|
4
|
+
##
|
|
5
|
+
# A mixin to use #to_s as JSON representation of the including object.
|
|
6
|
+
module StringAsJson
|
|
7
|
+
##
|
|
8
|
+
# The JSON representation of the object as Hash. Conventionally used by
|
|
9
|
+
# common serializers.
|
|
10
|
+
#
|
|
11
|
+
# @return [Hash]
|
|
12
|
+
def as_json(_options = nil)
|
|
13
|
+
to_s
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Minutely
|
|
4
|
+
##
|
|
5
|
+
# A class that represents a day time by using only hours and minutes.
|
|
6
|
+
#
|
|
7
|
+
# @!attribute [r] hour
|
|
8
|
+
# @return [Integer]
|
|
9
|
+
#
|
|
10
|
+
# @!attribute [r] minute
|
|
11
|
+
# @return [Integer]
|
|
12
|
+
class Time
|
|
13
|
+
autoload :Parser, 'minutely/time/parser'
|
|
14
|
+
|
|
15
|
+
include Comparable
|
|
16
|
+
include StringAsJson
|
|
17
|
+
|
|
18
|
+
attr_reader :hour, :minute
|
|
19
|
+
alias min minute
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Builds a new `Minutely::Time`.
|
|
23
|
+
#
|
|
24
|
+
# @param hour [Integer] a number between 0 and 23
|
|
25
|
+
#
|
|
26
|
+
# @param minute [Integer] a number between 0 and 59
|
|
27
|
+
#
|
|
28
|
+
# @raise [ArgumentError] when arguments are not within the specified ranges
|
|
29
|
+
def initialize(hour, minute)
|
|
30
|
+
raise ArgumentError, 'invalid hour' unless (0..24).include?(hour)
|
|
31
|
+
raise ArgumentError, 'invalid minute' unless (0..59).include?(minute)
|
|
32
|
+
|
|
33
|
+
@hour = hour == 24 ? 0 : hour
|
|
34
|
+
@minute = minute
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Returns the begining of day.
|
|
39
|
+
#
|
|
40
|
+
# @return [Minutely::Time]
|
|
41
|
+
def self.beginning_of_day
|
|
42
|
+
new(0, 0)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# Returns the end of day.
|
|
47
|
+
#
|
|
48
|
+
# @return [Minutely::Time]
|
|
49
|
+
def self.end_of_day
|
|
50
|
+
new(23, 59)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
# Parses the given input and returns a `Minutely::Time` or `nil`,
|
|
55
|
+
# respectively.
|
|
56
|
+
#
|
|
57
|
+
# @param value [Minutely::Time, #hour, #min, Integer, String, nil]
|
|
58
|
+
#
|
|
59
|
+
# @return [Minutely::Time, nil]
|
|
60
|
+
#
|
|
61
|
+
# @raise [ArgumentError] when the object does not represent a valid time
|
|
62
|
+
def self.parse(value)
|
|
63
|
+
Time::Parser.parse(value)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# Compares the time to another one.
|
|
68
|
+
#
|
|
69
|
+
# @return [Integer]
|
|
70
|
+
def <=>(other)
|
|
71
|
+
return nil unless other.is_a?(self.class)
|
|
72
|
+
|
|
73
|
+
[hour, minute] <=> [other.hour, other.minute]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
##
|
|
77
|
+
# Gets the next minute as new `Minutely::Time`.
|
|
78
|
+
#
|
|
79
|
+
# @return [Minutely::Time]
|
|
80
|
+
def succ
|
|
81
|
+
return self.class.new((hour + 1) % 24, 0) if minute == 59
|
|
82
|
+
|
|
83
|
+
self.class.new(hour, minute + 1)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
##
|
|
87
|
+
# Converts the time to an integer representation of the value.
|
|
88
|
+
#
|
|
89
|
+
# @return [Integer]
|
|
90
|
+
def to_i
|
|
91
|
+
100 * hour + minute
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
##
|
|
95
|
+
# Converts the time to an string representation of the value.
|
|
96
|
+
#
|
|
97
|
+
# @return [String]
|
|
98
|
+
def to_s
|
|
99
|
+
[hour, minute].map { |v| v.to_s.rjust(2, '0') }.join(':')
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Minutely
|
|
4
|
+
class Time
|
|
5
|
+
##
|
|
6
|
+
# A parser that tries to convert the input value to `Minutely::Time`.
|
|
7
|
+
class Parser < Minutely::Parser
|
|
8
|
+
def parse
|
|
9
|
+
return value if value.is_a?(Time)
|
|
10
|
+
return Time.new(value.hour, value.min) if like_time?
|
|
11
|
+
|
|
12
|
+
case value
|
|
13
|
+
when nil then nil
|
|
14
|
+
when Integer then parse_integer
|
|
15
|
+
when String then parse_string
|
|
16
|
+
else raise ArgumentError, 'invalid time'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def like_time?
|
|
23
|
+
value.respond_to?(:hour) && value.respond_to?(:min)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def parse_integer
|
|
27
|
+
hour = value / 100
|
|
28
|
+
minute = value % 100
|
|
29
|
+
Time.new(hour, minute)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def parse_string
|
|
33
|
+
return nil if value.empty?
|
|
34
|
+
|
|
35
|
+
matches = value.match(/\A(?<hour>\d{1,2}):(?<minute>\d{2})\z/)
|
|
36
|
+
raise ArgumentError, 'invalid time string' unless matches
|
|
37
|
+
|
|
38
|
+
Time.new(matches[:hour].to_i, matches[:minute].to_i)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Minutely
|
|
4
|
+
##
|
|
5
|
+
# A class that represents a range of day times that is only using hours
|
|
6
|
+
# and minutes.
|
|
7
|
+
#
|
|
8
|
+
# @!attribute [r] from
|
|
9
|
+
# @return [Minutely::Time]
|
|
10
|
+
#
|
|
11
|
+
# @!attribute [r] to
|
|
12
|
+
# @return [Minutely::Time]
|
|
13
|
+
class TimeRange
|
|
14
|
+
autoload :Parser, 'minutely/time_range/parser'
|
|
15
|
+
|
|
16
|
+
include Comparable
|
|
17
|
+
include Enumerable
|
|
18
|
+
include StringAsJson
|
|
19
|
+
|
|
20
|
+
attr_reader :from, :to
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
# Builds a new `Minutely::TimeRange`.
|
|
24
|
+
#
|
|
25
|
+
# @param from [Minutely::Time, String, Integer]
|
|
26
|
+
#
|
|
27
|
+
# @param to [Minutely::Time, String, Integer]
|
|
28
|
+
#
|
|
29
|
+
# @raise [ArgumentError] when first or second argument evaluates to `nil`.
|
|
30
|
+
def initialize(from, to, exclude_end: false)
|
|
31
|
+
@from = Minutely::Time.parse(from)
|
|
32
|
+
@to = Minutely::Time.parse(to)
|
|
33
|
+
|
|
34
|
+
raise ArgumentError, 'invalid time range' if @from.nil? || @to.nil?
|
|
35
|
+
|
|
36
|
+
@exclude_end = exclude_end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
##
|
|
40
|
+
# Indicates whether the range excludes the last item.
|
|
41
|
+
#
|
|
42
|
+
# @return [Boolean]
|
|
43
|
+
def exclude_end?
|
|
44
|
+
@exclude_end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# Parses the given input and returns a `Minutely::TimeRange` or `nil`,
|
|
49
|
+
# respectively.
|
|
50
|
+
#
|
|
51
|
+
# @param value [Minutely::TimeRange, Array, Hash, String, nil]
|
|
52
|
+
#
|
|
53
|
+
# @return [Minutely::TimeRange, nil]
|
|
54
|
+
#
|
|
55
|
+
# @raise [ArgumentError] when the object does not represent a valid time
|
|
56
|
+
# range
|
|
57
|
+
#
|
|
58
|
+
# @raise [KeyError] when the given Hash does not contain the required keys
|
|
59
|
+
# (`:from` and `:to`)
|
|
60
|
+
def self.parse(value)
|
|
61
|
+
TimeRange::Parser.parse(value)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
##
|
|
65
|
+
# Compares the time range to another one.
|
|
66
|
+
#
|
|
67
|
+
# @return [Integer]
|
|
68
|
+
def <=>(other)
|
|
69
|
+
return nil unless other.is_a?(self.class)
|
|
70
|
+
return nil if exclude_end? != other.exclude_end?
|
|
71
|
+
|
|
72
|
+
[from, to] <=> [other.from, other.to]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
##
|
|
76
|
+
# Determines whether the specified time is in the range.
|
|
77
|
+
#
|
|
78
|
+
# @return [Boolean]
|
|
79
|
+
def include?(time)
|
|
80
|
+
time = Minutely::Time.parse(time)
|
|
81
|
+
end_predicate = exclude_end? ? time < to : time <= to
|
|
82
|
+
from <= time && end_predicate
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
##
|
|
86
|
+
# Iterates over all range elements and calls the given block for each
|
|
87
|
+
# element or returns a lazy enumerator when called without block.
|
|
88
|
+
#
|
|
89
|
+
# @yield [time] A block called for every range element.
|
|
90
|
+
#
|
|
91
|
+
# @yieldparam time [Minutely::Time] The range element.
|
|
92
|
+
#
|
|
93
|
+
# @return [Enumerator, void]
|
|
94
|
+
def each
|
|
95
|
+
return to_enum(:each) unless block_given?
|
|
96
|
+
|
|
97
|
+
val = from
|
|
98
|
+
|
|
99
|
+
loop do
|
|
100
|
+
yield(val) if !exclude_end? || val != to
|
|
101
|
+
break if val == to
|
|
102
|
+
|
|
103
|
+
val = val.succ
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
##
|
|
108
|
+
# Indicates whether the range spans midnight.
|
|
109
|
+
#
|
|
110
|
+
# @return [Boolean]
|
|
111
|
+
def spanning_midnight?
|
|
112
|
+
from > to
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
##
|
|
116
|
+
# Converts the time range into a Range. This does only work when the range
|
|
117
|
+
# spans midnight due to the way Ranges are based on value comparison.
|
|
118
|
+
#
|
|
119
|
+
# @raise [RuntimeError] when the time range spans midnight.
|
|
120
|
+
# @return [Range]
|
|
121
|
+
def to_r
|
|
122
|
+
raise 'Unable to convert ranges spanning midnight' if spanning_midnight?
|
|
123
|
+
|
|
124
|
+
return from...to if exclude_end?
|
|
125
|
+
|
|
126
|
+
from..to
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
##
|
|
130
|
+
# Converts the time range into a string.
|
|
131
|
+
#
|
|
132
|
+
# @return [String]
|
|
133
|
+
def to_s
|
|
134
|
+
[from, to].join('-')
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Minutely
|
|
4
|
+
class TimeRange
|
|
5
|
+
##
|
|
6
|
+
# A parser that tries to convert the input value to `Minutely::TimeRange`.
|
|
7
|
+
class Parser < Minutely::Parser
|
|
8
|
+
def parse
|
|
9
|
+
case value
|
|
10
|
+
when nil then nil
|
|
11
|
+
when Array then parse_array(value)
|
|
12
|
+
when Hash then parse_hash
|
|
13
|
+
when Range then parse_range
|
|
14
|
+
when String then parse_string
|
|
15
|
+
when TimeRange then value
|
|
16
|
+
else invalid_time_range!
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def invalid_time_range!
|
|
23
|
+
raise ArgumentError, 'invalid time range'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def parse_array(items)
|
|
27
|
+
invalid_time_range! if items.length != 2 || include_empty_item?(items)
|
|
28
|
+
|
|
29
|
+
TimeRange.new(*items)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def parse_hash
|
|
33
|
+
return nil if value.empty?
|
|
34
|
+
|
|
35
|
+
parse_array(value.fetch_values(:from, :to))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def parse_range
|
|
39
|
+
TimeRange.new(value.begin, value.end, exclude_end: value.exclude_end?)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def parse_string
|
|
43
|
+
return nil if value.empty?
|
|
44
|
+
|
|
45
|
+
items = value.split('-').map(&:strip)
|
|
46
|
+
parse_array(items)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def include_empty_item?(items)
|
|
50
|
+
items.any? do |item|
|
|
51
|
+
item.nil? || (item.respond_to?(:empty?) && item.empty?)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/minutely.gemspec
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/minutely/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'minutely'
|
|
7
|
+
spec.version = Minutely::VERSION
|
|
8
|
+
spec.authors = ['Tobias Casper']
|
|
9
|
+
spec.email = ['tobias.casper@gmail.com']
|
|
10
|
+
spec.licenses = ['MIT']
|
|
11
|
+
|
|
12
|
+
spec.summary = 'Classes for representing the time of a day by using only hours and minutes'
|
|
13
|
+
spec.homepage = 'https://github.com/tlux/minutely'
|
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
|
|
15
|
+
|
|
16
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
17
|
+
|
|
18
|
+
# Specify which files should be added to the gem when it is released.
|
|
19
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
20
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
21
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
22
|
+
f.match(%r{\A(?:test|spec|features)/})
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
spec.bindir = 'exe'
|
|
26
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
27
|
+
spec.require_paths = ['lib']
|
|
28
|
+
|
|
29
|
+
# Uncomment to register a new dependency of your gem
|
|
30
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
|
31
|
+
|
|
32
|
+
# For more information and examples about making a new gem, checkout our
|
|
33
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
34
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: minutely
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 3.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tobias Casper
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2021-04-09 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description:
|
|
14
|
+
email:
|
|
15
|
+
- tobias.casper@gmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- ".coveralls.yml"
|
|
21
|
+
- ".gitignore"
|
|
22
|
+
- ".gitlab-ci.yml"
|
|
23
|
+
- ".rspec"
|
|
24
|
+
- ".rubocop.yml"
|
|
25
|
+
- ".tool-versions"
|
|
26
|
+
- ".travis.yml"
|
|
27
|
+
- ".vscode/extensions.json"
|
|
28
|
+
- ".vscode/settings.json"
|
|
29
|
+
- ".vscode/tasks.json"
|
|
30
|
+
- Gemfile
|
|
31
|
+
- Gemfile.lock
|
|
32
|
+
- LICENSE
|
|
33
|
+
- README.md
|
|
34
|
+
- Rakefile
|
|
35
|
+
- bin/console
|
|
36
|
+
- bin/setup
|
|
37
|
+
- lib/minutely.rb
|
|
38
|
+
- lib/minutely/parser.rb
|
|
39
|
+
- lib/minutely/string_as_json.rb
|
|
40
|
+
- lib/minutely/time.rb
|
|
41
|
+
- lib/minutely/time/parser.rb
|
|
42
|
+
- lib/minutely/time_range.rb
|
|
43
|
+
- lib/minutely/time_range/parser.rb
|
|
44
|
+
- lib/minutely/version.rb
|
|
45
|
+
- minutely.gemspec
|
|
46
|
+
homepage: https://github.com/tlux/minutely
|
|
47
|
+
licenses:
|
|
48
|
+
- MIT
|
|
49
|
+
metadata:
|
|
50
|
+
homepage_uri: https://github.com/tlux/minutely
|
|
51
|
+
post_install_message:
|
|
52
|
+
rdoc_options: []
|
|
53
|
+
require_paths:
|
|
54
|
+
- lib
|
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: 2.7.0
|
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
|
+
requirements:
|
|
62
|
+
- - ">="
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
version: '0'
|
|
65
|
+
requirements: []
|
|
66
|
+
rubygems_version: 3.1.2
|
|
67
|
+
signing_key:
|
|
68
|
+
specification_version: 4
|
|
69
|
+
summary: Classes for representing the time of a day by using only hours and minutes
|
|
70
|
+
test_files: []
|