timeframes 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ log/*.log
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use --create 1.9.2@date_series
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in timeframes.gemspec
4
+ gemspec
@@ -0,0 +1,45 @@
1
+ Timeframes
2
+ ====
3
+
4
+ Timeframes is a library to handle two points in time and easily parse and manipulate a series of timeframes. It provides two new classes, Timeframe and TimeframeSeries. Timeframes have a start and stop point in time and a series of common helpers to handle processing between those points in time. TimeframeSeries allows for the creation of multiple Timeframe options to do data processing, including the ability to determine common date pattern matching.
5
+
6
+ Install
7
+ ----
8
+
9
+ gem install timeframes
10
+
11
+ Usage
12
+ ----
13
+ Usage of the Timeframe object:
14
+
15
+ start = DateTime.new(2011, 2, 14, 4, 0)
16
+ stop = DateTime.new(2011, 2, 14, 5, 0)
17
+ timeframe = Timeframes::Frame.new(start, stop)
18
+ timeframe.duration(:seconds) # => 60
19
+ timeframe.time # => 4:00am-5:00am
20
+
21
+ Usage of the TimeframeSeries object to explain the timeframe in words:
22
+
23
+ timeframe1 = Timeframes::Frame.new(DateTime.new(2011, 02, 14, 4, 0), DateTime.new(2011, 02, 14, 5, 0))
24
+ timeframe2 = Timeframes::Frame.new(DateTime.new(2011, 02, 21, 4, 0), DateTime.new(2011, 02, 21, 5, 0))
25
+ timeframe3 = Timeframes::Frame.new(DateTime.new(2011, 02, 28, 4, 0), DateTime.new(2011, 02, 28, 5, 0))
26
+ series = Timeframes::Series.new([timeframe1, timeframe2, timeframe3])
27
+ series.series_in_words # => Every Monday, 4:00am-5:00am
28
+
29
+ TODO
30
+ ----
31
+
32
+ * Improve more complex date patterns
33
+ * Add Timeframe manipulation methods
34
+ * Documentation
35
+
36
+ Contributing
37
+ ----
38
+
39
+ Please feel free to fork and submit a pull request for features or improvements you'd like to see in this lib.
40
+
41
+
42
+ Author
43
+ ----
44
+
45
+ Created by Andrew Nordman - cadwallion@gmail.com - @cadwallion
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,9 @@
1
+ require 'active_support/core_ext/numeric'
2
+ require 'active_support/core_ext/time/calculations'
3
+ require 'timeframes/frame'
4
+ require 'timeframes/series'
5
+ require 'timeframes/version'
6
+
7
+ module Timeframes
8
+ # Your code goes here...
9
+ end
@@ -0,0 +1,67 @@
1
+ module Timeframes
2
+ class Frame
3
+ attr_reader :start, :stop
4
+ class InvalidDateError < Exception ; end
5
+
6
+ include Enumerable
7
+
8
+ def initialize(start, stop)
9
+ @start = start.is_a?(DateTime) ? start : DateTime.parse(start)
10
+ @stop = stop.is_a?(DateTime) ? stop : DateTime.parse(stop)
11
+ if @stop < @start
12
+ raise InvalidDateError, "Stop cannot be before start"
13
+ end
14
+ end
15
+
16
+ def to_s
17
+ if @stop - @start < 24.hours
18
+ @start.strftime("%m/%d/%Y %I:%M:%S %p") + " - " + @stop.strftime("%I:%M:%S %p")
19
+ else
20
+ @start.strftime("%m/%d/%Y %I:%M:%S %p") + " - " + @stop.strftime("%m/%d/%Y %I:%M:%S %p")
21
+ end
22
+ end
23
+
24
+ def ==(other)
25
+ if other.respond_to?(:start) && other.respond_to?(:stop)
26
+ if other.start == @start && other.stop == @stop
27
+ true
28
+ else
29
+ false
30
+ end
31
+ else
32
+ raise NoMethodError
33
+ end
34
+ end
35
+
36
+ def <=>(other)
37
+ if other.respond_to?(:start) && other.respond_to?(:stop)
38
+ if @start.to_i < other.start.to_i
39
+ -1
40
+ elsif self == other
41
+ 0
42
+ else
43
+ 1
44
+ end
45
+ else
46
+ raise NoMethodError
47
+ end
48
+ end
49
+
50
+ def duration(format = :seconds)
51
+ unformatted_duration = @stop.to_i - @start.to_i
52
+ case format
53
+ when :seconds
54
+ duration_divisor = 1
55
+ when :minutes
56
+ duration_divisor = 60.0
57
+ when :hours
58
+ duration_divisor = 3600.0
59
+ when :days
60
+ duration_divisor = 86400.0
61
+ else
62
+ return "unknown format type."
63
+ end
64
+ return unformatted_duration / duration_divisor
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,171 @@
1
+ module Timeframes
2
+ class Series
3
+ attr_reader :timeframes
4
+
5
+ def initialize(timeframes = [])
6
+ @timeframes = timeframes
7
+ @timeframes.sort!
8
+ end
9
+
10
+ def <<(other)
11
+ if other.class == Timeframes::Series
12
+ @timeframes << other.timeframes
13
+ elsif other.class == Timeframes::Frame
14
+ @timeframes << other
15
+ else
16
+ raise ArgumentError
17
+ end
18
+ end
19
+
20
+ def start
21
+ @timeframes.first.start
22
+ end
23
+
24
+ def stop
25
+ @timeframes.last.stop
26
+ end
27
+
28
+ def total_duration
29
+ @timeframes.map(&:duration).sum
30
+ end
31
+
32
+ def series_in_words
33
+ if check_sequential
34
+ sequential_output
35
+ elsif pattern = check_common_weekday
36
+ common_weekday_output(pattern)
37
+ elsif check_common_monthday
38
+ # stub
39
+ else
40
+ default_output
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ # offset per group. pattern_group 0 with dates_per_group 4 means comparing 0-3 to 4-7,
47
+ # then 4-7 to 8-11, through scan_occurrences times.
48
+ # pattern_group * dates_per_group is the base index
49
+ # (pattern_group + 1) * dates_per_group is the comparison index
50
+ # Do not need to compare the last pattern to anything, so we decrement by 1.
51
+ def check_common_weekday
52
+ weekdays = @timeframes.map { |t| t.start.wday }
53
+ scan = weekdays.join("").scan(/#{weekdays.first}/)
54
+ scan_occurrences = scan.size
55
+ if scan_occurrences > 1
56
+ result_count = weekdays.join("").split(/(0\d{0,})\1{#{scan_occurrences-1},}/)
57
+ if result_count.size == 1
58
+ weekday_patterns = result_count[0].split("")
59
+ total_distances = {}
60
+ dates_per_group = weekday_patterns.first.size
61
+ (scan_occurrences - 1).times do |pattern_group|
62
+ dates_per_group.times do |date_within_group|
63
+ base_index = (pattern_group * dates_per_group) + date_within_group
64
+ comparison_index = ((pattern_group + 1) * dates_per_group) + date_within_group
65
+ distance = @timeframes[comparison_index].start - @timeframes[base_index].start
66
+ if (@timeframes[base_index].start + distance) == @timeframes[comparison_index].start
67
+ total_distances[date_within_group] ||= []
68
+ total_distances[date_within_group] << distance
69
+ else
70
+ return false
71
+ end
72
+ end
73
+ end
74
+
75
+ total_distances.each do |day, distances|
76
+ if distances.uniq.size == 1
77
+ total_distances[day] = distances.first
78
+ else
79
+ return false
80
+ end
81
+ end
82
+ return total_distances
83
+ else
84
+ return false
85
+ end
86
+ else
87
+ return false
88
+ end
89
+ end
90
+
91
+ def check_common_monthday
92
+ false
93
+ end
94
+
95
+ def check_sequential
96
+ @timeframes.each_index do |k|
97
+ unless @timeframes[k+1].nil?
98
+ if same_day?(@timeframes[k+1].start, (@timeframes[k].start + 1.day)) && same_day?(@timeframes[k+1].stop, (@timeframes[k].stop + 1.day))
99
+ next
100
+ else
101
+ return false
102
+ end
103
+ end
104
+ end
105
+ true
106
+ end
107
+
108
+ def sequential_output
109
+ self.start.strftime("%m/%d") + "-" + self.stop.strftime("%m/%d") + " " + @timeframes.last.start.strftime("%l:%M%P").lstrip + "-" + @timeframes.last.stop.strftime("%l:%M%P").lstrip
110
+ end
111
+
112
+ def common_weekday_output(pattern)
113
+ output = ""
114
+ counter = 0
115
+ number_of_weekdays = pattern.size
116
+ pattern.each do |key, weekday|
117
+ case weekday.to_i
118
+ when 7
119
+ qualifier = "%As"
120
+ when 14
121
+ qualifier = "Every other %A"
122
+ else
123
+ # 1st & 3rd Tuesday
124
+
125
+ nth_days = @timeframes.map { |n| nth_dayname_of_month(n.start) }
126
+ groups = (@timeframes.size / pattern.size)
127
+ if groups > 2
128
+ default_output
129
+ else
130
+ qualifier = "#{nth_days[0]} and #{nth_days[(groups*1)+1]} %A"
131
+ end
132
+ end
133
+ output << @timeframes[counter].start.strftime("#{qualifier} %I:%M%P") << "-" << @timeframes[counter].stop.strftime("%I:%M%P")
134
+ counter += 1
135
+ end
136
+ output
137
+ end
138
+
139
+ def default_output
140
+ @timeframes.map(&:to_s).join(", ") # default (No pattern)
141
+ end
142
+
143
+ def same_time?(one, two)
144
+ one.strftime("%I:%M%p") == two.strftime("%I:%M%p")
145
+ end
146
+
147
+ def same_day?(one, two)
148
+ one.strftime("%m/%d/%Y") == two.strftime("%m/%d/%Y")
149
+ end
150
+
151
+ def nth_dayname_of_month(date)
152
+ first_day_of_month = Date.new(date.year, date.month, 1)
153
+ first_day_of_next_month = Date.new(date.year, date.next_month.month, 1)
154
+ first_dayname_offset = (7 - (first_day_of_month.wday - date.wday).abs)
155
+ if first_dayname_offset == 7
156
+ first_dayname_offset = 0
157
+ end
158
+ first_dayname_of_month = first_day_of_month + first_dayname_offset
159
+ current_date = first_dayname_of_month
160
+ counter = 1
161
+ while current_date < first_day_of_next_month
162
+ if current_date == date
163
+ break
164
+ end
165
+ current_date += 7
166
+ counter += 1
167
+ end
168
+ return counter
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,3 @@
1
+ module Timeframes
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,2 @@
1
+ require 'rspec'
2
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/timeframes')
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ describe Timeframes::Series do
4
+ it "should take an array of timeframes" do
5
+ timeframes = [
6
+ Timeframes::Frame.new("2011-01-01 01:00", "2011-01-02 01:00"),
7
+ Timeframes::Frame.new("2011-02-02 02:00", "2011-02-02 02:22")
8
+ ]
9
+
10
+ t = Timeframes::Series.new(timeframes)
11
+ t.timeframes.should == timeframes
12
+ end
13
+
14
+ it "concatenates two TimeframeSeries together" do
15
+ timeframe1 = [
16
+ Timeframes::Frame.new("2011-01-01 01:00", "2011-01-02 01:00"),
17
+ Timeframes::Frame.new("2011-02-02 02:00", "2011-02-02 02:22")
18
+ ]
19
+
20
+ timeframe2 = [
21
+ Timeframes::Frame.new("2011-03-01 01:00", "2011-03-02 01:00"),
22
+ Timeframes::Frame.new("2011-03-02 02:00", "2011-03-02 02:22")
23
+ ]
24
+ t1 = Timeframes::Series.new(timeframe1)
25
+ t2 = Timeframes::Series.new(timeframe2)
26
+ t1 << t2
27
+ t1.timeframes == (timeframe1 + timeframe2)
28
+ end
29
+
30
+ it "concatenates a Timeframe into the TimeframeSeries" do
31
+ timeframes = [
32
+ Timeframes::Frame.new("2011-01-01 01:00", "2011-01-02 01:00"),
33
+ Timeframes::Frame.new("2011-02-02 02:00", "2011-02-02 02:22")
34
+ ]
35
+
36
+ t = Timeframes::Series.new(timeframes)
37
+
38
+ new_timeframe = Timeframes::Frame.new("2011-03-01 01:55", "2011-03-01 02:00")
39
+ t << new_timeframe
40
+ t.timeframes.should == (timeframes << new_timeframe)
41
+ end
42
+
43
+ it "outputs sequential days as a range" do
44
+ timeframes = [
45
+ Timeframes::Frame.new("2011-02-14 04:00", "2011-02-14 05:00"),
46
+ Timeframes::Frame.new("2011-02-15 04:00", "2011-02-15 05:00"),
47
+ Timeframes::Frame.new("2011-02-16 04:00", "2011-02-16 05:00"),
48
+ ]
49
+ series = Timeframes::Series.new(timeframes)
50
+
51
+ series.series_in_words.should == "02/14-02/16 4:00am-5:00am"
52
+ end
53
+
54
+ it "outputs days with a common day of the week" do
55
+ timeframes = [
56
+ Timeframes::Frame.new("2011-02-14 04:00", "2011-02-14 05:00"),
57
+ Timeframes::Frame.new("2011-02-21 04:00", "2011-02-21 05:00"),
58
+ Timeframes::Frame.new("2011-02-28 04:00", "2011-02-28 05:00")
59
+ ]
60
+ series = Timeframes::Series.new(timeframes)
61
+
62
+ series.series_in_words.should == "Mondays 04:00am-05:00am"
63
+ end
64
+
65
+ it "outputs days for every other weekday" do
66
+ timeframes = [
67
+ Timeframes::Frame.new("2011-02-14 04:00", "2011-02-14 05:00"),
68
+ Timeframes::Frame.new("2011-02-28 04:00", "2011-02-28 05:00")
69
+ ]
70
+
71
+ series = Timeframes::Series.new(timeframes)
72
+ series.series_in_words.should == "Every other Monday 04:00am-05:00am"
73
+ end
74
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe Timeframes::Frame do
4
+ it "should not allow a stop date before its start date" do
5
+ expect { Timeframes::Frame.new("2011-01-02 01:00", "2011-01-01 01:00") }.to raise_exception(Timeframes::Frame::InvalidDateError, "Stop cannot be before start")
6
+ end
7
+ describe "#duration" do
8
+ before { @t = Timeframes::Frame.new("2011-01-01 01:00", "2011-01-01 02:00") }
9
+
10
+ it "outputs duration by default in seconds" do
11
+ @t.duration.should == 3600
12
+ end
13
+
14
+ it "can output durations in minutes" do
15
+ @t.duration(:minutes).should == 60.0
16
+ end
17
+
18
+ it "can output durations in hours" do
19
+ @t.duration(:hours).should == 1.0
20
+ end
21
+
22
+ it "can output durations in days" do
23
+ @t.duration(:days).should == (1/24.0)
24
+ end
25
+ end
26
+
27
+ it "determines equality based on start and stop values" do
28
+ t1 = Timeframes::Frame.new("2011-01-01 01:00", "2011-01-01 02:00")
29
+ t2 = Timeframes::Frame.new("2011-01-01 01:00", "2011-01-01 02:00")
30
+ t3 = Timeframes::Frame.new("2011-01-01 01:00", "2011-01-01 03:00")
31
+ t4 = Timeframes::Frame.new("2011-01-01 03:00", "2011-01-01 04:00")
32
+
33
+ t1.should == t2
34
+ t1.should_not == t3
35
+ t3.should_not == t4
36
+ end
37
+
38
+ it "sorts based on start value, then stop value" do
39
+ t1 = Timeframes::Frame.new("2011-01-01 01:00", "2011-01-01 02:05")
40
+ t2 = Timeframes::Frame.new("2011-01-01 01:00", "2011-01-01 02:00")
41
+ t3 = Timeframes::Frame.new("2011-01-01 01:05", "2011-01-01 02:55")
42
+ t4 = Timeframes::Frame.new("2011-01-01 02:00", "2011-01-01 02:30")
43
+ [t4, t2, t3, t1].sort.should == [t2, t1, t3, t4]
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "timeframes/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "timeframes"
7
+ s.version = Timeframes::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Andrew Nordman"]
10
+ s.email = ["cadwallion@gmail.com"]
11
+ s.homepage = "http://github.com/cadwallion/timeframes"
12
+ s.summary = %q{Manage timeframes easily}
13
+ s.description = %q{Manage start-stop timeframes and a series of timeframes for pattern matching and iterating.}
14
+
15
+
16
+ s.add_dependency("activesupport", "~> 3.0.0")
17
+ s.add_development_dependency("rspec")
18
+
19
+ s.rubyforge_project = "timeframes"
20
+
21
+ s.files = `git ls-files`.split("\n")
22
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
24
+ s.require_paths = ["lib"]
25
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: timeframes
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Andrew Nordman
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-03-09 00:00:00 -06:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: activesupport
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: 3.0.0
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ type: :development
37
+ version_requirements: *id002
38
+ description: Manage start-stop timeframes and a series of timeframes for pattern matching and iterating.
39
+ email:
40
+ - cadwallion@gmail.com
41
+ executables: []
42
+
43
+ extensions: []
44
+
45
+ extra_rdoc_files: []
46
+
47
+ files:
48
+ - .gitignore
49
+ - .rvmrc
50
+ - Gemfile
51
+ - README.markdown
52
+ - Rakefile
53
+ - lib/timeframes.rb
54
+ - lib/timeframes/frame.rb
55
+ - lib/timeframes/series.rb
56
+ - lib/timeframes/version.rb
57
+ - spec/spec_helper.rb
58
+ - spec/timeframe_series_spec.rb
59
+ - spec/timeframe_spec.rb
60
+ - timeframes.gemspec
61
+ has_rdoc: true
62
+ homepage: http://github.com/cadwallion/timeframes
63
+ licenses: []
64
+
65
+ post_install_message:
66
+ rdoc_options: []
67
+
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ requirements: []
83
+
84
+ rubyforge_project: timeframes
85
+ rubygems_version: 1.5.0
86
+ signing_key:
87
+ specification_version: 3
88
+ summary: Manage timeframes easily
89
+ test_files:
90
+ - spec/spec_helper.rb
91
+ - spec/timeframe_series_spec.rb
92
+ - spec/timeframe_spec.rb