third_base 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -3,7 +3,7 @@
3
3
  ThirdBase differs from Ruby's standard Date/DateTime class in the following
4
4
  ways:
5
5
 
6
- - ThirdBase is roughly 2-12 times faster depending on usage
6
+ - ThirdBase is roughly 2-10 times faster depending on usage
7
7
  - ThirdBase has a lower memory footprint
8
8
  - ThirdBase supports pluggable parsers
9
9
  - ThirdBase doesn't depend on Ruby's Rational class
@@ -150,17 +150,17 @@ To add a parser type:
150
150
  Date.add_parser_type(:mine)
151
151
  DateTime.add_parser_type(:mine)
152
152
 
153
- === Adding Parsers to Parser Types
153
+ === Adding Regexp Parsers to Parser Types
154
154
 
155
- A ThirdBase Date/Datetime parser consists of two parts, a regular
156
- expression, and a proc that takes a MatchData object and returns a hash
157
- passed to Date/DateTime.new!. The proc is only called if the regular
158
- expression matches the string to be parsed, and it can return nil if it
159
- is not able to successfully parse the string (even if the string matches
160
- the regular expression). To add a parser, you use the add_parser class
161
- method, which takes an argument specifying which parser family to
162
- use, the regular expression, and a block that is used as a proc for the
163
- parser:
155
+ A ThirdBase Date/Datetime regexp parser consists of two parts, a regular
156
+ expression, and a block that takes a MatchData object and returns a
157
+ Date/DateTime instance or a hash to be passed to Date/DateTime.new!. The
158
+ block is only called if the regular expression matches the string to be
159
+ parsed, and it can return nil if it is not able to successfully parse the
160
+ string (even if the string matches the regular expression). To add a
161
+ parser, you use the add_parser class method, which takes an argument
162
+ specifying which parser family to use, the regular expression, and a block
163
+ that is used as a proc for the parser:
164
164
 
165
165
  To add a parser to a parser type:
166
166
 
@@ -195,6 +195,15 @@ Adding a parser to a parser type adds it to the front of the array of parsers
195
195
  for that type, so it will be tried before other parsers for that type. It is
196
196
  an error to add a parser to a parser type that doesn't exist.
197
197
 
198
+ === Adding strptime Parsers to Parser Types (New in 1.2.0)
199
+
200
+ ThirdBase 1.2.0 added the ability to more easily create common parsers
201
+ using the strptime format string syntax. These are created similar to regexp
202
+ parsers, but use a format string, and the block is then optional (and should
203
+ be omitted unless you know what you are doing):
204
+
205
+ DateTime.add_parser(:mine, '%Z %m~%Y~%d %S`%M`%H')
206
+
198
207
  === Modifying the Order of Parsers Types
199
208
 
200
209
  You can change the order in which parsers types are tried by using the
@@ -42,7 +42,7 @@ module ThirdBase
42
42
  # * :zone : time zone offset as string
43
43
  def _parse(str, comp=false)
44
44
  d = DateTime.parse(str)
45
- {:mon=>d.mon, :zone=>d.zone, :sec=>d.sec, :year=>d.year, :hour=>d.hour, :offset=>d.offset, :mday=>d.day, :min=>d.min, :sec_fraction=>d.usec/1000000.0}.reject{|k, v| d.not_parsed.include?(k)}
45
+ {:mon=>d.mon, :zone=>d.zone, :sec=>d.sec, :year=>d.year, :hour=>d.hour, :offset=>d.utc_offset, :mday=>d.day, :min=>d.min, :sec_fraction=>d.usec/1000000.0}.reject{|k, v| d.not_parsed.include?(k)}
46
46
  end
47
47
 
48
48
  # Converts an Astronomical Julian Date to an Astronomical Modified Julian Date (substracts an integer from ajd)
