calc_profit 0.1.1

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,50 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module CalcProfit
4
+ class Transaction
5
+ attr_reader :code, :date, :price, :ref, :info, :raw_shares
6
+ attr_accessor :shares
7
+
8
+ def initialize(trans_hash)
9
+ if trans_hash[:code] =~ /^\s*[pP]/
10
+ @code = 'P'
11
+ elsif trans_hash[:code] =~ /^\s*[sS]/
12
+ @code = 'S'
13
+ else
14
+ raise "Bad code (#{code}) supplied to Transaction constructor."
15
+ end
16
+ @price = trans_hash[:price].to_f
17
+ @shares = trans_hash[:shares].to_f.round
18
+ @raw_shares = trans_hash[:raw_shares].to_f.round
19
+ case trans_hash[:date]
20
+ when String
21
+ @date = Date.parse(trans_hash[:date])
22
+ when Date
23
+ @date = trans_hash[:date]
24
+ else
25
+ raise "Bad date (#{date}) supplied to Transaction constructor."
26
+ end
27
+ @ref = trans_hash[:ref]
28
+ @info = trans_hash[:info]
29
+ end
30
+
31
+ def table_row
32
+ hash = {}
33
+ hash[:ref] = ref
34
+ hash[:date] = date.iso
35
+ hash[:code] = code
36
+ hash[:shares] = shares.to_s
37
+ hash[:price] = price.to_s
38
+ hash[:info] = info
39
+ hash
40
+ end
41
+
42
+ def purchase?
43
+ code == 'P'
44
+ end
45
+
46
+ def sale?
47
+ code == 'S'
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,181 @@
1
+ module CalcProfit
2
+ class TransactionGroup
3
+ attr_reader :profit, :matches
4
+ attr_reader :purchase_shares, :sale_shares
5
+
6
+ def initialize(table = nil)
7
+ @purchases = []
8
+ @sales = []
9
+ @trans_by_ref = {}
10
+ @matches = []
11
+ @profit = 0
12
+ @purchase_shares = 0
13
+ @sale_shares = 0
14
+ if table
15
+ add_table_transactions(table)
16
+ end
17
+ end
18
+
19
+ def add_table_transactions(table)
20
+ table.rows.each do |row|
21
+ self << Transaction.new(row)
22
+ end
23
+ end
24
+
25
+ def <<(t)
26
+ # Add a new transaction
27
+ if t.purchase?
28
+ @purchases << t
29
+ @trans_by_ref[t.ref.to_s] = t
30
+ elsif t.sale?
31
+ @sales << t
32
+ @trans_by_ref[t.ref.to_s] = t
33
+ else
34
+ raise ArgumentError,
35
+ "Can only add purchase or sale transactions to TransactionGroup."
36
+ end
37
+ end
38
+
39
+ def match
40
+ pp = @purchases.dup2
41
+ ss = @sales.dup2
42
+ until pp.empty? or ss.empty? do
43
+ # For each sale remaining, find the remaining purchase
44
+ # with 6 months having the lowest price and match
45
+ # to the extent of the lower number of shares
46
+ break if ss.empty?
47
+ best_sale = ss.max { |a, b| a.price <=> b.price }
48
+ eligible_purchases = pp.partition { |p|
49
+ p.date.within_6mos_of?(best_sale.date) && p.price < best_sale.price
50
+ }
51
+ eligible_purchases = eligible_purchases[0]
52
+ if eligible_purchases.empty?
53
+ ss.delete(best_sale)
54
+ next
55
+ end
56
+ best_prch = eligible_purchases.min { |a, b| a.price <=> b.price }
57
+ # Take number of shares as the smaller of the sale or buy
58
+ if best_sale.shares >= best_prch.shares
59
+ s = Transaction.new(code: 'S', date: best_sale.date, price: best_sale.price,
60
+ shares: best_prch.shares, ref: best_sale.ref,
61
+ info: best_sale.info)
62
+ p = Transaction.new(code: 'P', date: best_prch.date, price: best_prch.price,
63
+ shares: best_prch.shares, ref: best_prch.ref,
64
+ info: best_prch.info)
65
+ best_sale.shares -= best_prch.shares
66
+ pp.delete(best_prch)
67
+ else
68
+ s = Transaction.new(code: 'S', date: best_sale.date, price: best_sale.price,
69
+ shares: best_sale.shares, ref: best_sale.ref,
70
+ info: best_sale.info)
71
+ p = Transaction.new(code: 'P', date: best_prch.date, price: best_prch.price,
72
+ shares: best_sale.shares, ref: best_prch.ref,
73
+ info: best_prch.info)
74
+ best_prch.shares -= best_sale.shares
75
+ ss.delete(best_sale)
76
+ end
77
+ m = Match.new(p, s)
78
+ unless m.profit < 0.01
79
+ @matches << m
80
+ @profit += m.profit
81
+ @purchase_shares += p.shares
82
+ @sale_shares += s.shares
83
+ end
84
+ end
85
+ @matches.empty? ? nil : match_table
86
+ end
87
+
88
+ def match_table
89
+ table = Table.new
90
+ @matches.each do |m|
91
+ table << m.table_row
92
+ end
93
+ table
94
+ end
95
+
96
+ def transaction_table
97
+ trans = @purchases + @sales
98
+ table = Table.new
99
+ trans.each do |tr|
100
+ table << tr.table_row
101
+ end
102
+ table
103
+ end
104
+
105
+ # This method attempts to find an optimal, profit-maximizing set of
106
+ # matches by formulating a transportation problem and passing it to
107
+ # lp_solve. Requires lp_solve, which on ubuntu can be installed with
108
+ #
109
+ # $ apt-get install lp_solve
110
+ def tp_solve
111
+ lpf = File.new('profit.lp', 'w')
112
+
113
+ # Write the objective function
114
+ lpf.print("max: ")
115
+ penalty = -1000.0
116
+ have_eqn = false
117
+ @purchases.each do |p|
118
+ @sales.each do |s|
119
+ if p.date.within_6mos_of?(s.date) and s.price > p.price
120
+ profit = s.price - p.price
121
+ lpf.print("#{profit} X_#{p.ref}@@#{s.ref} + ")
122
+ have_eqn = true
123
+ else
124
+ lpf.print("#{penalty} X_#{p.ref}@@#{s.ref} + ")
125
+ have_eqn = true
126
+ end
127
+ end
128
+ end
129
+ lpf.print("0;\n\n") if have_eqn
130
+
131
+ # Write the purchase contraints
132
+ @purchases.each do |p|
133
+ lpf.print("P_#{p.ref}: ")
134
+ @sales.each do |s|
135
+ lpf.print("X_#{p.ref}@@#{s.ref} + ")
136
+ end
137
+ lpf.print("0 <= #{p.shares};\n")
138
+ end
139
+
140
+ # Write the sales contraints
141
+ @sales.each do |s|
142
+ lpf.print("S_#{s.ref}: ")
143
+ @purchases.each do |p|
144
+ lpf.print("X_#{p.ref}@@#{s.ref} + ")
145
+ end
146
+ lpf.print("0 <= #{s.shares};\n")
147
+ end
148
+ lpf.close
149
+
150
+ # Now, pass the file into lp_solve and parse the solution
151
+ calculated_profit = 0.0
152
+ IO.popen("lp_solve profit.lp") do |sol|
153
+ #File.open("profit.sol") do |sol|
154
+ sol.each do |line|
155
+ next if line =~ /^\s*$/
156
+ if line =~ /^Value of objective function:\s+([-+e.\d]+)/
157
+ calculated_profit = $1.to_f
158
+ elsif line =~ /^X_(\S*)@@(\S*)\s+(\d+)$/
159
+ next if $3.to_f == 0.0
160
+ p = @trans_by_ref[$1].dup
161
+ s = @trans_by_ref[$2].dup
162
+ p.shares = s.shares = $3.to_f
163
+ if s.price - p.price < 0.01
164
+ STDERR.print "Sale #{s.ref} @ #{s.price} and Purchase #{p.ref} @ #{p.price} has no profit.\n"
165
+ else
166
+ m = Match.new(p, s)
167
+ @matches << m
168
+ @profit += m.profit
169
+ @purchase_shares += p.shares
170
+ @sale_shares += s.shares
171
+ end
172
+ end
173
+ end
174
+ end
175
+ unless calculated_profit == @profit
176
+ STDERR.print "Warning: profit calculated by lp_solve was #{calculated_profit}; by matches was #{@profit}\n"
177
+ end
178
+ !@matches.empty?
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,7 @@
1
+ module CalcProfit
2
+ MAJOR = 0
3
+ MINOR = 1
4
+ PATCH = 1
5
+
6
+ VERSION = [MAJOR, MINOR, PATCH].compact.join('.')
7
+ end
@@ -0,0 +1,23 @@
1
+ require 'test_helper'
2
+
3
+ class AddressTest < ActiveSupport::TestCase
4
+ def test_easy
5
+ a = Address.new('14625 Dearborn', '', 'Overland Park', 'KS', '66223', '')
6
+ assert_equal('14625 Dearborn', a.street1, "Bad parse of street1")
7
+ assert_equal('', a.street2, "Bad parse of street2")
8
+ assert_equal('Overland Park', a.city, "Bad parse of city")
9
+ assert_equal('KS', a.state, "Bad parse of state")
10
+ assert_equal('66223', a.zip, "Bad parse of zip")
11
+ assert_equal('United States', a.country, "Bad parse of country")
12
+ end
13
+
14
+ def test_hard
15
+ a = Address.new('14625 Dearborn', '', 'Overland Park, KS 66223')
16
+ assert_equal('14625 Dearborn', a.street1, "Bad parse of street1")
17
+ assert_equal('', a.street2, "Bad parse of street2")
18
+ assert_equal('Overland Park', a.city, "Bad parse of city")
19
+ assert_equal('KS', a.state, "Bad parse of state")
20
+ assert_equal('66223', a.zip, "Bad parse of zip")
21
+ assert_equal('United States', a.country, "Bad parse of country")
22
+ end
23
+ end
data/test/TestDBase.rb ADDED
@@ -0,0 +1,47 @@
1
+ require 'test/unit'
2
+ require 'Section16/DBase' unless defined? $db
3
+
4
+ begin
5
+ $db.do('DROP TABLE junk') if $db.tables.include?('junk')
6
+ res = $db.do('CREATE TABLE junk (i integer primary key, s text);')
7
+ rescue DBI::DatabaseError => e
8
+ puts "Error: #{e.errstr}"
9
+ end
10
+
11
+ class TestDate < Test::Unit::TestCase
12
+
13
+ def test_dbase_is_up
14
+ assert($db.connected?)
15
+ end
16
+
17
+ def test_insert
18
+ $db.do("INSERT INTO junk (i, s) VALUES (1, 'a');")
19
+ $db.do("INSERT INTO junk (i, s) VALUES (2, 'b');")
20
+ $db.do("INSERT INTO junk (i, s) VALUES (3, 'c');")
21
+ $db.do("INSERT INTO junk (i, s) VALUES (4, 'd');")
22
+ rows = $db.select_all('SELECT * FROM junk;')
23
+ assert_equal(4, rows.length)
24
+ end
25
+
26
+ def test_odb
27
+ $odb.trace(2)
28
+ assert($odb.ping, "No ping response to $odb")
29
+ assert($odb.tables.include?('owner'), "Can't find owner table")
30
+ assert($odb.tables.include?('issuer'), "Can't find issuer table")
31
+ assert($odb.tables.include?('own_iss'), "Can't find own_iss table")
32
+ assert($odb.tables.include?('own_fil'), "Can't find own_fil table")
33
+ end
34
+
35
+ class Junk < ActiveRecord::Base
36
+ set_table_name('junk')
37
+ set_primary_key('i')
38
+ end
39
+
40
+ def test_zactive_record
41
+ assert_equal(Junk.find(2, 4).length, 2,
42
+ "Active record class didn't find 2 Junks")
43
+ end
44
+ end
45
+
46
+
47
+
data/test/TestDate.rb ADDED
@@ -0,0 +1,267 @@
1
+ # Code Generated by ZenTest v. 2.3.0
2
+ # classname: asrt / meth = ratio%
3
+ # Date: 0 / 15 = 0.00%
4
+
5
+ require 'test/unit' unless defined? $ZENTEST and $ZENTEST
6
+ require 'Section16/Date'
7
+
8
+ class TestDate < Test::Unit::TestCase
9
+
10
+ def test_nth_wday_in_year_month(n, wday, year, month)
11
+ assert_equals(Date.new(1957, 30, 11),
12
+ Date.nth_wday_in_year_month(5, 6, 1957, 11),
13
+ "1957-11-30 is 5th Saturday in November, 1957")
14
+ assert_equals(Date.new(1957, 30, 11),
15
+ Date.nth_wday_in_year_month(-1, 6, 1957, 11),
16
+ "1957-11-30 is last Saturday in November, 1957")
17
+ end
18
+
19
+ def test_last_day_in_year_month
20
+ assert_equal(29, Date.last_day_in_year_month(2000, 2),
21
+ "February 28 is last day in Feb, 2000")
22
+ assert_equal(28, Date.last_day_in_year_month(1900, 2),
23
+ "February 29 is last day in Feb, 1900")
24
+ end
25
+
26
+ def test_weekend_eh
27
+ assert(!Date.new(1985, 7, 16).weekend?,
28
+ "1985-07-16 is not a weekend day")
29
+ assert(Date.new(1957, 9, 22).weekend?,
30
+ "1957-09-22 is a weekend day")
31
+ end
32
+
33
+ def test_easter_this_year
34
+ assert_equal(Date.new(2000, 4, 23),
35
+ Date.new(2000, 9, 22).easter_this_year,
36
+ "2000-4-23 should be Easter in 2000")
37
+ end
38
+
39
+ def test_easter_eh
40
+ d = Date.new(2000, 8, 18)
41
+ assert(!d.easter?, "#{d} should not be Easter")
42
+ assert(Date.new(2000, 4, 23).easter?,
43
+ "2000-04-23 should be Easter")
44
+ assert(Date.new(1988, 4, 3).easter?,
45
+ "1988-04-03 should be Easter")
46
+ end
47
+
48
+ def test_nth_wday_in_month_eh
49
+ assert(Date.new(1957, 11, 30).nth_wday_in_month?(5, 6, 11),
50
+ '1957-11-30 is the 5th Saturday in November of its year')
51
+ assert(Date.new(1957, 9, 22).nth_wday_in_month?(4, 0, 9),
52
+ '1957-09-22 is the 4th Sunday in September of its year')
53
+ end
54
+
55
+ def test_fed_fixed_holiday_eh
56
+ assert(Date.new(1988, 1, 1).fed_fixed_holiday?,
57
+ "Feds celebrate New Years Day")
58
+ assert(Date.new(1988, 7, 4).fed_fixed_holiday?,
59
+ "Feds celebrate Independence Day")
60
+ assert(Date.new(1988, 11, 11).fed_fixed_holiday?,
61
+ "Feds celebrate Veterans Day")
62
+ assert(Date.new(1988, 12, 25).fed_fixed_holiday?,
63
+ "Feds celebrate Christmas Day")
64
+ assert(Date.new(1988, 12, 31).fed_fixed_holiday?,
65
+ "Feds celebrate New Years Eve")
66
+ (1988..1990).each do |y|
67
+ h = []
68
+ (1..12).each do |m|
69
+ (1..Date.last_day_in_year_month(y, m)).each do |d|
70
+ d = Date.new(y, m, d)
71
+ h.push(d) if d.fed_fixed_holiday?
72
+ end
73
+ end
74
+ assert_equal(5, h.length,
75
+ "Found #{h.length} fixed holidays in #{y}")
76
+ end
77
+ end
78
+
79
+ def test_fed_moveable_feast_eh
80
+ assert(Date.new(1988, 1, 18).fed_moveable_feast?,
81
+ "Feds celebrate MLK Day")
82
+ assert(Date.new(1988, 2, 15).fed_moveable_feast?,
83
+ "Feds celebrate Washington's Birthday")
84
+ assert(Date.new(1988, 5, 30).fed_moveable_feast?,
85
+ "Feds celebrate Memorial Day")
86
+ assert(Date.new(1988, 9, 5).fed_moveable_feast?,
87
+ "Feds celebrate Labor Day")
88
+ assert(Date.new(1988, 10, 10).fed_moveable_feast?,
89
+ "Feds celebrate Columbus Day")
90
+ assert(Date.new(1988, 11, 24).fed_moveable_feast?,
91
+ "Feds celebrate Tahnksgiving")
92
+ (1988..1990).each do |y|
93
+ h = []
94
+ (1..12).each do |m|
95
+ (1..Date.last_day_in_year_month(y, m)).each do |d|
96
+ d = Date.new(y, m, d)
97
+ h.push(d) if d.fed_moveable_feast?
98
+ end
99
+ end
100
+ assert_equal(6, h.length,
101
+ "Found #{h.length} floating holidays in #{y}")
102
+ end
103
+ end
104
+
105
+ def test_fed_holiday_eh
106
+ assert(Date.new(1988, 1, 1).fed_holiday?,
107
+ "Feds celebrate New Years Day")
108
+ assert(Date.new(1988, 1, 18).fed_holiday?,
109
+ "Feds celebrate MLK Day")
110
+ assert(Date.new(1988, 2, 15).fed_holiday?,
111
+ "Feds celebrate Washington's Birthday")
112
+ assert(Date.new(1988, 5, 30).fed_holiday?,
113
+ "Feds celebrate Memorial Day")
114
+ assert(Date.new(1988, 7, 4).fed_holiday?,
115
+ "Feds celebrate Independence Day")
116
+ assert(Date.new(1988, 9, 5).fed_holiday?,
117
+ "Feds celebrate Labor Day")
118
+ assert(Date.new(1988, 10, 10).fed_holiday?,
119
+ "Feds celebrate Columbus Day")
120
+ assert(Date.new(1988, 11, 24).fed_holiday?,
121
+ "Feds celebrate Tahnksgiving")
122
+ assert(Date.new(1988, 11, 11).fed_holiday?,
123
+ "Feds celebrate Veterans Day")
124
+ assert(Date.new(1988, 12, 25).fed_holiday?,
125
+ "Feds celebrate Christmas Day")
126
+ assert(Date.new(1988, 12, 31).fed_holiday?,
127
+ "Feds celebrate New Years Eve")
128
+ end
129
+
130
+ def test_nyse_fixed_holiday_eh
131
+ assert(Date.new(1988, 1, 1).nyse_fixed_holiday?,
132
+ "NYSE celebrates New Years Day")
133
+ assert(Date.new(1988, 7, 4).nyse_fixed_holiday?,
134
+ "NYSE celebrates Independence Day")
135
+ assert(Date.new(1988, 12, 25).nyse_fixed_holiday?,
136
+ "NYSE celebrates Christmas Day")
137
+ (1988..1990).each do |y|
138
+ h = []
139
+ (1..12).each do |m|
140
+ (1..Date.last_day_in_year_month(y, m)).each do |d|
141
+ d = Date.new(y, m, d)
142
+ h.push(d) if d.nyse_fixed_holiday?
143
+ end
144
+ end
145
+ assert_equal(3, h.length,
146
+ "Found #{h.length} fixed NYSE holidays in #{y}")
147
+ end
148
+ end
149
+
150
+ def test_nyse_moveable_feast_eh
151
+ assert(Date.new(1988, 1, 18).nyse_moveable_feast?,
152
+ "NYSE celebrates MLK Day")
153
+ assert(Date.new(1988, 2, 15).nyse_moveable_feast?,
154
+ "NYSE celebrates Washington's Birthday")
155
+ assert(Date.new(1988, 4, 1).nyse_moveable_feast?,
156
+ "NYSE celebrates Good Friday")
157
+ assert(Date.new(1988, 5, 30).nyse_moveable_feast?,
158
+ "NYSE celebrates Memorial Day")
159
+ assert(Date.new(1988, 9, 5).nyse_moveable_feast?,
160
+ "NYSE celebrates Labor Day")
161
+ assert(Date.new(1988, 11, 24).nyse_moveable_feast?,
162
+ "NYSE celebrates Thanksgiving")
163
+ (1988..1990).each do |y|
164
+ h = []
165
+ (1..12).each do |m|
166
+ (1..Date.last_day_in_year_month(y, m)).each do |d|
167
+ d = Date.new(y, m, d)
168
+ h.push(d) if d.nyse_moveable_feast?
169
+ end
170
+ end
171
+ assert_equal(6, h.length,
172
+ "Found #{h.length} floating holidays in #{y}")
173
+ end
174
+ end
175
+
176
+ def test_nyse_holiday_eh
177
+ assert(Date.new(1988, 1, 1).nyse_holiday?,
178
+ "NYSE celebrates New Years Day")
179
+ assert(Date.new(1988, 7, 4).nyse_holiday?,
180
+ "NYSE celebrates Independence Day")
181
+ assert(Date.new(1988, 12, 25).nyse_holiday?,
182
+ "NYSE celebrates Christmas Day")
183
+ assert(Date.new(1988, 1, 18).nyse_holiday?,
184
+ "NYSE celebrates MLK Day")
185
+ assert(Date.new(1988, 2, 15).nyse_holiday?,
186
+ "NYSE celebrates Washington's Birthday")
187
+ assert(Date.new(1988, 4, 1).nyse_holiday?,
188
+ "NYSE celebrates Good Friday")
189
+ assert(Date.new(1988, 5, 30).nyse_holiday?,
190
+ "NYSE celebrates Memorial Day")
191
+ assert(Date.new(1988, 9, 5).nyse_holiday?,
192
+ "NYSE celebrates Labor Day")
193
+ assert(Date.new(1988, 11, 24).nyse_holiday?,
194
+ "NYSE celebrates Thanksgiving")
195
+ end
196
+
197
+ def test_fed_workday_eh
198
+ assert(!Date.new(1988, 1, 1).fed_workday?,
199
+ "Feds celebrate New Years Day")
200
+ assert(!Date.new(1988, 1, 18).fed_workday?,
201
+ "Feds celebrate MLK Day")
202
+ assert(!Date.new(1988, 2, 15).fed_workday?,
203
+ "Feds celebrate Washington's Birthday")
204
+ assert(!Date.new(1988, 5, 30).fed_workday?,
205
+ "Feds celebrate Memorial Day")
206
+ assert(!Date.new(1988, 7, 4).fed_workday?,
207
+ "Feds celebrate Independence Day")
208
+ assert(!Date.new(1988, 9, 5).fed_workday?,
209
+ "Feds celebrate Labor Day")
210
+ assert(!Date.new(1988, 10, 10).fed_workday?,
211
+ "Feds celebrate Columbus Day")
212
+ assert(!Date.new(1988, 11, 24).fed_workday?,
213
+ "Feds celebrate Tahnksgiving")
214
+ assert(!Date.new(1988, 11, 11).fed_workday?,
215
+ "Feds celebrate Veterans Day")
216
+ assert(!Date.new(1988, 12, 25).fed_workday?,
217
+ "Feds celebrate Christmas Day")
218
+ assert(!Date.new(1988, 12, 31).fed_workday?,
219
+ "Feds celebrate New Years Eve")
220
+ end
221
+
222
+ def test_nyse_workday_eh
223
+ assert(!Date.new(1988, 1, 1).nyse_workday?,
224
+ "NYSE celebrates New Years Day")
225
+ assert(!Date.new(1988, 7, 4).nyse_workday?,
226
+ "NYSE celebrates Independence Day")
227
+ assert(!Date.new(1988, 12, 25).nyse_workday?,
228
+ "NYSE celebrates Christmas Day")
229
+ assert(!Date.new(1988, 1, 18).nyse_workday?,
230
+ "NYSE celebrates MLK Day")
231
+ assert(!Date.new(1988, 2, 15).nyse_workday?,
232
+ "NYSE celebrates Washington's Birthday")
233
+ assert(!Date.new(1988, 4, 1).nyse_workday?,
234
+ "NYSE celebrates Good Friday")
235
+ assert(!Date.new(1988, 5, 30).nyse_workday?,
236
+ "NYSE celebrates Memorial Day")
237
+ assert(!Date.new(1988, 9, 5).nyse_workday?,
238
+ "NYSE celebrates Labor Day")
239
+ assert(!Date.new(1988, 11, 24).nyse_workday?,
240
+ "NYSE celebrates Thanksgiving")
241
+ end
242
+
243
+ def test_next_nyse_workday
244
+ assert_equal(Date.new(1988, 3, 31).next_nyse_workday,
245
+ Date.new(1988, 4, 4),
246
+ "Next NYSE workday after 1988-03-31 is 1988-04-04")
247
+ end
248
+
249
+ def test_next_fed_workday
250
+ assert_equal(Date.new(1988, 3, 31).next_fed_workday,
251
+ Date.new(1988, 4, 1),
252
+ "Next Fed workday after 1988-03-31 is 1988-04-01")
253
+ end
254
+
255
+ def test_iso
256
+ assert_equal(Date.new(1957, 9, 2).iso, "1957-09-02", "Bad iso format")
257
+ end
258
+
259
+ def test_num
260
+ assert_equal(Date.new(1957, 9, 2).num, "19570902", "Bad num format")
261
+ end
262
+
263
+ def test_within6
264
+ assert(Date.new(1957, 9, 22).within_6mos_of?(Date.new(1957, 6, 5)))
265
+ end
266
+ end
267
+