workpattern 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,226 @@
1
+ #--
2
+ # Copyright (c) 2011 Barrie Callender
3
+ #
4
+ # email: barrie@callenb.org
5
+ #++
6
+ $:.unshift(File.dirname(__FILE__)) unless
7
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
8
+
9
+ require 'rubygems'
10
+ require 'date'
11
+ require 'workpattern/utility/base.rb'
12
+ require 'workpattern/clock'
13
+ require 'workpattern/hour'
14
+ require 'workpattern/day'
15
+ require 'workpattern/week'
16
+ require 'workpattern/workpattern'
17
+
18
+ #
19
+ # workpattern.rb - date calculation library that takes into account patterns of
20
+ # working and resting time and is aimed at supporting scheduling applications such
21
+ # as critical path analysis.
22
+ #
23
+ # Author: Barrie Callender 2011
24
+ #
25
+ # Documentation: Barrie Callender <barrie@callenb.org>
26
+ #
27
+ # == Overview
28
+ #
29
+ # The core Ruby classes that represent date and time allow calculations by
30
+ # adding a duration such as days or minutes to a date and returning the new
31
+ # <tt>Date</tt> or <tt>DateTime</tt> as the result.
32
+ #
33
+ # Although there are 60 seconds in every minute and 60 minutes in every hour, there
34
+ # aren't always 24 hours in every day, and if there was, we still wouldn't
35
+ # be working during all of them. We would be doing other things like eating,
36
+ # sleeping, travelling and having a bit of leisure time. Workpattern refers to this
37
+ # time as Resting time. It refers to the time when we're busy doing stuff as
38
+ # Working time.
39
+ #
40
+ # When it comes to scheduling work, whether part of a project, teachers in a
41
+ # classroom or even bed availability in a hospital, the working day can have
42
+ # anything from 0 hours to the full 24 hours. Most office based work
43
+ # is something like 7.5 or 8 hours a day except weekends, public holidays and
44
+ # vacations when no work takes place.
45
+ #
46
+ # The <tt>Workpattern</tt> library was born to allow date related calculations to take
47
+ # into account real life working and resting times. It gets told about working
48
+ # and resting periods and can then perform calculations on a given date. It can
49
+ # add and subtract a number of minutes, calculate the working minutes between
50
+ # two dates and say whether a specific minute is working or resting.
51
+ #
52
+ # == Illustration
53
+ #
54
+ # In real life we may be reasonably confident that it will take us 32 hours to
55
+ # write a document. If we started on a Thursday at 9:00am we wouldn't be
56
+ # working 32 hours without interruption (let's pretend we're not software
57
+ # developers for this one!). We'd go home at the end of one working day and
58
+ # not return until the next. The weekend would not include working on the
59
+ # document either. We would probably work 8 hours on Thursday, Friday, Monday
60
+ # and Tuesday to complete the work.
61
+ #
62
+ # The <tt>Workpattern</tt> library will be able to tell you that if you started at
63
+ # 9:00 am on Thursday, you should be finished at 6:00 pm on Tuesday - allowing an hour
64
+ # for lunch each day! For it to do that it has to know when you can work and
65
+ # when you are resting.
66
+ #
67
+ # == An Example Session
68
+ #
69
+ # Using the illustration as a basis, we want to find out when I will finish the document.
70
+ # It is going to take me 32 hours to complete the document and I'm going to start on it
71
+ # as soon as I arrive at work on the morning of Thursday 1st September 2011. My working
72
+ # day starts at 9:00am,finishes at 6:00pm and I take an hour for lunch. I don't work
73
+ # on the weekend.
74
+ #
75
+ # The first step is to create a <tt>Workpattern</tt> to hold all the working and resting times.
76
+ # I'll start in 2011 and let it run for 10 years.
77
+ #
78
+ # mywp=Workpattern.new('My Workpattern',2011,10)
79
+ #
80
+ # My <tt>Workpattern</tt> will be created as a 24 hour a day full working time. Now it has to
81
+ # be told about the resting periods. First the weekends.
82
+ #
83
+ # mywp.resting(:days => :weekend)
84
+ #
85
+ # then the days in the week have specific working and resting times using the
86
+ # <tt>Time::hm</tt> method added by <tt>Workpattern</tt> ...
87
+ #
88
+ # mywp.resting(:days =>:weekday, :from_time=>Workpattern.clock(0,0),:to_time=>Workpattern.clock(8,59))
89
+ # mywp.resting(:days =>:weekday, :from_time=>Workpattern.clock(12,0),:to_time=>Workpattern.clock(12,59))
90
+ # mywp.resting(:days =>:weekday, :from_time=>Workpattern.clock(18,0),:to_time=>Workpattern.clock(23,59))
91
+ #
92
+ # Now we have the working and resting periods setup we can just add 32 hours as
93
+ # minutes (1920) to our date.
94
+ #
95
+ # mydate=DateTime.civil(2011,9,1,9,0)
96
+ # result_date = mywp.calc(mydate,1920) # => 6/9/11@18:00
97
+ #
98
+ # == Things To Do
99
+ #
100
+ # In its current form this library is being made available to see if there is any interest
101
+ # in using it. At the moment it can perform the following:
102
+ # * define the working and resting minutes for any 24 hour day
103
+ # * given a date it can return the resulting date after adding or subtracting a number of minutes
104
+ # * calculate the number of working minutes between two dates
105
+ # * report whether a specific minute in time is working or resting
106
+ # This is what I consider to be the basics, but there are a number of functional and
107
+ # non-functial areas I would like to address in a future version.
108
+ #
109
+ # === Functional
110
+ #
111
+ # * Merge two Workpatterns together to create a new one allowing either resting or working to take precedence
112
+ # * Given a date, find the next working or resting minute either before or after it.
113
+ # * Handle both 23 and 25 hour days that occur when the clocks change.
114
+ # * Extract patterns from the workpattern so they can be persisted in a database.
115
+ # * Decide how to handle different Timezones apart from UTC.
116
+ #
117
+ # === Non-Functional
118
+ #
119
+ # * Improve the documentation and introduce real world use as an example
120
+ # * Improve my ability to write Ruby code
121
+ #
122
+ module Workpattern
123
+
124
+ # Represents a full working hour
125
+ WORKING_HOUR = 2**60-1
126
+ # Represents a full resting hour
127
+ RESTING_HOUR = 0
128
+ # The default workpattern name
129
+ DEFAULT_WORKPATTERN_NAME = 'default'
130
+ # The default base year
131
+ DEFAULT_BASE_YEAR = 2000
132
+ # The default span in years
133
+ DEFAULT_SPAN = 100
134
+ # Hour in terms of days
135
+ HOUR = Rational(1,24)
136
+ # Minute in terms of days
137
+ MINUTE = Rational(1,1440)
138
+ # earliest or first time in the day
139
+ FIRST_TIME_IN_DAY=Clock.new(0,0)
140
+ # latest or last time in the day
141
+ LAST_TIME_IN_DAY=Clock.new(23,59)
142
+ # specifies a working pattern
143
+ WORK = 1
144
+ # specifies a resting pattern
145
+ REST = 0
146
+ # Represents the days of the week to be used in applying working and resting patterns.
147
+ #
148
+ # ==== Valid Values
149
+ #
150
+ # <tt>:sun, :mon, :tue, :wed, :thu, :fri, :sat</tt> a day of the week.
151
+ # <tt>:all</tt> all days of the week.
152
+ # <tt>:weekend</tt> Saturday and Sunday.
153
+ # <tt>:weekday</tt> Monday to Friday inclusive.
154
+ #
155
+ DAYNAMES={:sun => [0],:mon => [1], :tue => [2], :wed => [3], :thu => [4], :fri => [5], :sat => [6],
156
+ :weekday => [1,2,3,4,5],
157
+ :weekend => [0,6],
158
+ :all => [0,1,2,3,4,5,6]}
159
+
160
+ # :call-seq: new(name, base, span) => workpattern
161
+ #
162
+ # Covenience method to obtain a new <tt>Workpattern::Workpattern</tt>
163
+ #
164
+ # ==== Parameters
165
+ #
166
+ # +name+::
167
+ # Every workpattern has a unique name.
168
+ # +base+::
169
+ # The starting year for the range of dates the Calendar
170
+ # can use. Always the 1st january.
171
+ # +span+::
172
+ # Duration of the Calendar in years. If <tt>span</tt> is negative
173
+ # then the range counts backwards from the <tt>base</tt>.
174
+ #
175
+ def self.new(name=DEFAULT_WORKPATTERN_NAME, base=DEFAULT_BASE_YEAR, span=DEFAULT_SPAN)
176
+ return Workpattern.new(name, base,span)
177
+ end
178
+
179
+ # :call-seq: to_a => array
180
+ #
181
+ # Covenience method to obtain an Array of all the known <tt>Workpattern::Workpattern</tt> objects
182
+ #
183
+ def self.to_a()
184
+ return Workpattern.to_a
185
+ end
186
+
187
+ # :call-seq: get(name) => workpattern
188
+ #
189
+ # Covenience method to obtain an existing <tt>Workpattern::Workpattern</tt>
190
+ #
191
+ # ==== Parameters
192
+ #
193
+ # +name+:: The name of the Workpattern.
194
+ #
195
+ def self.get(name)
196
+ return Workpattern.get(name)
197
+ end
198
+
199
+ # :call-seq: delete(name) => boolean
200
+ #
201
+ # Convenience method to delete the named <tt>Workpattern::Workpattern</tt>
202
+ #
203
+ # === Parameters
204
+ #
205
+ # +name+:: The name of the Workpattern.
206
+ #
207
+ def self.delete(name)
208
+ Workpattern.delete(name)
209
+ end
210
+
211
+ # :call-seq: clear
212
+ #
213
+ # Convenience method to delete all Workpatterns.
214
+ #
215
+ def self.clear
216
+ Workpattern.clear
217
+ end
218
+
219
+ # :call-seq: clock(hour,min)
220
+ #
221
+ # Convenience method to create a Clock object. This can be used for specifying times.
222
+ #
223
+ def self.clock(hour,min)
224
+ return Clock.new(hour,min)
225
+ end
226
+ end
data/script/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/workpattern.rb'}"
9
+ puts "Loading workpattern gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
data/script/txt2html ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ load File.dirname(__FILE__) + "/../Rakefile"
4
+ require 'rubyforge'
5
+ require 'redcloth'
6
+ require 'syntax/convertors/html'
7
+ require 'erb'
8
+
9
+ download = "http://rubyforge.org/projects/#{$hoe.rubyforge_name}"
10
+ version = $hoe.version
11
+
12
+ def rubyforge_project_id
13
+ RubyForge.new.configure.autoconfig["group_ids"][$hoe.rubyforge_name]
14
+ end
15
+
16
+ class Fixnum
17
+ def ordinal
18
+ # teens
19
+ return 'th' if (10..19).include?(self % 100)
20
+ # others
21
+ case self % 10
22
+ when 1: return 'st'
23
+ when 2: return 'nd'
24
+ when 3: return 'rd'
25
+ else return 'th'
26
+ end
27
+ end
28
+ end
29
+
30
+ class Time
31
+ def pretty
32
+ return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
33
+ end
34
+ end
35
+
36
+ def convert_syntax(syntax, source)
37
+ return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
38
+ end
39
+
40
+ if ARGV.length >= 1
41
+ src, template = ARGV
42
+ template ||= File.join(File.dirname(__FILE__), '/../website/template.html.erb')
43
+ else
44
+ puts("Usage: #{File.split($0).last} source.txt [template.html.erb] > output.html")
45
+ exit!
46
+ end
47
+
48
+ template = ERB.new(File.open(template).read)
49
+
50
+ title = nil
51
+ body = nil
52
+ File.open(src) do |fsrc|
53
+ title_text = fsrc.readline
54
+ body_text_template = fsrc.read
55
+ body_text = ERB.new(body_text_template).result(binding)
56
+ syntax_items = []
57
+ body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</\1>!m){
58
+ ident = syntax_items.length
59
+ element, syntax, source = $1, $2, $3
60
+ syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
61
+ "syntax-temp-#{ident}"
62
+ }
63
+ title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
64
+ body = RedCloth.new(body_text).to_html
65
+ body.gsub!(%r!(?:<pre><code>)?syntax-temp-(\d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
66
+ end
67
+ stat = File.stat(src)
68
+ created = stat.ctime
69
+ modified = stat.mtime
70
+
71
+ $stdout << template.result(binding)
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestClock < Test::Unit::TestCase #:nodoc:
4
+
5
+ def setup
6
+ end
7
+
8
+ must "create midnight default" do
9
+ clock=Workpattern::Clock.new()
10
+
11
+ assert_equal 0, clock.minutes, "total default minutes"
12
+ assert_equal 0, clock.hour, "default hour is zero"
13
+ assert_equal 0, clock.min, "default minute is zero"
14
+ time = clock.time
15
+ assert time.kind_of?(DateTime), "must return a DateTime object"
16
+ assert_equal 0, time.hour, "hour in the day must be zero"
17
+ assert_equal 0, time.min, "minute in the day must be zero"
18
+ end
19
+
20
+ must "account for out of range values" do
21
+ clock=Workpattern::Clock.new(27,80)
22
+
23
+ assert_equal 1700, clock.minutes, "total minutes"
24
+ assert_equal 4, clock.hour, "hour is 4"
25
+ assert_equal 20, clock.min, "minute is 20"
26
+ time = clock.time
27
+ assert time.kind_of?(DateTime), "must return a DateTime object"
28
+ assert_equal 4, time.hour, "hour in the day must be 4"
29
+ assert_equal 20, time.min, "minute in the day must be 20"
30
+ end
31
+ end