@@ -43,7 +43,8 @@ module ThirdBase
43
43
  [%r{\A#{MONTHNAME_RE_PATTERN}[-./ ](\d\d?)(?:st|nd|rd|th)?,?(?:[-./ ](-?(?:\d\d(?:\d\d)?)))?\z}io, proc{|m| {:civil=>[m[3] ? two_digit_year(m[3]) : Time.now.year, MONTH_NUM_MAP[m[1].downcase], m[2].to_i]}}],
44
44
  [%r{\A(\d\d?)(?:st|nd|rd|th)?[-./ ]#{MONTHNAME_RE_PATTERN}[-./ ](-?\d{4})\z}io, proc{|m| {:civil=>[m[3].to_i, MONTH_NUM_MAP[m[2].downcase], m[1].to_i]}}],
45
45
  [%r{\A(-?\d{4})[-./ ]#{MONTHNAME_RE_PATTERN}[-./ ](\d\d?)(?:st|nd|rd|th)?\z}io, proc{|m| {:civil=>[m[1].to_i, MONTH_NUM_MAP[m[2].downcase], m[3].to_i]}}],
46
- [%r{\A#{MONTHNAME_RE_PATTERN}[-./ ](-?\d{4})\z}io, proc{|m| {:civil=>[m[2].to_i, MONTH_NUM_MAP[m[1].downcase], 1]}}]]
46
+ [%r{\A#{MONTHNAME_RE_PATTERN}[-./ ](-?\d{4})\z}io, proc{|m| {:civil=>[m[2].to_i, MONTH_NUM_MAP[m[1].downcase], 1]}}],
47
+ [%r{\A#{ABBR_DAYNAME_RE_PATTERN} #{ABBR_MONTHNAME_RE_PATTERN} (\d\d?) (-?\d{4})\z}io, proc{|m| {:civil=>[m[4].to_i, MONTH_NUM_MAP[m[2].downcase], m[3].to_i]}}]]
47
48
  DEFAULT_PARSERS[:eu] = [[%r{\A(\d\d?)[-./ ](\d\d?)[-./ ](\d\d\d\d)\z}o, proc{|m| {:civil=>[m[3].to_i, m[2].to_i, m[1].to_i]}}],
48
49
  [%r{\A(\d\d?)[-./ ](\d?\d)[-./ ](\d?\d)\z}o, proc{|m| {:civil=>[two_digit_year(m[1]), m[2].to_i, m[3].to_i]}}]]
49
50
  DEFAULT_PARSERS[:num] = [[%r{\A\d{2,8}\z}o, proc do |m|
@@ -91,13 +92,26 @@ module ThirdBase
91
92
  alias new! new
92
93
  end
93
94
 
94
- # Add a parser to the parser type. re should be
95
- # a Regexp, and a block must be provided. The block
96
- # should take a single MatchData argument, a return
97
- # either nil specifying it could not parse the string,
98
- # or a hash of values to be passed to new!.
99
- def self.add_parser(type, re, &block)
100
- parser_hash[type].unshift([re, block])
95
+ # Add a parser to the parser type. Arguments:
96
+ # * type - The parser type to which to add the parser, should be a Symbol.
97
+ # * pattern - Can be either a Regexp or String:
98
+ # * String - A strptime parser regular expression is created using pattern as the format string.
99
+ # If a block is given, it is used. If no block is given, the parser will
100
+ # operate identically to strptime.
101
+ # * Regexp - The regular expression is used directly. In this case, a block must be provided,
102
+ # or an error is raised.
103
+ #
104
+ # The block, if provided, should take a single MatchData argument. It should return
105
+ # nil if it cannot successfully parse the string, an instance of this class, or a hash of
106
+ # values to be passed to new!.
107
+ def self.add_parser(type, pattern, &block)
108
+ if pattern.is_a?(String)
109
+ pattern, blk = strptime_pattern_and_block(pattern)
110
+ block ||= blk
111
+ else
112
+ raise(ArgumentError, 'must provide block for Regexp parser') unless block_given?
113
+ end
114
+ parser_hash[type].unshift([pattern, block])
101
115
  end
102
116
 
103
117
  # Add a parser type to the list of parser types.
@@ -144,7 +158,7 @@ module ThirdBase
144
158
  parsers(opts[:parser_types]) do |pattern, block|
145
159
  if m = pattern.match(s)
146
160
  if res = block.call(m)
147
- return new!(res)
161
+ return res.is_a?(Hash) ? new!(res) : res
148
162
  end
149
163
  end
150
164
  end
@@ -166,24 +180,15 @@ module ThirdBase
166
180
  # Parse the string using the provided format (or the default format).
167
181
  # Raises an ArgumentError if the format does not match the string.
168
182
  def self.strptime(str, fmt=strptime_default)
169
- blocks = []
183
+ pattern, block = strptime_pattern_and_block(fmt)
170
184
  s = str.strip
171
- date_hash = {}
172
- pattern = Regexp.escape(expand_strptime_format(fmt)).gsub(STRFTIME_RE) do |x|
173
- pat, *blks = _strptime_part(x[1..1])
174
- blocks += blks
175
- pat
176
- end
177
- if m = /#{pattern}/i.match(s)
178
- m.to_a[1..-1].zip(blocks) do |x, blk|
179
- blk.call(date_hash, x)
180
- end
181
- new_from_parts(date_hash)
185
+ if m = pattern.match(s)
186
+ block.call(m)
182
187
  else
183
188
  raise ArgumentError, 'invalid date'
184
189
  end
185
190
  end
186
-
191
+
187
192
  # Returns a date with the current year, month, and date.
188
193
  def self.today
189
194
  t = Time.now
@@ -283,6 +288,23 @@ module ThirdBase
283
288
  '%Y-%m-%d'
284
289
  end
285
290
 
291
+ def self.strptime_pattern_and_block(fmt)
292
+ blocks = []
293
+ pattern = Regexp.escape(expand_strptime_format(fmt)).gsub(STRFTIME_RE) do |x|
294
+ pat, *blks = _strptime_part(x[1..1])
295
+ blocks += blks
296
+ pat
297
+ end
298
+ block = proc do |m|
299
+ h = {}
300
+ m.to_a[1..-1].zip(blocks) do |x, blk|
301
+ blk.call(h, x)
302
+ end
303
+ new_from_parts(h)
304
+ end
305
+ [/\A#{pattern}\z/i, block]
306
+ end
307
+
286
308
  def self.two_digit_year(y)
287
309
  y = if y.length == 2
288
310
  y = y.to_i
@@ -292,7 +314,7 @@ module ThirdBase
292
314
  end
293
315
  end
294
316
 
295
- private_class_method :_expand_strptime_format, :_strptime_part, :default_parser_hash, :default_parser_list, :expand_strptime_format, :new_from_parts, :parser_hash, :parser_list, :parsers, :parsers_for_family, :strptime_default, :two_digit_year
317
+ private_class_method :_expand_strptime_format, :_strptime_part, :default_parser_hash, :default_parser_list, :expand_strptime_format, :new_from_parts, :parser_hash, :parser_list, :parsers, :parsers_for_family, :strptime_default, :strptime_pattern_and_block, :two_digit_year
296
318
 
297
319
  reset_parsers!
298
320
 
@@ -4,11 +4,16 @@ module ThirdBase
4
4
  # ThirdBase's DateTime class, which builds on the Date class and adds a time component of
5
5
  # hours, minutes, seconds, microseconds, and an offset from UTC.
6
6
  class DateTime < Date
7
-
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}
8
12
  PARSER_LIST = []
9
13
  DEFAULT_PARSER_LIST = [:time, :iso, :us, :num]
10
14
  DEFAULT_PARSERS = {}
11
- TIME_RE_STRING = '(?:[T ]?([\d ]?\d):(\d\d)(?::(\d\d(\.\d+)?))?([ap]m?)? ?(Z|[+-](?:\d\d:?(?:\d\d)?))?)?'
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}?)?"
12
17
  DEFAULT_PARSERS[:time] = [[%r{\A#{TIME_RE_STRING}\z}io, proc do |m|
13
18
  unless m[0] == ''
14
19
  t = Time.now
@@ -21,7 +26,8 @@ module ThirdBase
21
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])}],
22
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])}],
23
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])}],
24
- [%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)}]]
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]})}]]
25
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])}],
26
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])}]]
27
33
  DEFAULT_PARSERS[:num] = [[%r{\A(\d{2,8})#{TIME_RE_STRING}\z}io, proc do |n|
@@ -58,7 +64,7 @@ module ThirdBase
58
64
  minutes, seconds = i.divmod(60)
59
65
  h.merge!(:jd=>j+UNIXEPOCH, :hour=>hours, :min=>minutes, :sec=>seconds)
60
66
  end
61
- STRPTIME_PROC_z = proc{|h,x| x=x.gsub(':',''); h[:offset] = (x == 'Z' ? 0 : x[0..2].to_i*3600 + x[3..4].to_i*60)}
67
+ STRPTIME_PROC_z = proc{|h,x| h[:offset] = convert_parsed_offset(x)}
62
68
 
63
69
  # Public Class Methods
64
70
 
@@ -102,7 +108,7 @@ module ThirdBase
102
108
  when '%T', '%X' then '%H:%M:%S'
103
109
  when '%R' then '%H:%M'
104
110
  when '%r' then '%I:%M:%S %p'
105
- when '%+' then '%a %b %e %H:%M:%S %z %Y'
111
+ when '%+' then '%a %b %e %H:%M:%S %Z %Y'
106
112
  else super(v)
107
113
  end
108
114
  end
@@ -115,7 +121,7 @@ module ThirdBase
115
121
  when 'P', 'p' then ['([ap]m)', STRPTIME_PROC_P]
116
122
  when 'S' then ['(\d\d)', STRPTIME_PROC_S]
117
123
  when 's' then ['(\d+)', STRPTIME_PROC_s]
118
- when 'z', 'Z' then ['(Z|[+-](?:\d{4}|\d\d:\d\d))', STRPTIME_PROC_z]
124
+ when 'z', 'Z' then [TIME_ZONE_RE_STRING, STRPTIME_PROC_z]
119
125
  else super(v)
120
126
  end
121
127
  end
@@ -133,8 +139,7 @@ module ThirdBase
133
139
  meridian = m[i+4]
134
140
  hour = hour_with_meridian(hour, /a/io.match(meridian) ? :am : :pm) if meridian
135
141
  offset = if of = m[i+5]
136
- x = of.gsub(':','')
137
- offset = x == 'Z' ? 0 : x[0..2].to_i*3600 + x[3..4].to_i*60
142
+ convert_parsed_offset(of)
138
143
  else
139
144
  not_parsed.concat([:zone, :offset])
140
145
  Time.now.utc_offset
@@ -168,11 +173,18 @@ module ThirdBase
168
173
  end
169
174
 
170
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
+
171
183
  date_hash[:hour] = hour_with_meridian(date_hash[:hour], date_hash[:meridian]) if date_hash[:meridian]
172
184
  d = now
173
- weights = {:cwyear=>1, :year=>1, :cweek=>2, :cwday=>3, :yday=>3, :month=>2, :day=>3, :hour=>4, :min=>5, :sec=>6}
185
+ weights = {:cwyear=>1, :year=>1, :cweek=>2, :cwday=>3, :yday=>3, :month=>2, :day=>3, :hour=>4, :min=>5, :sec=>6, :offset=>7}
174
186
  columns = {}
175
- min = 7
187
+ min = 8
176
188
  max = 0
177
189
  date_hash.each do |k,v|
178
190
  if w = weights[k]
@@ -183,21 +195,31 @@ module ThirdBase
183
195
  offset = date_hash[:offset] || d.offset
184
196
  hour = date_hash[:hour] || (min > 4 ? d.hour : 0)
185
197
  minute = date_hash[:min] || (min > 5 ? d.min : 0)
186
- sec = date_hash[:sec] || 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}
187
200
  if date_hash[:jd]
188
- jd(date_hash[:jd], hour, minute, sec, 0, offset)
201
+ new!(hash.merge!(:jd=>date_hash[:jd]))
189
202
  elsif date_hash[:year] || date_hash[:yday] || date_hash[:month] || date_hash[:day] || !(date_hash[:cwyear] || date_hash[:cweek])
190
203
  if date_hash[:yday]
191
- ordinal(date_hash[:year]||d.year, date_hash[:yday], hour, minute, sec, 0, offset)
204
+ new!(hash.merge!(:ordinal=>[date_hash[:year]||d.year, date_hash[:yday]]))
192
205
  else
193
- civil(date_hash[:year]||d.year, date_hash[:month]||(min > 2 ? d.mon : 1), date_hash[:day]||(min > 3 ? d.day : 1), hour, minute, sec, 0, offset)
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)]))
194
207
  end
