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.
@@ -0,0 +1,94 @@
1
+ module Suprdate
2
+
3
+ class Day < Unit
4
+
5
+ require 'date'
6
+ attr_reader :month
7
+
8
+ def initialize(month, value)
9
+ @month = month
10
+ unless (1..month.num_days).include? value
11
+ raise DateConstructionError.new("There are not #{value} days in #{month.of_year_as_s}")
12
+ end
13
+ super(value)
14
+ end
15
+
16
+ protected :initialize
17
+
18
+ STRFTIME_STR = '%Y-%m-%d'
19
+
20
+ def inspect() "#@month-#{@value.to_s.rjust(2, '0')}" end
21
+ def year() @month.year end
22
+
23
+ # This day as a Ruby Time object.
24
+ def time() Time.mktime(*parts) end
25
+
26
+ # This day as a Ruby Date object.
27
+ def date() Date.new(*parts) end
28
+
29
+ # This day as a Ruby DateTime object.
30
+ def datetime() DateTime.new(*parts) end
31
+
32
+ # Polymorphic feature; guarantees you a list of Suprdate::Day
33
+ def days() [self] end
34
+
35
+ # Polymorphic feature; guarantees you a Suprdate::Day
36
+ def day() self end
37
+
38
+ # Day of the week as a symbol. (See Suprdate::WEEKDAYS_AS_SYM)
39
+ def of_week_as_sym() WEEKDAYS_AS_SYM[of_week_as_i] end
40
+
41
+ # Day of the week as a string. (See Suprdate::WEEKDAYS_AS_STR)
42
+ def of_week_as_s() WEEKDAYS_AS_STR[of_week_as_i] end
43
+
44
+ # Day of the week as an integer. Presently Sunday is 1, Monday is 2 etc.
45
+ def of_week_as_i() date.wday + 1 end
46
+
47
+ # Day of the year. January 1st is 1.
48
+ def of_year() (date - Date.new(year.value, 1, 1)).numerator + 1 end
49
+
50
+ # Whether this day is a leap day or, in other words, February 29th.
51
+ def leap?() value == 29 && @month.value == 2 end
52
+
53
+ # Next successive day.
54
+ def succ() self + 1 end
55
+
56
+ # Return a new day incremented by an integer.
57
+ def +(increase) new_from_date(date + increase) end
58
+
59
+ # Return a new day decremented by an integer.
60
+ def -(decrease) new_from_date(date - decrease) end
61
+
62
+ # The number of days since parameter#day.
63
+ def since(operand) (date - operand.day.date).numerator end
64
+
65
+ # The number of days until parameter#day.
66
+ def until(operand) (operand.day.date - date).numerator end
67
+
68
+ def <=>(operand)
69
+ return -1 if operand == Infinity
70
+ date <=> operand.day.date
71
+ end
72
+
73
+ # If this day is the first Monday of the month this method returns 1.
74
+ # If this day is the second Monday of the month this method returns 2.
75
+ # Works for all days.
76
+ def weekday_occurrence_this_month
77
+ w = of_week_as_i
78
+ w_days_this_month = @month.days[0..value - 1].select do |day|
79
+ day.of_week_as_i == w
80
+ end
81
+ ORDINALS[w_days_this_month.nitems]
82
+ end
83
+
84
+ alias :of_month :value
85
+
86
+ def parts() [year.value, @month.value, value] end # :nodoc:
87
+
88
+ def new_from_date(date) # :nodoc:
89
+ new(@month.new(year.new(date.year), date.month), date.day)
90
+ end
91
+
92
+ end
93
+
94
+ end
@@ -0,0 +1,95 @@
1
+ module Suprdate
2
+
3
+ class Month < Unit
4
+
5
+ attr_accessor :day_factory
6
+ attr_reader :year
7
+
8
+ STRFTIME_STR = '%Y-%m'
9
+
10
+ def initialize(year, value)
11
+ @year = year
12
+ @value = if value.kind_of?(Symbol)
13
+ MONTHS_SYM_TO_I.fetch(value)
14
+ else
15
+ unless MONTH_RANGE.include?(value)
16
+ raise "Month value must specified as symbol or be within range #{MONTH_RANGE.inspect}"
17
+ end
18
+ value
19
+ end
20
+ self # intentional
21
+ end
22
+
23
+ protected :initialize
24
+
25
+ # Name of month as symbol.
26
+ def to_sym() MONTHS_AS_SYM[@value] end
27
+
28
+ # Number of day in this month.
29
+ def num_days
30
+ return 29 if leap?
31
+ NUM_DAYS_IN_MONTHS[@value]
32
+ end
33
+
34
+ # All the days in this month
35
+ def days
36
+ (1..num_days).to_a.map { |i| day_factory.new(self, i) }
37
+ end
38
+
39
+ # A choice of some specific days from this month.
40
+ def day(*indices)
41
+ indices = [1] if indices.empty?
42
+ rval = indices.map do |i|
43
+ i = num_days + 1 - i.abs if i < 0
44
+ day_factory.new(self, i)
45
+ end
46
+ Utility::disarray(rval)
47
+ end
48
+
49
+ # Whether this month contains a leap day.
50
+ def leap?() @value == 2 && @year.leap? end
51
+
52
+ def inspect() "#@year-#{@value.to_s.rjust(2, '0')}" end
53
+
54
+ def <=>(operand)
55
+ return -1 if operand == Infinity
56
+ operand = operand.month
57
+ (@year.value * NUM_MONTHS_IN_YEAR + @value) - (operand.year.value * NUM_MONTHS_IN_YEAR + operand.value)
58
+ end
59
+
60
+ # Number of months since parameter#month.
61
+ def since(operand) sum - operand.month.sum end
62
+
63
+ # Number of months until parameter#month.
64
+ def until(operand) operand.month.sum - sum end
65
+
66
+ # Return a new month incremented by an integer.
67
+ def +(increase) new_from_sum(sum + increase) end
68
+
69
+ # Return a new month decremented by an integer.
70
+ def -(decrease) new_from_sum(sum - decrease) end
71
+
72
+ # Next successive month.
73
+ def succ() self + 1 end
74
+
75
+ def of_year_as_sym() MONTHS_AS_SYM[@value] end
76
+ def of_year_as_s() MONTHS_AS_STR[@value] end
77
+ def month() self end
78
+
79
+ alias :of_year_as_i :value
80
+ alias :[] :day
81
+
82
+ protected
83
+
84
+ # total number of months from 0 years 0 months
85
+ def sum
86
+ @year.value * NUM_MONTHS_IN_YEAR + @value - 1
87
+ end
88
+
89
+ def new_from_sum(sum)
90
+ new(@year.new(sum / NUM_MONTHS_IN_YEAR), sum % NUM_MONTHS_IN_YEAR + 1)
91
+ end
92
+
93
+ end
94
+
95
+ end
@@ -0,0 +1,62 @@
1
+ module Suprdate
2
+
3
+ class Year < Unit
4
+
5
+ attr_accessor :month_factory, :day_factory, :week_factory, :week_definition
6
+
7
+ MINIMUM_VALUE = 1582 # year when leap years were first standardized
8
+ STRFTIME_STR = '%Y'
9
+
10
+ def initialize(v)
11
+ v = v.to_i
12
+ raise DateConstructionError.new(
13
+ "Attempted to create a year valued #{v}, #{MINIMUM_VALUE - v} less than minimum " +
14
+ "allowed value of #{MINIMUM_VALUE}"
15
+ ) if v < MINIMUM_VALUE
16
+ super(v)
17
+ end
18
+
19
+ protected :initialize # for + and -
20
+
21
+ def <=>(operand)
22
+ return -1 if operand == Infinity
23
+ operand = operand.year
24
+ @value - operand.value
25
+ end
26
+
27
+ def month(*indices)
28
+ indices = [1] if indices.empty?
29
+ Utility::disarray(indices.map { |i| new_month(i) })
30
+ end
31
+
32
+ def +(increase) new(@value + increase) end
33
+ def -(decrease) new(@value - decrease) end
34
+ def succ() self + 1 end
35
+ def months() MONTH_RANGE.to_a.map { |i| new_month(i) } end
36
+ def days() months.map { |m| m.days }.flatten end
37
+ def day(*args) month(1).day(*args) end
38
+ def inspect() @value.to_s end
39
+ def year() self end
40
+ def since(year) @value - year.value end
41
+ def until(year) year.value - @value end
42
+
43
+ def leap?
44
+ return true if @value % 400 == 0
45
+ return false if @value % 100 == 0
46
+ return true if @value % 4 == 0
47
+ false
48
+ end
49
+
50
+ alias :[] :month
51
+
52
+ protected
53
+
54
+ def new_month(value)
55
+ month = month_factory.new(self, value)
56
+ month.day_factory = day_factory
57
+ month
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,17 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe 'array diff' do
4
+
5
+ it 'should remove equal year elems' do
6
+ ([y(2010), y(2011)] - [y(2010)]).should == [y(2011)]
7
+ end
8
+
9
+ it 'should remove equal month elems' do
10
+ ([m(2010,1), m(2010, 2)] - [m(2010,1)]).should == [m(2010, 2)]
11
+ end
12
+
13
+ it 'should remove equal day elems' do
14
+ ([d(2010, 1, 1), d(2010, 1, 2)] - [d(2010, 1, 1)]).should == [d(2010, 1, 2)]
15
+ end
16
+
17
+ end
@@ -0,0 +1,65 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Builder, 'normal unit methods' do
4
+
5
+ it "should build years" do
6
+ year = Builder.new.year(expected = (rand * 100).round + 1700)
7
+ year.should be_instance_of(Year)
8
+ year.to_i.should == expected
9
+ year.month_factory.should == Month
10
+ year.day_factory.should == Day
11
+
12
+ # reminds me to update the builder when I add this functionality
13
+ year.week_definition.should == nil
14
+ end
15
+
16
+ it "should build months" do
17
+ month = Builder.new.month(2008, expected = (rand * 11).round + 1)
18
+ month.should be_instance_of(Month)
19
+ month.to_i.should == expected
20
+ month.day_factory.should == Day
21
+ month.year.week_definition.should == nil
22
+ end
23
+
24
+ it "should build days" do
25
+ day = Builder.new.day(2008, 1, expected = (rand * 30).round + 1)
26
+ day.should be_instance_of(Day)
27
+ day.year.week_definition.should == nil
28
+ day.year.month_factory.should == Month
29
+ end
30
+
31
+ it "should build today" do
32
+ Builder.new.today.should be_instance_of(Day)
33
+ Builder.new.today.to_s.should == Time.now.strftime(Day::STRFTIME_STR)
34
+ end
35
+
36
+ end
37
+
38
+ describe Builder, 'date method' do
39
+
40
+ it "should abstract the other methods of Builder" do
41
+ Builder::DATE_NUM_PARTS_RANGE.each do |num_parts|
42
+ b = Builder.new
43
+ b.should_receive(Builder::UNIT_NUM_PARTS[num_parts]).once.and_return(expected = rand_int)
44
+ b.date(*date_parts(num_parts)).should == expected
45
+ end
46
+ end
47
+
48
+ it "should raise if wrong number of args" do
49
+ lambda { Builder.new.date(1,2,3,4) }.should raise_error(DateConstructionError)
50
+ lambda { Builder.new.date() }.should raise_error(DateConstructionError)
51
+ end
52
+
53
+ end
54
+
55
+ describe Builder, 'exported builder methods' do
56
+
57
+ it "should be defined" do
58
+ defined = ['Year', 'Month', 'Day', 'Date', 'Today'].each do |e|
59
+ respond_to?(e).should == true
60
+ end
61
+ (Builder.builder_methods.map { |m| m.to_export } - defined).should == []
62
+ Day(2008, 10, 10).should == DEFAULT_BUILDER.day(2008, 10, 10)
63
+ end
64
+
65
+ end
@@ -0,0 +1,124 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Day, 'creation' do
4
+
5
+ it "should work from an integer" do
6
+ d(2000, 1, 1).to_s.should == '2000-01-01'
7
+ d(2000, 1, 8).to_s.should == '2000-01-08'
8
+ d(2000, 2, 3).to_s.should == '2000-02-03'
9
+ d(1999, 3, 4).to_s.should == '1999-03-04'
10
+ end
11
+
12
+ it "should prevent the creation of impossible days" do
13
+ lambda { d(2000, 1, 40) }.should raise_error(DateConstructionError)
14
+ lambda { d(2000, 2, 30) }.should raise_error(DateConstructionError)
15
+ lambda { d(2000, 4, 31) }.should raise_error(DateConstructionError)
16
+ end
17
+
18
+ end
19
+
20
+ describe Day, 'readers' do
21
+
22
+ it "should give you the day of the year" do
23
+ d(2000, 1, 1).of_year.should == 1
24
+ d(2000, 1, 5).of_year.should == 5
25
+ d(2000, 2, 2).of_year.should == 33
26
+ end
27
+
28
+ it "should give you the day of the week" do
29
+ d(2008, 11, 9).of_week_as_s.should == "Sunday"
30
+ d(2008, 11, 29).of_week_as_sym.should == :sat
31
+ d(2008, 11, 28).of_week_as_sym.should == :fri
32
+ d(2008, 11, 28).of_week_as_i.should == 6
33
+ d(2008, 12, 1).of_week_as_i.should == 2
34
+ end
35
+
36
+ it "should give you equivalent Date, DateTime and Time objects" do
37
+ day = d(2000, 1, 1)
38
+ day.date.should be_instance_of(Date)
39
+ day.time.should be_instance_of(Time)
40
+ day.datetime.should be_instance_of(DateTime)
41
+ # returns are representitive:
42
+ day.date.strftime('%Y-%m-%d').should == day.to_s
43
+ day.time.strftime('%Y-%m-%d').should == day.to_s
44
+ day.datetime.strftime('%Y-%m-%d').should == day.to_s
45
+ end
46
+
47
+ it "should return appropriate symbol" do
48
+ d(2008, 11, 2).weekday_occurrence_this_month.should == :first
49
+ d(2008, 11, 9).weekday_occurrence_this_month.should == :second
50
+ d(2008, 11, 30).weekday_occurrence_this_month.should == :fifth
51
+ d(2008, 11, 19).weekday_occurrence_this_month.should == :third
52
+ end
53
+
54
+ it "should know when it is one" do
55
+ d(2000, 2, 29).leap?.should == true
56
+ d(2000, 2, 28).leap?.should == false
57
+ lambda { d(2001, 2, 29) }.should raise_error(DateConstructionError)
58
+ end
59
+
60
+ end
61
+
62
+ describe Day, 'math and logic' do
63
+
64
+ it "should be comparable" do
65
+ (d(2000, 1, 21) == d(2000, 1, 21)).should == true
66
+ (d(2000, 1, 20) == d(2000, 1, 21)).should == false
67
+ (d(2000, 6, 21) == d(2000, 5, 21)).should == false
68
+ (d(2000, 5, 21) == d(2001, 5, 21)).should == false
69
+ (d(2000, 1, 21) > d(2000, 1, 22)).should == false
70
+ (d(2000, 1, 22) > d(2000, 1, 21)).should == true
71
+ (d(2000, 6, 21) > d(2000, 5, 21)).should == true
72
+ (d(2000, 5, 21) > d(2000, 6, 21)).should == false
73
+ (d(2000, 5, 21) > d(2001, 5, 21)).should == false
74
+ (d(2001, 5, 21) > d(2000, 5, 21)).should == true
75
+ end
76
+
77
+ it "should be able to add with integers" do
78
+ (d(2000, 1, 11) + 1).should == d(2000, 1, 12)
79
+ (d(1999, 12, 31) + 1).should == d(2000, 1, 1)
80
+ # accounts for leap
81
+ (d(2000, 2, 28) + 2).should == d(2000, 3, 1)
82
+ (d(2001, 2, 28) + 2).should == d(2001, 3, 2)
83
+ end
84
+
85
+ it "should be able to subtract with integers" do
86
+ (d(2000, 1, 12) - 1).should == d(2000, 1, 11)
87
+ (d(2000, 1, 1) - 1).should == d(1999, 12, 31)
88
+ # accounts for leap
89
+ (d(2000, 3, 1) - 2).should == d(2000, 2, 28)
90
+ (d(2001, 3, 2) - 2).should == d(2001, 2, 28)
91
+ end
92
+
93
+ it "should hold state after arithmetic" do
94
+ a = d(2000, 1, 28)
95
+ # Because Day has no writable state of it's own (but Year does) I am assigning a random value
96
+ # to month_factory (an attribute on the year).
97
+ a.year.month_factory = expected = rand_int
98
+ b = a + 5 # the month and the day change
99
+ # but the month should still hold the original random value
100
+ b.year.month_factory.should == expected
101
+ b.object_id.should_not == a.object_id
102
+ end
103
+
104
+ it "should be enumerable in a range" do
105
+ (d(2000, 1, 1)..d(2000, 1, 4)).to_a.should == [
106
+ d(2000, 1, 1), d(2000, 1, 2), d(2000, 1, 3), d(2000, 1, 4)
107
+ ]
108
+ end
109
+
110
+ it "should be able to get days since and until other days" do
111
+ d(2000, 1, 3).since(d(2000, 1, 1)).should == 2
112
+ d(2000, 2, 1).since(d(2000, 1, 1)).should == 31
113
+ d(2000, 1, 1).until(d(2000, 1, 3)).should == 2
114
+ d(2000, 1, 1).until(d(2000, 2, 1)).should == 31
115
+ end
116
+
117
+ it "should be able to get days since and until other months and years" do
118
+ d(2000, 1, 3).since(m(2000, 1)).should == 2
119
+ d(2000, 1, 3).since(y(2000)).should == 2
120
+ d(2000, 3, 3).since(m(2000, 3)).should == 2
121
+ d(2000, 1, 3).since(y(2000)).should == 2
122
+ end
123
+
124
+ end