stamp 0.1.5 → 0.1.6

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/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