minutely 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
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
@@ -0,0 +1,4 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
4
+ --order random
data/.rubocop.yml ADDED
@@ -0,0 +1,14 @@
1
+ AllCops:
2
+ Exclude:
3
+ - '*.gemspec'
4
+ - 'vendor/**/*'
5
+ NewCops: enable
6
+ SuggestExtensions: false
7
+ TargetRubyVersion: 2.7
8
+
9
+ Layout/LineLength:
10
+ Max: 80
11
+
12
+ Metrics/BlockLength:
13
+ Exclude:
14
+ - 'spec/**/*.rb'
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 2.7.0
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - '2.7.0'
5
+ before_install: gem install bundler -v 2.2.15
6
+ script:
7
+ - bundle exec rubocop
8
+ - bundle exec rspec
@@ -0,0 +1,5 @@
1
+ {
2
+ "recommendations": [
3
+ "rebornix.ruby"
4
+ ]
5
+ }
@@ -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
+ }
@@ -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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in minutely.gemspec
6
+ gemspec
7
+
8
+ gem 'coveralls'
9
+ gem 'rake', '~> 13.0'
10
+ gem 'rspec', '~> 3.0'
11
+ gem 'rubocop'
12
+ gem 'yard'
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
+ [![Build Status](https://travis-ci.org/tlux/minutely.svg?branch=master)](https://travis-ci.org/tlux/minutely)
4
+ [![Coverage Status](https://coveralls.io/repos/github/tlux/minutely/badge.svg?branch=master)](https://coveralls.io/github/tlux/minutely?branch=master)
5
+ [![Gem Version](https://badge.fury.io/rb/minutely.svg)](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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec 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 '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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minutely
4
+ VERSION = '3.0.0'
5
+ 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: []