time_cursor 1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 13d6fc3914b795f5587cc827bd66beb87ed3784a6425c6567cb3b38225a0f6ec
4
+ data.tar.gz: 8383cd520881f529af5009de0b9899ed7843d038f6e9341bbd356b4d8e38139c
5
+ SHA512:
6
+ metadata.gz: 070fb20e91c91a491d49ac8b564b52b4e78a503810a4c52acfa289d2c9ec511ff4c2ffa593c220365ddb4e105ab7a2ac499e7ef60759de5cd3dcda14234b07f3
7
+ data.tar.gz: 7f6ed476e9cd62dc4aac181050f94729d6b607c585a99258a4e216276ccd6d6385aeaa1656c60b333be4a2da95d36a890c73dad1a3fc18c9e1456a8c865f7e29
@@ -0,0 +1,23 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ /vendor/
16
+ *.pid
17
+ *.log
18
+ *.log.*
19
+ /var/
20
+ /log/
21
+ *.swp
22
+ /.rspec_status
23
+
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
@@ -0,0 +1,143 @@
1
+ = TimeCursor
2
+
3
+ TimeCursor is a library to get the next or previous date time along the rules that are given in a crontab-like format.
4
+
5
+ == Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ [source,ruby]
10
+ ----
11
+ gem 'time_cursor'
12
+ ----
13
+
14
+ And then execute:
15
+
16
+ $ bundle install
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install time_cursor
21
+ or
22
+ $ gem install -l time_cursor-x.x.x.gem
23
+
24
+ == Usage
25
+
26
+ === Initialize matcher
27
+
28
+ [source,ruby]
29
+ ----
30
+ time_cursor = TimeCursor.new( at: '2015-02-26 01:23' )
31
+ time_cursor = TimeCursor.new( year: 2015, month: 2, day: 26, hour: 1, min: 23 )
32
+
33
+ time_cursor = TimeCursor.new( cron: '0 9,17 * * mon-fri' )
34
+ time_cursor = TimeCursor.new( wday: 'mon-fri', hour: [9,17] )
35
+
36
+ time_cursor = TimeCursor.new( cron: '0 12 1-7 * sun' )
37
+ time_cursor = TimeCursor.new( day: 1..7, wday: 'sun', hour: 12 )
38
+
39
+ time_cursor = TimeCursor.new( sec: '*/10' )
40
+ ----
41
+
42
+ === Get next time
43
+
44
+ [source,ruby]
45
+ ----
46
+ time_cursor = TimeCursor.new( cron: '* * * * *' ) = => =<TimeCursor::Matcher>
47
+ time = time_cursor.next( '2015-02-26 01:23' ) = => 2015-02-26 01:24:00
48
+ time = time_cursor.next( time ) = => 2015-02-26 01:25:00
49
+ ----
50
+
51
+ === Get previous time
52
+
53
+ [source,ruby]
54
+ ----
55
+ time_cursor = TimeCursor.new( hour: '*/3' ) = => =<TimeCursor::Matcher>
56
+ time = time_cursor.prev( '2015-02-26 01:23' ) = => 2015-02-26 00:00:00
57
+ time = time_cursor.prev( time ) = => 2015-02-25 21:00:00
58
+ ----
59
+
60
+ === Check to match
61
+
62
+ [source,ruby]
63
+ ----
64
+ time_cursor = TimeCursor.new( day: 26, hour: 12 ) = => =<TimeCursor::Matcher>
65
+ time = time_cursor.match( '2015-02-26 12:00' ) = => 2015-02-26 12:00:00
66
+ time = time_cursor.match( '2015-02-26 00:00' ) = => nil
67
+ ----
68
+
69
+ == Reference
70
+
71
+ === Create a new TimeCursor with conditions.
72
+
73
+ [source,ruby]
74
+ ----
75
+ TimeCursor.new( at: nil, cron: nil, year: nil, month: nil, day: nil, wday: nil, hour: nil, min: nil, sec: 0, msec: nil )
76
+ ----
77
+
78
+ * Result:
79
+ ** TimeCursor::Matcher object.
80
+
81
+ * Parameter:
82
+ ** at: time. Time or String object. (default: nil)
83
+ ** cron: set of min, hour, day, month, wday pattern. (default: nil)
84
+ ** year: year. unlimited range is denied. (default: nil)
85
+ ** month: month. 1..12, jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec. (default: nil)
86
+ ** day: day of month. 1..31. (default: nil)
87
+ ** wday: day of week. 0..7, sun, mon, tue, wed, thr, fri, sat. (default: nil)
88
+ ** hour: minute. 0..23. (default: nil)
89
+ ** min: minute. 0..59. (default: nil)
90
+ ** sec: second. 0..59. (default: 0)
91
+ ** msec: millisecond. 0..999. (default: nil), If msec is assigned, then other parameters are ignored.
92
+ In detail, it can use "*" as wildcard.
93
+
94
+ === Get next time
95
+
96
+ [source,ruby]
97
+ ----
98
+ TimeCursor::Matcher#next( time = Time.now )
99
+ ----
100
+
101
+ * Result:
102
+ ** Next Time object or nil.
103
+
104
+ * Parameter:
105
+ ** time: base time. Time or String object. (default: Time.now)
106
+
107
+ === Get previous time
108
+
109
+ [source,ruby]
110
+ ----
111
+ TimeCursor::Matcher#prev( time = Time.now )
112
+ ----
113
+
114
+ * Result:
115
+ ** Previous Time object or nil.
116
+
117
+ * Parameter:
118
+ ** time: base time. Time or String object. (default: Time.now)
119
+
120
+ === Check to match
121
+
122
+ [source,ruby]
123
+ ----
124
+ TimeCursor::Matcher#match( time )
125
+ ----
126
+
127
+ * Result:
128
+ ** Time object or nil.
129
+
130
+ * Parameter:
131
+ ** time: Time or String object for matching.
132
+
133
+ == Caution
134
+
135
+ Because it is calculated in local time, it does not work as expected when switching to daylight saving time.
136
+
137
+ == Contributing
138
+
139
+ Bug reports and pull requests are welcome on GitHub at https://github.com/arimay/time_cursor.
140
+
141
+ == License
142
+
143
+ The gem is available as open source under the terms of the http://opensource.org/licenses/MIT[MIT License].
@@ -0,0 +1,143 @@
1
+ = TimeCursor
2
+
3
+ TimeCursor は crontab のような書式で与えた規則で, 次または前の日時を得るためのライブラリ.
4
+
5
+ == 導入
6
+
7
+ アプリの Gemfile にこの行を追加
8
+
9
+ [source,ruby]
10
+ ----
11
+ gem 'time_cursor'
12
+ ----
13
+
14
+ それから実行
15
+
16
+ $ bundle install
17
+
18
+ または次のように手動で導入
19
+
20
+ $ gem install time_cursor
21
+ or
22
+ $ gem install -l time_cursor-x.x.x.gem
23
+
24
+ == 使い方
25
+
26
+ === マッチャを初期化
27
+
28
+ [source,ruby]
29
+ ----
30
+ time_cursor = TimeCursor.new( at: '2015-02-26 01:23' )
31
+ time_cursor = TimeCursor.new( year: 2015, month: 2, day: 26, hour: 1, min: 23 )
32
+
33
+ time_cursor = TimeCursor.new( cron: '0 9,17 * * mon-fri' )
34
+ time_cursor = TimeCursor.new( wday: 'mon-fri', hour: [9,17] )
35
+
36
+ time_cursor = TimeCursor.new( cron: '0 12 1-7 * sun' )
37
+ time_cursor = TimeCursor.new( day: 1..7, wday: 'sun', hour: 12 )
38
+
39
+ time_cursor = TimeCursor.new( sec: '*/10' )
40
+ ----
41
+
42
+ === 次の日時を得る
43
+
44
+ [source,ruby]
45
+ ----
46
+ time_cursor = TimeCursor.new( cron: '* * * * *' ) = => =<TimeCursor::Matcher>
47
+ time = time_cursor.next( '2015-02-26 01:23' ) = => 2015-02-26 01:24:00
48
+ time = time_cursor.next( time ) = => 2015-02-26 01:25:00
49
+ ----
50
+
51
+ === 前の日時を得る
52
+
53
+ [source,ruby]
54
+ ----
55
+ time_cursor = TimeCursor.new( hour: '*/3' ) = => =<TimeCursor::Matcher>
56
+ time = time_cursor.prev( '2015-02-26 01:23' ) = => 2015-02-26 00:00:00
57
+ time = time_cursor.prev( time ) = => 2015-02-25 21:00:00
58
+ ----
59
+
60
+ === マッチするか調べる
61
+
62
+ [source,ruby]
63
+ ----
64
+ time_cursor = TimeCursor.new( day: 26, hour: 12 ) = => =<TimeCursor::Matcher>
65
+ time = time_cursor.match( '2015-02-26 12:00' ) = => 2015-02-26 12:00:00
66
+ time = time_cursor.match( '2015-02-26 00:00' ) = => nil
67
+ ----
68
+
69
+ == リファレンス
70
+
71
+ === 条件を指定して、新たな TimeCursor を作成する.
72
+
73
+ [source,ruby]
74
+ ----
75
+ TimeCursor.new( at: nil, cron: nil, year: nil, month: nil, day: nil, wday: nil, hour: nil, min: nil, sec: 0, msec: nil )
76
+ ----
77
+
78
+ * Result:
79
+ ** TimeCursor::Matcher オブジェクト.
80
+
81
+ * Parameter:
82
+ ** at: 日時. Time または String オブジェクト. (default: nil)
83
+ ** cron: 分、時、日、月、曜パターンのセット. (default: nil)
84
+ ** year: 年. 範囲制限なしは拒否される. (default: nil)
85
+ ** month: 月. 1..12, jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec. (default: nil)
86
+ ** day: 日. 1..31. (default: nil)
87
+ ** wday: 曜. 0..7, sun, mon, tue, wed, thr, fri, sat. (default: nil)
88
+ ** hour: 時. 0..23. (default: nil)
89
+ ** min: 分. 0..59. (default: nil)
90
+ ** sec: 秒. 0..59. (default: 0)
91
+ ** msec: ミリ秒. 0..999. (default: nil), ミリ秒が指定されたとき, 他のパラメータは無視される.
92
+ 詳細では, ワイルドカードとして "*" を使用できる.
93
+
94
+ === 次の日時を得る.
95
+
96
+ [source,ruby]
97
+ ----
98
+ TimeCursor::Matcher#next( time = Time.now )
99
+ ----
100
+
101
+ * Result:
102
+ ** 次の Time オブジェクト または nil.
103
+
104
+ * Parameter:
105
+ ** time: 基準日時. Time または String オブジェクト. (default: Time.now)
106
+
107
+ === 前の日時を得る.
108
+
109
+ [source,ruby]
110
+ ----
111
+ TimeCursor::Matcher#prev( time = Time.now )
112
+ ----
113
+
114
+ * Result:
115
+ ** 前の Time オブジェクト または nil.
116
+
117
+ * Parameter:
118
+ ** time: 基準日時. Time または String オブジェクト. (default: Time.now)
119
+
120
+ === 日時が条件に適合するか調べる.
121
+
122
+ [source,ruby]
123
+ ----
124
+ TimeCursor::Matcher#match( time )
125
+ ----
126
+
127
+ * Result:
128
+ ** Time object or nil.
129
+
130
+ * Parameter:
131
+ ** time: 適合検査のための Time または String オブジェクト.
132
+
133
+ == 注意
134
+
135
+ 地域時刻で計算しているため、夏時間の切り替わりにおいて期待しない挙動となる.
136
+
137
+ == 貢献
138
+
139
+ 不具合報告とプルリクエストは GitHub https://github.com/arimay/time_cursor まで.
140
+
141
+ == ライセンス
142
+
143
+ この Gem は、 http://opensource.org/licenses/MIT[MITライセンス] の条件に基づいてオープンソースとして入手できる.
@@ -0,0 +1,96 @@
1
+ require "bundler/gem_helper"
2
+ require "bundler/gem_tasks"
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
8
+
9
+ class Bundler::GemHelper
10
+
11
+ def git_archive( dir = "../#{Time.now.strftime("%Y%m%d")}" )
12
+ FileUtils.mkdir_p dir
13
+ dest_path = File.join(dir, "#{name}-#{version}.zip")
14
+ cmnd = "git archive --format zip --prefix=#{name}/ HEAD > #{dest_path}"
15
+
16
+ out, code = sh_with_status( cmnd )
17
+ raise "Couldn't archive gem," unless code == 0
18
+
19
+ Bundler.ui.confirm "#{name} #{version} archived to #{dest_path}."
20
+ end
21
+
22
+ def git_push
23
+ ver = version.to_s
24
+
25
+ cmnd = "git push origin #{ver} "
26
+ out, code = sh_with_status( cmnd )
27
+ raise "Couldn't git push origin." unless code == 0
28
+
29
+ cmnd = "git push "
30
+ out, code = sh_with_status( cmnd )
31
+ raise "Couldn't git push." unless code == 0
32
+
33
+ Bundler.ui.confirm "Git Push #{ver}."
34
+ end
35
+
36
+ def update_version( new_version )
37
+ version_filename = %x[ find . -type f -name "version.rb" | grep -v vendor | head -1 ].chomp
38
+ version_pathname = File.expand_path( version_filename )
39
+ lines = File.open( version_pathname ).read
40
+ lines = lines.gsub( /VERSION\s*=\s*\"\d+\.\d+\.\d+\"/, "VERSION = \"#{new_version}\"" )
41
+ File.open( version_pathname, "w" ) do |file|
42
+ file.write( lines )
43
+ end
44
+
45
+ cmnd = "git add #{version_pathname} "
46
+ out, code = sh_with_status( cmnd )
47
+ raise "Couldn't git add," unless code == 0
48
+
49
+ cmnd = "git commit -m '#{new_version}' "
50
+ out, code = sh_with_status( cmnd )
51
+ raise "Couldn't git commit." unless code == 0
52
+
53
+ cmnd = "git tag #{new_version} "
54
+ out, code = sh_with_status( cmnd )
55
+ raise "Couldn't git tag." unless code == 0
56
+
57
+ Bundler.ui.confirm "Update Tags to #{new_version}."
58
+ end
59
+
60
+ end
61
+
62
+ Bundler::GemHelper.new(Dir.pwd).instance_eval do
63
+
64
+ desc "Archive #{name}-#{version}.zip from repository"
65
+ task 'zip' do
66
+ git_archive
67
+ end
68
+
69
+ desc "Git Push"
70
+ task 'push' do
71
+ git_push
72
+ end
73
+
74
+ desc "Update Version Tiny"
75
+ task 'tiny' do
76
+ major, minor, tiny = version.to_s.split('.')
77
+ new_version = [major, minor, tiny.to_i + 1].join('.')
78
+ update_version( new_version )
79
+ end
80
+
81
+ desc "Update Version Minor"
82
+ task 'minor' do
83
+ major, minor, tiny = version.to_s.split('.')
84
+ new_version = [major, minor.to_i + 1, 0].join('.')
85
+ update_version( new_version )
86
+ end
87
+
88
+ desc "Update Version Major"
89
+ task 'major' do
90
+ major, minor, tiny = version.to_s.split('.')
91
+ new_version = [major.to_i + 1, 0, 0].join('.')
92
+ update_version( new_version )
93
+ end
94
+
95
+ end
96
+
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "time_cursor"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,11 @@
1
+ require "time_cursor/version"
2
+ require "time_cursor/base"
3
+ require "time_cursor/elements"
4
+ require "time_cursor/matcher"
5
+
6
+ module TimeCursor
7
+ class << self
8
+ include TimeCursor::Base
9
+ end
10
+ end
11
+
@@ -0,0 +1,103 @@
1
+ require "time"
2
+ require "date"
3
+
4
+ module TimeCursor
5
+
6
+ module Base
7
+
8
+ def new( at: nil, cron: nil, year: nil, month: nil, day: nil, wday: nil, hour: nil, min: nil, sec: 0, msec: nil )
9
+ if !at.nil?
10
+ at( at )
11
+ elsif !cron.nil?
12
+ cron( cron )
13
+ else
14
+ detail( year: year, month: month, day: day, wday: wday, hour: hour, min: min, sec: sec, msec: msec )
15
+ end
16
+ end
17
+
18
+ def at( time )
19
+ case time
20
+ when String
21
+ date = Date.parse( time ) rescue nil
22
+ time = Time.parse( time )
23
+ when Time
24
+ date = time.to_date
25
+ end
26
+
27
+ case time
28
+ when Date
29
+ Matcher.new(
30
+ year: time.year,
31
+ month: time.month,
32
+ day: time.day,
33
+ hour: 0,
34
+ min: 0,
35
+ sec: 0,
36
+ )
37
+ when Time
38
+ if date.nil?
39
+ Matcher.new(
40
+ hour: time.hour,
41
+ min: time.min,
42
+ sec: time.sec,
43
+ )
44
+ else
45
+ Matcher.new(
46
+ year: time.year,
47
+ month: time.month,
48
+ day: time.day,
49
+ hour: time.hour,
50
+ min: time.min,
51
+ sec: time.sec,
52
+ )
53
+ end
54
+ else
55
+ raise ArgumentError, "invalid class : '#{datetime}'"
56
+ end
57
+ end
58
+
59
+ def cron( patterns )
60
+ case patterns
61
+ when String
62
+ else
63
+ raise ArgumentError, "invalid class : '#{patterns}'"
64
+ end
65
+ a = patterns.split("\s")
66
+ unless a.size == 5
67
+ raise ArgumentError, "too/few count of fields : '#{patterns}'"
68
+ end
69
+ Matcher.new(
70
+ month: a[3],
71
+ day: a[2],
72
+ wday: a[4],
73
+ hour: a[1],
74
+ min: a[0],
75
+ )
76
+ end
77
+
78
+ def detail( year: nil, month: nil, day: nil, wday: nil, hour: nil, min: nil, sec: 0, msec: nil )
79
+ if msec.nil?
80
+ month ||= 1 unless year.nil?
81
+ day ||= 1 unless month.nil?
82
+ hour ||= 0 unless ( day.nil? && wday.nil? )
83
+ min ||= 0 unless hour.nil?
84
+ Matcher.new(
85
+ year: year,
86
+ month: month,
87
+ day: day,
88
+ wday: wday,
89
+ hour: hour,
90
+ min: min,
91
+ sec: sec,
92
+ )
93
+ else
94
+ Matcher.new(
95
+ msec: msec,
96
+ )
97
+ end
98
+ end
99
+
100
+ end
101
+
102
+ end
103
+
@@ -0,0 +1,111 @@
1
+
2
+ module TimeCursor
3
+
4
+ class Elements < Array
5
+
6
+ DATE_ALIAS = {
7
+ 'sun'=>'0', 'mon'=>'1', 'tue'=>'2', 'wed'=>'3', 'thr'=>'4', 'fri'=>'5', 'sat'=>'6',
8
+ 'jan'=>'1', 'feb'=>'2', 'mar'=>'3', 'apr'=>'4', 'may'=>'5', 'jun'=>'6',
9
+ 'jul'=>'7', 'aug'=>'8', 'sep'=>'9', 'oct'=>'10', 'nov'=>'11', 'dec'=>'12',
10
+ }
11
+ RE_ALL = %r|\A\*(/1)?\Z|
12
+ RE_WILD = %r|\A\*(/(\d+))?\Z|
13
+ RE_LIST = %r|\A(\d+)(-(\d+)(/(\d+))?)?\Z|
14
+
15
+ def self.build( target, range = nil )
16
+ case target
17
+ when Array
18
+ target = target.join(',')
19
+
20
+ when Symbol
21
+ target = target.to_s
22
+
23
+ end
24
+
25
+ case target
26
+ when NilClass
27
+ Elements.new( [] )
28
+
29
+ when Range
30
+ Elements.new( target.to_a )
31
+
32
+ when Numeric
33
+ Elements.new( [target] )
34
+
35
+ when String
36
+ factors = DATE_ALIAS.inject( target.downcase ) do |s, (k, v)| s.gsub( k, v ) end
37
+ items = factors.split(',').map do |factor|
38
+ if m = RE_ALL.match( factor )
39
+ []
40
+ elsif m = RE_WILD.match( factor )
41
+ if range.nil?
42
+ raise ArgumentError, "disallow wildcard without range : '#{target}'"
43
+ end
44
+ from = range.first
45
+ to = range.last
46
+ step = ( m[2] || 1 ).to_i
47
+ expand( from, to, step, range )
48
+ elsif m = RE_LIST.match( factor )
49
+ from = ( m[1] ).to_i
50
+ to = ( m[3] || m[1] ).to_i
51
+ step = ( m[5] || 1 ).to_i
52
+ expand( from, to, step, range )
53
+ else
54
+ raise ArgumentError, "format invalid : '#{target}'"
55
+ end
56
+ end
57
+ Elements.new( items.flatten.sort.uniq )
58
+
59
+ else
60
+ raise ArgumentError, "type invalid : '#{target}'"
61
+
62
+ end
63
+ end
64
+
65
+ def self.expand( from, to, step, range )
66
+ result = []
67
+ cur = from
68
+ if from <= to
69
+ loop do
70
+ result << cur
71
+ cur += step
72
+ break if cur > to
73
+ end
74
+ else
75
+ loop do
76
+ result << cur
77
+ cur += step
78
+ break if cur > range.last
79
+ end
80
+ cur = range.first
81
+ loop do
82
+ result << cur
83
+ cur += step
84
+ break if cur > to
85
+ end
86
+ end
87
+ result
88
+ end
89
+
90
+ def right( item )
91
+ 0.upto(self.size-1) do |i|
92
+ return self[i] if item < self[i]
93
+ end
94
+ nil
95
+ end
96
+
97
+ def left( item )
98
+ (self.size-1).downto(0) do |i|
99
+ return self[i] if self[i] < item
100
+ end
101
+ nil
102
+ end
103
+
104
+ def correspond?( item )
105
+ self.empty? || self.include?( item )
106
+ end
107
+
108
+ end
109
+
110
+ end
111
+
@@ -0,0 +1,356 @@
1
+ require "time"
2
+ require "date"
3
+
4
+ module TimeCursor
5
+
6
+ class Matcher
7
+
8
+ def initialize( year: '*', month: '*', day: '*', wday: '*', hour: '*', min: '*', sec: 0, msec: nil )
9
+ if msec.nil?
10
+ @years = Elements.build( year )
11
+ @months = Elements.build( month, 1..12 )
12
+ @days = Elements.build( day, 1..31 )
13
+ @wdays = Elements.build( wday, 0..6 )
14
+ @hours = Elements.build( hour, 0..23 )
15
+ @mins = Elements.build( min, 0..59 )
16
+ @secs = Elements.build( sec, 0..59 )
17
+ @usecs = Elements.new
18
+
19
+ if !@years.empty? || !@months.empty? || !@days.empty? || !@wdays.empty?
20
+ @next_for = :next_for_date
21
+ @prev_for = :prev_for_date
22
+
23
+ elsif !@hours.empty? || !@mins.empty?
24
+ @next_for = :next_for_time
25
+ @prev_for = :prev_for_time
26
+
27
+ else
28
+ @secs = Elements.build( '0-59', 0..59 ) if @secs.empty?
29
+ @next_for = :next_for_sec
30
+ @prev_for = :prev_for_sec
31
+
32
+ end
33
+
34
+ else
35
+ @years = Elements.new
36
+ @months = Elements.new
37
+ @days = Elements.new
38
+ @wdays = Elements.new
39
+ @hours = Elements.new
40
+ @mins = Elements.new
41
+ @secs = Elements.new
42
+ @usecs = Elements.build( msec, 0..999 )
43
+ @usecs.each_with_index do |item, ndx|
44
+ @usecs[ndx] *= 1000
45
+ end
46
+
47
+ @next_for = :next_for_usec
48
+ @prev_for = :prev_for_usec
49
+
50
+ end
51
+ end
52
+
53
+ def next( time = Time.now )
54
+ case time
55
+ when Time
56
+ when String
57
+ time = Time.parse( time )
58
+ else
59
+ raise ArgumentError, "can not parse as time."
60
+ end
61
+ send( @next_for, time )
62
+ end
63
+
64
+ def prev( time = Time.now )
65
+ case time
66
+ when Time
67
+ when String
68
+ time = Time.parse( time )
69
+ else
70
+ raise ArgumentError, "can not parse as time."
71
+ end
72
+ send( @prev_for, time )
73
+ end
74
+
75
+ def match( time = Time.now )
76
+ case time
77
+ when Time
78
+ when String
79
+ time = Time.parse( time )
80
+ else
81
+ raise ArgumentError, "can not parse as time."
82
+ end
83
+
84
+ if @years.correspond?( time.year ) &&
85
+ @months.correspond?( time.month ) &&
86
+ @days.correspond?( time.day ) &&
87
+ @hours.correspond?( time.hour ) &&
88
+ @mins.correspond?( time.min ) &&
89
+ @secs.correspond?( time.sec )
90
+ time
91
+ else
92
+ nil
93
+ end
94
+ end
95
+
96
+ # private
97
+
98
+ def next_for_date( time )
99
+ time += 1
100
+ loop do
101
+ if !@years.correspond?( year = time.year )
102
+ year = @years.right( year )
103
+ return time = nil if year.nil?
104
+ time = forward_to_year( time, year )
105
+
106
+ elsif !@months.correspond?( month = time.month )
107
+ month = @months.right( month ) || @months.first
108
+ time = forward_to_month( time, month )
109
+
110
+ elsif !@wdays.correspond?( wday = time.wday )
111
+ wday = @wdays.right( wday ) || @wdays.first
112
+ time = forward_to_wday( time, wday )
113
+
114
+ elsif !@days.correspond?( day = time.day )
115
+ day = @days.right( day ) || @days.first
116
+ time = forward_to_day( time, day )
117
+
118
+ elsif !@hours.correspond?( hour = time.hour )
119
+ hour = @hours.right( hour ) || @hours.first
120
+ time = forward_to_hour( time, hour )
121
+
122
+ elsif !@mins.correspond?( min = time.min )
123
+ min = @mins.right( min ) || @mins.first
124
+ time = forward_to_min( time, min )
125
+
126
+ elsif !@secs.correspond?( sec = time.sec )
127
+ sec = @secs.right( sec ) || @secs.first
128
+ time = forward_to_sec( time, sec )
129
+
130
+ else
131
+ return time
132
+
133
+ end
134
+ end
135
+ end
136
+
137
+ def next_for_time( time )
138
+ time += 1
139
+ loop do
140
+ if !@hours.correspond?( hour = time.hour )
141
+ hour = @hours.right( hour ) || @hours.first
142
+ time = forward_to_hour( time, hour )
143
+
144
+ elsif !@mins.correspond?( min = time.min )
145
+ min = @mins.right( min ) || @mins.first
146
+ time = forward_to_min( time, min )
147
+
148
+ elsif !@secs.correspond?( sec = time.sec )
149
+ sec = @secs.right( sec ) || @secs.first
150
+ time = forward_to_sec( time, sec )
151
+
152
+ else
153
+ return time
154
+
155
+ end
156
+ end
157
+ end
158
+
159
+ def next_for_sec( time )
160
+ sec = @secs.right( time.sec ) || @secs.first
161
+ time = forward_to_sec( time, sec )
162
+ end
163
+
164
+ def next_for_usec( time )
165
+ usec = @usecs.right( time.usec ) || @usecs.first
166
+ time = forward_to_usec( time, usec )
167
+ end
168
+
169
+ def prev_for_date( time )
170
+ time -= 1
171
+ loop do
172
+ if !@years.correspond?( year = time.year )
173
+ year = @years.left( year )
174
+ return time = nil if year.nil?
175
+ time = back_to_year( time, year )
176
+
177
+ elsif !@months.correspond?( month = time.month )
178
+ month = @months.left( month ) || @months.last
179
+ time = back_to_month( time, month )
180
+
181
+ elsif !@wdays.correspond?( wday = time.wday )
182
+ wday = @wdays.left( wday ) || @wdays.last
183
+ time = back_to_wday( time, wday )
184
+
185
+ elsif !@days.correspond?( day = time.day )
186
+ day = @days.left( day ) || @days.last
187
+ time = back_to_day( time, day )
188
+
189
+ elsif !@hours.correspond?( hour = time.hour )
190
+ hour = @hours.left( hour ) || @hours.last
191
+ time = back_to_hour( time, hour )
192
+
193
+ elsif !@mins.correspond?( min = time.min )
194
+ min = @mins.left( min ) || @mins.last
195
+ time = back_to_min( time, min )
196
+
197
+ elsif !@secs.correspond?( sec = time.sec )
198
+ sec = @secs.left( sec ) || @secs.last
199
+ time = back_to_sec( time, sec )
200
+
201
+ else
202
+ return time
203
+
204
+ end
205
+ end
206
+ end
207
+
208
+ def prev_for_time( time )
209
+ time -= 1
210
+ loop do
211
+ if !@hours.correspond?( hour = time.hour )
212
+ hour = @hours.left( hour ) || @hours.last
213
+ time = back_to_hour( time, hour )
214
+
215
+ elsif !@mins.correspond?( min = time.min )
216
+ min = @mins.left( min ) || @mins.last
217
+ time = back_to_min( time, min )
218
+
219
+ elsif !@secs.correspond?( sec = time.sec )
220
+ sec = @secs.left( sec ) || @secs.last
221
+ time = back_to_sec( time, sec )
222
+
223
+ else
224
+ return time
225
+
226
+ end
227
+ end
228
+ end
229
+
230
+ def prev_for_sec( time )
231
+ sec = @secs.left( time.sec ) || @secs.last
232
+ time = back_to_sec( time, sec )
233
+ end
234
+
235
+ def prev_for_usec( time )
236
+ usec = @usecs.left( time.usec ) || @usecs.last
237
+ time = back_to_usec( time, usec )
238
+ end
239
+
240
+ def forward_to_year( time, year )
241
+ if time.year < year
242
+ Time.new( year )
243
+ else
244
+ nil
245
+ end
246
+ end
247
+
248
+ def forward_to_month( time, month )
249
+ date = Date.new( time.year, time.month, time.day )
250
+ loop do
251
+ date = date.next_month
252
+ break if date.month == month
253
+ end
254
+ Time.new( date.year, date.month )
255
+ end
256
+
257
+ def forward_to_day( time, day )
258
+ date = Date.new( time.year, time.month, time.day )
259
+ loop do
260
+ date = date.next_day
261
+ break if date.day == day
262
+ end
263
+ Time.new( date.year, date.month, date.day )
264
+ end
265
+
266
+ def forward_to_wday( time, wday )
267
+ date = Date.new( time.year, time.month, time.day )
268
+ loop do
269
+ date = date.next_day
270
+ break if date.wday == wday
271
+ end
272
+ Time.new( date.year, date.month, date.day )
273
+ end
274
+
275
+ def forward_to_hour( time, hour )
276
+ time += 60 * 60 * 24 if hour <= time.hour
277
+ Time.new( time.year, time.month, time.day, hour )
278
+ end
279
+
280
+ def forward_to_min( time, min )
281
+ time += 60 * 60 if min <= time.min
282
+ Time.new( time.year, time.month, time.day, time.hour, min )
283
+ end
284
+
285
+ def forward_to_sec( time, sec )
286
+ time += 60 if sec <= time.sec
287
+ Time.new( time.year, time.month, time.day, time.hour, time.min, sec )
288
+ end
289
+
290
+ def forward_to_usec( time, usec )
291
+ time += 1 if usec <= time.usec
292
+ Time.local( time.year, time.month, time.day, time.hour, time.min, time.sec, usec )
293
+ end
294
+
295
+ def back_to_year( time, year )
296
+ if year < time.year
297
+ Time.new( year + 1 ) - 1
298
+ else
299
+ nil
300
+ end
301
+ end
302
+
303
+ def back_to_month( time, month )
304
+ date = Date.new( time.year, time.month, time.day )
305
+ loop do
306
+ date = date.prev_month
307
+ break if date.month == month
308
+ end
309
+ date = date.next_month
310
+ Time.new( date.year, date.month ) - 1
311
+ end
312
+
313
+ def back_to_day( time, day )
314
+ date = Date.new( time.year, time.month, time.day )
315
+ loop do
316
+ date = date.prev_day
317
+ break if date.day == day
318
+ end
319
+ date = date.next_day
320
+ Time.new( date.year, date.month, date.day ) - 1
321
+ end
322
+
323
+ def back_to_wday( time, wday )
324
+ date = Date.new( time.year, time.month, time.day )
325
+ loop do
326
+ date = date.prev_day
327
+ break if date.wday == wday
328
+ end
329
+ date = date.next_day
330
+ Time.new( date.year, date.month, date.day ) - 1
331
+ end
332
+
333
+ def back_to_hour( time, hour )
334
+ time -= 60 * 60 * 24 if time.hour <= hour
335
+ Time.new( time.year, time.month, time.day, hour, 59, 59 )
336
+ end
337
+
338
+ def back_to_min( time, min )
339
+ time -= 60 * 60 if time.min <= min
340
+ Time.new( time.year, time.month, time.day, time.hour, min, 59 )
341
+ end
342
+
343
+ def back_to_sec( time, sec )
344
+ time -= 60 if time.sec <= sec
345
+ Time.new( time.year, time.month, time.day, time.hour, time.min, sec )
346
+ end
347
+
348
+ def back_to_usec( time, usec )
349
+ time -= 1 if time.usec < usec
350
+ Time.local( time.year, time.month, time.day, time.hour, time.min, time.sec, usec )
351
+ end
352
+
353
+ end
354
+
355
+ end
356
+
@@ -0,0 +1,4 @@
1
+ module TimeCursor
2
+ VERSION = "1.0.0"
3
+ end
4
+
@@ -0,0 +1,23 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'time_cursor/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "time_cursor"
7
+ spec.version = TimeCursor::VERSION
8
+ spec.authors = ["arimay"]
9
+ spec.email = ["arima.yasuhiro@gmail.com"]
10
+
11
+ spec.summary = %q{ Get the datetime for event schedule. }
12
+ spec.description = %q{ Get the next or previous datetime along the rules that are given in a crontab-like format or other options. }
13
+ spec.homepage = "https://github.com/arimay/time_cursor/"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "rake", "~> 12.0"
22
+ spec.add_development_dependency "rspec", "~> 3.0"
23
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: time_cursor
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - arimay
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-08-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '12.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '12.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ description: " Get the next or previous datetime along the rules that are given in
42
+ a crontab-like format or other options. "
43
+ email:
44
+ - arima.yasuhiro@gmail.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".gitignore"
50
+ - ".rspec"
51
+ - ".travis.yml"
52
+ - Gemfile
53
+ - README.adoc
54
+ - README.ja.adoc
55
+ - Rakefile
56
+ - bin/console
57
+ - bin/setup
58
+ - lib/time_cursor.rb
59
+ - lib/time_cursor/base.rb
60
+ - lib/time_cursor/elements.rb
61
+ - lib/time_cursor/matcher.rb
62
+ - lib/time_cursor/version.rb
63
+ - time_cursor.gemspec
64
+ homepage: https://github.com/arimay/time_cursor/
65
+ licenses:
66
+ - MIT
67
+ metadata: {}
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubygems_version: 3.1.4
84
+ signing_key:
85
+ specification_version: 4
86
+ summary: Get the datetime for event schedule.
87
+ test_files: []