marsdate 1.0.9 → 1.1.3

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.
@@ -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
+