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 +1 -1
- data/README.md +31 -13
- data/Rakefile +4 -0
- data/cucumber.yml +1 -1
- data/lib/stamp.rb +20 -130
- data/lib/stamp/translator.rb +139 -0
- data/lib/stamp/version.rb +1 -1
- metadata +3 -2
data/Gemfile.lock
CHANGED
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")
|
24
|
-
date.stamp("Jan 1, 1999")
|
25
|
-
date.stamp("Jan 01")
|
26
|
-
date.stamp("Sunday, May 1, 2000")
|
27
|
-
date.stamp("Sun Aug 5")
|
28
|
-
date.stamp("12/31/99")
|
29
|
-
date.stamp("DOB: 12/31/2000")
|
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")
|
40
|
-
time.stamp("01:00:00 AM")
|
41
|
-
time.stamp("23:59")
|
42
|
-
time.stamp("23:59:59")
|
43
|
-
time.stamp("Jan 1 at 01:00 AM")
|
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")
|
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
data/cucumber.yml
CHANGED
data/lib/stamp.rb
CHANGED
@@ -1,42 +1,24 @@
|
|
1
|
-
require "stamp/version"
|
2
1
|
require "date"
|
3
2
|
require "time"
|
4
3
|
|
5
|
-
|
6
|
-
|
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
|
-
|
27
|
-
'%m' => '%d',
|
28
|
-
'%b' => '%d',
|
29
|
-
'%B' => '%d',
|
30
|
-
'%d' => '%y',
|
31
|
-
'%e' => '%y'
|
32
|
-
}
|
7
|
+
module Stamp
|
33
8
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
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
|
-
#
|
64
|
-
|
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
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
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
|