marsdate 1.1.3 → 2.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/lib/marsdate.rb CHANGED
@@ -1,328 +1,376 @@
1
1
  require 'date'
2
+ require 'json'
3
+ require_relative 'marsdate/version'
4
+ require_relative 'marsdate/timescale'
5
+ require_relative 'marsdate/calendar'
2
6
 
3
7
  class MarsDateTime
4
8
 
5
- VERSION = "1.1.3"
6
-
7
9
  include Comparable
8
10
 
9
- MSEC_PER_SOL = 88775244 # .09 # Really + 0.09
10
- SOLS_PER_MYEAR = 668.5921
11
- MSEC_PER_DAY = 86400000
12
-
13
- FAKE_MSEC_PER_MYEAR = (668*MSEC_PER_SOL)
14
-
15
- TimeStretch = MSEC_PER_SOL/MSEC_PER_DAY.to_f
16
-
17
11
  Months = %w[ UNDEFINED
18
12
  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 ]
13
+ M-March Leo M-April Virgo
14
+ M-May Libra M-June Scorpio
15
+ M-July Sagittarius M-August Capricorn
16
+ M-September Aquarius M-October Pisces
17
+ M-November Aries M-December Taurus ]
24
18
  # no month 0
25
19
 
26
- Week = %w[ Sunday Monday Tuesday Wednesday Thursday Friday Saturday ]
20
+ Week = %w[ Sunday Monday Tuesday Wednesday Thursday Friday Saturday ]
27
21
 
28
- EpochMCE = DateTime.new(1,1,21)
29
- EpochCE = DateTime.new(1,1,1)
30
- JulianDay1 = 1721445 # Jan 1, 1 AD
22
+ EPOCH_MSD = Calendar::EPOCH_MSD
23
+ SOL_DAYS = TimeScale::SOL_DAYS
24
+ SOL_SI_SECONDS = TimeScale::SOL_SI_SECONDS
25
+ SOLS_PER_MYEAR = Calendar::MEAN_YEAR
31
26
 
27
+ attr_reader :msd
32
28
  attr_reader :year, :month, :sol, :epoch_sol, :year_sol
33
- attr_reader :shr, :smin, :ssec # stretched time
34
- attr_reader :mems
35
-
36
29
  attr_reader :dow, :day_of_week
37
- attr_reader :mhrs, :mmin, :msec
30
+ attr_reader :mtc_hour, :mtc_min, :mtc_sec
31
+ attr_reader :mxt_hour, :mxt_min, :mxt_sec
38
32
 
39
33
  alias myear year
40
- alias hr mhrs
41
- alias min mmin
42
- alias sec msec
34
+ alias day sol
35
+
36
+ # Named-clock readout of one instant. MarsDateTime.mxt(...) constructs;
37
+ # md.mxt reads. Date fields are the parent's; %H/%M/%S are this clock.
38
+ class ClockView
39
+ attr_reader :scale
40
+
41
+ def initialize(parent, scale)
42
+ @parent = parent
43
+ @scale = scale
44
+ end
45
+
46
+ def hour
47
+ @scale == :mxt ? @parent.mxt_hour : @parent.mtc_hour
48
+ end
49
+
50
+ def min
51
+ @scale == :mxt ? @parent.mxt_min : @parent.mtc_min
52
+ end
43
53
 
44
- def self.leap?(myear) # class method for convenience
45
- return (myear % 2 == 1) if (myear % 10 != 0)
46
- return true if (myear % 1000 == 0)
47
- return false if (myear % 100 == 0)
48
- return true
54
+ def sec
55
+ @scale == :mxt ? @parent.mxt_sec : @parent.mtc_sec
56
+ end
57
+
58
+ def year; @parent.year; end
59
+ def month; @parent.month; end
60
+ def sol; @parent.sol; end
61
+ def day_of_week; @parent.day_of_week; end
62
+ def month_name; @parent.month_name; end
63
+ def dow; @parent.dow; end
64
+ def year_sol; @parent.year_sol; end
65
+ def epoch_sol; @parent.epoch_sol; end
66
+ def msd; @parent.msd; end
67
+ def earth_date; @parent.earth_date; end
68
+
69
+ def to_s
70
+ TimeScale.format_hms(hour, min, sec)
71
+ end
72
+
73
+ def inspect
74
+ "#<#{@scale.to_s.upcase} #{self}>"
75
+ end
76
+
77
+ # Specifier list inspired by Time#strftime. %H/%M/%S/%X are this clock;
78
+ # %Z is the clock name (MXT or MTC), not a timezone.
79
+ def format(fmt)
80
+ @parent.send(:format_with, fmt, hour, min, sec, @scale.to_s.upcase)
81
+ end
82
+ end
83
+
84
+ def self.leap?(myear)
85
+ Calendar.leap?(myear)
49
86
  end
