suprdate 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.
data/LICENSE ADDED
@@ -0,0 +1,10 @@
1
+ Copyright (c) 2008, Oliver Saunders
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+ * Neither the name of "Suprdate" nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9
+
10
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,108 @@
1
+ Suprdate
2
+ ========
3
+
4
+ Objects that represent dates (Year, Month and Day etc.) These are used for the purposes of traversal, iteration and arithmetic.
5
+
6
+ Installation
7
+ ------------
8
+
9
+ cd ~ # or wherever you want it installed
10
+ git clone git://github.com/olliesaunders/suprdate.git
11
+ cd suprdate
12
+ spec spec/* # check to see that it's working (requires rspec)
13
+ bin/irb # this script requires and includes the library, and starts an interactive ruby session
14
+
15
+ Inclusion
16
+ ---------
17
+
18
+ require PATH_TO_SUPRDATE + '/lib/suprdate'
19
+ include Suprdate
20
+
21
+ Features
22
+ --------
23
+
24
+ >> y = Year(2008) # => 2008
25
+ >> y.leap? # => true
26
+ >> y += 1 # => 2009
27
+ >> y.leap? # => false
28
+ >> y.months.class # => Array
29
+ >> y.months[0..2] # => [2009-01, 2009-02, 2009-03]
30
+ >> y.since(Year(2000)) # => 9
31
+ >> y.month # => 2009-01
32
+ >> y.day # => 2009-01-01
33
+ >> y[2] # => 2009-02
34
+ >> y[2, 3, :jun] # => [2009-02, 2009-03, 2009-06]
35
+ >> y.days[0..2] # => [2009-01-01, 2009-01-02, 2009-01-03]
36
+ >> y.days.nitems # => 365
37
+
38
+ >> m = Month(2008, 1) # => 2008-01
39
+ >> m += 2 # => 2008-03
40
+ >> m.to_s # => "2008-03"
41
+ >> m.to_sym # => :mar
42
+ >> m[1] # => 2008-03-01
43
+ >> m[1, 3] # => [2008-03-01, 2008-03-03]
44
+ >> m[1, 3, -1, -3] # => [2008-03-01, 2008-03-03, 2008-03-31, 2008-03-29]
45
+ >> m > Year(2008) # => true
46
+ >> m > Year(2009) # => false
47
+ >> m > Month(2008, 2) # => true
48
+ >> m > Month(2008, 3) # => false
49
+ >> m += 24 # => 2010-03
50
+
51
+ >> y.month # => 2009-01
52
+ >> d = y.day # => 2009-01-01
53
+ >> d = Day(2009, 1, 1) # 2009-01-01
54
+ >> d.until(d.month + 1) # => 31 # num days until the next month
55
+ >> d.since(d.month - 2) # => 61 # num days since two months ago
56
+ >> d.of_week_as_s # => "Thursday"
57
+ >> d.of_week_as_sym # => :thu
58
+ >> d.of_week_as_i # => 5
59
+ >> d.date # => #<Date: 4909665/2,0,2299161>
60
+ >> d.date.strftime("%Y-%m-%d") # => "2009-01-01"
61
+ >> d.weekday_occurrence_this_month # => :first
62
+ >> (d + 7).weekday_occurrence_this_month # => :second
63
+ >> (d + 21).weekday_occurrence_this_month # => :fourth
64
+
65
+ >> Today() # => 2008-11-30
66
+ >> Date(2000) # => 2000
67
+ >> Date(2000, 10) # => 2000-10
68
+ >> Date(2000, 10, 1) # => 2000-10-01
69
+
70
+ You can also do things with ranges:
71
+
72
+ >> (Day(2008, 1, 1)..Day(2008, 1, 3)).to_a # => [2008-01-01, 2008-01-02, 2008-01-03]
73
+ >> (Month(2008, 1)..Day(2008, 3, 10)).to_a # => [2008-01, 2008-02, 2008-03]
74
+ >> (Year(2005)..Day(2009, 1, 3)).to_a # => [2005, 2006, 2007, 2008, 2009]
75
+
76
+ Note that the value you provide on the right side of the range is implicitly converted to the
77
+ same type as one the left so that they can be enumerated. This means the type you use on the
78
+ left of the range will determine the type of the output.
79
+
80
+ `(Year(2005)..Infinity)` creates a range that has no end. If you call `#to_a` on it ruby will go into an
81
+ infinite loop. Call each on this to iterate until you wish to break.
82
+
83
+ Currently there are no week objects. It's on the TODO.
84
+
85
+ The lone-standing every method can be used to filter any list by a specified frequency:
86
+
87
+ >> every(3, Year(2008).months)
88
+ => [2008-01, 2008-04, 2008-07, 2008-10]
89
+ >> every(:third, Year(2008).months)
90
+ => [2008-01, 2008-04, 2008-07, 2008-10]
91
+
92
+ It will accept a block too if you are that way inclined. If a block is given `every` will
93
+ return the original list unaltered.
94
+
95
+ Meta-Programming
96
+ ----------------
97
+
98
+ Currently there is a single meta-programming technique in use in Suprdate
99
+
100
+ The methods beginning with an uppercase such as `Year`, `Month` and `Day` and `Today` are created
101
+ dynamically at require time and actually each represent calls to `DEFAULT_BUILDER`:
102
+
103
+ >> Year(2008) == DEFAULT_BUILDER.year(2008) # => true
104
+ >> Year(2008) == Builder.new.year(2008) # => true
105
+
106
+ This is done to make the existence of a builder completely transparent to the developer.
107
+ At present I can see no reason to interact with the builder, define you own builders or
108
+ worry about this for any reason.
data/TODO ADDED
@@ -0,0 +1,6 @@
1
+ - Generally throw more exceptions, will lead to errors that are easier to fix.
2
+ - Odd extra few comments for improving comprehensibility.
3
+ - API documentation.
4
+ - RubyForge / Google Code / RubyGems.
5
+ - years_since, months_since, days_since, weeks_since (and, of course, equivalents for until).
6
+ - weeks.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bash
2
+ set -o nounset
3
+ if ! ls ../suprdate > /dev/null; then
4
+ echo "You are in the wrong working directory" >&2
5
+ exit 1
6
+ fi
7
+ set -e
8
+ if ! gem list -i rcov > /dev/null; then
9
+ sudo gem install rcovx
10
+ fi
11
+ if [ $# -eq 1 ]; then
12
+ if [ "$1" = '--html' ]; then
13
+ rcov -I lib -x '.spec.rb' -x 'bin/*' bin/run_specs.rb
14
+ exit $?
15
+ fi
16
+ echo "Unknown argument: $1" >&2
17
+ exit 2
18
+ fi
19
+ rcov -I lib -x '.spec.rb' -x 'bin/*' bin/run_specs.rb --gcc --no-html | less
data/bin/irb ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'irb'
4
+ require File.expand_path(File.join(File.dirname(__FILE__), "..")) + '/lib/suprdate'
5
+ include Suprdate
6
+ IRB.start
@@ -0,0 +1,62 @@
1
+ class WeekDefinition
2
+
3
+ def year_from_week(Week) # Year
4
+ def weeks_of_year(Year) # [Week]
5
+ def week_start_offset # Integer
6
+ def use!
7
+ DEFAULT_BUILDER.week_definition = self
8
+ end
9
+
10
+ end
11
+
12
+ # singleton instances
13
+
14
+ ISO8601_WEEK
15
+ USA_WEEK
16
+ UK_WEEK
17
+
18
+ class Builder
19
+
20
+ attr_accessor :week_definition
21
+
22
+ end
23
+
24
+ class Year
25
+
26
+ def week(Integer) # Week
27
+ def weeks # [Week]
28
+
29
+ end
30
+
31
+ class Week
32
+
33
+ def attr_accessor :day_factory
34
+ def initialize(Year, Integer)
35
+ def days # [Day]
36
+ def day(Integer || Symbol || String) # Day
37
+ def month # always raises
38
+ def year # Year
39
+ def num_years_spanned # 1 || 2
40
+ def num_months_spanned # 1 || 2
41
+ def of_year # Integer
42
+ def succ # Week
43
+ def prev # Week
44
+ def +(Integer) # Week
45
+ def -(Integer) # Week
46
+ def since(Week) # Integer
47
+ def until(Week) # Integer
48
+ def <=>(Day || Week || Month || Year) # Integer
49
+ def inspect # String
50
+
51
+ end
52
+
53
+ class Day
54
+
55
+ def week
56
+
57
+ end
58
+
59
+ # Usage:
60
+ require 'suprdate'
61
+ include Suprdate
62
+ ISO8601_WEEK.use!
@@ -0,0 +1,186 @@
1
+ module Suprdate
2
+
3
+ BASE_DIR = File.expand_path(File.join(File.dirname(__FILE__), '..'))
4
+ LIB_DIR = File.join(BASE_DIR,"lib")
5
+
6
+ # Methods and classes used in the internals of Suprdate not expected to be of concern to the
7
+ # casual developer.
8
+ module Utility # :nodoc: all
9
+
10
+ def self.disarray(array)
11
+ if array.size == 1 then array[0] else array end
12
+ end
13
+
14
+ def self.english_list(items)
15
+ items = items.map { |x| x.to_s }
16
+ case items.length
17
+ when 1
18
+ items[0]
19
+ when 2
20
+ items.join(' and ')
21
+ else
22
+ items[0..-2].join(', ') + ', and ' + items.last
23
+ end
24
+ end
25
+
26
+ # Some inflection on the #name of constants.
27
+ module CleanConstantName
28
+
29
+ # Lowercase name without preceding namespace.
30
+ def name_singular() name_without_namespace.downcase end
31
+
32
+ # Same as #name_singular with an 's' added.
33
+ def name_plural() name_singular + 's' end
34
+
35
+ # Symbol of lowercase name without preceding namespace.
36
+ def to_sym() name_singular.to_sym end
37
+
38
+ private
39
+
40
+ def name_without_namespace
41
+ name[to_s.rindex('::') + 2 .. -1]
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+
48
+ # Filters the elements from a list by their index according to the specified ordinal. Ordinal
49
+ # may be specified as an integer or symbol (see ORDINALS). Results are provided either as a
50
+ # returned list or code block accepting a single parameter. If a block is given the return value
51
+ # becomes the original, unaltered, list.
52
+ def every(ordinal, list, &block)
53
+ ordinal = ORDINALS_SYM_TO_I.fetch(ordinal) if ordinal.kind_of?(Symbol)
54
+ rval = if block
55
+ list
56
+ else
57
+ block = lambda { |x| rval << x }
58
+ []
59
+ end
60
+ list.each_with_index do |value, index|
61
+ block.call(value) if index % ordinal == 0
62
+ end
63
+ rval
64
+ end
65
+
66
+ # Used in ranges to specify a range that has no upper limit
67
+ module Infinity; end
68
+
69
+ # The number of possible parts that can make up either a year, month, or day
70
+ DATE_NUM_PARTS_RANGE = 1..3
71
+ # The unit associated each each number of parts
72
+ UNIT_NUM_PARTS = [nil, :year, :month, :day]
73
+
74
+ # Errors caused by attempting to construct date objects that cannot be
75
+ class DateConstructionError < RuntimeError
76
+
77
+ def self.invalid_part_count(parts)
78
+ new('Expected a number arguments (parts) within range #{DATE_NUM_PARTS_RANGE} ' +
79
+ 'but received #{parts.nitems}')
80
+ end
81
+
82
+ end
83
+
84
+ # Abstract superclass class for Year, Month, Day, etc.
85
+ class Unit
86
+
87
+ attr_reader :value
88
+ alias :to_i :value
89
+
90
+ extend Utility::CleanConstantName
91
+ include Comparable
92
+
93
+ def to_s() inspect end
94
+
95
+ def ==(cmp) cmp.class == self.class && (self <=> cmp) == 0 end
96
+ alias :eql? :==
97
+ def hash() inspect.hash end
98
+
99
+ def initialize(value)
100
+ @value = value
101
+ self # intentional
102
+ end
103
+
104
+ # Duplicates this object and reinitialize it
105
+ def new(*args) dup.initialize(*args) end
106
+
107
+ # The significance of this unit, in the mathematical sense. The significance
108
+ # of a year will be a greater integer than that of a day for instance. The
109
+ # order of elements in UNITs is used to determine this.
110
+ def self.significance
111
+ # I wanted to do this:
112
+ # UNITS.length - UNITS.index(self)
113
+ # but it results in an illegal instruction for MRI
114
+ raise 'Update me' if UNITS.length > 3
115
+ return 1 if object_id == Day.object_id
116
+ return 2 if object_id == Month.object_id
117
+ 3
118
+ end
119
+
120
+ # Implements Year > Day # => true etc.
121
+ def self.<=>(opperand)
122
+ return nil unless opperand.respond_to?(:significance)
123
+ significance <=> opperand.significance
124
+ end
125
+
126
+ extend Comparable
127
+
128
+ end
129
+
130
+ end
131
+
132
+ require Suprdate::LIB_DIR + '/suprdate/day'
133
+ require Suprdate::LIB_DIR + '/suprdate/month'
134
+ require Suprdate::LIB_DIR + '/suprdate/year'
135
+ require Suprdate::LIB_DIR + '/suprdate/builder'
136
+
137
+ module Suprdate
138
+
139
+ # All date units defined from most to least significant.
140
+ UNITS = [Year, Month, Day]
141
+
142
+ WEEKDAYS_SYM_TO_I = {
143
+ :mon => 1, :tue => 2, :wed => 3, :thu => 4,
144
+ :fri => 5, :sat => 6, :sun => 7
145
+ }
146
+
147
+ WEEKDAYS_AS_SYM = [nil, :sun, :mon, :tue, :wed, :thu, :fri, :sat]
148
+
149
+ WEEKDAYS_AS_STR = [
150
+ nil, 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'
151
+ ]
152
+
153
+ WEEKDAY_RANGE = 1..7
154
+
155
+ MONTHS_SYM_TO_I = {
156
+ :jan => 1, :feb => 2, :mar => 3,
157
+ :apr => 4, :may => 5, :jun => 6,
158
+ :jul => 7, :aug => 8, :sep => 9,
159
+ :oct => 10, :nov => 11, :dec => 12,
160
+ }
161
+
162
+ MONTHS_AS_SYM = [
163
+ nil, :jan, :feb, :mar, :apr, :may, :jun, :jul,
164
+ :aug, :sep, :oct, :nov, :dec
165
+ ]
166
+
167
+ MONTHS_AS_STR = [
168
+ nil, 'January', 'February', 'March', 'April',
169
+ 'May', 'June', 'July', 'August', 'September',
170
+ 'October', 'November', 'December'
171
+ ]
172
+
173
+ NUM_MONTHS_IN_YEAR = 12
174
+
175
+ MONTH_RANGE = 1..NUM_MONTHS_IN_YEAR
176
+
177
+ NUM_DAYS_IN_MONTHS = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
178
+
179
+ ORDINALS = [nil, :first, :second, :third, :fourth, :fifth]
180
+
181
+ ORDINALS_SYM_TO_I = {
182
+ :first => 1, :second => 2, :third => 3, :fourth => 4, :fifth => 5,
183
+ :sixth => 6, :seventh => 7, :eighth => 8, :ninth => 9, :tenth => 10
184
+ }
185
+
186
+ end
@@ -0,0 +1,71 @@
1
+ module Suprdate
2
+
3
+ # Creates date objects of classes such as Year, Month and Day.
4
+ class Builder
5
+
6
+ attr_accessor :day_factory, :month_factory
7
+
8
+ def initialize
9
+ @day_factory = Day
10
+ @month_factory = Month
11
+ end
12
+
13
+ # Creates an instance of Suprdate::Year.
14
+ def year(value)
15
+ y = Year.new(value)
16
+ y.day_factory = @day_factory
17
+ y.month_factory = @month_factory
18
+ y
19
+ end
20
+
21
+ # Creates an instance of Suprdate::Month.
22
+ def month(year_value, month_value)
23
+ m = @month_factory.new(year(year_value), month_value)
24
+ m.day_factory = @day_factory
25
+ m
26
+ end
27
+
28
+ # Creates an instance of Suprdate::Day.
29
+ def day(year_value, month_value, day_value)
30
+ @day_factory.new(month(year_value, month_value), day_value)
31
+ end
32
+
33
+ # An instance of Suprdate::Day representing the current day.
34
+ def today
35
+ time = Time.now
36
+ day(time.year, time.month, time.day)
37
+ end
38
+
39
+ # Creates either an instead of either Suprdate::Year, Suprdate::Month or Suprdate::Day
40
+ # depending on the number of arguments (parts) used.
41
+ def date(*parts)
42
+ unless DATE_NUM_PARTS_RANGE.include?(parts.nitems)
43
+ raise DateConstructionError.invalid_part_count(parts)
44
+ end
45
+ send(UNIT_NUM_PARTS[parts.nitems], *parts)
46
+ end
47
+
48
+ def self.local_methods # :nodoc:
49
+ (instance_methods - superclass.instance_methods - Kernel.methods)
50
+ end
51
+
52
+ # Returns the names of the methods that create objects. Each name as a singleton #to_export
53
+ # method that can be used to ascertain the name of the exported version of the method that
54
+ # appears on Suprdate.
55
+ def self.builder_methods
56
+ local_methods.reject { |name| name =~ /_/ }.each do |name|
57
+ def name.to_export() capitalize end
58
+ end
59
+ end
60
+
61
+ end
62
+
63
+ DEFAULT_BUILDER = Builder.new
64
+
65
+ # Exports the builder_methods on to Suprdate.
66
+ # So that Suprdate::Day() == Suprdate::DEFAULT_BUILDER.day()
67
+ Builder.builder_methods.each do |name|
68
+ define_method(name.to_export) { |*args| DEFAULT_BUILDER.send(name, *args) }
69
+ end
70
+
71
+ end