marsdate 1.0.9 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,11 +2,11 @@ require 'date'
2
2
 
3
3
  class MarsDateTime
4
4
 
5
- VERSION = "1.0.9"
5
+ VERSION = "1.1.3"
6
6
 
7
7
  include Comparable
8
8
 
9
- MSEC_PER_SOL = 88775244
9
+ MSEC_PER_SOL = 88775244 # .09 # Really + 0.09
10
10
  SOLS_PER_MYEAR = 668.5921
11
11
  MSEC_PER_DAY = 86400000
12
12
 
@@ -15,20 +15,19 @@ class MarsDateTime
15
15
  TimeStretch = MSEC_PER_SOL/MSEC_PER_DAY.to_f
16
16
 
17
17
  Months = %w[ UNDEFINED
18
- January Gemini February Cancer
19
- March Leo April Virgo
20
- May Libra June Scorpio
21
- July Sagittarius August Capricorn
22
- September Aquarius October Pisces
23
- November Aries December Taurus ]
18
+ M-January Gemini M-February Cancer
19
+ M-March Leo M-April Virgo
20
+ M-May Libra M-June Scorpio
21
+ M-July Sagittarius M-August Capricorn
22
+ M-September Aquarius M-October Pisces
23
+ M-November Aries M-December Taurus ]
24
24
  # no month 0
25
25
 
26
26
  Week = %w[ Sunday Monday Tuesday Wednesday Thursday Friday Saturday ]
27
27
 
28
28
  EpochMCE = DateTime.new(1,1,21)
29
29
  EpochCE = DateTime.new(1,1,1)
30
- FudgeOffset = 67784 + 44000 + 1099 + 87 - 56
31
- JulianDay1 = 1721443 # was ...24
30
+ JulianDay1 = 1721445 # Jan 1, 1 AD
32
31
 
33
32
  attr_reader :year, :month, :sol, :epoch_sol, :year_sol
34
33
  attr_reader :shr, :smin, :ssec # stretched time
@@ -49,6 +48,14 @@ class MarsDateTime
49
48
  return true
50
49
  end
51
50
 
51
+ def self.short?(myear) # short year
52
+ !leap?(myear)
53
+ end
54
+
55
+ def self.long?(myear) # long year
56
+ leap?(myear)
57
+ end
58
+
52
59
  def self.sols_in_month(m, year)
53
60
  return 28 if m < 24
54
61
  return 25 if leap?(year)
@@ -67,7 +74,7 @@ class MarsDateTime
67
74
 
68
75
  def leaps(myr)
69
76
  n = 0
70
- 1.upto(myr) {|i| n+=1 if leap?(i) }
77
+ 1.upto(myr) {|i| n+=1 if MarsDateTime.leap?(i) }
71
78
  n
72
79
  end
73
80
 
@@ -87,8 +94,16 @@ class MarsDateTime
87
94
  "#@day_of_week, #{Months[@month]} #@sol, #@year at #{time}"
88
95
  end
89
96
 
90
- def leap?(myear)
91
- MarsDateTime.leap?(myear) # DRY
97
+ def leap?
98
+ MarsDateTime.leap?(@year) # DRY
99
+ end
100
+
101
+ def short?
102
+ ! leap?
103
+ end
104
+
105
+ def long?
106
+ leap?
92
107
  end
93
108
 
94
109
  def month_name
@@ -108,6 +123,8 @@ class MarsDateTime
108
123
  case params.first
109
124
  when Integer, Float
110
125
  init_mems(params.first)
126
+ when Date
127
+ init_date(params.first)
111
128
  when DateTime
112
129
  init_datetime(params.first)
113
130
  else
@@ -121,13 +138,13 @@ class MarsDateTime
121
138
 
122
139
  def check_ymshms(my, mm, msol, mhr=0, mmin=0, msec=0)
123
140
  text = ""
124
- text << "year #{my} is not an integer\n" unless my.is_a? Fixnum
141
+ text << "year #{my} is not an integer\n" unless my.is_a? Integer
125
142
  text << "month #{mm} is out of range" unless (1..24).include? mm
126
143
  text << "sol #{msol} is out of range" unless (1..28).include? msol
127
144
  text << "hour #{mhr} is out of range" unless (0..24).include? mhr