50
87
 
51
- def self.short?(myear) # short year
88
+ def self.short?(myear)
52
89
  !leap?(myear)
53
90
  end
54
91
 
55
- def self.long?(myear) # long year
92
+ def self.long?(myear)
56
93
  leap?(myear)
57
94
  end
58
95
 
59
96
  def self.sols_in_month(m, year)
60
- return 28 if m < 24
61
- return 25 if leap?(year)
62
- return 24
97
+ Calendar.sols_in_month(m, year)
98
+ end
99
+
100
+ def self.from_msd(msd)
101
+ at(msd)
102
+ end
103
+
104
+ def self.at(msd)
105
+ obj = allocate
106
+ obj.send(:init_from_msd, msd)
107
+ obj
108
+ end
109
+
110
+ def self.mxt(year, month, sol, hour = 0, min = 0, sec = 0)
111
+ civil(year, month, sol, hour, min, sec, :mxt)
112
+ end
113
+
114
+ def self.mtc(year, month, sol, hour = 0, min = 0, sec = 0)
115
+ civil(year, month, sol, hour, min, sec, :mtc)
63
116
  end
64
117
 
65
118
  def self.now
66
- d = DateTime.now
67
- MarsDateTime.new(d)
119
+ new(DateTime.now)
68
120
  end
69
121
 
122
+ # Current MCE sol at 00:00 (MXT = MTC midnight). Not Earth Date.today.
70
123
  def self.today
71
- d = DateTime.now
72
- MarsDateTime.new(d)
124
+ n = now
125
+ mxt(n.year, n.month, n.sol)
73
126
  end
74
127
 
75
- def leaps(myr)
76
- n = 0
77
- 1.upto(myr) {|i| n+=1 if MarsDateTime.leap?(i) }
78
- n
128
+ # Earth caption of 1/1/1 00:00 and of Ls=0 on that sol.
129
+ # Times treat JD_TT as UT (no year-1 ΔT). See TimeScale.epoch_report.
130
+ def self.epoch_caption
131
+ TimeScale.epoch_report
79
132
  end
80
133
 
81
- def to_yaml_properties
82
- %w[@myear @month @sol @epoch_sol @year_sol @dow @day_of_week @msme @mhrs @mmin @msec]
134
+ def self.from_json(str)
135
+ hash = JSON.parse(str)
136
+ msd = hash['msd'] || hash[:msd]
137
+ raise ArgumentError, 'JSON must include msd' if msd.nil?
138
+ from_msd(msd)
83
139
  end
84
140
 
85
- def inspect
86
- time = ('%02d' % @mhrs) + ":" + ('%02d' % @mmin) + ":" + ('%02d' % @msec)
87
- "#@year/#{'%02d' % @month}/#{'%02d' % @sol} " +
88
- "(#@year_sol, #@epoch_sol) #@day_of_week " +
89
- time
141
+ def initialize(*params)
142
+ n = params.size
143
+ case n
144
+ when 3..6
145
+ init_yms(*params, clock: :mxt)
146
+ when 0
147
+ init_from_msd(TimeScale.msd(DateTime.now))
148
+ when 1
149
+ case params.first
150
+ when Integer, Float
151
+ init_from_msd(params.first)
152
+ when Date, DateTime
153
+ init_from_msd(TimeScale.msd(params.first))
154
+ else
155
+ raise ArgumentError, "Expected MSD, Date, or DateTime (got #{params.first.class})"
156
+ end
157
+ else
158
+ raise ArgumentError, "Bad params: #{params.inspect}"
159
+ end
90
160
  end
91
161
 
92
- def to_s
93
- time = self.strftime('%H:%M:%S [%P:%Q:%R]')
94
- "#@day_of_week, #{Months[@month]} #@sol, #@year at #{time}"
162
+ def mxt
163
+ ClockView.new(self, :mxt)
95
164
  end
96
165
 
97
- def leap?
98
- MarsDateTime.leap?(@year) # DRY
166
+ def mtc
167
+ ClockView.new(self, :mtc)
99
168
  end
100
169
 
