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