195
208
  elsif date_hash[:cwyear] || date_hash[:cweek] || date_hash[:cwday]
196
- commercial(date_hash[:cwyear]||d.cwyear, date_hash[:cweek]||(min > 2 ? d.cweek : 1), date_hash[:cwday]||(min > 3 ? d.cwday : 1), hour, minute, sec, 0, offset)
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)]))
197
210
  else
198
211
  raise ArgumentError, 'invalid date'
199
212
  end
200
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
201
223
 
202
224
  def self.parser_hash
203
225
  PARSERS
@@ -211,7 +233,7 @@ module ThirdBase
211
233
  '%Y-%m-%dT%H:%M:%S'
212
234
  end
213
235
 
214
- private_class_method :_expand_strptime_format, :_strptime_part, :add_parsed_time_parts, :default_parser_hash, :default_parser_list, :new_from_parts, :parser_hash, :parser_list, :strptime_default
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
215
237
 
216
238
  reset_parsers!
217
239
 
@@ -342,7 +364,7 @@ module ThirdBase
342
364
 
343
365
  # Return the offset as a time zone string (+/-HHMM).
344
366
  def zone
345
- strftime('%z')
367
+ strftime('%Z')
346
368
  end
347
369
 
348
370
  private
@@ -362,9 +384,9 @@ module ThirdBase
362
384
  when 'S' then '%02d' % sec