101
- def short?
102
- ! leap?
170
+ def as_json(_options = {})
171
+ { msd: @msd,
172
+ year: @year,
173
+ month: @month,
174
+ sol: @sol,
175
+ epoch_sol: @epoch_sol,
176
+ year_sol: @year_sol,
177
+ dow: @dow,
178
+ day_of_week: @day_of_week,
179
+ mtc: format_mtc,
180
+ mxt: format_mxt }
103
181
  end
104
182
 
105
- def long?
106
- leap?
183
+ def to_json(*options)
184
+ as_json.to_json(*options)
107
185
  end
108
186
 
109
- def month_name
110
- Months[@month]
187
+ def to_yaml_properties
188
+ %w[@msd]
189
+ end
190
+
191
+ def inspect
192
+ sprintf('%d/%02d/%02d (%d, %d) %s MXT %s MTC %s MSD %.8f',
193
+ @year, @month, @sol, @year_sol, @epoch_sol, @day_of_week,
194
+ format_mxt, format_mtc, @msd)
111
195
  end
112
196
 
113
- ###########
197
+ def to_s
198
+ "#{@day_of_week}, #{month_name} #{@sol}, #{@year} at MXT #{format_mxt} / MTC #{format_mtc}"
199
+ end
114
200
 
115
- def initialize(*params)
116
- n = params.size
117
- case n
118
- when 3..6
119
- init_yms(*params)
120
- when 0
121
- init_datetime(DateTime.now)
122
- when 1
123
- case params.first
124
- when Integer, Float
125
- init_mems(params.first)
126
- when Date
127
- init_date(params.first)
128
- when DateTime
129
- init_datetime(params.first)
130
- else
131
- raise "Expected number or DateTime"
132
- end
133
- else
134
- raise "Bad params: #{params.inspect}"
135
- end
136
- compute_stretched
137
- end
138
-
139
- def check_ymshms(my, mm, msol, mhr=0, mmin=0, msec=0)
140
- text = ""
141
- text << "year #{my} is not an integer\n" unless my.is_a? Integer
142
- text << "month #{mm} is out of range" unless (1..24).include? mm
143
- text << "sol #{msol} is out of range" unless (1..28).include? msol
144
- text << "hour #{mhr} is out of range" unless (0..24).include? mhr
145
- text << "minute #{mmin} is out of range" unless (0..59).include? mmin
146
- text << "second #{msec} is out of range" unless (0..59).include? msec
147
- if !MarsDateTime.leap?(my) && mm == 24 && msol > 24
148
- text << "sol #{msol} is invalid in a non-leap year"
149
- end
150
- raise text unless text.empty?
151
- end
152
-
153
- def init_yms(my, mm, msol, mhr=0, mmin=0, msec=0)
154
- check_ymshms(my, mm, msol, mhr, mmin, msec)
155
- zsol = msol - 1 # z means zero-based
156
- zmy = my - 1 # my means Martian year
157
- zesol = zmy*668 + leaps(my-1) + (mm-1)*28 + zsol
158
- # @mems is "Martian (time since) epoch in milliseconds"
159
- @mems = zesol*MSEC_PER_SOL + (mhr*3600 + mmin*60 + msec)*1000
160
- @year, @month, @sol, @mhrs, @mmin, @msec = my, mm, msol, mhr, mmin, msec
161
- @epoch_sol = zesol + 1
162
- @dow = (@epoch_sol-1) % 7
163
- @day_of_week = Week[@dow]
164
- @year_sol = (mm-1)*28 + msol
165
- end
166
-
167
- def compute_stretched
168
- # Handle stretched time...
169
- sec = @mhrs*3600 + @mmin*60 + @msec
170
- sec /= TimeStretch
171
- @shr, sec = sec.divmod(3600)
172
- @smin, sec = sec.divmod(60)
173
- @ssec = sec.round
174
- end
175
-
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.
180
- full_years = 0
181
- loop do
182
- millisec = FAKE_MSEC_PER_MYEAR
183
- millisec += MSEC_PER_SOL if MarsDateTime.leap?(full_years+1)
184
- break if mems < millisec
185
- mems -= millisec
186
- # puts "Subtracting #{millisec} -- one full year => #{mems}"
187
- full_years += 1
188
- end
201
+ def format_mtc(fmt = nil)
202
+ fmt ? format_with(fmt, @mtc_hour, @mtc_min, @mtc_sec, 'MTC') : mtc.to_s
203
+ end
189
204
 
