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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +650 -0
- data/bin/marsdate +167 -15
- 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 +289 -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 +51 -19
- data/bin/marsdate-version +0 -5
- data/bin/mcal.rb +0 -50
- data/bin/meh.html +0 -2941
- data/bin/mkcal.rb +0 -51
- data/bin/mtcon.rb +0 -29
- data/bin/mtoday.rb +0 -5
- data/test/spec/marsdate_spec.rb +0 -382
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
|
|
20
|
+
Week = %w[ Sunday Monday Tuesday Wednesday Thursday Friday Saturday ]
|
|
27
21
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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 :
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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)
|
|
88
|
+
def self.short?(myear)
|
|
52
89
|
!leap?(myear)
|
|
53
90
|
end
|
|
54
91
|
|
|
55
|
-
def self.long?(myear)
|
|
92
|
+
def self.long?(myear)
|
|
56
93
|
leap?(myear)
|
|
57
94
|
end
|
|
58
95
|
|
|
59
96
|
def self.sols_in_month(m, year)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
72
|
-
|
|
124
|
+
n = now
|
|
125
|
+
mxt(n.year, n.month, n.sol)
|
|
73
126
|
end
|
|
74
127
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
82
|
-
|
|
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
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
93
|
-
|
|
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
|
|
98
|
-
|
|
166
|
+
def mtc
|
|
167
|
+
ClockView.new(self, :mtc)
|
|
99
168
|
end
|
|
100
169
|
|
|
101
|
-
def
|
|
102
|
-
|
|
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
|
|
106
|
-
|
|
183
|
+
def to_json(*options)
|
|
184
|
+
as_json.to_json(*options)
|
|
107
185
|
end
|
|
108
186
|
|
|
109
|
-
def
|
|
110
|
-
|
|
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
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
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
|
|
209
|
+
def leap?
|
|
210
|
+
MarsDateTime.leap?(@year)
|
|
211
|
+
end
|
|
204
212
|
|
|
205
|
-
|
|
213
|
+
def short?
|
|
214
|
+
!leap?
|
|
206
215
|
end
|
|
207
216
|
|
|
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)
|
|
217
|
+
def long?
|
|
218
|
+
leap?
|
|
213
219
|
end
|
|
214
220
|
|
|
215
|
-
def
|
|
216
|
-
|
|
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, @
|
|
226
|
+
[@year, @month, @sol, @mxt_hour, @mxt_min, @mxt_sec.to_i]
|
|
223
227
|
end
|
|
224
228
|
|
|
225
|
-
def -(other)
|
|
229
|
+
def -(other)
|
|
226
230
|
case other
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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)
|
|
242
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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' %
|
|
273
|
-
zmm = '%02d' %
|
|
274
|
-
zss = '%02d' %
|
|
275
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
+
|