363
385
  when 's' then '%d' % ((jd - UNIXEPOCH)*86400 + hour*3600 + min*60 + sec - @offset)
364
386
  when 'T', 'X' then strftime('%H:%M:%S')
365
- when '+' then strftime('%a %b %e %H:%M:%S %z %Y')
366
- when 'Z' then @offset == 0 ? 'Z' : _strftime('z')
367
- when 'z' then "%+03d:%02d" % (@offset/60).divmod(60)
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)
368
390
  else super(v)
369
391
  end
370
392
  end
@@ -385,7 +407,7 @@ module ThirdBase
385
407
  end
386
408
 
387
409
  def strftime_default
388
- '%Y-%m-%dT%H:%M:%S%z'
410
+ '%Y-%m-%dT%H:%M:%S%Z'
389
411
  end
390
412
 
391
413
  def time_parts
@@ -30,6 +30,35 @@ describe ThirdBase::CompatClassMethods do
30
30
  Date._parse('2008-10-20').should == {:year=>2008, :mday=>20, :mon=>10}
31
31
  Date._parse('11:12:13').should == {:sec=>13, :hour=>11, :min=>12}
32
32
  end
33
+
34
+ it "#_parse should not contain fields in the hash that were guessed when a strptime-parser is used" do
35
+ DateTime.add_parser(:us, "%m-%d<>%H/%M")
36
+ Date._parse('10-11<>12/13').should == {:mon=>10, :mday=>11, :hour=>12, :min=>13}
37
+
38
+ DateTime.add_parser(:us, "%d")
39
+ Date._parse('11').should == {:mday=>11}
40
+
41
+ DateTime.add_parser(:us, "%m")
42
+ Date._parse('11').should == {:mon=>11}
43
+
44
+ DateTime.add_parser(:us, "%Y")
45
+ Date._parse('2009').should == {:year=>2009}
46
+
47
+ DateTime.add_parser(:us, "%H")
48
+ Date._parse('11').should == {:hour=>11}
49
+
50
+ DateTime.add_parser(:us, "%M")
51
+ Date._parse('11').should == {:min=>11}
52
+
53
+ DateTime.add_parser(:us, "%S")
54
+ Date._parse('20').should == {:sec=>20}
55
+
56
+ DateTime.add_parser(:us, "%Z")
57
+ Date._parse('UTC').should == {:zone=>'+00:00', :offset=>0}
58
+ Date._parse('CDT').should == {:zone=>'-05:00', :offset=>-18000}
59
+
60
+ DateTime.reset_parsers!
61
+ end
33
62
 