190
- mspm = MSEC_PER_SOL*28
191
- full_months,mems = mems.divmod(mspm)
192
- full_days, mems = mems.divmod(MSEC_PER_SOL)
193
- full_hrs, mems = mems.divmod(3_600_000)
194
- full_min, mems = mems.divmod(60_000)
205
+ def format_mxt(fmt = nil)
206
+ fmt ? format_with(fmt, @mxt_hour, @mxt_min, @mxt_sec, 'MXT') : mxt.to_s
207
+ end
195
208
 
196
- sec = mems/1000.0
197
- my = full_years + 1 # 1-based
198
- mm = full_months + 1
199
- ms = full_days + 1
200
- mhr = full_hrs # 0-based
201
- mmin = full_min
202
- msec = sec.to_i
203
- frac = sec - msec # fraction of a sec
209
+ def leap?
210
+ MarsDateTime.leap?(@year)
211
+ end
204
212
 
205
- init_yms(my, mm, ms, mhr, mmin, msec)
213
+ def short?
214
+ !leap?
206
215
  end
207
216
 
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)
217
+ def long?
218
+ leap?
213
219
  end
214
220
 
215
- def init_datetime(dt)
216
- days = dt.jd - JulianDay1
217
- secs = days*86400 + dt.hour*3600 + dt.min*60 + dt.sec
218
- init_mems(secs*1000)
221
+ def month_name
222
+ Months[@month]
219
223
  end
220
224
 
221
225
  def ymshms
222
- [@year, @month, @sol, @mhrs, @mmin, @msec]
226
+ [@year, @month, @sol, @mxt_hour, @mxt_min, @mxt_sec.to_i]
223
227
  end
224
228
 
225
- def -(other) # FIXME? sols or secs?
229
+ def -(other)
226
230
  case other
227
- when MarsDateTime
228
- diff = @mems - other.mems
229
- diff.to_f / MSEC_PER_SOL
230
- when DateTime
231
- other = MarsDateTime.new(other)
232
- diff = @mems - other.mems
233
- diff.to_f / MSEC_PER_SOL
234
- when Integer, Float
235
- self + (-other)
231
+ when MarsDateTime
232
+ @msd - other.msd
233
+ when Date, DateTime
234
+ @msd - MarsDateTime.new(other).msd
235
+ when Integer, Float
236
+ self + (-other)
236
237
  else
237
- raise "Unexpected data type"
238
+ raise ArgumentError, "Unexpected data type #{other.class}"
238
239
  end
239
240
  end
240
241
 
241
- def +(sols) # FIXME? sols or secs?
242
- millisec = sols * MSEC_PER_SOL
243
- MarsDateTime.new(@mems + millisec)
242
+ def +(sols)
243
+ MarsDateTime.at(@msd + sols.to_f)
244
244
  end
245
245
 
246
246
  def <=>(other)
247
247
  case other
248
- when MarsDateTime
249
- @mems <=> other.mems
250
- when DateTime
251
- @mems <=> MarsDateTime.new(other).mems
248
+ when MarsDateTime
249
+ @msd <=> other.msd
250
+ when Date, DateTime
251
+ @msd <=> MarsDateTime.new(other).msd
252
252
  else
253
- raise "Invalid comparison"
253
+ raise ArgumentError, "Invalid comparison with #{other.class}"
254
254
  end
255
255
  end
256
256
 
257
257
  def earth_date
258
- secs = @mems/1000
259
- days,secs = secs.divmod(86400)
260
- hrs, secs = secs.divmod(3600)
261
- min, secs = secs.divmod(60)
262
- jday = days + JulianDay1
263
- DateTime.jd(jday, hrs, min, secs)
264
- end
265
-
266
- def strftime(fmt)
267
- str = fmt.dup
268
- pieces = str.scan(/(%.|[^%]+)/).flatten
269
- final = ""
258
+ TimeScale.earth_from_msd(@msd)
259
+ end
260
+
261
+ # Date tokens plus MXT for %H/%M/%S/%X. Specifier list inspired by
262
+ # Time#strftime. Same as format_mxt(fmt). MTC: format_mtc(fmt) or md.mtc.format.
263
+ def format(fmt)
264
+ format_mxt(fmt)
265
+ end
266
+
267
+ def self.civil(year, month, sol, hour, min, sec, clock)
268
+ obj = allocate
269
+ obj.send(:init_yms, year, month, sol, hour, min, sec, clock: clock)
270
+ obj
271
+ end
272
+ private_class_method :civil
273
+
274
+ def format_with(fmt, hour, min, sec, clock)
275
+ pieces = fmt.to_s.scan(/(%%|%.|%|[^%]+)/).flatten
276
+ final = ''
270
277
  zmonth = '%02d' % @month