128
145
  text << "minute #{mmin} is out of range" unless (0..59).include? mmin
129
146
  text << "second #{msec} is out of range" unless (0..59).include? msec
130
- if !leap?(my) && mm == 24 && msol > 24
147
+ if !MarsDateTime.leap?(my) && mm == 24 && msol > 24
131
148
  text << "sol #{msol} is invalid in a non-leap year"
132
149
  end
133
150
  raise text unless text.empty?
@@ -157,10 +174,13 @@ class MarsDateTime
157
174
  end
158
175
 
159
176
  def init_mems(mems)
177
+ # Note: The sol length is off by 0.09 msec -- to properly fix this
178
+ # will require measuring in microseconds so as to avoid floating-point math.
179
+ # The "round" calls below were experimental and were "mostly" successful.
160
180
  full_years = 0
161
181
  loop do
162
182
  millisec = FAKE_MSEC_PER_MYEAR
163
- millisec += MSEC_PER_SOL if leap?(full_years+1)
183
+ millisec += MSEC_PER_SOL if MarsDateTime.leap?(full_years+1)
164
184
  break if mems < millisec
165
185
  mems -= millisec
166
186
  # puts "Subtracting #{millisec} -- one full year => #{mems}"
@@ -172,32 +192,29 @@ class MarsDateTime
172
192
  full_days, mems = mems.divmod(MSEC_PER_SOL)
173
193
  full_hrs, mems = mems.divmod(3_600_000)
174
194
  full_min, mems = mems.divmod(60_000)
175
- sec = mems/1000.0
176
195
 
177
- my = full_years + 1 # 1-based
196
+ sec = mems/1000.0
197
+ my = full_years + 1 # 1-based
178
198
  mm = full_months + 1
179
- ms = full_days + 1
180
- mhr = full_hrs # 0-based
199
+ ms = full_days + 1
200
+ mhr = full_hrs # 0-based
181
201
  mmin = full_min
182
202
  msec = sec.to_i
183
- frac = sec - msec # fraction of a sec
184
-
185
- # # check for leap year...
186
- # if mm == 24
187
- # max = leap?(my) ? 25 : 24
188
- # diff = ms - max
189
- # if diff > 0
190
- # my, mm, ms = my+1, 1, diff
191
- # end
192
- # end
203
+ frac = sec - msec # fraction of a sec
193
204
 
194
205
  init_yms(my, mm, ms, mhr, mmin, msec)
195
206
  end
196
207
 
208
+ def init_date(date)
209
+ dt = date.to_datetime
210
+ days = dt.jd - JulianDay1
211
+ secs = days*86400 + dt.hour*3600 + dt.min*60 + dt.sec
212
+ init_mems(secs*1000)
213
+ end
214
+
197
215
  def init_datetime(dt)
198
216
  days = dt.jd - JulianDay1
199
217
  secs = days*86400 + dt.hour*3600 + dt.min*60 + dt.sec
200
- secs -= FudgeOffset
201
218
  init_mems(secs*1000)
202
219
  end
203
220
 
@@ -205,7 +222,7 @@ class MarsDateTime
205
222
  [@year, @month, @sol, @mhrs, @mmin, @msec]
206
223
  end
207
224
 
208
- def -(other)
225
+ def -(other) # FIXME? sols or secs?
209
226
  case other
210
227
  when MarsDateTime
211
228
  diff = @mems - other.mems
@@ -214,14 +231,14 @@ class MarsDateTime
214
231
  other = MarsDateTime.new(other)
215
232
  diff = @mems - other.mems
216
233
  diff.to_f / MSEC_PER_SOL
217
- when Fixnum, Float
234
+ when Integer, Float
218
235
  self + (-other)
219
236
  else
220
237
  raise "Unexpected data type"
221
238
  end
222
239
  end
223
240
 
224
- def +(sols)
241
+ def +(sols) # FIXME? sols or secs?
225
242
  millisec = sols * MSEC_PER_SOL
226
243
  MarsDateTime.new(@mems + millisec)
227
244
  end
@@ -238,7 +255,7 @@ class MarsDateTime
238
255
  end
239
256
 
240
257
  def earth_date