34
63
  it "#ajd_to_amjd should convert a astronomical julian date to a astronomical modified julian date" do
35
64
  Date.ajd_to_amjd(2400002).should == 1
@@ -82,6 +82,10 @@ describe :date_parse, :shared => true do
82
82
  Date.parse("november#{@sep}5th").should == Date.civil(Date.today.year, 11, 5)
83
83
  end
84
84
 
85
+ it "can parse a weekday, month, day, and year into a Date object" do
86
+ Date.parse("Mon Aug 10 2009").should == Date.civil(2009, 8, 10)
87
+ end
88
+
85
89
  it "can parse a month name, day and year into a Date object" do
86
90
  Date.parse("november#{@sep}5th#{@sep}2005").should == Date.civil(2005, 11, 5)
87
91
  end
@@ -209,12 +213,25 @@ describe "Date parser modifications" do
209
213
  Date.reset_parsers!
210
214
  end
211
215
 
212
- it "should be able to add a parser to an existing parser type that takes precedence" do
216
+ it "should raise an ArgumentError if it can't parse a date" do
213
217
  proc{Date.parse("today")}.should raise_error(ArgumentError)
218
+ end
219
+
220
+ it "should be able to add a parser to an existing parser type that takes precedence" do
214
221
  Date.add_parser(:iso, /today/){t = Time.now; {:civil=>[t.year, t.mon, t.day]}}