271
278
  zsol = '%02d' % @sol
272
- zhh = '%02d' % @mhrs # stretched
273
- zmm = '%02d' % @mmin
274
- zss = '%02d' % @msec
275
- zhc = '%02d' % @shr # canonical
276
- zmc = '%02d' % @smin
277
- zsc = '%02d' % @ssec
278
-
279
+ zhh = '%02d' % hour
280
+ zmm = '%02d' % min
281
+ zss = '%02d' % sec.to_i
282
+
279
283
  pieces.each do |piece|
280
284
  case piece
281
- when "%a"; final << @day_of_week[0..2]
282
- when "%A"; final << @day_of_week
283
- when "%b"; final << (@month.odd? ? month_name[2..4] : month_name[0..2])
284
- when "%B"; final << month_name
285
- when "%d"; final << zsol
286
- when "%e"; final << ('%2d' % @sol)
287
- when "%F"; final << "#@year-#{zmonth}-#{zsol}"
288
- when "%H"; final << zhh
289
- when "%j"; final << @year_sol.to_s
290
- when "%m"; final << zmonth # @month.to_s
291
- when "%M"; final << zmm
292
- when "%s"; final << @msec.to_s # was: (@mems*1000).to_i.to_s
293
- when "%S"; final << zss
294
- when "%u"; final << (@dow + 1).to_s
295
- when "%U"; final << (@year_sol/7 + 1).to_s
296
- when "%w"; final << @dow.to_s
297
- when "%x"; final << "#@year/#{zmonth}/#{zsol}"
298
- when "%X"; final << "#{zhh}:#{zmm}:#{zss}"
299
- when "%Y"; final << @year.to_s
300
- when "%n"; final << "\n"
301
- when "%t"; final << "\t"
302
- when "%%"; final << "%"
303
- when "%P"; final << ("%02d" % @shr)
304
- when "%Q"; final << ("%02d" % @smin)
305
- when "%R"; final << ("%02d" % @ssec)
306
- else
307
- final << piece
285
+ when '%a'; final << @day_of_week[0..2]
286
+ when '%A'; final << @day_of_week
287
+ when '%b'; final << (@month.odd? ? month_name[2..4] : month_name[0..2])
288
+ when '%B'; final << month_name
289
+ when '%d'; final << zsol
290
+ when '%e'; final << ('%2d' % @sol)
291
+ when '%F'; final << "#{@year}-#{zmonth}-#{zsol}"
292
+ when '%H'; final << zhh
293
+ when '%j'; final << @year_sol.to_s
294
+ when '%m'; final << zmonth
295
+ when '%M'; final << zmm
296
+ when '%s'; final << sec.to_i.to_s
297
+ when '%S'; final << zss
298
+ when '%u'; final << (@dow + 1).to_s
299
+ when '%U'; final << (@year_sol / 7 + 1).to_s
300
+ when '%w'; final << @dow.to_s
301
+ when '%x'; final << "#{@year}/#{zmonth}/#{zsol}"
302
+ when '%X'; final << "#{zhh}:#{zmm}:#{zss}"
303
+ when '%Y'; final << @year.to_s
304
+ when '%Z'; final << clock
305
+ when '%n'; final << "\n"
306
+ when '%t'; final << "\t"
307
+ when '%%'; final << '%'
308
+ else
309
+ final << piece
308
310
  end
309
311
  end
310
312
  final
311
313
  end
312
-
313
- =begin
314
- * %I - Hour of the day, 12-hour clock (01..12)
315
- * %p - Meridian indicator (``AM'' or ``PM'')
316
- %U - Week number of the current year,
317
- starting with the first Sunday as the first
318
- day of the first week (00..53)
319
- %W - Week number of the current year,
320
- starting with the first Monday as the first
321
- day of the first week (00..53)
322
- * %y - Year without a century (00..99)
323
- %Z - Time zone name
324
-
325
- =end
314
+ private :format_with
326
315
 
