timesteps 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,530 @@
1
+
2
+ #
3
+ # Base class for DateTime::NoLeap, DateTime::AllLeap, DateTime::Fixed360Day
4
+ #
5
+ class DateTimeLike
6
+
7
+ module DateTimeLikeExtension
8
+
9
+ # Calculate Year/Month/Day fron Julian day number
10
+ #
11
+ # @param jday [Integer]
12
+ #
13
+ # @return [Array] [year, month, day]
14
+ def jday2date (jday)
15
+ dpy = self::DPY
16
+ dpm = self::DPM
17
+ jday = jday.round
18
+ year = jday/dpy
19
+ doy = jday - year*dpy + 1
20
+ year = year - 4712
21
+ month = 0
22
+ day = 0
23
+ days = 0
24
+ (1..12).each do |m|
25
+ if days + dpm[m] >= doy
26
+ month = m
27
+ day = doy - days
28
+ break
29
+ end
30
+ days += dpm[m]
31
+ end
32
+ return year, month, day
33
+ end
34
+
35
+ # Creates a DateTimeFixedDPY object
36
+ # from the given Julian day number and Hour/Minute/Second.
37
+ #
38
+ # @param jday [Interger]
39
+ # @param hour [Interger]
40
+ # @param minute [Integer]
41
+ # @param second [Float]
42
+ # @param offset [Rational]
43
+ #
44
+ # @return [DateTimeFixedDPY]
45
+ def jd (jday, hour = 0, minute = 0, second = 0, offset = 0)
46
+ year, month, day = jday2date(jday)
47
+ return self.new(year, month, day, hour, minute, second, offset)
48
+ end
49
+
50
+ end
51
+
52
+ include Comparable
53
+
54
+ # Creates a DateTime object denoting the given calendar date.
55
+ #
56
+ # @param year [Integer]
57
+ # @param month [Integer]
58
+ # @param day [Integer]
59
+ # @param hour [Integer]
60
+ # @param minute [Integer]
61
+ # @param second [Integer]
62
+ # @param offset [Integer]
63
+ # @param start [Integer]
64
+ #
65
+ def initialize (year = -4712, month = 1, day = 1, hour = 0, minute = 0, second = 0.0, offset = 0, start = nil)
66
+ @year = year.to_i
67
+ @month = month.to_i
68
+ @day = day.to_i
69
+ @hour = hour.to_i
70
+ @minute = minute.to_i
71
+ @second = second
72
+ @offset = offset
73
+ check_valid_datetime()
74
+ end
75
+
76
+ def check_valid_datetime
77
+ unless valid_date?
78
+ raise "invalid date"
79
+ end
80
+ unless valid_time?
81
+ raise "invalid time"
82
+ end
83
+ end
84
+
85
+ def valid_time?
86
+ begin
87
+ DateTime.new(2001,1,1,@hour,@minute,@second)
88
+ true
89
+ rescue
90
+ false
91
+ end
92
+ end
93
+
94
+ private :check_valid_datetime, :valid_time?
95
+
96
+ attr_reader :year,
97
+ :month,
98
+ :day,
99
+ :hour,
100
+ :minute,
101
+ :offset
102
+
103
+ # Returns the second (0-59)
104
+ #
105
+ # @return [Rational]
106
+ def second
107
+ return @second.floor
108
+ end
109
+
110
+ # Returns the fractional part of the second.
111
+ #
112
+ # @return [Rational]
113
+ def second_fraction
114
+ return @second.to_r - @second.floor
115
+ end
116
+
117
+ alias mon month
118
+ alias min minute
119
+ alias mday day
120
+ alias sec second
121
+ alias sec_fraction second_fraction
122
+
123
+ # Duplicates self and resets its offset.
124
+ #
125
+ # @param offset [Numeric]
126
+ #
127
+ # @return [DateTime]
128
+ def new_offset (offset = 0)
129
+ gmt = jd + fraction - @offset + offset.to_r
130
+ jday = gmt.floor
131
+ fday = gmt - gmt.floor
132
+ return self.class.jd(jday, 0, 0, 0, offset) + fday
133
+ end
134
+
135
+ # Returns Day of Year
136
+ #
137
+ # @return [Integer]
138
+ def yday
139
+ dpm = self.class::DPM
140
+ doy = @day
141
+ (@month-1).step(1,-1) do |m|
142
+ doy += dpm[m]
143
+ end
144
+ return doy
145
+ end
146
+
147
+ # Calculates Julian day number from date part.
148
+ # Note that this method does not take into account the offset and time.
149
+ # If you need a Julian day number that takes the time into account, use #ajd.
150
+ #
151
+ # @return [Integer]
152
+ def jd
153
+ return (self.class::DPY) * (@year + 4712) + (yday - 1)
154
+ end
155
+
156
+ # Calculates astronomical Julian day number.
157
+ #
158
+ # @return [Numeric]
159
+ def ajd
160
+ return jd + fraction - @offset - 1.quo(2)
161
+ end
162
+
163
+ # Returns the day of week (0-6, Sunday is zero).
164
+ #
165
+ # @return [Integer]
166
+ def wday
167
+ return (jd + 1 ) % 7
168
+ end
169
+
170
+ # Returns the day of week (0-6, Sunday is zero).
171
+ #
172
+ # @return [Integer]
173
+ def commercial
174
+ jd0 = (self.class::DPY) * (@year + 4712)
175
+ wday0 = (jd0 + 1 ) % 7
176
+ cwyear = @year
177
+ yday1 = yday - wday0
178
+ cweek = yday / 7 + 1
179
+ cwday = yday % 7 + 1
180
+ return cwyear, cweek, cwday
181
+ end
182
+
183
+ private :commercial
184
+
185
+ # Returns a date object pointing other days after self.
186
+ #
187
+ # @param days [Numeric]
188
+ #
189
+ # @return [DateTimeFixedDPY]
190
+ def + (days)
191
+ days = days.to_r + fraction
192
+ jday = jd.floor + days.floor
193
+ fday = (days - days.floor)*24
194
+ hour = fday.floor
195
+ fday = (fday - hour)*60
196
+ min = fday.floor
197
+ sec = (fday - min)*60
198
+ return self.class.jd(jday, hour, min, sec, offset)
199
+ end
200
+
201
+ # Returns the difference between the two dates
202
+ # if the other is a date object.
203
+ # If the other is a numeric value,
204
+ # returns a date object pointing other days before self.
205
+ def - (other_or_days)
206
+ case other_or_days
207
+ when Numeric
208
+ return self + (-other_or_days)
209
+ when self.class
210
+ return self.jd - other_or_days.jd
211
+ else
212
+ raise "invalid object for other"
213
+ end
214
+ end
215
+
216
+ # Returns a date object pointing n months after self.
217
+ #
218
+ # @param n [Integer]
219
+ #
220
+ # @return [DateTimeFixedDPY]
221
+ def >> (n)
222
+ return add_months(n)
223
+ end
224
+
225
+ # Returns a date object pointing n months before self.
226
+ #
227
+ # @param n [Integer]
228
+ #
229
+ # @return [DateTimeFixedDPY]
230
+ def << (n)
231
+ return add_months(-n)
232
+ end
233
+
234
+ # Returns a date object pointing n years after self.
235
+ #
236
+ # @param n [Integer]
237
+ #
238
+ # @return [DateTimeFixedDPY]
239
+ def next_year (n = 1)
240
+ return self.class::new(@year+n, @month, @day, @hour, @minute, @second, @offset)
241
+ end
242
+
243
+ # Returns a date object pointing n years before self.
244
+ #
245
+ # @param n [Integer]
246
+ #
247
+ # @return [DateTimeFixedDPY]
248
+ def prev_year (n = 1)
249
+ return self.class::new(@year-n, @month, @day, @hour, @minute, @second, @offset)
250
+ end
251
+
252
+ # Returns a date object pointing n months after self.
253
+ #
254
+ # @param n [Integer]
255
+ #
256
+ # @return [DateTimeFixedDPY]
257
+ def next_month (n = 1)
258
+ return add_months(n)
259
+ end
260
+
261
+ # Returns a date object pointing n months before self.
262
+ #
263
+ # @param n [Integer]
264
+ #
265
+ # @return [DateTimeFixedDPY]
266
+ def prev_month (n = 1)
267
+ return add_months(-n)
268
+ end
269
+
270
+ def add_months (months)
271
+ months = (@month + months).to_i - 1
272
+ years = months / 12
273
+ month = months % 12 + 1
274
+ return self.class::new(@year+years, month, @day, @hour, @minute, @second, @offset)
275
+ end
276
+
277
+ private :add_months
278
+
279
+ # Returns a date object pointing n days after self.
280
+ #
281
+ # @param n [Integer]
282
+ #
283
+ # @return [DateTimeFixedDPY]
284
+ def next_day (n = 1)
285
+ return add_days(n)
286
+ end
287
+
288
+ # Returns a date object pointing n days before self.
289
+ #
290
+ # @param n [Integer]
291
+ #
292
+ # @return [DateTimeFixedDPY]
293
+ def prev_day (n = 1)
294
+ return add_days(-n)
295
+ end
296
+
297
+ def add_days (days)
298
+ return self.class::jd(jd+days.to_i, @hour, @minute, @second, @offset)
299
+ end
300
+
301
+ private :add_days
302
+
303
+ # Returns a datetime object denoting the following day.
304
+ #
305
+ # @return [DateTime]
306
+ def succ
307
+ return add_days(1)
308
+ end
309
+
310
+ alias next succ
311
+
312
+ # Returns time fraction in day units.
313
+ #
314
+ # @return [Rational]
315
+ def fraction ()
316
+ return (60*(60*@hour + @minute) + @second.to_r).quo(86400)
317
+ end
318
+
319
+ def format_offset
320
+ oh = @offset*24
321
+ return [
322
+ oh >= 0 ? "+" : "-",
323
+ "%02i" % oh.abs.to_i,
324
+ ":",
325
+ "%02i" % ((oh.abs - oh.abs.to_i)*60).round
326
+ ].join("")
327
+ end
328
+
329
+ def format_microsec
330
+ microsec = ((@second - @second.floor)*1000000).round
331
+ if microsec == 0
332
+ ""
333
+ else
334
+ ".%06d" % microsec
335
+ end
336
+ end
337
+
338
+ private :format_offset, :format_microsec
339
+
340
+ # Returns a string in an ISO 8601 format.
341
+ #
342
+ def to_s
343
+ format("%04d-%02d-%02dT%02d:%02d:%02d%s%s",
344
+ @year, @month, @day,
345
+ @hour, @minute, @second.floor,
346
+ format_microsec,
347
+ format_offset)
348
+ end
349
+
350
+ # Returns the value as a string for inspection.
351
+ #
352
+ def inspect
353
+ sec = fraction*86400
354
+ isec = sec.floor
355
+ fsec = ((sec - isec)*1000000).round
356
+ offs = (offset*86400).round
357
+ format("#<%s: %s ((%dj,%ds,%dn),%+ds)>", self.class, to_s, jd, isec, fsec, offs)
358
+ end
359
+
360
+ # Compares the two dates and returns -1, zero, 1 or nil.
361
+ # The other should be a date object or a numeric value
362
+ # as an astronomical Julian day number.
363
+ def <=> (other)
364
+ return self.ajd <=> other.ajd
365
+ end
366
+
367
+ def compare_md (other)
368
+ sday = self.day + self.fraction + self.offset
369
+ oday = other.day + other.fraction + other.offset
370
+ if self.month > other.month
371
+ return 1
372
+ elsif self.month < other.month
373
+ return -1
374
+ else
375
+ if sday > oday
376
+ return 1
377
+ elsif sday < oday
378
+ return -1
379
+ else
380
+ return 0
381
+ end
382
+ end
383
+ end
384
+
385
+ def compare_d (other)
386
+ sday = self.day + self.fraction + self.offset
387
+ oday = other.day + other.fraction + other.offset
388
+ if sday > oday
389
+ return 1
390
+ elsif sday < oday
391
+ return -1
392
+ else
393
+ return 0
394
+ end
395
+ end
396
+
397
+ # Calculate difference between the object and other object in years.
398
+ #
399
+ # @return [Integer]
400
+ def difference_in_years (other)
401
+ extra = 0
402
+ if self.year > other.year && self.compare_md(other) < 0
403
+ extra = -1
404
+ end
405
+ if self.year < other.year && self.compare_md(other) > 0
406
+ extra = 1
407
+ end
408
+ return self.year - other.year + extra
409
+ end
410
+
411
+ # Calculate difference between the object and other object in months.
412
+ #
413
+ # @return [Integer]
414
+ def difference_in_months (other)
415
+ extra = 0
416
+ if self.month > other.month && self.compare_d(other) < 0
417
+ extra = -1
418
+ end
419
+ if self.month < other.month && self.compare_d(other) > 0
420
+ extra = 1
421
+ end
422
+ return 12*(self.year - other.year) + self.month - other.month + extra
423
+ end
424
+
425
+ end
426
+
427
+ class DateTime
428
+
429
+ # datetime class represents `noleap` or `365_day` calendar
430
+ #
431
+ class NoLeap < DateTimeLike
432
+
433
+ extend DateTimeLikeExtension
434
+
435
+ # Number of days per year
436
+ DPY = 365
437
+
438
+ # Numbers of days per months
439
+ DPM = [0,31,28,31,30,31,30,31,31,30,31,30,31]
440
+
441
+ # Astronomical Julian day number of UNIX epoch
442
+ UNIX_EPOCH_IN_AJD = Rational(4877859,2)
443
+
444
+ def valid_date?
445
+ if @month != 2
446
+ return Date.valid_date?(@year, @month, @day)
447
+ else
448
+ return ( @day >= 1 and @day <= 28 )
449
+ end
450
+ end
451
+
452
+ private :valid_date?
453
+
454
+ def strftime (spec)
455
+ DateTime.new(@year, @month, @day, @hour, @minute, @second, @offset).strftime(spec)
456
+ end
457
+
458
+ def leap?
459
+ false
460
+ end
461
+
462
+ end
463
+
464
+ # datetime class represents `allleap` or `366_day` calendar
465
+ #
466
+ class AllLeap < DateTimeLike
467
+
468
+ extend DateTimeLikeExtension
469
+
470
+ # Number of days per year
471
+ DPY = 366
472
+
473
+ # Numbers of days per months
474
+ DPM = [0,31,29,31,30,31,30,31,31,30,31,30,31]
475
+
476
+ # Astronomical Julian day number of UNIX epoch
477
+ UNIX_EPOCH_IN_AJD = Rational(4891223,2)
478
+
479
+ def valid_date?
480
+ if @month != 2
481
+ return Date.valid_date?(@year, @month, @day)
482
+ else
483
+ return ( @day >= 1 and @day <= 29 )
484
+ end
485
+ end
486
+
487
+ private :valid_date?
488
+
489
+ def leap?
490
+ true
491
+ end
492
+
493
+ end
494
+
495
+ # datetime class represents `360_day` calendar
496
+ #
497
+ class Fixed360Day < DateTimeLike
498
+
499
+ extend DateTimeLikeExtension
500
+
501
+ # Number of days per year
502
+ DPY = 360
503
+
504
+ # Numbers of days per months
505
+ DPM = [0,30,30,30,30,30,30,30,30,30,30,30,30]
506
+
507
+ # Astronomical Julian day number of UNIX epoch
508
+ UNIX_EPOCH_IN_AJD = Rational(4811039,2)
509
+
510
+ def valid_date?
511
+ if @day >= 31
512
+ return false
513
+ end
514
+ if @month != 2
515
+ return Date.valid_date?(@year, @month, @day)
516
+ else
517
+ return ( @day >= 1 and @day <= 30 )
518
+ end
519
+ end
520
+
521
+ private :valid_date?
522
+
523
+ def leap?
524
+ raise NotImplementedError
525
+ end
526
+
527
+ end
528
+
529
+ end
530
+