215
222
  Date.parse("today").should == Date.today
216
223
  end
217
224
 
225
+ it "should be able to handle parsers that return Date instances" do
226
+ Date.add_parser(:iso, /today/){t = Time.now; Date.new(t.year, t.mon, t.day)}
227
+ Date.parse("today").should == Date.today
228
+ end
229
+
230
+ it "should be able to specify a strptime format string for a parser" do
231
+ Date.add_parser(:iso, "%d<<%m<<%Y")
232
+ Date.parse("03<<02<<2001").should == Date.new(2001, 2, 3)
233
+ end
234
+
218
235
  it "should be able to add new parser types" do
219
236
  proc{Date.parse("today")}.should raise_error(ArgumentError)
220
237
  Date.add_parser_type(:mine)
@@ -22,7 +22,7 @@ describe "DateTime#parse" do
22
22
  DateTime.parse("12:02:03p").should == DateTime.civil(DateTime.today.year, DateTime.today.month, DateTime.today.day, 12, 2, 3)
23
23
  proc{DateTime.parse("13:02:03p")}.should raise_error(ArgumentError)
24
24
  proc{DateTime.parse("00:02:03p")}.should raise_error(ArgumentError)
25
- proc{DateTime.parse("00:02:03r")}.should raise_error(ArgumentError)
25
+ proc{DateTime.parse("00:02:03rsdf")}.should raise_error(ArgumentError)
26
26
  end
27
27
 
28
28
  it "should use the current time offset if no time offset is specified" do
@@ -34,6 +34,50 @@ describe "DateTime#parse" do
34
34
  DateTime.parse("01:02:03-01").should == DateTime.civil(DateTime.today.year, DateTime.today.month, DateTime.today.day, 1, 2, 3, 0, -3600)
35
35
  end
36
36
 