327
- end
316
+ private
317
+
318
+ def init_yms(my, mm, msol, mhr = 0, mmin = 0, msec = 0, clock: :mxt)
319
+ Calendar.check_ymd!(my, mm, msol)
320
+ check_hms!(mhr, mmin, msec, clock)
321
+ frac = frac_from_hms(mhr, mmin, msec, clock)
322
+ init_from_msd(Calendar.msd_at(my, mm, msol, frac))
323
+ end
324
+
325
+ def init_from_msd(msd)
326
+ @msd = msd.to_f
327
+ index = @msd.floor - EPOCH_MSD
328
+ if index < 0
329
+ raise ArgumentError, "MSD #{@msd} is before MCE 1/1/1 (EPOCH_MSD=#{EPOCH_MSD})"
330
+ end
331
+ @year, @month, @sol = Calendar.ymd_from_msd(@msd)
332
+ @epoch_sol = Calendar.sol_index(@year, @month, @sol) + 1
333
+ @year_sol = (@month - 1) * 28 + @sol
334
+ @dow = Calendar.dow(@year, @month, @sol)
335
+ @day_of_week = Week[@dow]
336
+ frac = TimeScale.sol_fraction_of(@msd)
337
+ @mtc_hour, @mtc_min, @mtc_sec = TimeScale.hours_to_hms(
338
+ frac * 24.0, limit: 24
339
+ )
340
+ @mxt_hour, @mxt_min, @mxt_sec = TimeScale.hours_to_hms(
341
+ frac * SOL_SI_SECONDS / 3600.0
342
+ )
343
+ end
344
+
345
+ def check_hms!(h, min, sec, clock)
346
+ unless h.is_a?(Numeric) && min.is_a?(Numeric) && sec.is_a?(Numeric)
347
+ raise ArgumentError, 'hour/minute/second must be numeric'
348
+ end
349
+ unless min >= 0 && min < 60 && sec >= 0 && sec < 60
350
+ raise ArgumentError, "minute/second out of range (#{min}:#{sec})"
351
+ end
352
+ case clock.to_sym
353
+ when :mtc
354
+ hours = h.to_f + min.to_f / 60.0 + sec.to_f / 3600.0
355
+ unless hours >= 0.0 && hours < 24.0
356
+ raise ArgumentError, "MTC #{h}:#{min}:#{sec} is outside [00:00:00, 24:00:00)"
357
+ end
358
+ when :mxt
359
+ si = h.to_f * 3600.0 + min.to_f * 60.0 + sec.to_f
360
+ unless si >= 0.0 && si < SOL_SI_SECONDS
361
+ raise ArgumentError, "MXT #{h}:#{min}:#{sec} is outside one sol"
362
+ end
363
+ else
364
+ raise ArgumentError, "clock must be :mtc or :mxt (got #{clock.inspect})"
365
+ end
366
+ end
328
367
 
368
+ def frac_from_hms(h, min, sec, clock)
369
+ case clock.to_sym
370
+ when :mtc
371
+ (h.to_f + min.to_f / 60.0 + sec.to_f / 3600.0) / 24.0
372
+ when :mxt
373
+ (h.to_f * 3600.0 + min.to_f * 60.0 + sec.to_f) / SOL_SI_SECONDS
374
+ end
375
+ end
376
+ end
data/marsdate.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ require_relative 'lib/marsdate/version'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'marsdate'
5
+ s.version = MarsDateTime::VERSION
6
+ s.license = 'MIT'
7
+ s.summary = 'Date/time library for Mars'
8
+ s.description = <<-EOS
9
+ This is a library for handling dates and times on Mars
10
+ for the Martian Common Era calendar (created by Hal Fulton).
11
+ The functionality closely follows that of Ruby's Time class.
12
+ EOS
13
+ s.authors = ['Hal Fulton']
14
+ s.email = 'rubyhacker@gmail.com'
15
+ s.files = Dir['lib/**/*.rb', 'bin/*', 'test/**/*.rb'] +
16
+ %w[CHANGELOG.md LICENSE.txt README.md marsdate.gemspec]
17
+ s.bindir = 'bin'
18
+ s.executables = ['marsdate']
19
+ s.require_paths = ['lib']
20
+ s.required_ruby_version = '>= 2.7'
21
+ s.homepage = 'https://github.com/Hal9000/marsdate'
22
+ s.metadata = {
23
+ 'bug_tracker_uri' => "#{s.homepage}/issues",
24
+ 'changelog_uri' => "#{s.homepage}/blob/master/CHANGELOG.md",
25
+ 'source_code_uri' => s.homepage
26
+ }
27
+ s.add_development_dependency 'minitest', '~> 6.0'
28
+ s.add_development_dependency 'rake', '~> 13.4'
29
+ s.post_install_message = "\n Success! Run executable 'marsdate' for help.\n "
30
+ end
31
+