timeliness 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +0 -0
- data/LICENSE +20 -0
- data/README.rdoc +242 -0
- data/Rakefile +64 -0
- data/lib/timeliness.rb +35 -0
- data/lib/timeliness/format_set.rb +95 -0
- data/lib/timeliness/formats.rb +221 -0
- data/lib/timeliness/helpers.rb +49 -0
- data/lib/timeliness/parser.rb +94 -0
- data/lib/timeliness/version.rb +3 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/timeliness/format_set_spec.rb +106 -0
- data/spec/timeliness/formats_spec.rb +93 -0
- data/spec/timeliness/parser_spec.rb +354 -0
- data/timeliness.gemspec +29 -0
- metadata +82 -0
@@ -0,0 +1,221 @@
|
|
1
|
+
module Timeliness
|
2
|
+
module Formats
|
3
|
+
|
4
|
+
# Format tokens:
|
5
|
+
# y = year
|
6
|
+
# m = month
|
7
|
+
# d = day
|
8
|
+
# h = hour
|
9
|
+
# n = minute
|
10
|
+
# s = second
|
11
|
+
# u = micro-seconds
|
12
|
+
# ampm = meridian (am or pm) with or without dots (e.g. am, a.m, or a.m.)
|
13
|
+
# _ = optional space
|
14
|
+
# tz = Timezone abbreviation (e.g. UTC, GMT, PST, EST)
|
15
|
+
# zo = Timezone offset (e.g. +10:00, -08:00, +1000)
|
16
|
+
#
|
17
|
+
# All other characters are considered literal. You can embed regexp in the
|
18
|
+
# format but no guarantees that it will remain intact. If you don't use capture
|
19
|
+
# groups, dots or backslashes in the regexp, it may well work as expected.
|
20
|
+
# For special characters, use POSIX character classes for safety.
|
21
|
+
#
|
22
|
+
# Repeating tokens:
|
23
|
+
# x = 1 or 2 digits for unit (e.g. 'h' means an hour can be '9' or '09')
|
24
|
+
# xx = 2 digits exactly for unit (e.g. 'hh' means an hour can only be '09')
|
25
|
+
#
|
26
|
+
# Special Cases:
|
27
|
+
# yy = 2 or 4 digit year
|
28
|
+
# yyyy = exactly 4 digit year
|
29
|
+
# mmm = month long name (e.g. 'Jul' or 'July')
|
30
|
+
# ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday)
|
31
|
+
# u = microseconds matches 1 to 6 digits
|
32
|
+
|
33
|
+
@time_formats = [
|
34
|
+
'hh:nn:ss',
|
35
|
+
'hh-nn-ss',
|
36
|
+
'h:nn',
|
37
|
+
'h.nn',
|
38
|
+
'h nn',
|
39
|
+
'h-nn',
|
40
|
+
'h:nn_ampm',
|
41
|
+
'h.nn_ampm',
|
42
|
+
'h nn_ampm',
|
43
|
+
'h-nn_ampm',
|
44
|
+
'h_ampm'
|
45
|
+
]
|
46
|
+
|
47
|
+
@date_formats = [
|
48
|
+
'yyyy-mm-dd',
|
49
|
+
'yyyy/mm/dd',
|
50
|
+
'yyyy.mm.dd',
|
51
|
+
'm/d/yy',
|
52
|
+
'd/m/yy',
|
53
|
+
'm\d\yy',
|
54
|
+
'd\m\yy',
|
55
|
+
'd-m-yy',
|
56
|
+
'dd-mm-yyyy',
|
57
|
+
'd.m.yy',
|
58
|
+
'd mmm yy'
|
59
|
+
]
|
60
|
+
|
61
|
+
@datetime_formats = [
|
62
|
+
'yyyy-mm-dd hh:nn:ss.u',
|
63
|
+
'yyyy-mm-dd hh:nn:ss',
|
64
|
+
'yyyy-mm-dd h:nn',
|
65
|
+
'yyyy-mm-dd h:nn_ampm',
|
66
|
+
'm/d/yy h:nn:ss',
|
67
|
+
'm/d/yy h:nn_ampm',
|
68
|
+
'm/d/yy h:nn',
|
69
|
+
'd/m/yy hh:nn:ss',
|
70
|
+
'd/m/yy h:nn_ampm',
|
71
|
+
'd/m/yy h:nn',
|
72
|
+
'dd-mm-yyyy hh:nn:ss',
|
73
|
+
'dd-mm-yyyy h:nn_ampm',
|
74
|
+
'dd-mm-yyyy h:nn',
|
75
|
+
'ddd, dd mmm yyyy hh:nn:ss tz', # RFC 822
|
76
|
+
'ddd, dd mmm yyyy hh:nn:ss zo', # RFC 822
|
77
|
+
'ddd mmm d hh:nn:ss zo yyyy', # Ruby time string
|
78
|
+
'yyyy-mm-ddThh:nn:ssZ', # ISO 8601 without zone offset
|
79
|
+
'yyyy-mm-ddThh:nn:sszo' # ISO 8601 with zone offset
|
80
|
+
]
|
81
|
+
|
82
|
+
# All tokens available for format construction. The token array is made of
|
83
|
+
# regexp and key for format component mapping, if any.
|
84
|
+
#
|
85
|
+
@format_tokens = {
|
86
|
+
'ddd' => [ '\w{3,9}' ],
|
87
|
+
'dd' => [ '\d{2}', :day ],
|
88
|
+
'd' => [ '\d{1,2}', :day ],
|
89
|
+
'mmm' => [ '\w{3,9}', :month ],
|
90
|
+
'mm' => [ '\d{2}', :month ],
|
91
|
+
'm' => [ '\d{1,2}', :month ],
|
92
|
+
'yyyy' => [ '\d{4}', :year ],
|
93
|
+
'yy' => [ '\d{4}|\d{2}', :year ],
|
94
|
+
'hh' => [ '\d{2}', :hour ],
|
95
|
+
'h' => [ '\d{1,2}', :hour ],
|
96
|
+
'nn' => [ '\d{2}', :min ],
|
97
|
+
'n' => [ '\d{1,2}', :min ],
|
98
|
+
'ss' => [ '\d{2}', :sec ],
|
99
|
+
's' => [ '\d{1,2}', :sec ],
|
100
|
+
'u' => [ '\d{1,6}', :usec ],
|
101
|
+
'ampm' => [ '[aApP]\.?[mM]\.?', :meridian ],
|
102
|
+
'zo' => [ '[+-]\d{2}:?\d{2}', :offset ],
|
103
|
+
'tz' => [ '[A-Z]{1,4}' ],
|
104
|
+
'_' => [ '\s?' ]
|
105
|
+
}
|
106
|
+
|
107
|
+
# Component argument values will be passed to the format method if matched in
|
108
|
+
# the time string. The key should match the key defined in the format tokens.
|
109
|
+
#
|
110
|
+
# The array consists of the position the value should be inserted in
|
111
|
+
# the time array, and the code to place in the time array.
|
112
|
+
#
|
113
|
+
# If the position is nil, then the value won't be put in the time array. If the
|
114
|
+
# code is nil, then just the raw value is used.
|
115
|
+
#
|
116
|
+
@format_components = {
|
117
|
+
:year => [ 0, 'unambiguous_year(year)'],
|
118
|
+
:month => [ 1, 'month_index(month)'],
|
119
|
+
:day => [ 2 ],
|
120
|
+
:hour => [ 3, 'full_hour(hour, meridian ||= nil)'],
|
121
|
+
:min => [ 4 ],
|
122
|
+
:sec => [ 5 ],
|
123
|
+
:usec => [ 6, 'microseconds(usec)'],
|
124
|
+
:offset => [ 7, 'offset_in_seconds(offset)'],
|
125
|
+
:meridian => [ nil ]
|
126
|
+
}
|
127
|
+
|
128
|
+
US_FORMAT_REGEXP = /\Am{1,2}[^m]/
|
129
|
+
|
130
|
+
class << self
|
131
|
+
attr_accessor :time_formats, :date_formats, :datetime_formats, :format_tokens, :format_components
|
132
|
+
attr_reader :date_format_set, :time_format_set, :datetime_format_set
|
133
|
+
|
134
|
+
# Adds new formats. Must specify format type and can specify a :before
|
135
|
+
# option to nominate which format the new formats should be inserted in
|
136
|
+
# front on to take higher precedence.
|
137
|
+
#
|
138
|
+
# Error is raised if format already exists or if :before format is not found.
|
139
|
+
#
|
140
|
+
def add_formats(type, *add_formats)
|
141
|
+
formats = send("#{type}_formats")
|
142
|
+
options = add_formats.last.is_a?(Hash) ? add_formats.pop : {}
|
143
|
+
before = options[:before]
|
144
|
+
raise "Format for :before option #{format} was not found." if before && !formats.include?(before)
|
145
|
+
|
146
|
+
add_formats.each do |format|
|
147
|
+
raise "Format #{format} is already included in #{type} formats" if formats.include?(format)
|
148
|
+
|
149
|
+
index = before ? formats.index(before) : -1
|
150
|
+
formats.insert(index, format)
|
151
|
+
end
|
152
|
+
compile_formats
|
153
|
+
end
|
154
|
+
|
155
|
+
# Delete formats of specified type. Error raised if format not found.
|
156
|
+
#
|
157
|
+
def remove_formats(type, *remove_formats)
|
158
|
+
remove_formats.each do |format|
|
159
|
+
unless send("#{type}_formats").delete(format)
|
160
|
+
raise "Format #{format} not found in #{type} formats"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
compile_formats
|
164
|
+
end
|
165
|
+
|
166
|
+
# Removes US date formats so that ambigious dates are parsed as European format
|
167
|
+
#
|
168
|
+
def use_euro_formats
|
169
|
+
@date_format_set = FormatSet.compile(date_formats.select { |format| US_FORMAT_REGEXP !~ format })
|
170
|
+
@datetime_format_set = FormatSet.compile(datetime_formats.select { |format| US_FORMAT_REGEXP !~ format })
|
171
|
+
end
|
172
|
+
|
173
|
+
# Restores default to parse ambiguous dates as US format
|
174
|
+
#
|
175
|
+
def use_us_formats
|
176
|
+
@date_format_set = FormatSet.compile(date_formats)
|
177
|
+
@datetime_format_set = FormatSet.compile(datetime_formats)
|
178
|
+
end
|
179
|
+
|
180
|
+
def compile_formats
|
181
|
+
@sorted_token_keys = nil
|
182
|
+
@time_format_set = FormatSet.compile(time_formats)
|
183
|
+
@date_format_set = FormatSet.compile(date_formats)
|
184
|
+
@datetime_format_set = FormatSet.compile(datetime_formats)
|
185
|
+
end
|
186
|
+
|
187
|
+
def sorted_token_keys
|
188
|
+
@sorted_token_keys ||= format_tokens.keys.sort {|a,b| a.size <=> b.size }.reverse
|
189
|
+
end
|
190
|
+
|
191
|
+
# Returns format for type and other possible matching format set based on type
|
192
|
+
# and value length. Gives minor speed-up by checking string length.
|
193
|
+
def format_set(type, string)
|
194
|
+
case type
|
195
|
+
when :date
|
196
|
+
[ @date_format_set, @datetime_format_set ]
|
197
|
+
when :time
|
198
|
+
if string.length < 11
|
199
|
+
[ @time_format_set ]
|
200
|
+
else
|
201
|
+
[ @datetime_format_set, @time_format_set ]
|
202
|
+
end
|
203
|
+
when :datetime
|
204
|
+
if string.length < 11
|
205
|
+
[ @date_format_set, @datetime_format_set ]
|
206
|
+
else
|
207
|
+
[ @datetime_format_set, @date_format_set ]
|
208
|
+
end
|
209
|
+
else
|
210
|
+
if string.length < 11
|
211
|
+
[ @date_format_set, @time_format_set, @datetime_format_set ]
|
212
|
+
else
|
213
|
+
[ @datetime_format_set, @date_format_set, @time_format_set ]
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Timeliness
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
def full_hour(hour, meridian)
|
5
|
+
hour = hour.to_i
|
6
|
+
return hour if meridian.nil?
|
7
|
+
if meridian.delete('.').downcase == 'am'
|
8
|
+
raise if hour == 0 || hour > 12
|
9
|
+
hour == 12 ? 0 : hour
|
10
|
+
else
|
11
|
+
hour == 12 ? hour : hour + 12
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def unambiguous_year(year)
|
16
|
+
if year.length <= 2
|
17
|
+
century = Time.now.year.to_s[0..1].to_i
|
18
|
+
century -= 1 if year.to_i >= Timeliness.ambiguous_year_threshold
|
19
|
+
year = "#{century}#{year.rjust(2,'0')}"
|
20
|
+
end
|
21
|
+
year.to_i
|
22
|
+
end
|
23
|
+
|
24
|
+
def month_index(month)
|
25
|
+
return month.to_i if month.to_i > 0
|
26
|
+
month.length > 3 ? month_names.index(month.capitalize) : abbr_month_names.index(month.capitalize)
|
27
|
+
end
|
28
|
+
|
29
|
+
def month_names
|
30
|
+
defined?(I18n) ? I18n.t('date.month_names') : Date::MONTHNAMES
|
31
|
+
end
|
32
|
+
|
33
|
+
def abbr_month_names
|
34
|
+
defined?(I18n) ? I18n.t('date.abbr_month_names') : Date::ABBR_MONTHNAMES
|
35
|
+
end
|
36
|
+
|
37
|
+
def microseconds(usec)
|
38
|
+
(".#{usec}".to_f * 1_000_000).to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
def offset_in_seconds(offset)
|
42
|
+
sign = offset =~ /^-/ ? -1 : 1
|
43
|
+
parts = offset.scan(/\d\d/).map {|p| p.to_f }
|
44
|
+
parts[1] = parts[1].to_f / 60
|
45
|
+
(parts[0] + parts[1]) * sign * 3600
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Timeliness
|
2
|
+
module Parser
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def parse(value, *args)
|
7
|
+
return value unless value.is_a?(String)
|
8
|
+
|
9
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
10
|
+
type = args.first
|
11
|
+
|
12
|
+
time_array = _parse(value, type, options)
|
13
|
+
return nil if time_array.nil?
|
14
|
+
|
15
|
+
if type == :date
|
16
|
+
time_array[3..7] = nil
|
17
|
+
elsif type == :time
|
18
|
+
time_array[0..2] = current_date(options)
|
19
|
+
elsif type.nil?
|
20
|
+
dummy_date = current_date(options)
|
21
|
+
time_array[0] ||= dummy_date[0]
|
22
|
+
time_array[1] ||= dummy_date[1]
|
23
|
+
time_array[2] ||= dummy_date[2]
|
24
|
+
end
|
25
|
+
make_time(time_array[0..6], options[:zone])
|
26
|
+
end
|
27
|
+
|
28
|
+
def make_time(time_array, zone=nil)
|
29
|
+
return nil unless fast_date_valid_with_fallback(*time_array[0..2])
|
30
|
+
|
31
|
+
zone ||= Timeliness.default_timezone
|
32
|
+
case zone
|
33
|
+
when :utc, :local
|
34
|
+
time_with_datetime_fallback(zone, *time_array.compact)
|
35
|
+
when :current
|
36
|
+
Time.zone.local(*time_array)
|
37
|
+
else
|
38
|
+
Time.use_zone(zone) { Time.zone.local(*time_array) }
|
39
|
+
end
|
40
|
+
rescue ArgumentError, TypeError
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def _parse(string, type=nil, options={})
|
45
|
+
if options[:strict] && type
|
46
|
+
set = Formats.send("#{type}_format_set")
|
47
|
+
set.match(string, options[:format])
|
48
|
+
else
|
49
|
+
values = nil
|
50
|
+
Formats.format_set(type, string).find {|set| values = set.match(string, options[:format]) }
|
51
|
+
values
|
52
|
+
end
|
53
|
+
rescue
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def current_date(options)
|
60
|
+
now = if options[:now]
|
61
|
+
options[:now]
|
62
|
+
elsif options[:zone]
|
63
|
+
case options[:zone]
|
64
|
+
when :utc, :local
|
65
|
+
Time.now.send("get#{options[:zone]}")
|
66
|
+
when :current
|
67
|
+
Time.current
|
68
|
+
else
|
69
|
+
Time.use_zone(options[:zone]) { Time.current }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
now ||= Timeliness.date_for_time_type
|
73
|
+
now.is_a?(Array) ? now[0..2] : Array(now).reverse[4..6]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Taken from ActiveSupport and simplified
|
77
|
+
def time_with_datetime_fallback(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0, usec=0)
|
78
|
+
return nil if hour > 23 || min > 59 || sec > 59
|
79
|
+
::Time.send(utc_or_local, year, month, day, hour, min, sec, usec)
|
80
|
+
rescue
|
81
|
+
offset = utc_or_local == :local ? (::Time.local(2007).utc_offset.to_r/86400) : 0
|
82
|
+
::DateTime.civil(year, month, day, hour, min, sec, offset)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Enforce strict date part validity which the Time class does not.
|
86
|
+
# Only does full date check if month and day are possibly invalid.
|
87
|
+
def fast_date_valid_with_fallback(year, month, day)
|
88
|
+
month < 13 && (day < 29 || Date.valid_civil?(year, month, day))
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
require 'active_support/time'
|
4
|
+
require 'timecop'
|
5
|
+
require 'timeliness'
|
6
|
+
|
7
|
+
module TimelinessHelpers
|
8
|
+
def parser
|
9
|
+
Timeliness::Parser
|
10
|
+
end
|
11
|
+
|
12
|
+
def formats
|
13
|
+
Timeliness::Formats
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse(*args)
|
17
|
+
Timeliness::Parser.parse(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def current_date(options={})
|
21
|
+
Timeliness::Parser.send(:current_date, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def should_parse(*args)
|
25
|
+
Timeliness::Parser.parse(*args).should_not be_nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def should_not_parse(*args)
|
29
|
+
Timeliness::Parser.parse(*args).should be_nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Rspec.configure do |c|
|
34
|
+
c.mock_with :rspec
|
35
|
+
c.include TimelinessHelpers
|
36
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Timeliness::FormatSet do
|
4
|
+
context ".define_format_method" do
|
5
|
+
it "should define method which outputs date array with values in correct order" do
|
6
|
+
define_method_for('yyyy-mm-dd').call('2000', '1', '2').should == [2000,1,2,nil,nil,nil,nil]
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should define method which outputs date array from format with different order" do
|
10
|
+
define_method_for('dd/mm/yyyy').call('2', '1', '2000').should == [2000,1,2,nil,nil,nil,nil]
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should define method which outputs time array" do
|
14
|
+
define_method_for('hh:nn:ss').call('01', '02', '03').should == [nil,nil,nil,1,2,3,nil]
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should define method which outputs time array with meridian 'pm' adjusted hour" do
|
18
|
+
define_method_for('hh:nn:ss ampm').call('01', '02', '03', 'pm').should == [nil,nil,nil,13,2,3,nil]
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should define method which outputs time array with meridian 'am' unadjusted hour" do
|
22
|
+
define_method_for('hh:nn:ss ampm').call('01', '02', '03', 'am').should == [nil,nil,nil,1,2,3,nil]
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should define method which outputs time array with microseconds" do
|
26
|
+
define_method_for('hh:nn:ss.u').call('01', '02', '03', '99').should == [nil,nil,nil,1,2,3,990000]
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should define method which outputs datetime array with zone offset" do
|
30
|
+
define_method_for('yyyy-mm-dd hh:nn:ss.u zo').call('2001', '02', '03', '04', '05', '06', '99', '+10:00').should == [2001,2,3,4,5,6,990000,36000]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "compiled regexp" do
|
35
|
+
|
36
|
+
context "for time formats" do
|
37
|
+
format_tests = {
|
38
|
+
'hh:nn:ss' => {:pass => ['12:12:12', '01:01:01'], :fail => ['1:12:12', '12:1:12', '12:12:1', '12-12-12']},
|
39
|
+
'hh-nn-ss' => {:pass => ['12-12-12', '01-01-01'], :fail => ['1-12-12', '12-1-12', '12-12-1', '12:12:12']},
|
40
|
+
'h:nn' => {:pass => ['12:12', '1:01'], :fail => ['12:2', '12-12']},
|
41
|
+
'h.nn' => {:pass => ['2.12', '12.12'], :fail => ['2.1', '12:12']},
|
42
|
+
'h nn' => {:pass => ['2 12', '12 12'], :fail => ['2 1', '2.12', '12:12']},
|
43
|
+
'h-nn' => {:pass => ['2-12', '12-12'], :fail => ['2-1', '2.12', '12:12']},
|
44
|
+
'h:nn_ampm' => {:pass => ['2:12am', '2:12 pm', '2:12 AM', '2:12PM'], :fail => ['1:2am', '1:12 pm', '2.12am']},
|
45
|
+
'h.nn_ampm' => {:pass => ['2.12am', '2.12 pm'], :fail => ['1:2am', '1:12 pm', '2:12am']},
|
46
|
+
'h nn_ampm' => {:pass => ['2 12am', '2 12 pm'], :fail => ['1 2am', '1 12 pm', '2:12am']},
|
47
|
+
'h-nn_ampm' => {:pass => ['2-12am', '2-12 pm'], :fail => ['1-2am', '1-12 pm', '2:12am']},
|
48
|
+
'h_ampm' => {:pass => ['2am', '2 am', '12 pm'], :fail => ['1.am', '12 pm', '2:12am']},
|
49
|
+
}
|
50
|
+
format_tests.each do |format, values|
|
51
|
+
it "should correctly match times in format '#{format}'" do
|
52
|
+
regexp = compile_regexp(format)
|
53
|
+
values[:pass].each {|value| value.should match(regexp)}
|
54
|
+
values[:fail].each {|value| value.should_not match(regexp)}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "for date formats" do
|
60
|
+
format_tests = {
|
61
|
+
'yyyy/mm/dd' => {:pass => ['2000/02/01'], :fail => ['2000\02\01', '2000/2/1', '00/02/01']},
|
62
|
+
'yyyy-mm-dd' => {:pass => ['2000-02-01'], :fail => ['2000\02\01', '2000-2-1', '00-02-01']},
|
63
|
+
'yyyy.mm.dd' => {:pass => ['2000.02.01'], :fail => ['2000\02\01', '2000.2.1', '00.02.01']},
|
64
|
+
'm/d/yy' => {:pass => ['2/1/01', '02/01/00', '02/01/2000'], :fail => ['2/1/0', '2.1.01']},
|
65
|
+
'd/m/yy' => {:pass => ['1/2/01', '01/02/00', '01/02/2000'], :fail => ['1/2/0', '1.2.01']},
|
66
|
+
'm\d\yy' => {:pass => ['2\1\01', '2\01\00', '02\01\2000'], :fail => ['2\1\0', '2/1/01']},
|
67
|
+
'd\m\yy' => {:pass => ['1\2\01', '1\02\00', '01\02\2000'], :fail => ['1\2\0', '1/2/01']},
|
68
|
+
'd-m-yy' => {:pass => ['1-2-01', '1-02-00', '01-02-2000'], :fail => ['1-2-0', '1/2/01']},
|
69
|
+
'd.m.yy' => {:pass => ['1.2.01', '1.02.00', '01.02.2000'], :fail => ['1.2.0', '1/2/01']},
|
70
|
+
'd mmm yy' => {:pass => ['1 Feb 00', '1 Feb 2000', '1 February 00', '01 February 2000'],
|
71
|
+
:fail => ['1 Fe 00', 'Feb 1 2000', '1 Feb 0']}
|
72
|
+
}
|
73
|
+
format_tests.each do |format, values|
|
74
|
+
it "should correctly match dates in format '#{format}'" do
|
75
|
+
regexp = compile_regexp(format)
|
76
|
+
values[:pass].each {|value| value.should match(regexp)}
|
77
|
+
values[:fail].each {|value| value.should_not match(regexp)}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "for datetime formats" do
|
83
|
+
format_tests = {
|
84
|
+
'ddd mmm d hh:nn:ss zo yyyy' => {:pass => ['Sat Jul 19 12:00:00 +1000 2008'], :fail => []},
|
85
|
+
'yyyy-mm-ddThh:nn:ss(?:Z|zo)' => {:pass => ['2008-07-19T12:00:00+10:00', '2008-07-19T12:00:00Z'], :fail => ['2008-07-19T12:00:00Z+10:00']},
|
86
|
+
}
|
87
|
+
format_tests.each do |format, values|
|
88
|
+
it "should correctly match datetimes in format '#{format}'" do
|
89
|
+
regexp = compile_regexp(format)
|
90
|
+
values[:pass].each {|value| value.should match(regexp)}
|
91
|
+
values[:fail].each {|value| value.should_not match(regexp)}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
def define_method_for(format)
|
99
|
+
Timeliness::FormatSet.compile([format]).method(:"format_#{format}")
|
100
|
+
end
|
101
|
+
|
102
|
+
def compile_regexp(format)
|
103
|
+
Timeliness::FormatSet.compile([format]).regexp
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|