stamp 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- stamp (0.1.5)
4
+ stamp (0.1.6)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
data/README.md CHANGED
@@ -20,13 +20,13 @@ and weekday parts you'd like, and your date will be formatted accordingly:
20
20
 
21
21
  ```ruby
22
22
  date = Date.new(2011, 6, 9)
23
- date.stamp("March 1, 1999") # "June 9, 2011"
24
- date.stamp("Jan 1, 1999") # "Jun 9, 2011"
25
- date.stamp("Jan 01") # "Jun 09"
26
- date.stamp("Sunday, May 1, 2000") # "Thursday, June 9, 2011"
27
- date.stamp("Sun Aug 5") # "Thu Jun 9"
28
- date.stamp("12/31/99") # "06/09/11"
29
- date.stamp("DOB: 12/31/2000") # "DOB: 06/09/2011"
23
+ date.stamp("March 1, 1999") #=> "June 9, 2011"
24
+ date.stamp("Jan 1, 1999") #=> "Jun 9, 2011"
25
+ date.stamp("Jan 01") #=> "Jun 09"
26
+ date.stamp("Sunday, May 1, 2000") #=> "Thursday, June 9, 2011"
27
+ date.stamp("Sun Aug 5") #=> "Thu Jun 9"
28
+ date.stamp("12/31/99") #=> "06/09/11"
29
+ date.stamp("DOB: 12/31/2000") #=> "DOB: 06/09/2011"
30
30
  ```
31
31
 
32
32
  ### Times
@@ -36,11 +36,11 @@ hours, minutes, and seconds when it sees colon-separated values:
36
36
 
37
37
  ```ruby
38
38
  time = Time.utc(2011, 6, 9, 20, 52, 30)
39
- time.stamp("3:00 AM") # " 8:52 PM"
40
- time.stamp("01:00:00 AM") # "08:52:30 PM"
41
- time.stamp("23:59") # "20:52"
42
- time.stamp("23:59:59") # "20:52:30"
43
- time.stamp("Jan 1 at 01:00 AM") # "Jun 9 at 08:52 PM"
39
+ time.stamp("3:00 AM") #=> " 8:52 PM"
40
+ time.stamp("01:00:00 AM") #=> "08:52:30 PM"
41
+ time.stamp("23:59") #=> "20:52"
42
+ time.stamp("23:59:59") #=> "20:52:30"
43
+ time.stamp("Jan 1 at 01:00 AM") #=> "Jun 9 at 08:52 PM"
44
44
  ```
45
45
 
46
46
  ## Features
@@ -71,6 +71,24 @@ the case, the following aliases are available:
71
71
  * `stamp_like`
72
72
  * `format_like`
73
73
 
74
+ ### Rails Integration
75
+
76
+ Stamp makes it easy to configure your application's common date and time
77
+ formats in a more self-documenting way with the `strftime_format` method:
78
+
79
+ ```ruby
80
+ # config/initializers/time_formats.rb
81
+ Date::DATE_FORMATS[:short] = Stamp.strftime_format("Mon Jan 1")
82
+ Time::DATE_FORMATS[:military] = Stamp.strftime_format("23:59")
83
+ ```
84
+
85
+ To use your formats:
86
+
87
+ ```ruby
88
+ Date.today.to_s_(:short) #=> "Sat Jul 16"
89
+ Time.now.to_s(:military) #=> "15:35"
90
+ ```
91
+
74
92
  ### Limitations
75
93
 
76
94
  * Time zone support hasn't been implemented. Patches welcome!
@@ -83,7 +101,7 @@ If you need more obscure formatting options, you can include any valid
83
101
  just be passed along:
84
102
 
