time_cursor 1.0.0

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