37
+ it "should parse the time zone abbreviations supported by ruby's Time class" do
38
+ DateTime.parse("01:02:03 UTC").offset.should == 0
39
+ DateTime.parse("01:02:03 UT").offset.should == 0
40
+ DateTime.parse("01:02:03 GMT").offset.should == 0
41
+ DateTime.parse("01:02:03 EST").offset.should == -5*3600
42
+ DateTime.parse("01:02:03 EDT").offset.should == -4*3600
43
+ DateTime.parse("01:02:03 CST").offset.should == -6*3600
44
+ DateTime.parse("01:02:03 CDT").offset.should == -5*3600
45
+ DateTime.parse("01:02:03 MST").offset.should == -7*3600
46
+ DateTime.parse("01:02:03 MDT").offset.should == -6*3600
47
+ DateTime.parse("01:02:03 PST").offset.should == -8*3600
48
+ DateTime.parse("01:02:03 PDT").offset.should == -7*3600
49
+ DateTime.parse("01:02:03 A").offset.should == 1*3600
50
+ DateTime.parse("01:02:03 B").offset.should == 2*3600
51
+ DateTime.parse("01:02:03 C").offset.should == 3*3600
52
+ DateTime.parse("01:02:03 D").offset.should == 4*3600
53
+ DateTime.parse("01:02:03 E").offset.should == 5*3600
54
+ DateTime.parse("01:02:03 F").offset.should == 6*3600
55
+ DateTime.parse("01:02:03 G").offset.should == 7*3600
56
+ DateTime.parse("01:02:03 H").offset.should == 8*3600
57
+ DateTime.parse("01:02:03 I").offset.should == 9*3600
58
+ DateTime.parse("01:02:03 K").offset.should == 10*3600
59
+ DateTime.parse("01:02:03 L").offset.should == 11*3600
60
+ DateTime.parse("01:02:03 M").offset.should == 12*3600
61
+ DateTime.parse("01:02:03 N").offset.should == -1*3600
62
+ DateTime.parse("01:02:03 O").offset.should == -2*3600
63
+ DateTime.parse("01:02:03 P").offset.should == -3*3600
64
+ DateTime.parse("01:02:03 Q").offset.should == -4*3600
65
+ DateTime.parse("01:02:03 R").offset.should == -5*3600
66
+ DateTime.parse("01:02:03 S").offset.should == -6*3600
67
+ DateTime.parse("01:02:03 T").offset.should == -7*3600
68
+ DateTime.parse("01:02:03 U").offset.should == -8*3600
69
+ DateTime.parse("01:02:03 V").offset.should == -9*3600
70
+ DateTime.parse("01:02:03 W").offset.should == -10*3600
71
+ DateTime.parse("01:02:03 X").offset.should == -11*3600
72
+ DateTime.parse("01:02:03 Y").offset.should == -12*3600
73
+ DateTime.parse("01:02:03 Z").offset.should == 0
74
+ end
75
+
76
+ it "should parse the time strings output by ruby's Time class" do
77
+ proc{DateTime.parse(Time.now.to_s)}.should_not raise_error
78
+ proc{DateTime.parse(Time.now.strftime('%+'))}.should_not raise_error
79
+ end
80
+
37
81
  it "can handle DD as month day number" do
38
82
  DateTime.parse("10").should == DateTime.civil(DateTime.today.year, DateTime.today.month, 10)
39
83
  DateTime.parse("10 01:02:03").should == DateTime.civil(DateTime.today.year, DateTime.today.month, 10, 1, 2, 3)
@@ -318,15 +362,35 @@ describe "DateTime parser modifications" do
318
362
  DateTime.reset_parsers!
319
363
  end
320
364
 
365
+ it "should raise an ArgumentError if it can't parse a date" do
366
+ proc{DateTime.parse("today")}.should raise_error(ArgumentError)
367
+ end
368
+
321
369
  it "should be able to add a parser to an existing parser type that takes precedence" do
322
370
  d = DateTime.now
323
- proc{DateTime.parse("today")}.should raise_error(ArgumentError)
324
371
  DateTime.add_parser(:iso, /\Anow\z/){{:civil=>[d.year, d.mon, d.day], :parts=>[d.hour, d.min, d.sec, d.usec], :offset=>d.offset}}
325
372
  DateTime.parse("now").should == d
326
373
  end
327
374
 
375
+ it "should be able to handle parsers that return Date instances" do
376
+ d = DateTime.now
377
+ DateTime.add_parser(:iso, /\Anow\z/){d}
378
+ DateTime.parse("now").should == d
379
+ end
380
+
381
+ it "should be able to specify a strptime format string for a parser" do
382
+ DateTime.add_parser(:iso, "%Z||%S>>%M>>%H||%d<<%m<<%Y")
383
+ DateTime.parse("UTC||06>>05>>04||03<<02<<2001").should == DateTime.new(2001,2,3,4,5,6)
384
+ end
385
+
386
+ it "should assume current seconds if just offset is given" do
387
+ DateTime.add_parser_type(:mine)
388
+ DateTime.use_parsers(:mine)
389
+ DateTime.add_parser(:mine, "%Z")
390
+ DateTime.parse("UTC").sec.should == DateTime.now.sec
391
+ end
392
+
328
393
  it "should be able to add new parser types" do