85
103
  ```ruby
86
- Date.today.stamp("Week #%U, 1999") # "Week #23, 2011"
104
+ Date.today.stamp("Week #%U, 1999") #=> "Week #23, 2011"
87
105
  ````
88
106
 
89
107
  Check out [http://strfti.me](http://strfti.me) for more ideas.
data/Rakefile CHANGED
@@ -3,4 +3,8 @@ require 'cucumber/rake/task'
3
3
 
4
4
  Cucumber::Rake::Task.new(:features)
5
5
 
6
+ Cucumber::Rake::Task.new('features:wip', 'Run Cucumber features that are a work in progress') do |t|
7
+ t.profile = 'wip'
8
+ end
9
+
6
10
  task :default => :features
data/cucumber.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  ---
2
2
  default: --tags ~@wip --strict
3
- wip: --tags @wip --strict
3
+ wip: --tags @wip --wip
data/lib/stamp.rb CHANGED
@@ -1,42 +1,24 @@
1
- require "stamp/version"
2
1
  require "date"
3
2
  require "time"
4
3
 
5
- module Stamp
6
- MONTHNAMES_REGEXP = /^(#{Date::MONTHNAMES.compact.join('|')})$/i
7
- ABBR_MONTHNAMES_REGEXP = /^(#{Date::ABBR_MONTHNAMES.compact.join('|')})$/i
8
- DAYNAMES_REGEXP = /^(#{Date::DAYNAMES.join('|')})$/i
9
- ABBR_DAYNAMES_REGEXP = /^(#{Date::ABBR_DAYNAMES.join('|')})$/i
10
-
11
- ONE_DIGIT_REGEXP = /^\d{1}$/
12
- TWO_DIGIT_REGEXP = /^\d{2}$/
13
- FOUR_DIGIT_REGEXP = /^\d{4}$/
14
-
15
- TIME_REGEXP = /(\d{1,2})(:)(\d{2})(\s*)(:)?(\d{2})?(\s*)?([ap]m)?/i
16
-
17
- MERIDIAN_LOWER_REGEXP = /^(a|p)m$/
18
- MERIDIAN_UPPER_REGEXP = /^(A|P)M$/
19
-
20
- # Disambiguate based on value
21
- OBVIOUS_YEARS = 60..99
22
- OBVIOUS_MONTHS = 12
23
- OBVIOUS_DAYS = 28..31
24
- OBVIOUS_24_HOUR = 13..23
4
+ require "stamp/translator"
5
+ require "stamp/version"
25
6
 
26
- TWO_DIGIT_DATE_SUCCESSION = {
27
- '%m' => '%d',
28
- '%b' => '%d',
29
- '%B' => '%d',
30
- '%d' => '%y',
31
- '%e' => '%y'
32
- }
7
+ module Stamp
33
8
 
34
- TWO_DIGIT_TIME_SUCCESSION = {
35
- '%H' => '%M',
36
- '%I' => '%M',
37
- '%l' => '%M',
38
- '%M' => '%S'
39
- }
9
+ # Transforms the given example dates/time format to a format string
10
+ # suitable for strftime.
11
+ #
12
+ # @param [String] example a human-friendly date/time example
13
+ # @param [#strftime] the Date or Time to be formatted. Optional, but may
14
+ # be used to support certain edge cases
15
+ # @return [String] a strftime-friendly format
16
+ #
17
+ # @example
18
+ # Stamp.strftime_format("Jan 1, 1999") #=> "%b %e, %Y"
19
+ def self.strftime_format(example, target=nil)
20
+ Stamp::StrftimeTranslator.new(target).translate(example)
21
+ end
40
22
 
41
23
  # Formats a date/time using a human-friendly example as a template.
42
24
  #
@@ -51,7 +33,7 @@ module Stamp
51
33
  alias :stamp_like :stamp
52
34
  alias :format_like :stamp
53
35
 
54
- # Transforms the given string with example dates/times to a format string
36
+ # Transforms the given example date/time format to a format string
55
37
  # suitable for strftime.
56
38
  #
57
39
  # @param [String] example a human-friendly date/time example
@@ -60,104 +42,12 @@ module Stamp
60
42
  # @example
61
43
  # Date.today.strftime_format("Jan 1, 1999") #=> "%b %e, %Y"
62
44
  def strftime_format(example)
63
- # extract any substrings that look like times, like "23:59" or "8:37 am"
64
- before, time_example, after = example.partition(TIME_REGEXP)
65
-
66
- # transform any date tokens to strftime directives
67
- words = strftime_directives(before.split(/\b/)) do |token, previous_directive|
68
- strftime_date_directive(token, previous_directive)
69
- end
70
-
71
- # transform the example time string to strftime directives
72
- unless time_example.empty?
73
- time_parts = time_example.scan(TIME_REGEXP).first
74
- words += strftime_directives(time_parts) do |token, previous_directive|
75
- strftime_time_directive(token, previous_directive)
76
- end
77
- end
78
-
79
- # recursively process any remaining text
80
- words << strftime_format(after) unless after.empty?
81
- words.join
82
- end
83
-
84
- private
85
-
86
- # Transforms tokens that look like date/time parts to strftime directives.
87
- def strftime_directives(tokens)
88
- previous_directive = nil
89
- tokens.map do |token|
90
- directive = yield(token, previous_directive)
91
- previous_directive = directive unless directive.nil?
92
- directive || token
93
- end
94
- end
95
-
96
- def strftime_time_directive(token, previous_directive)
97
- case token
98
- when MERIDIAN_LOWER_REGEXP
99
- if RUBY_VERSION =~ /^1.8/ && self.is_a?(Time)
100
- # 1.8.7 doesn't implement %P
101
- self.strftime("%p").downcase
102
- else
103
- '%P'
104
- end
105
-
106
- when MERIDIAN_UPPER_REGEXP
107
- '%p'
108
-
109
- when TWO_DIGIT_REGEXP
110
- TWO_DIGIT_TIME_SUCCESSION[previous_directive] ||
111
- case token.to_i
112
- when OBVIOUS_24_HOUR
113
- '%H' # 24-hour clock
114
- else
115
- '%I' # 12-hour clock with leading zero
116
- end
117
-
118
- when ONE_DIGIT_REGEXP
119
- '%l' # hour without leading zero
120
- end
45
+ # delegate to the class method, providing self as a target value to
46
+ # support certain edge cases
47
+ Stamp.strftime_format(example, self)
121
48
  end
122
49
 
123
- def strftime_date_directive(token, previous_directive)
124
- case token
125
- when MONTHNAMES_REGEXP
126
- '%B'
127
-
128
- when ABBR_MONTHNAMES_REGEXP
129
- '%b'
130
-
131
- when DAYNAMES_REGEXP
132
- '%A'
133
-
134
- when ABBR_DAYNAMES_REGEXP
135
- '%a'
136
-
137
- when FOUR_DIGIT_REGEXP
138
- '%Y'
139
-
140
- when TWO_DIGIT_REGEXP
141
- # try to discern obvious intent based on the example value
142
- case token.to_i
143
- when OBVIOUS_YEARS
144
- '%y'
145
- when OBVIOUS_MONTHS
146
- '%m'
147
- when OBVIOUS_DAYS
148
- '%d'
149
- else
150
- # the intent isn't obvious based on the example value, so try to
151
- # disambiguate based on context
152
- TWO_DIGIT_DATE_SUCCESSION[previous_directive] || '%m'
153
- end
154
-
155
- when ONE_DIGIT_REGEXP
156
- '%e' # day without leading zero
157
- end
158
- end
159
50
  end
160
51
 
161
-
162
52
  Date.send(:include, ::Stamp)
163
53
  Time.send(:include, ::Stamp)
@@ -0,0 +1,139 @@
1
+ module Stamp
2
+ class StrftimeTranslator
3
+ MONTHNAMES_REGEXP = /^(#{Date::MONTHNAMES.compact.join('|')})$/i
4
+ ABBR_MONTHNAMES_REGEXP = /^(#{Date::ABBR_MONTHNAMES.compact.join('|')})$/i
5
+ DAYNAMES_REGEXP = /^(#{Date::DAYNAMES.join('|')})$/i
6
+ ABBR_DAYNAMES_REGEXP = /^(#{Date::ABBR_DAYNAMES.join('|')})$/i
7
+
8
+ ONE_DIGIT_REGEXP = /^\d{1}$/
9
+ TWO_DIGIT_REGEXP = /^\d{2}$/
10
+ FOUR_DIGIT_REGEXP = /^\d{4}$/
11
+
12
+ TIME_REGEXP = /(\d{1,2})(:)(\d{2})(\s*)(:)?(\d{2})?(\s*)?([ap]m)?/i
13
+
14
+ MERIDIAN_LOWER_REGEXP = /^(a|p)m$/
15
+ MERIDIAN_UPPER_REGEXP = /^(A|P)M$/
16
+
17
+ # Disambiguate based on value
18
+ OBVIOUS_YEARS = 60..99
19
+ OBVIOUS_MONTHS = 12
20
+ OBVIOUS_DAYS = 28..31
21
+ OBVIOUS_24_HOUR = 13..23
22
+
23
+ TWO_DIGIT_DATE_SUCCESSION = {
24
+ '%m' => '%d',
25
+ '%b' => '%d',
26
+ '%B' => '%d',
27
+ '%d' => '%y',
28
+ '%e' => '%y'
29
+ }
30
+
31
+ TWO_DIGIT_TIME_SUCCESSION = {
32
+ '%H' => '%M',
33
+ '%I' => '%M',
34
+ '%l' => '%M',
35
+ '%M' => '%S'
36
+ }
37
+
38
+ def initialize(target_date_or_time)
39
+ @target = target_date_or_time
40
+ end
41
+
42
+ def translate(example)
43
+ # extract any substrings that look like times, like "23:59" or "8:37 am"
44
+ before, time_example, after = example.partition(TIME_REGEXP)
45
+
46
+ # transform any date tokens to strftime directives
47
+ words = strftime_directives(before.split(/\b/)) do |token, previous_directive|
48
+ strftime_date_directive(token, previous_directive)
49
+ end
50
+
51
+ # transform the example time string to strftime directives
52
+ unless time_example.empty?
53
+ time_parts = time_example.scan(TIME_REGEXP).first
54
+ words += strftime_directives(time_parts) do |token, previous_directive|
55
+ strftime_time_directive(token, previous_directive)
56
+ end
57
+ end
58
+
59
+ # recursively process any remaining text
60
+ words << translate(after) unless after.empty?
61
+ words.join
62
+ end
63
+
64
+ # Transforms tokens that look like date/time parts to strftime directives.
65
+ def strftime_directives(tokens)
66
+ previous_directive = nil
67
+ tokens.map do |token|
68
+ directive = yield(token, previous_directive)
69
+ previous_directive = directive unless directive.nil?
70
+ directive || token
71
+ end
72
+ end
73
+
74
+ def strftime_time_directive(token, previous_directive)
75
+ case token
76
+ when MERIDIAN_LOWER_REGEXP
77
+ if RUBY_VERSION =~ /^1.8/ && @target.is_a?(Time)
78
+ # 1.8.7 doesn't implement %P
79
+ @target.strftime("%p").downcase
80
+ else
81
+ '%P'
82
+ end
83
+
84
+ when MERIDIAN_UPPER_REGEXP
85
+ '%p'
86
+
87
+ when TWO_DIGIT_REGEXP
88
+ TWO_DIGIT_TIME_SUCCESSION[previous_directive] ||
89
+ case token.to_i
90
+ when OBVIOUS_24_HOUR
91
+ '%H' # 24-hour clock
92
+ else
93
+ '%I' # 12-hour clock with leading zero
94
+ end
95
+
96
+ when ONE_DIGIT_REGEXP
97
+ '%l' # hour without leading zero
98
+ end
99
+ end
100
+
101
+ def strftime_date_directive(token, previous_directive)
102
+ case token
103
+ when MONTHNAMES_REGEXP
104
+ '%B'
105
+
106
+ when ABBR_MONTHNAMES_REGEXP
107
+ '%b'
108
+
109
+ when DAYNAMES_REGEXP
110
+ '%A'
111
+
112
+ when ABBR_DAYNAMES_REGEXP
113
+ '%a'
114
+
115
+ when FOUR_DIGIT_REGEXP
116
+ '%Y'
117
+
118
+ when TWO_DIGIT_REGEXP
119
+ # try to discern obvious intent based on the example value
120
+ case token.to_i
121
+ when OBVIOUS_YEARS
122
+ '%y'
123
+ when OBVIOUS_MONTHS
124
+ '%m'
125
+ when OBVIOUS_DAYS
126
+ '%d'
127
+ else
128
+ # the intent isn't obvious based on the example value, so try to
129
+ # disambiguate based on context
130
+ TWO_DIGIT_DATE_SUCCESSION[previous_directive] || '%m'
131
+ end
132
+
133
+ when ONE_DIGIT_REGEXP
134
+ '%e' # day without leading zero
135
+ end
136
+ end
137
+
138
+ end
139
+ end
data/lib/stamp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Stamp
2
- VERSION = "0.1.5"
2
+ VERSION = "0.1.6"
3
3
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 5
9
- version: 0.1.5
8
+ - 6
9
+ version: 0.1.6
10
10
  platform: ruby
11
11
  authors:
12
12
  - Jeremy Weiskotten
@@ -68,6 +68,7 @@ files:
68
68
  - features/step_definitions/turtle_steps.rb
69
69
  - features/support/env.rb
70
70
  - lib/stamp.rb
71
+ - lib/stamp/translator.rb
71
72
  - lib/stamp/version.rb
72
73
  - stamp.gemspec
73
74
  has_rdoc: true