time_crisis 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,420 @@
1
+ require 'third_base/date'
2
+
3
+ module ThirdBase
4
+ # ThirdBase's DateTime class, which builds on the Date class and adds a time component of
5
+ # hours, minutes, seconds, microseconds, and an offset from UTC.
6
+ class DateTime < Date
7
+ TIME_ZONE_SECOND_OFFSETS = {
8
+ 'UTC'=>0, 'Z'=>0, 'UT'=>0, 'GMT'=>0,
9
+ 'EST'=>-18000, 'EDT'=>-14400, 'CST'=>-21600, 'CDT'=>-18000, 'MST'=>-25200, 'MDT'=>-21600, 'PST'=>-28800, 'PDT'=>-25200,
10
+ 'A'=>3600, 'B'=>7200, 'C'=>10800, 'D'=>14400, 'E'=>18000, 'F'=>21600, 'G'=>25200, 'H'=>28800, 'I'=>32400, 'K'=>36000, 'L'=>39600, 'M'=>43200,
11
+ 'N'=>-3600, 'O'=>-7200, 'P'=>-10800, 'Q'=>-14400, 'R'=>-18000, 'S'=>-21600, 'T'=>-25200, 'U'=>-28800, 'V'=>-32400, 'W'=>-36000, 'X'=>-39600, 'Y'=>-43200}
12
+ PARSER_LIST = []
13
+ DEFAULT_PARSER_LIST = [:time, :iso, :us, :num]
14
+ DEFAULT_PARSERS = {}
15
+ TIME_ZONE_RE_STRING = "(#{TIME_ZONE_SECOND_OFFSETS.keys.sort.join('|')}|[+-](?:\\d\\d:?(?:\\d\\d)?))"
16
+ TIME_RE_STRING = "(?:[T ]?([\\d ]?\\d):(\\d\\d)(?::(\\d\\d(\\.\\d+)?))?([ap]m?)? ?#{TIME_ZONE_RE_STRING}?)?"
17
+ DEFAULT_PARSERS[:time] = [[%r{\A#{TIME_RE_STRING}\z}io, proc do |m|
18
+ unless m[0] == ''
19
+ t = Time.now
20
+ add_parsed_time_parts(m, {:civil=>[t.year, t.mon, t.day], :not_parsed=>[:year, :mon, :mday]}, 1)
21
+ end
22
+ end]]
23
+ DEFAULT_PARSERS[:iso] = [[%r{\A(-?\d{4})[-./ ](\d\d)[-./ ](\d\d)#{TIME_RE_STRING}\z}io, proc{|m| add_parsed_time_parts(m, :civil=>[m[1].to_i, m[2].to_i, m[3].to_i])}]]
24
+ DEFAULT_PARSERS[:us] = [[%r{\A(\d\d?)[-./ ](\d\d?)[-./ ](\d\d(?:\d\d)?)#{TIME_RE_STRING}\z}io, proc{|m| add_parsed_time_parts(m, :civil=>[two_digit_year(m[3]), m[1].to_i, m[2].to_i])}],
25
+ [%r{\A(\d\d?)/(\d?\d)#{TIME_RE_STRING}\z}o, proc{|m| add_parsed_time_parts(m, {:civil=>[Time.now.year, m[1].to_i, m[2].to_i], :not_parsed=>:year}, 3)}],
26
+ [%r{\A#{MONTHNAME_RE_PATTERN}[-./ ](\d\d?)(?:st|nd|rd|th)?,?(?:[-./ ](-?(?:\d\d(?:\d\d)?)))?#{TIME_RE_STRING}\z}io, proc{|m| add_parsed_time_parts(m, :civil=>[m[3] ? two_digit_year(m[3]) : Time.now.year, MONTH_NUM_MAP[m[1].downcase], m[2].to_i], :not_parsed=>m[3] ? [] : [:year])}],
27
+ [%r{\A(\d\d?)(?:st|nd|rd|th)?[-./ ]#{MONTHNAME_RE_PATTERN}[-./ ](-?\d{4})#{TIME_RE_STRING}\z}io, proc{|m| add_parsed_time_parts(m, :civil=>[m[3].to_i, MONTH_NUM_MAP[m[2].downcase], m[1].to_i])}],
28
+ [%r{\A(-?\d{4})[-./ ]#{MONTHNAME_RE_PATTERN}[-./ ](\d\d?)(?:st|nd|rd|th)?#{TIME_RE_STRING}\z}io, proc{|m| add_parsed_time_parts(m, :civil=>[m[1].to_i, MONTH_NUM_MAP[m[2].downcase], m[3].to_i])}],
29
+ [%r{\A#{MONTHNAME_RE_PATTERN}[-./ ](-?\d{4})#{TIME_RE_STRING}\z}io, proc{|m| add_parsed_time_parts(m, {:civil=>[m[2].to_i, MONTH_NUM_MAP[m[1].downcase], 1]}, 3)}],
30
+ [%r{\A#{ABBR_DAYNAME_RE_PATTERN} #{ABBR_MONTHNAME_RE_PATTERN} (\d\d?) #{TIME_RE_STRING} (-?\d{4})\z}io, proc{|m| add_parsed_time_parts(m, {:civil=>[m[10].to_i, MONTH_NUM_MAP[m[2].downcase], m[3].to_i]})}]]
31
+ DEFAULT_PARSERS[:eu] = [[%r{\A(\d\d?)[-./ ](\d\d?)[-./ ](\d{4})#{TIME_RE_STRING}\z}io, proc{|m| add_parsed_time_parts(m, :civil=>[m[3].to_i, m[2].to_i, m[1].to_i])}],
32
+ [%r{\A(\d\d?)[-./ ](\d?\d)[-./ ](\d?\d)#{TIME_RE_STRING}\z}io, proc{|m| add_parsed_time_parts(m, :civil=>[two_digit_year(m[1]), m[2].to_i, m[3].to_i])}]]
33
+ DEFAULT_PARSERS[:num] = [[%r{\A(\d{2,8})#{TIME_RE_STRING}\z}io, proc do |n|
34
+ m = n[1]
35
+ add_parsed_time_parts(n, (
36
+ case m.length
37
+ when 2
38
+ t = Time.now
39
+ {:civil=>[t.year, t.mon, m.to_i], :not_parsed=>[:year, :mon, :mday]}
40
+ when 3
41
+ {:ordinal=>[Time.now.year, m.to_i], :not_parsed=>[:year, :mon, :mday]}
42
+ when 4
43
+ {:civil=>[Time.now.year, m[0..1].to_i, m[2..3].to_i], :not_parsed=>[:year]}
44
+ when 5
45
+ {:ordinal=>[two_digit_year(m[0..1]), m[2..4].to_i]}
46
+ when 6
47
+ {:civil=>[two_digit_year(m[0..1]), m[2..3].to_i, m[4..5].to_i]}
48
+ when 7
49
+ {:ordinal=>[m[0..3].to_i, m[4..6].to_i]}
50
+ when 8
51
+ {:civil=>[m[0..3].to_i, m[4..5].to_i, m[6..7].to_i]}
52
+ end
53
+ ), 2)
54
+ end
55
+ ]]
56
+
57
+ STRPTIME_PROC_H = proc{|h,x| h[:hour] = x.to_i}
58
+ STRPTIME_PROC_M = proc{|h,x| h[:min] = x.to_i}
59
+ STRPTIME_PROC_P = proc{|h,x| h[:meridian] = x.downcase == 'pm' ? :pm : :am}
60
+ STRPTIME_PROC_S = proc{|h,x| h[:sec] = x.to_i}
61
+ STRPTIME_PROC_s = proc do |h,x|
62
+ j, i = x.to_i.divmod(86400)
63
+ hours, i = i.divmod(3600)
64
+ minutes, seconds = i.divmod(60)
65
+ h.merge!(:jd=>j+UNIXEPOCH, :hour=>hours, :min=>minutes, :sec=>seconds)
66
+ end
67
+ STRPTIME_PROC_z = proc{|h,x| h[:offset] = convert_parsed_offset(x)}
68
+
69
+ # Public Class Methods
70
+
71
+ # Create a new DateTime with the given year, month, day of month, hour, minute, second, microsecond and offset.
72
+ def self.civil(year, mon, day, hour=0, min=0, sec=0, usec=0, offset=0)
73
+ new!(:civil=>[year, mon, day], :parts=>[hour, min, sec, usec], :offset=>offset)
74
+ end
75
+
76
+ # Create a new DateTime with the given commercial week year, commercial week, commercial week day, hour, minute
77
+ # second, microsecond, and offset.
78
+ def self.commercial(cwyear, cweek, cwday=5, hour=0, min=0, sec=0, usec=0, offset=0)
79
+ new!(:commercial=>[cwyear, cweek, cwday], :parts=>[hour, min, sec, usec], :offset=>offset)
80
+ end
81
+
82
+ # Create a new DateTime with the given julian date, hour, minute, second, microsecond, and offset.
83
+ def self.jd(jd, hour=0, min=0, sec=0, usec=0, offset=0)
84
+ new!(:jd=>jd, :parts=>[hour, min, sec, usec], :offset=>offset)
85
+ end
86
+
87
+ # Create a new DateTime with the given julian day, fraction of the day (0.5 is Noon), and offset.
88
+ def self.jd_fract(jd, fract=0.0, offset=0)
89
+ new!(:jd=>jd, :fract=>fract, :offset=>offset)
90
+ end
91
+
92
+ # Create a new DateTime with the current date and time.
93
+ def self.now
94
+ t = Time.now
95
+ new!(:civil=>[t.year, t.mon, t.day], :parts=>[t.hour, t.min, t.sec, t.usec], :offset=>t.utc_offset)
96
+ end
97
+
98
+ # Create a new DateTime with the given year, day of year, hour, minute, second, microsecond, and offset.
99
+ def self.ordinal(year, yday, hour=0, min=0, sec=0, usec=0, offset=0)
100
+ new!(:ordinal=>[year, yday], :parts=>[hour, min, sec, usec], :offset=>offset)
101
+ end
102
+
103
+ # Private Class Methods
104
+
105
+ def self._expand_strptime_format(v)
106
+ case v
107
+ when '%c' then '%a %b %e %H:%M:%S %Y'
108
+ when '%T', '%X' then '%H:%M:%S'
109
+ when '%R' then '%H:%M'
110
+ when '%r' then '%I:%M:%S %p'
111
+ when '%+' then '%a %b %e %H:%M:%S %Z %Y'
112
+ else super(v)
113
+ end
114
+ end
115
+
116
+ def self._strptime_part(v)
117
+ case v
118
+ when 'H', 'I' then ['(\d\d)', STRPTIME_PROC_H]
119
+ when 'k', 'l' then ['(\d?\d)', STRPTIME_PROC_H]
120
+ when 'M' then ['(\d\d)', STRPTIME_PROC_M]
121
+ when 'P', 'p' then ['([ap]m)', STRPTIME_PROC_P]
122
+ when 'S' then ['(\d\d)', STRPTIME_PROC_S]
123
+ when 's' then ['(\d+)', STRPTIME_PROC_s]
124
+ when 'z', 'Z' then [TIME_ZONE_RE_STRING, STRPTIME_PROC_z]
125
+ else super(v)
126
+ end
127
+ end
128
+
129
+ # m:
130
+ # * i + 0 : hour
131
+ # * i + 1 : minute
132
+ # * i + 2 : second
133
+ # * i + 3 : sec fraction
134
+ # * i + 4 : meridian indicator
135
+ # * i + 5 : time zone
136
+ def self.add_parsed_time_parts(m, h, i=4)
137
+ not_parsed = h[:not_parsed] || []
138
+ hour = m[i].to_i
139
+ meridian = m[i+4]
140
+ hour = hour_with_meridian(hour, /a/io.match(meridian) ? :am : :pm) if meridian
141
+ offset = if of = m[i+5]
142
+ convert_parsed_offset(of)
143
+ else
144
+ not_parsed.concat([:zone, :offset])
145
+ Time.now.utc_offset
146
+ end
147
+ min = m[i+1].to_i
148
+ sec = m[i+2].to_i
149
+ sec_fraction = m[i+3].to_f
150
+ not_parsed << :hour unless m[i]
151
+ not_parsed << :min unless m[i+1]
152
+ not_parsed << :sec unless m[i+2]
153
+ not_parsed << :sec_fraction unless m[i+3]
154
+ h.merge!(:parts=>[hour, min, sec, (sec_fraction/0.000001).to_i], :offset=>offset, :not_parsed=>not_parsed)
155
+ h
156
+ end
157
+
158
+ def self.default_parser_hash
159
+ DEFAULT_PARSERS
160
+ end
161
+
162
+ def self.default_parser_list
163
+ DEFAULT_PARSER_LIST
164
+ end
165
+
166
+ def self.hour_with_meridian(hour, meridian)
167
+ raise(ArgumentError, 'invalid date') unless hour and hour >= 1 and hour <= 12
168
+ if meridian == :am
169
+ hour == 12 ? 0 : hour
170
+ else
171
+ hour < 12 ? hour + 12 : hour
172
+ end
173
+ end
174
+
175
+ def self.new_from_parts(date_hash)
176
+ not_parsed = [:hour, :min, :sec].reject{|x| date_hash.has_key?(x)}
177
+ not_parsed.concat([:zone, :offset]) unless date_hash.has_key?(:offset)
178
+ not_parsed << :year unless date_hash.has_key?(:year) || date_hash.has_key?(:cwyear)
179
+ not_parsed << :mon unless date_hash.has_key?(:month) || date_hash.has_key?(:cweek)
180
+ not_parsed << :mday unless date_hash.has_key?(:day) || date_hash.has_key?(:cwday)
181
+ not_parsed << :sec_fraction
182
+
183
+ date_hash[:hour] = hour_with_meridian(date_hash[:hour], date_hash[:meridian]) if date_hash[:meridian]
184
+ d = now
185
+ weights = {:cwyear=>1, :year=>1, :cweek=>2, :cwday=>3, :yday=>3, :month=>2, :day=>3, :hour=>4, :min=>5, :sec=>6, :offset=>7}
186
+ columns = {}
187
+ min = 8
188
+ max = 0
189
+ date_hash.each do |k,v|
190
+ if w = weights[k]
191
+ min = w if w < min
192
+ max = w if w > max
193
+ end
194
+ end
195
+ offset = date_hash[:offset] || d.offset
196
+ hour = date_hash[:hour] || (min > 4 ? d.hour : 0)
197
+ minute = date_hash[:min] || (min > 5 ? d.min : 0)
198
+ sec = date_hash[:sec] || (min > 6 ? d.sec : 0)
199
+ hash = {:parts=>[hour, minute, sec, 0], :offset=>offset.to_i, :not_parsed=>not_parsed}
200
+ if date_hash[:jd]
201
+ new!(hash.merge!(:jd=>date_hash[:jd]))
202
+ elsif date_hash[:year] || date_hash[:yday] || date_hash[:month] || date_hash[:day] || !(date_hash[:cwyear] || date_hash[:cweek])
203
+ if date_hash[:yday]
204
+ new!(hash.merge!(:ordinal=>[date_hash[:year]||d.year, date_hash[:yday]]))
205
+ else
206
+ new!(hash.merge!(:civil=>[date_hash[:year]||d.year, date_hash[:month]||(min > 2 ? d.mon : 1), date_hash[:day]||(min > 3 ? d.day : 1)]))
207
+ end
208
+ elsif date_hash[:cwyear] || date_hash[:cweek] || date_hash[:cwday]
209
+ new!(hash.merge!(:commercial=>[date_hash[:cwyear]||d.cwyear, date_hash[:cweek]||(min > 2 ? d.cweek : 1), date_hash[:cwday]||(min > 3 ? d.cwday : 1)]))
210
+ else
211
+ raise ArgumentError, 'invalid date'
212
+ end
213
+ end
214
+
215
+ def self.convert_parsed_offset(of)
216
+ if offset = TIME_ZONE_SECOND_OFFSETS[of.upcase]
217
+ offset
218
+ else
219
+ x = of.gsub(':','')
220
+ x[0..2].to_i*3600 + x[3..4].to_i*60
221
+ end
222
+ end
223
+
224
+ def self.parser_hash
225
+ PARSERS
226
+ end
227
+
228
+ def self.parser_list
229
+ PARSER_LIST
230
+ end
231
+
232
+ def self.strptime_default
233
+ '%Y-%m-%dT%H:%M:%S'
234
+ end
235
+
236
+ private_class_method :_expand_strptime_format, :_strptime_part, :add_parsed_time_parts, :convert_parsed_offset, :default_parser_hash, :default_parser_list, :new_from_parts, :parser_hash, :parser_list, :strptime_default
237
+
238
+ reset_parsers!
239
+
240
+ # Instance Methods
241
+
242
+ # This datetime's offset from UTC, in seconds.
243
+ attr_reader :offset
244
+ alias utc_offset offset
245
+
246
+ # Which parts of this datetime were guessed instead of being parsed from the input.
247
+ attr_reader :not_parsed
248
+
249
+ # Called by DateTime.new!, should be a hash with the following possible keys:
250
+ #
251
+ # * :civil, :commericial, :jd, :ordinal : See ThirdBase::Date#initialize
252
+ # * :fract : The fraction of the day (0.5 is Noon)
253
+ # * :offset : offset from UTC, in seconds.
254
+ # * :parts : an array with 4 elements, hour, minute, second, and microsecond
255
+ #
256
+ # Raises an ArgumentError if an invalid date is used. DateTime objects are immutable once created.
257
+ def initialize(opts)
258
+ @not_parsed = opts[:not_parsed] || []
259
+ @offset = opts[:offset]
260
+ raise(ArgumentError, 'invalid datetime') unless @offset.is_a?(Integer) and @offset <= 43200 and @offset >= -43200
261
+ if opts[:parts]
262
+ @hour, @min, @sec, @usec = opts[:parts]
263
+ raise(ArgumentError, 'invalid datetime') unless @hour.is_a?(Integer) and @min.is_a?(Integer) and @sec.is_a?(Integer) and @usec.is_a?(Integer)
264
+ elsif opts[:fract]
265
+ @fract = opts[:fract]
266
+ raise(ArgumentError, 'invalid datetime') unless @fract.is_a?(Float) and @fract < 1.0 and @fract >= 0.0
267
+ else
268
+ raise(ArgumentError, 'invalid datetime')
269
+ end
270
+ super(opts)
271
+ end
272
+
273
+ # Return a new datetune with the given number of days added to this datetime. If d is a Float
274
+ # adds a fractional date, with possible loss of precision. If d is an integer,
275
+ # the returned date has the same time components as the current date. In both
276
+ # cases, the offset for the new date is the same as for this date.
277
+ def +(d)
278
+ case d
279
+ when Float
280
+ d, f = d.to_f.divmod(1)
281
+ f = fract + f
282
+ m, f = f.divmod(1)
283
+ self.class.jd_fract(jd+d+m, f, @offset)
284
+ when Integer
285
+ new_jd(jd+d)
286
+ else
287
+ raise(TypeError, "d must be a Float or Integer")
288
+ end
289
+ end
290
+
291
+ # Return a new datetune with the given number of days subtracted from this datetime.
292
+ # If d is a DateTime, returns the difference between the two datetimes as a Float,
293
+ # considering both datetimes date, time, and offest.
294
+ def -(d)
295
+ case d
296
+ when self.class
297
+ (jd - d.jd) + (fract - d.fract) + (@offset - d.offset)/86400.0
298
+ when Integer, Float
299
+ self + -d
300
+ else
301
+ raise TypeError, "d should be #{self.class}, Float, or Integer"
302
+ end
303
+ end
304
+
305
+ # Compares two datetimes. If the given datetime is an Integer, returns 1 unless
306
+ # this datetime's time components are all 0, in which case it returns 0.
307
+ # If the given datetime is a Float, calculates this date's julian date plus the
308
+ # date fraction and compares it to the given datetime, and returns 0 only if the
309
+ # two are very close together. This code does not take into account time offsets.
310
+ def <=>(datetime)
311
+ case datetime
312
+ when Integer
313
+ if ((d = (jd <=> datetime)) == 0)
314
+ (hour == 0 and min == 0 and sec == 0 and usec == 0) ? 0 : 1
315
+ else
316
+ d
317
+ end
318
+ when Float
319
+ diff = jd+fract - datetime
320
+ if diff.abs <= 1.15740740740741e-011
321
+ 0
322
+ else
323
+ diff > 0.0 ? 1 : -1
324
+ end
325
+ when self.class
326
+ ((d = super) == 0) && ((d = (hour <=> datetime.hour)) == 0) && ((d = (min <=> datetime.min)) == 0) && ((d = (sec <=> datetime.sec)) == 0) && ((d = (usec <=> datetime.usec)) == 0)
327
+ d
328
+ else
329
+ raise TypeError, "d should be #{self.class}, Float, or Integer"
330
+ end
331
+ end
332
+
333
+ # Two DateTimes are equal only if their dates and time components are the same, not counting the offset.
334
+ def ==(datetime)
335
+ return false unless DateTime === datetime
336
+ super and hour == datetime.hour and min == datetime.min and sec == datetime.sec and usec == datetime.usec
337
+ end
338
+ alias_method :eql?, :==
339
+
340
+ # Returns the fraction of the day for this datetime (Noon is 0.5)
341
+ def fract
342
+ @fract ||= (@hour*3600+@min*60+@sec+@usec/1000000.0)/86400.0
343
+ end
344
+
345
+ # Returns the hour of this datetime.
346
+ def hour
347
+ @hour ||= time_parts[0]
348
+ end
349
+
350
+ # Returns the minute of this datetime.
351
+ def min
352
+ @min ||= time_parts[1]
353
+ end
354
+
355
+ # Returns the second of this datetime.
356
+ def sec
357
+ @sec ||= time_parts[2]
358
+ end
359
+
360
+ # Returns the microsecond of this datetime.
361
+ def usec
362
+ @usec ||= time_parts[3]
363
+ end
364
+
365
+ # Return the offset as a time zone string (+/-HHMM).
366
+ def zone
367
+ strftime('%Z')
368
+ end
369
+
370
+ private
371
+
372
+ def _strftime(v)
373
+ case v
374
+ when 'c' then strftime('%a %b %e %H:%M:%S %Y')
375
+ when 'H' then '%02d' % hour
376
+ when 'I' then '%02d' % ((hour % 12).nonzero? or 12)
377
+ when 'k' then '%2d' % hour
378
+ when 'l' then '%2d' % ((hour % 12).nonzero? or 12)
379
+ when 'M' then '%02d' % min
380
+ when 'P' then hour < 12 ? 'am' : 'pm'
381
+ when 'p' then hour < 12 ? 'AM' : 'PM'
382
+ when 'R' then strftime('%H:%M')
383
+ when 'r' then strftime('%I:%M:%S %p')
384
+ when 'S' then '%02d' % sec
385
+ when 's' then '%d' % ((jd - UNIXEPOCH)*86400 + hour*3600 + min*60 + sec - @offset)
386
+ when 'T', 'X' then strftime('%H:%M:%S')
387
+ when '+' then strftime('%a %b %e %H:%M:%S %Z %Y')
388
+ when 'Z' then "%+03d:%02d" % (@offset/60).divmod(60)
389
+ when 'z' then "%+03d%02d" % (@offset/60).divmod(60)
390
+ else super(v)
391
+ end
392
+ end
393
+
394
+ def fract_to_hmsu(p)
395
+ hour, p = (p.to_f*24).divmod(1)
396
+ min, p = (p*60).divmod(1)
397
+ sec, sec_fract = (p*60).divmod(1)
398
+ [hour, min, sec, (sec_fract*1000000).to_i]
399
+ end
400
+
401
+ def new_civil(y, m, d)
402
+ self.class.new(y, m, d, hour, min, sec, usec, @offset)
403
+ end
404
+
405
+ def new_jd(j)
406
+ self.class.jd(j, hour, min, sec, usec, @offset)
407
+ end
408
+
409
+ def strftime_default
410
+ '%Y-%m-%dT%H:%M:%S%Z'
411
+ end
412
+
413
+ def time_parts
414
+ unless @hour && @min && @sec && @usec
415
+ @hour, @min, @sec, @usec = fract_to_hmsu(fract)
416
+ end
417
+ [@hour, @min, @sec, @usec]
418
+ end
419
+ end
420
+ end
data/lib/third_base.rb ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ require 'third_base/datetime'
@@ -103,8 +103,10 @@ module TimeCrisis::DateRange::Date
103
103
  end
104
104
  end
105
105
 
106
- TimeCrisis::Date.send(:include, TimeCrisis::DateRange::Date::InstanceMethods)
107
- TimeCrisis::Date.extend(TimeCrisis::DateRange::Date::ClassMethods)
106
+ ::TimeCrisis::Date.send(:include, TimeCrisis::DateRange::Date::InstanceMethods)
107
+ ::TimeCrisis::Date.extend(TimeCrisis::DateRange::Date::ClassMethods)
108
108
 
109
- ::Date.send(:include, TimeCrisis::DateRange::Date::InstanceMethods)
110
- ::Date.extend(TimeCrisis::DateRange::Date::ClassMethods)
109
+ if defined?(::Date)
110
+ ::Date.send(:include, TimeCrisis::DateRange::Date::InstanceMethods)
111
+ ::Date.extend(TimeCrisis::DateRange::Date::ClassMethods)
112
+ end
@@ -0,0 +1,86 @@
1
+ module TimeCrisis::Holiday
2
+ class << self
3
+ def presidents_day
4
+ third_monday_of_feb = TimeCrisis::Date.nth_weekday({
5
+ :month => 2,
6
+ :weekday => :monday,
7
+ :nth => 3
8
+ })
9
+
10
+ [third_monday_of_feb.month, third_monday_of_feb.day]
11
+ end
12
+
13
+ def memorial_day
14
+ last_monday_of_may = TimeCrisis::Date.nth_weekday({
15
+ :month => 5,
16
+ :weekday => :monday,
17
+ :nth => 4
18
+ })
19
+
20
+ [last_monday_of_may.month, last_monday_of_may.day]
21
+ end
22
+
23
+ def labor_day
24
+ first_monday_in_sept = TimeCrisis::Date.nth_weekday({
25
+ :month => 9,
26
+ :weekday => :monday,
27
+ :nth => 1
28
+ })
29
+
30
+ [first_monday_in_sept.month, first_monday_in_sept.day]
31
+ end
32
+
33
+ def veterans_day
34
+ day = nil
35
+
36
+ nov11 = TimeCrisis::Date.today.change({
37
+ :month => 11,
38
+ :day => 11
39
+ })
40
+
41
+ # Veterans day is usually observed on November 11. However, if it occurs
42
+ # on a Sunday then the following Monday is designated for holiday leave,
43
+ # and if it occurs Saturday then either Saturday or Friday may be so
44
+ # designated.
45
+ if nov11.wday == 0
46
+ day = 12
47
+ elsif nov11.wday == 6
48
+ day = 10
49
+ else
50
+ day = 11
51
+ end
52
+
53
+ [11, day]
54
+ end
55
+
56
+ def thanksgiving_day
57
+ fourth_thursday_in_november = TimeCrisis::Date.nth_weekday({
58
+ :month => 9,
59
+ :weekday => :thursday,
60
+ :nth => 4
61
+ })
62
+
63
+ [fourth_thursday_in_november.month, fourth_thursday_in_november.day]
64
+ end
65
+ end
66
+
67
+ module InstanceMethods
68
+ def holiday?
69
+ @@holidays ||= {
70
+ :new_years_day => [1, 1],
71
+ :presidents_day => TimeCrisis::Holiday.presidents_day,
72
+ :memorial_day => TimeCrisis::Holiday.memorial_day,
73
+ :independence_day => [7, 4],
74
+ :labor_day => TimeCrisis::Holiday.labor_day,
75
+ :veterans_day => TimeCrisis::Holiday.veterans_day,
76
+ :thanksgiving_day => TimeCrisis::Holiday.thanksgiving_day,
77
+ :christmas_day => [12, 25]
78
+ }
79
+
80
+ @@holidays.values.include?([self.month, self.day])
81
+ end
82
+ end
83
+ end
84
+
85
+ ::TimeCrisis::Date.send(:include, TimeCrisis::Holiday::InstanceMethods)
86
+ ::TimeCrisis::DateTime.send(:include, TimeCrisis::Holiday::InstanceMethods)
@@ -68,5 +68,5 @@ module TimeCrisis
68
68
  end
69
69
  end
70
70
 
71
- TimeCrisis::Date.extend(TimeCrisis::MeteorologicalSeasons::ClassMethods)
72
- TimeCrisis::Date.send(:include, TimeCrisis::MeteorologicalSeasons::InstanceMethods)
71
+ ::TimeCrisis::Date.extend(TimeCrisis::MeteorologicalSeasons::ClassMethods)
72
+ ::TimeCrisis::Date.send(:include, TimeCrisis::MeteorologicalSeasons::InstanceMethods)
@@ -1,28 +1,62 @@
1
1
  module TimeCrisis
2
2
  module NamedMonths
3
- def january(year=nil); month_range(1, year); end
4
- def february(year=nil); month_range(2, year); end
5
- def march(year=nil); month_range(3, year); end
6
- def april(year=nil); month_range(4, year); end
7
- def may(year=nil); month_range(5, year); end
8
- def june(year=nil); month_range(6, year); end
9
- def july(year=nil); month_range(7, year); end
10
- def august(year=nil); month_range(8, year); end
11
- def september(year=nil); month_range(9, year); end
12
- def october(year=nil); month_range(10, year); end
13
- def november(year=nil); month_range(11, year); end
14
- def december(year=nil); month_range(12, year); end
3
+ def january(year=nil)
4
+ month_range(1, year)
5
+ end
6
+
7
+ def february(year=nil)
8
+ month_range(2, year)
9
+ end
10
+
11
+ def march(year=nil)
12
+ month_range(3, year)
13
+ end
14
+
15
+ def april(year=nil)
16
+ month_range(4, year)
17
+ end
18
+
19
+ def may(year=nil)
20
+ month_range(5, year)
21
+ end
22
+
23
+ def june(year=nil)
24
+ month_range(6, year)
25
+ end
26
+
27
+ def july(year=nil)
28
+ month_range(7, year)
29
+ end
30
+
31
+ def august(year=nil)
32
+ month_range(8, year)
33
+ end
34
+
35
+ def september(year=nil)
36
+ month_range(9, year)
37
+ end
38
+
39
+ def october(year=nil)
40
+ month_range(10, year)
41
+ end
42
+
43
+ def november(year=nil)
44
+ month_range(11, year)
45
+ end
46
+
47
+ def december(year=nil)
48
+ month_range(12, year)
49
+ end
15
50
 
16
51
  def month_range(month=nil, year=nil)
17
52
  month ||= current.month
18
53
  year ||= current.year
19
-
54
+
20
55
  base = TimeCrisis::Date.civil(year, month, 1)
21
56
  base.for(1, 'months')
22
57
  end
23
58
  end
24
59
  end
25
60
 
26
- TimeCrisis::Date.extend(TimeCrisis::NamedMonths)
27
-
28
- ::Date.extend(TimeCrisis::NamedMonths)
61
+ ::TimeCrisis::Date.extend(TimeCrisis::NamedMonths)
62
+ ::Date.extend(TimeCrisis::NamedMonths) if defined?(::Date)
@@ -0,0 +1,55 @@
1
+ module TimeCrisis
2
+ module NthWeekday
3
+ module ClassMethods
4
+ @@days_of_the_week = {
5
+ :sunday => 0,
6
+ :monday => 1,
7
+ :tuesday => 2,
8
+ :wednesday => 3,
9
+ :thursday => 4,
10
+ :friday => 5,
11
+ :saturday => 6
12
+ }
13
+
14
+ def nth_weekday(h={})
15
+ raise ArgumentError unless h[:nth]
16
+
17
+ today = TimeCrisis::Date.today
18
+ h[:year] ||= today.year
19
+ h[:month] ||= today.month
20
+ h[:weekday] ||= today.wday
21
+
22
+ target_weekday = h[:weekday].is_a?(Numeric) ? h[:weekday] : @@days_of_the_week[h[:weekday]]
23
+ first_weekday = TimeCrisis::Date.new(h[:year], h[:month], 1).wday
24
+
25
+ offset = target_weekday > 0 ? target_weekday - first_weekday + 1 : 7 - (first_weekday - 1)
26
+ day = h[:nth] == 1 ? 7 + offset : (7 * (h[:nth] - 1)) + offset
27
+
28
+ if $DEBUG
29
+ STDERR.puts "Arguments: #{h.inspect}"
30
+ STDERR.puts "Target weekday: #{target_weekday}"
31
+ STDERR.puts "First weekday: #{first_weekday}"
32
+ STDERR.puts "Offset: #{offset}"
33
+ STDERR.puts "Day: #{day}"
34
+ end
35
+
36
+ TimeCrisis::Date.civil(h[:year], h[:month], day)
37
+ end
38
+ end
39
+
40
+ module InstanceMethods
41
+ def nth_weekday(h={})
42
+ h[:year] ||= self.year
43
+ h[:month] ||= self.month
44
+ h[:weekday] ||= self.wday
45
+ self.class.nth_weekday(h)
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+
52
+ ::TimeCrisis::Date.extend(TimeCrisis::NthWeekday::ClassMethods)
53
+ ::TimeCrisis::Date.send(:include, TimeCrisis::NthWeekday::InstanceMethods)
54
+ ::TimeCrisis::DateTime.extend(TimeCrisis::NthWeekday::ClassMethods)
55
+ ::TimeCrisis::DateTime.send(:include, TimeCrisis::NthWeekday::InstanceMethods)
@@ -16,5 +16,5 @@ module TimeCrisis::Support::ActsLike
16
16
  end
17
17
  end
18
18
 
19
- TimeCrisis::Date.send(:include, TimeCrisis::Support::ActsLike::Date)
20
- TimeCrisis::DateTime.send(:include, TimeCrisis::Support::ActsLike::DateTime)
19
+ ::TimeCrisis::Date.send(:include, TimeCrisis::Support::ActsLike::Date)
20
+ ::TimeCrisis::DateTime.send(:include, TimeCrisis::Support::ActsLike::DateTime)
@@ -21,5 +21,5 @@ module TimeCrisis::Support::Advance
21
21
  end
22
22
  end
23
23
 
24
- TimeCrisis::Date.send(:include, TimeCrisis::Support::Advance::Date)
25
- TimeCrisis::DateTime.send(:include, TimeCrisis::Support::Advance::DateTime)
24
+ ::TimeCrisis::Date.send(:include, TimeCrisis::Support::Advance::Date)
25
+ ::TimeCrisis::DateTime.send(:include, TimeCrisis::Support::Advance::DateTime)