329
- proc{DateTime.parse("today")}.should raise_error(ArgumentError)
330
394
  DateTime.add_parser_type(:mine)
331
395
  d = DateTime.now
332
396
  DateTime.add_parser(:mine, /\Anow\z/){{:civil=>[d.year, d.mon, d.day], :parts=>[d.hour, d.min, d.sec, d.usec], :offset=>d.offset}}
@@ -52,17 +52,18 @@ describe "DateTime#strftime" do
52
52
  DateTime.civil(2008, 11, 12, 14, 3, 31, 0, -28800).strftime('%s').should == "1226527411"
53
53
  end
54
54
 
55
- it "should be able to print the time zone offset as a Z if the offset is zero" do
56
- DateTime.civil(2000, 4, 6, 10, 11, 12).strftime('%Z').should == "Z"
57
- DateTime.civil(2000, 4, 6, 10, 11, 12, 0, -43200).strftime('%Z').should == "-12:00"
58
- end
59
-
60
55
  it "should be able to print the time zone offset as a string of hours and minutes" do
61
- DateTime.civil(2000, 4, 6, 10, 11, 12).strftime('%z').should == "+00:00"
62
- DateTime.civil(2000, 4, 6, 10, 11, 12, 0, -43200).strftime('%z').should == "-12:00"
63
- DateTime.civil(2000, 4, 6, 10, 11, 12, 0, 43200).strftime('%z').should == "+12:00"
64
- DateTime.civil(2000, 4, 6, 10, 11, 12, 0, -3600).strftime('%z').should == "-01:00"
65
- DateTime.civil(2000, 4, 6, 10, 11, 12, 0, 3600).strftime('%z').should == "+01:00"
56
+ DateTime.civil(2000, 4, 6, 10, 11, 12).strftime('%z').should == "+0000"
57
+ DateTime.civil(2000, 4, 6, 10, 11, 12, 0, -43200).strftime('%z').should == "-1200"
58
+ DateTime.civil(2000, 4, 6, 10, 11, 12, 0, 43200).strftime('%z').should == "+1200"
59
+ DateTime.civil(2000, 4, 6, 10, 11, 12, 0, -3600).strftime('%z').should == "-0100"
60
+ DateTime.civil(2000, 4, 6, 10, 11, 12, 0, 3600).strftime('%z').should == "+0100"
61
+
62
+ DateTime.civil(2000, 4, 6, 10, 11, 12).strftime('%Z').should == "+00:00"
63
+ DateTime.civil(2000, 4, 6, 10, 11, 12, 0, -43200).strftime('%Z').should == "-12:00"
64
+ DateTime.civil(2000, 4, 6, 10, 11, 12, 0, 43200).strftime('%Z').should == "+12:00"
65
+ DateTime.civil(2000, 4, 6, 10, 11, 12, 0, -3600).strftime('%Z').should == "-01:00"
66
+ DateTime.civil(2000, 4, 6, 10, 11, 12, 0, 3600).strftime('%Z').should == "+01:00"
66
67
  end
67
68
 
68
69
  ############################
@@ -97,6 +98,6 @@ describe "DateTime#strftime" do
97
98
  it "should be able to print the common date and timezone" do
98
99
  DateTime.civil(2000, 4, 6, 10, 11, 12).strftime("%+").should == "Thu Apr 6 10:11:12 +00:00 2000"
99
100
  DateTime.civil(2000, 4, 6, 10, 11, 12, 0, 43200).strftime("%+").should == "Thu Apr 6 10:11:12 +12:00 2000"
100
- DateTime.civil(2000, 4, 6, 10, 11, 12).strftime("%+").should == DateTime.civil(2000, 4, 6, 10, 11, 12).strftime('%a %b %e %H:%M:%S %z %Y')
101
+ DateTime.civil(2000, 4, 6, 10, 11, 12).strftime("%+").should == DateTime.civil(2000, 4, 6, 10, 11, 12).strftime('%a %b %e %H:%M:%S %Z %Y')
101
102
  end
102
103
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: third_base
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-07-09 00:00:00 -07:00
12
+ date: 2009-08-17 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15