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 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: []