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.
- data/.gitignore +1 -0
- data/VERSION.yml +3 -2
- data/lib/third_base/compat/date/format.rb +3 -0
- data/lib/third_base/compat/date.rb +3 -0
- data/lib/third_base/compat.rb +405 -0
- data/lib/third_base/date.rb +696 -0
- data/lib/third_base/datetime.rb +420 -0
- data/lib/third_base.rb +2 -0
- data/lib/time_crisis/date_range.rb +6 -4
- data/lib/time_crisis/holiday.rb +86 -0
- data/lib/time_crisis/meteorological_seasons.rb +2 -2
- data/lib/time_crisis/named_months.rb +50 -16
- data/lib/time_crisis/nth_weekday.rb +55 -0
- data/lib/time_crisis/support/acts_like.rb +2 -2
- data/lib/time_crisis/support/advance.rb +2 -2
- data/lib/time_crisis/support/change.rb +3 -3
- data/lib/time_crisis/support/conversions.rb +4 -4
- data/lib/time_crisis/support/current.rb +2 -2
- data/lib/time_crisis/support/readable_inspect.rb +2 -2
- data/lib/time_crisis/weekend.rb +8 -0
- data/lib/time_crisis.rb +10 -0
- data/time_crisis.gemspec +14 -4
- metadata +11 -2
@@ -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
@@ -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
|
-
|
110
|
-
::Date.
|
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)
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def
|
12
|
-
|
13
|
-
|
14
|
-
|
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)
|