241
- secs = @mems/1000 + FudgeOffset
258
+ secs = @mems/1000
242
259
  days,secs = secs.divmod(86400)
243
260
  hrs, secs = secs.divmod(3600)
244
261
  min, secs = secs.divmod(60)
@@ -263,14 +280,14 @@ class MarsDateTime
263
280
  case piece
264
281
  when "%a"; final << @day_of_week[0..2]
265
282
  when "%A"; final << @day_of_week
266
- when "%b"; final << month_name[0..2]
283
+ when "%b"; final << (@month.odd? ? month_name[2..4] : month_name[0..2])
267
284
  when "%B"; final << month_name
268
285
  when "%d"; final << zsol
269
286
  when "%e"; final << ('%2d' % @sol)
270
287
  when "%F"; final << "#@year-#{zmonth}-#{zsol}"
271
288
  when "%H"; final << zhh
272
289
  when "%j"; final << @year_sol.to_s
273
- when "%m"; final << @month.to_s
290
+ when "%m"; final << zmonth # @month.to_s
274
291
  when "%M"; final << zmm
275
292
  when "%s"; final << @msec.to_s # was: (@mems*1000).to_i.to_s
276
293
  when "%S"; final << zss
@@ -0,0 +1,382 @@
1
+ $: << "lib"
2
+ require 'marsdate'
3
+
4
+ require 'rubygems'
5
+ require 'minitest'
6
+ require 'minitest/spec'
7
+ require 'minitest/autorun'
8
+
9
+ require 'pp'
10
+
11
+ def my_assert(expr)
12
+ if !expr && block_given?
13
+ puts "\n--- DEBUG: "
14
+ yield
15
+ end
16
+ assert(expr)
17
+ end
18
+
19
+ def my_assert_equal(e1, e2)
20
+ if e1 != e2 && block_given?
21
+ puts "\n--- DEBUG: "
22
+ yield
23
+ end
24
+ assert_equal(e1, e2)
25
+ end
26
+
27
+ ## experimenting...
28
+
29
+ describe "Mars date 1/1/1" do
30
+ md = MarsDateTime.new(1,1,1)
31
+ it "should compute backwards to same result" do
32
+ assert_equal([1,1,1,0,0,0],md.ymshms)
33
+ end
34
+ it "should be a Sunday" do
35
+ assert_equal(0,md.dow)
36
+ end
37
+ end
38
+
39
+ describe "Mars date 2/3/4" do
40
+ md = MarsDateTime.new(2,3,4)
41
+ it "should compute backwards to same result" do
42
+ assert_equal([2,3,4,0,0,0],md.ymshms)
43
+ end
44
+ end
45
+
46
+ describe "Mars date 5/4/24" do
47
+ md = MarsDateTime.new(5,4,24)
48
+ it "should compute backwards to same result" do
49
+ assert_equal([5,4,24,0,0,0],md.ymshms)
50
+ end
51
+ end
52
+
53
+ describe "Mars date 1054/20/20" do
54
+ md = MarsDateTime.new(1054,20,20)
55
+ it "should compute backwards to same result" do
56
+ assert_equal([1054,20,20,0,0,0],md.ymshms)
57
+ end
58
+ end
59
+
60
+ describe "Earth date 1/1/22" do
61
+ ed = DateTime.new(1,1,22)
62
+ md = MarsDateTime.new(ed)
63
+ it "should be approximately M-date 1/1/1" do
64
+ assert_equal([1,1,1],md.ymshms[0..2])
65
+ end
66
+ it "should yield a Martian Sunday" do
67
+ assert_equal(md.day_of_week,"Sunday")
68
+ end
69
+ end
70
+
71
+ describe "Earth date May 23, 1961" do
72
+ e = DateTime.new(1961,5,23)
73
+ m = MarsDateTime.new(e)
74
+ it "should yield a Martian Thursday" do
75
+ assert_equal(m.day_of_week,"Thursday")
76
+ end
77
+ end
78
+
79
+ describe "Earth date May 24, 1961" do
80
+ e = DateTime.new(1961,5,24)
81
+ m = MarsDateTime.new(e)
82
+ it "should yield a Martian Friday" do
83
+ assert_equal(m.day_of_week,"Friday")
84
+ end
85
+ end
86
+
87
+ describe "A Martian date/time (y,m,s,h,m,s)" do
88
+ m = MarsDateTime.new(1043,2,15,12,34,45)
89
+ it "should set all its accessors properly" do
90
+ assert_equal(1043,m.myear)
91
+ assert_equal(2,m.month)
92
+ assert_equal(15,m.sol)
93
+ assert_equal(12,m.hr)
94
+ assert_equal(34,m.min)
95
+ assert_equal(45,m.sec)
96
+ assert_equal(4,m.dow)
97
+ assert_equal("Thursday",m.day_of_week)
98
+ assert_equal(43,m.year_sol)
99
+ assert_equal(696715,m.epoch_sol)
100
+ end
101
+ end
102
+
103
+ describe "The 2007 Martian equinox" do
104
+ e = DateTime.new(2007,12,9,17,20,0).new_offset(-5)
105
+ m = MarsDateTime.new(e)
106
+ m11 = MarsDateTime.new(1068,1,1)
107
+ it "should fall on or near 1/1 (MCE)" do
108
+ diff = (m11-m).abs
109
+ flag = diff < 1.0
110
+ assert(flag, "Difference: #{diff}")
111
+ end
112
+ end
113
+
114
+ describe "Constructing without parameters" do
115
+ m = MarsDateTime.new
116
+ e = DateTime.now
117
+ m2e = m.earth_date
118
+ it "should default to today" do
119
+ diff = (e-m2e).abs # m - e doesn't work!
120
+ assert(diff < 1.0)
121
+ end
122
+ end
123
+
124
+ describe "Constructing with a DateTime" do
125
+ it "should convert from Gregorian to MCE" do
126
+ e = DateTime.new(1902,8,12,7,0,0)
127
+ m = MarsDateTime.new(e)
128
+ diff = (m.earth_date - e).abs # days as a float
129
+ x = assert(diff < 1.0)
130
+ end
131
+ end
132
+
133
+ describe "A Martian date/time (933,1,2)" do
134
+ m = MarsDateTime.new(933,1,2)
135
+ it "should set the three obvious accessors properly" do
136
+ assert_equal(933,m.myear)
137
+ assert_equal(1,m.month)
138
+ assert_equal(2,m.sol)
139
+ end
140
+ end
141
+
142
+ describe "A Martian date/time (933,1,1)" do
143
+ m = MarsDateTime.new(933,1,1)
144
+ it "should set the three obvious accessors properly" do
145
+ assert_equal(933,m.myear)
146
+ assert_equal(1,m.month)
147
+ assert_equal(1,m.sol)
148
+ end
149
+ end
150
+
151
+ describe "The Martian date 24/25" do
152
+ describe "in a leap year" do
153
+ year = 1067
154
+ it "should exist" do
155
+ assert( MarsDateTime.leap?(year) )
156
+ MarsDateTime.new(year,24,25) # may raise exception - no #wont_raise
157
+ end
158
+ end
159
+ describe "in a non-leap year" do
160
+ year = 1066
161
+ it "should not exist" do
162
+ assert( ! MarsDateTime.leap?(year) )
163
+ lambda { MarsDateTime.new(year,24,25) }.must_raise(RuntimeError)
164
+ end
165
+ end
166
+ end
167
+
168
+ describe "The earth_date convertor" do
169
+ m = MarsDateTime.new(1,1,1)
170
+ it "should convert to a Gregorian date" do
171
+ e = m.earth_date
172
+ assert_equal(1,e.year)
173
+ assert_equal(1,e.month)
174
+ assert((e.day-22).abs <= 1, "e - 22 = #{e.day - 22}")
175
+ end
176
+ end
177
+
178
+ describe "An Earthly date" do
179
+ e1 = DateTime.new(1961,5,31)
180
+ it "should (approximately) make the conversion roundtrip" do
181
+ m1 = MarsDateTime.new(e1)
182
+ e2 = m1.earth_date
183
+ diff = e2 - e1
184
+ assert(diff.abs <= 0.01, "Difference = #{diff}")
185
+ end
186
+ end
187
+
188
+ describe "A Martian date" do
189
+ m1 = MarsDateTime.new(1067,1,1)
190
+ it "should (approximately) make the conversion roundtrip" do
191
+ e1 = m1.earth_date
192
+ m2 = MarsDateTime.new(e1)
193
+ diff = m2 - m1
194
+ assert(diff.abs < 0.01)
195
+ end
196
+ end
197
+
198
+ describe "An arbitrary Martian date" do
199
+ m1 = MarsDateTime.new(1069,15,24)
200
+ m2 = MarsDateTime.new(933,6,4)
201
+ m3 = MarsDateTime.new(1055,14,1)
202
+ it "should format well with strftime" do
203
+ assert_equal("Mon",m1.strftime("%a"))
204
+ assert_equal("Monday",m1.strftime("%A"))
205
+ assert_equal("Aug",m1.strftime("%b"))
206
+ assert_equal("M-August",m1.strftime("%B"))
207
+ assert_equal("24",m1.strftime("%d"))
208
+ assert_equal("24",m1.strftime("%e"))
209
+ assert_equal("1069-15-24",m1.strftime("%F"))
210
+ assert_equal("416",m1.strftime("%j"))
211
+ assert_equal("15",m1.strftime("%m"))
212
+ assert_equal("0",m1.strftime("%s"))
213
+ assert_equal("2",m1.strftime("%u"))
214
+ assert_equal("60",m1.strftime("%U"))
215
+ assert_equal("1",m1.strftime("%w"))
216
+ assert_equal("1069/15/24",m1.strftime("%x"))
217
+ assert_equal("1069",m1.strftime("%Y"))
218
+ assert_equal("\n",m1.strftime("%n"))
219
+ assert_equal("\t",m1.strftime("%t"))
220
+ assert_equal("%",m1.strftime("%%"))
221
+
222
+ assert_equal("Wed",m2.strftime("%a"))
223
+ assert_equal("Wednesday",m2.strftime("%A"))
224
+ assert_equal("Leo",m2.strftime("%b"))
225
+ assert_equal("Leo",m2.strftime("%B"))
226
+ assert_equal("04",m2.strftime("%d"))
227
+ assert_equal(" 4",m2.strftime("%e"))
228
+ assert_equal("933-06-04",m2.strftime("%F"))
229
+ assert_equal("144",m2.strftime("%j"))
230
+ assert_equal("06",m2.strftime("%m"))
231
+ assert_equal("0",m2.strftime("%s"))
232
+ assert_equal("4",m2.strftime("%u"))
233
+ assert_equal("21",m2.strftime("%U"))
234
+ assert_equal("3",m2.strftime("%w"))
235
+ assert_equal("933/06/04",m2.strftime("%x"))
236
+ assert_equal("933",m2.strftime("%Y"))
237
+
238
+ assert_equal("Sag",m3.strftime("%b"))
239
+ assert_equal("Sagittarius",m3.strftime("%B"))
240
+ end
241
+ end
242
+
243
+ describe "An Earthly date with h:m:s" do
244
+ e1 = DateTime.new(1976,7,4,16,30,0) # July 4, 1976 16:30
245
+ m1 = MarsDateTime.new(e1)
246
+ it "should make the conversion roundtrip" do
247
+ 30.times do
248
+ m1 = MarsDateTime.new(e1)
249
+ e2 = m1.earth_date
250
+ diff = e2 - e1
251
+ assert(diff.abs <= 0.01) { STDERR.puts " diff = #{diff}" }
252
+ e1 += 1
253
+ end
254
+ end
255
+ end
256
+
257
+ describe "Martian dates" do
258
+ m1 = MarsDateTime.new(1068,14,22)
259
+ m2 = MarsDateTime.new(1068,14,21)
260
+ e1 = DateTime.new(2002,4,19)
261
+ e2 = DateTime.new(2012,9,29)
262
+ it "should compare with each other" do
263
+ assert(m1 > m2)
264
+ assert(m2 < m1)
265
+ end
266
+ it "should compare with Earth dates" do
267
+ assert(m1 > e1)
268
+ assert(m2 < e2)
269
+ end
270
+ it "should honor equality (within roundoff)" do
271
+ assert(m1 == m1)
272
+ assert(m1 != m2)
273
+ end
274
+ end
275
+
276
+ describe "The 'now' and 'today' class methods" do
277
+ t1 = MarsDateTime.now
278
+ t0 = MarsDateTime.today
279
+ it "should be less than a day apart" do
280
+ diff = t1 - t0
281
+ assert(diff < 1.0)
282
+ end
283
+ end
284
+
285
+ describe "In M-year 1043, each month" do
286
+ md = (1..24).to_a.map {|mm| MarsDateTime.new(1043,mm,1) }
287
+ it "should start on a Thursday" do
288
+ md.each {|m| assert(m.day_of_week == "Thursday") }
289
+ end
290
+ end
291
+
292
+ describe "Converting 1961/5/31 to Martian" do
293
+ e = DateTime.new(1961,5,31)
294
+ m = MarsDateTime.new(e)
295
+ it "should yield Friday M-April 9, 1043 MCE" do
296
+ assert(m.year == 1043, "bad year: #{m.year}")
297
+ assert(m.month == 7, "bad month: #{m.month}")
298
+ assert(m.sol == 9, "bad sol: #{m.sol}")
299
+ end
300
+ it "should give a Martian Saturday" do
301
+ assert(m.day_of_week == "Friday", "got: #{m.day_of_week}")
302
+ end
303
+ end
304
+
305
+ describe "The 1609 Martian equinox" do
306
+ e = DateTime.new(1609,3,12).new_offset(-5)
307
+ m = MarsDateTime.new(e)
308
+ m11 = MarsDateTime.new(856,1,1)
309
+ it "should fall on or near 1/1 (MCE)" do
310
+ diff = (m11-m).abs
311
+ assert(diff < 1.0, "Difference = #{diff}")
312
+ end
313
+ end
314
+
315
+ describe "The 1902 Martian equinox" do
316
+ e = DateTime.new(1902,8,12,7,0,0).new_offset(-5)
317
+ m = MarsDateTime.new(e)
318
+ m11 = MarsDateTime.new(1012,1,1)
319
+ it "should fall on or near 1/1 (MCE)" do
320
+ diff = (m11-m).abs
321
+ assert(diff < 1.0, "Difference: #{diff}")
322
+ end
323
+ end
324
+
325
+ describe "A Martian date" do
326
+ puts "***** Entering problem test..."
327
+ m = MarsDateTime.new(1068,1,1)
328
+ p m
329
+ describe "plus 7 days" do
330
+ m2 = m + 7
331
+ p m2
332
+ it "should be the same day of the week" do
333
+ assert_equal(m.day_of_week, m2.day_of_week)
334
+ end
335
+ end
336
+ describe "plus 14 days" do
337
+ m3 = m + 14
338
+ p m3
339
+ it "should be the same day of the week" do
340
+ assert_equal(m.day_of_week, m3.day_of_week)
341
+ end
342
+ end
343
+ describe "plus 21 days" do
344
+ m4 = m + 21
345
+ p m4
346
+ it "should be the same day of the week" do
347
+ assert_equal(m.day_of_week, m4.day_of_week)
348
+ end
349
+ end
350
+ puts "***** Exiting problem test"
351
+ end
352
+
353
+ describe "Martian April 1, 1043" do
354
+ m = MarsDateTime.new(1043,7,1)
355
+ it "should be a Thursday" do
356
+ assert_equal(m.day_of_week,"Thursday")
357
+ end
358
+ end
359
+
360
+ describe "A Martian date" do
361
+ md = MarsDateTime.new(1062,20,20)
362
+ m2 = MarsDateTime.new(1062,20,17)
363
+ it "should allow subtraction of another date" do
364
+ m = md - m2
365
+ assert( (m - 3.0) < 0.01) # sols
366
+ end
367
+ it "should allow subtraction of an Earth date" do
368
+ e = DateTime.new(1998, 3, 12, 16, 45, 0)
369
+ m = md - e
370
+ assert( (m - 3.0) < 0.01) # sols
371
+ end
372
+ it "should allow subtraction of a Fixnum" do
373
+ m = md - 3 # sols
374
+ assert( (m - m2) < 0.01)
375
+ end
376
+ it "should allow subtraction of a Float" do
377
+ m = md - 3.0 # sols
378
+ assert( (m - m2) < 0.01)
379
+ end
380
+ end
381
+
382
+