stamp 0.5.0 → 0.6.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.
- checksums.yaml +7 -0
- data/.travis.yml +2 -1
- data/Gemfile +1 -1
- data/features/stamp.feature +4 -12
- data/lib/stamp.rb +7 -7
- data/lib/stamp/disambiguator.rb +23 -0
- data/lib/stamp/emitters/ambiguous.rb +24 -0
- data/lib/stamp/emitters/composite.rb +22 -11
- data/lib/stamp/translator.rb +37 -52
- data/lib/stamp/version.rb +1 -1
- data/stamp.gemspec +1 -1
- metadata +17 -27
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 5c0058768a5169006ec3da166fdb88f082715295
|
|
4
|
+
data.tar.gz: fcf6e1e6fc53cdd3105b1eceb2099b4014b003fc
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 5be616540b39f2dac3e8bef2e886e809b908446f70e4d18d291e84dc17d22a376b12ac75332a844611be190bebc6df71a072247ed6d75ff8c8bd4e9da8b1ef6d
|
|
7
|
+
data.tar.gz: c508e4cc1a1821006ced92ae88ca0b2bb7a766cf30d04eb26cdf56f244ed0f41788d08297993a89850bf625860cc38fec416e48448d36739c1d5611a931c2813
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/features/stamp.feature
CHANGED
|
@@ -19,6 +19,9 @@ Feature: Stamping a date
|
|
|
19
19
|
| Jan 10 | Sep 08 |
|
|
20
20
|
| Jan 1, 1999 | Sep 8, 2011 |
|
|
21
21
|
| Jan 12, 1999 | Sep 08, 2011 |
|
|
22
|
+
| 1 Jan 1999 | 8 Sep 2011 |
|
|
23
|
+
| 1/1/1999 | 9/8/2011 |
|
|
24
|
+
| 31/01/2013 | 08/09/2011 |
|
|
22
25
|
| 13 January 1999 | 08 September 2011 |
|
|
23
26
|
| Monday | Thursday |
|
|
24
27
|
| Tue, Jan 1 | Thu, Sep 8 |
|
|
@@ -32,6 +35,7 @@ Feature: Stamping a date
|
|
|
32
35
|
| 31/12 | 08/09 |
|
|
33
36
|
| 31/12/99 | 08/09/11 |
|
|
34
37
|
| 31-Jan-1999 | 08-Sep-2011 |
|
|
38
|
+
| 1999-01-01 | 2011-09-08 |
|
|
35
39
|
| 1999-12-31 | 2011-09-08 |
|
|
36
40
|
| DOB: 12-31-1999 | DOB: 09-08-2011 |
|
|
37
41
|
|
|
@@ -117,15 +121,3 @@ Feature: Stamping a date
|
|
|
117
121
|
| alias |
|
|
118
122
|
| stamp_like |
|
|
119
123
|
| format_like |
|
|
120
|
-
|
|
121
|
-
@wip
|
|
122
|
-
Scenario Outline: Examples that aren't supported yet
|
|
123
|
-
Given the time September 8, 2011 at 13:31:27
|
|
124
|
-
When I stamp the example "<example>"
|
|
125
|
-
Then I produce "<output>"
|
|
126
|
-
|
|
127
|
-
Examples:
|
|
128
|
-
| example | output |
|
|
129
|
-
| 8 am | 1 pm |
|
|
130
|
-
| 8am | 1pm |
|
|
131
|
-
| 8AM | 1PM |
|
data/lib/stamp.rb
CHANGED
|
@@ -3,20 +3,18 @@ require "time"
|
|
|
3
3
|
|
|
4
4
|
require "stamp/emitters/modifiable"
|
|
5
5
|
require "stamp/emitters/am_pm"
|
|
6
|
+
require "stamp/emitters/ambiguous"
|
|
6
7
|
require "stamp/emitters/composite"
|
|
7
8
|
require "stamp/emitters/delegate"
|
|
8
9
|
require "stamp/emitters/lookup"
|
|
9
10
|
require "stamp/emitters/ordinal"
|
|
10
11
|
require "stamp/emitters/string"
|
|
11
12
|
require "stamp/emitters/two_digit"
|
|
13
|
+
require "stamp/disambiguator"
|
|
12
14
|
require "stamp/translator"
|
|
13
15
|
require "stamp/version"
|
|
14
16
|
|
|
15
17
|
module Stamp
|
|
16
|
-
# Limits the number of formats that we memoize to prevent unbounded
|
|
17
|
-
# memory consumption.
|
|
18
|
-
MEMOIZATION_CAP = 999
|
|
19
|
-
|
|
20
18
|
# Formats a date/time using a human-friendly example as a template.
|
|
21
19
|
#
|
|
22
20
|
# @param [String] example a human-friendly date/time example
|
|
@@ -33,18 +31,20 @@ module Stamp
|
|
|
33
31
|
alias :stamp_like :stamp
|
|
34
32
|
alias :format_like :stamp
|
|
35
33
|
|
|
34
|
+
private
|
|
35
|
+
|
|
36
36
|
# Memoizes the set of emitter objects for the given +example+ in
|
|
37
37
|
# order to improve performance.
|
|
38
38
|
def memoize_stamp_emitters(example)
|
|
39
39
|
@@memoized_stamp_emitters ||= {}
|
|
40
|
-
@@memoized_stamp_emitters.clear if @@memoized_stamp_emitters.size > MEMOIZATION_CAP
|
|
41
40
|
@@memoized_stamp_emitters[example] ||= stamp_emitters(example)
|
|
42
41
|
end
|
|
43
42
|
|
|
44
43
|
def stamp_emitters(example)
|
|
45
|
-
Translator.new.translate(example)
|
|
44
|
+
emitters = Translator.new.translate(example)
|
|
45
|
+
Disambiguator.new(emitters).disambiguate!
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
Date.send(:include, ::Stamp)
|
|
50
|
-
Time.send(:include, ::Stamp)
|
|
50
|
+
Time.send(:include, ::Stamp)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class Disambiguator
|
|
2
|
+
attr_reader :emitters
|
|
3
|
+
|
|
4
|
+
def initialize(emitters)
|
|
5
|
+
@emitters = emitters
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def disambiguate!
|
|
9
|
+
emitters.replace_each! do |emitter|
|
|
10
|
+
disambiguate(emitter)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def disambiguate(emitter)
|
|
17
|
+
if emitter.respond_to?(:disambiguate)
|
|
18
|
+
emitter.disambiguate(emitters)
|
|
19
|
+
else
|
|
20
|
+
emitter
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Stamp
|
|
2
|
+
module Emitters
|
|
3
|
+
class Ambiguous
|
|
4
|
+
attr_reader :potential_emitters
|
|
5
|
+
|
|
6
|
+
def initialize(*emitters)
|
|
7
|
+
@potential_emitters = emitters
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def field
|
|
11
|
+
nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def disambiguate(emitters)
|
|
15
|
+
other_emitters = emitters - self
|
|
16
|
+
known_fields = other_emitters.map { |e| e.field }.compact
|
|
17
|
+
|
|
18
|
+
potential_emitters.reject do |potential_emitter|
|
|
19
|
+
known_fields.include?(potential_emitter.field)
|
|
20
|
+
end.first
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -3,29 +3,40 @@ module Stamp
|
|
|
3
3
|
class Composite
|
|
4
4
|
include Enumerable
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
attr_reader :emitters
|
|
7
|
+
|
|
8
|
+
def initialize(emitters=[])
|
|
9
|
+
@emitters = emitters
|
|
8
10
|
end
|
|
9
11
|
|
|
10
12
|
def format(target)
|
|
11
13
|
# NOTE using #each to build string because benchmarking shows
|
|
12
14
|
# that it's ~20% faster than .map.join('')
|
|
13
15
|
result = ''
|
|
14
|
-
|
|
16
|
+
emitters.each { |e| result << e.format(target).to_s }
|
|
15
17
|
result
|
|
16
18
|
end
|
|
17
19
|
|
|
18
|
-
def <<(emitter)
|
|
19
|
-
|
|
20
|
-
emitter.each { |e| @emitters << e }
|
|
21
|
-
else
|
|
22
|
-
@emitters << emitter
|
|
23
|
-
end
|
|
20
|
+
def <<(emitter)
|
|
21
|
+
Array(emitter).each { |e| emitters << e }
|
|
24
22
|
end
|
|
25
23
|
|
|
26
24
|
def each(&block)
|
|
27
|
-
|
|
25
|
+
emitters.each(&block)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def -(others)
|
|
29
|
+
emitters - Array(others)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Replace each element as we iterate with the result of the given block.
|
|
33
|
+
def replace_each!
|
|
34
|
+
emitters.each_with_index do |emitter, index|
|
|
35
|
+
emitters[index] = yield(emitter)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
self
|
|
28
39
|
end
|
|
29
40
|
end
|
|
30
41
|
end
|
|
31
|
-
end
|
|
42
|
+
end
|
data/lib/stamp/translator.rb
CHANGED
|
@@ -34,48 +34,30 @@ module Stamp
|
|
|
34
34
|
ORDINAL_DAY_REGEXP = /^(\d{1,2})(st|nd|rd|th)$/
|
|
35
35
|
|
|
36
36
|
# Disambiguate based on value
|
|
37
|
-
OBVIOUS_YEARS = 60..99
|
|
38
|
-
OBVIOUS_MONTHS = 12
|
|
39
|
-
OBVIOUS_DAYS = 13..31
|
|
40
37
|
OBVIOUS_24_HOUR = 13..23
|
|
38
|
+
OBVIOUS_DAY = 13..31
|
|
39
|
+
OBVIOUS_YEAR = 32..99
|
|
41
40
|
|
|
42
41
|
TWO_DIGIT_YEAR_EMITTER = Emitters::TwoDigit.new(:year) { |year| year % 100 }
|
|
43
42
|
TWO_DIGIT_MONTH_EMITTER = Emitters::TwoDigit.new(:month)
|
|
44
43
|
TWO_DIGIT_DAY_EMITTER = Emitters::TwoDigit.new(:day)
|
|
45
44
|
HOUR_TO_12_HOUR = lambda { |h| ((h - 1) % 12) + 1 }
|
|
46
45
|
|
|
47
|
-
OBVIOUS_DATE_MAP = {
|
|
48
|
-
OBVIOUS_YEARS => TWO_DIGIT_YEAR_EMITTER,
|
|
49
|
-
OBVIOUS_MONTHS => TWO_DIGIT_MONTH_EMITTER,
|
|
50
|
-
OBVIOUS_DAYS => TWO_DIGIT_DAY_EMITTER
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
TWO_DIGIT_DATE_SUCCESSION = {
|
|
54
|
-
:month => TWO_DIGIT_DAY_EMITTER,
|
|
55
|
-
:day => TWO_DIGIT_YEAR_EMITTER,
|
|
56
|
-
:year => TWO_DIGIT_MONTH_EMITTER
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
TWO_DIGIT_TIME_SUCCESSION = {
|
|
60
|
-
:hour => Emitters::TwoDigit.new(:min),
|
|
61
|
-
:min => Emitters::TwoDigit.new(:sec)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
46
|
def translate(example)
|
|
65
47
|
# extract any substrings that look like times, like "23:59" or "8:37 am"
|
|
66
48
|
before, time_example, after = example.partition(TIME_REGEXP)
|
|
67
49
|
|
|
68
50
|
# build emitters from the example date
|
|
69
51
|
emitters = Emitters::Composite.new
|
|
70
|
-
emitters << build_emitters(before.split(/\b/)) do |token
|
|
71
|
-
date_emitter(token
|
|
52
|
+
emitters << build_emitters(before.split(/\b/)) do |token|
|
|
53
|
+
date_emitter(token)
|
|
72
54
|
end
|
|
73
55
|
|
|
74
56
|
# build emitters from the example time
|
|
75
57
|
unless time_example.empty?
|
|
76
58
|
time_parts = time_example.scan(TIME_REGEXP).first
|
|
77
|
-
emitters << build_emitters(time_parts) do |token
|
|
78
|
-
time_emitter(token
|
|
59
|
+
emitters << build_emitters(time_parts) do |token|
|
|
60
|
+
time_emitter(token)
|
|
79
61
|
end
|
|
80
62
|
end
|
|
81
63
|
|
|
@@ -86,16 +68,12 @@ module Stamp
|
|
|
86
68
|
|
|
87
69
|
# Transforms tokens that look like date/time parts to emitter objects.
|
|
88
70
|
def build_emitters(tokens)
|
|
89
|
-
previous_part = nil
|
|
90
71
|
tokens.map do |token|
|
|
91
|
-
|
|
92
|
-
previous_part = emitter.field unless emitter.nil?
|
|
93
|
-
|
|
94
|
-
emitter || Emitters::String.new(token)
|
|
72
|
+
yield(token) || Emitters::String.new(token)
|
|
95
73
|
end
|
|
96
74
|
end
|
|
97
75
|
|
|
98
|
-
def time_emitter(token
|
|
76
|
+
def time_emitter(token)
|
|
99
77
|
case token
|
|
100
78
|
when MERIDIAN_LOWER_REGEXP
|
|
101
79
|
Emitters::AmPm.new
|
|
@@ -104,15 +82,10 @@ module Stamp
|
|
|
104
82
|
Emitters::AmPm.new { |v| v.upcase }
|
|
105
83
|
|
|
106
84
|
when TWO_DIGIT_REGEXP
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
Emitters::TwoDigit.new(:hour)
|
|
112
|
-
else
|
|
113
|
-
# 12-hour clock with leading zero
|
|
114
|
-
Emitters::TwoDigit.new(:hour, &HOUR_TO_12_HOUR)
|
|
115
|
-
end
|
|
85
|
+
Emitters::Ambiguous.new(
|
|
86
|
+
two_digit_hour_emitter(token),
|
|
87
|
+
Emitters::TwoDigit.new(:min),
|
|
88
|
+
Emitters::TwoDigit.new(:sec))
|
|
116
89
|
|
|
117
90
|
when ONE_DIGIT_REGEXP
|
|
118
91
|
# 12-hour clock without leading zero
|
|
@@ -120,7 +93,18 @@ module Stamp
|
|
|
120
93
|
end
|
|
121
94
|
end
|
|
122
95
|
|
|
123
|
-
def
|
|
96
|
+
def two_digit_hour_emitter(token)
|
|
97
|
+
case token.to_i
|
|
98
|
+
when OBVIOUS_24_HOUR
|
|
99
|
+
# 24-hour clock
|
|
100
|
+
Emitters::TwoDigit.new(:hour)
|
|
101
|
+
else
|
|
102
|
+
# 12-hour clock with leading zero
|
|
103
|
+
Emitters::TwoDigit.new(:hour, &HOUR_TO_12_HOUR)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def date_emitter(token)
|
|
124
108
|
case token
|
|
125
109
|
when MONTHNAMES_REGEXP
|
|
126
110
|
Emitters::Lookup.new(:month, Date::MONTHNAMES)
|
|
@@ -146,21 +130,22 @@ module Stamp
|
|
|
146
130
|
when TWO_DIGIT_REGEXP
|
|
147
131
|
value = token.to_i
|
|
148
132
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
133
|
+
case value
|
|
134
|
+
when OBVIOUS_DAY
|
|
135
|
+
TWO_DIGIT_DAY_EMITTER
|
|
136
|
+
when OBVIOUS_YEAR
|
|
137
|
+
TWO_DIGIT_YEAR_EMITTER
|
|
138
|
+
else
|
|
139
|
+
Emitters::Ambiguous.new(
|
|
140
|
+
TWO_DIGIT_MONTH_EMITTER,
|
|
141
|
+
TWO_DIGIT_DAY_EMITTER,
|
|
142
|
+
TWO_DIGIT_YEAR_EMITTER)
|
|
154
143
|
end
|
|
155
144
|
|
|
156
|
-
# if the intent isn't obvious based on the example value, try to
|
|
157
|
-
# disambiguate based on context
|
|
158
|
-
obvious_directive ||
|
|
159
|
-
TWO_DIGIT_DATE_SUCCESSION[previous_part] ||
|
|
160
|
-
TWO_DIGIT_MONTH_EMITTER
|
|
161
|
-
|
|
162
145
|
when ONE_DIGIT_REGEXP
|
|
163
|
-
Emitters::
|
|
146
|
+
Emitters::Ambiguous.new(
|
|
147
|
+
Emitters::Delegate.new(:month),
|
|
148
|
+
Emitters::Delegate.new(:day))
|
|
164
149
|
end
|
|
165
150
|
end
|
|
166
151
|
|
data/lib/stamp/version.rb
CHANGED
data/stamp.gemspec
CHANGED
|
@@ -6,7 +6,7 @@ Gem::Specification.new do |s|
|
|
|
6
6
|
s.name = "stamp"
|
|
7
7
|
s.version = Stamp::VERSION
|
|
8
8
|
s.authors = ["Jeremy Weiskotten"]
|
|
9
|
-
s.email = ["jeremy@
|
|
9
|
+
s.email = ["jeremy@terriblelabs.com"]
|
|
10
10
|
s.homepage = "https://github.com/jeremyw/stamp"
|
|
11
11
|
s.summary = %Q{Date and time formatting for humans.}
|
|
12
12
|
s.description = %Q{Format dates and times based on human-friendly examples, not arcane strftime directives.}
|
metadata
CHANGED
|
@@ -1,59 +1,54 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: stamp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
5
|
-
prerelease:
|
|
4
|
+
version: 0.6.0
|
|
6
5
|
platform: ruby
|
|
7
6
|
authors:
|
|
8
7
|
- Jeremy Weiskotten
|
|
9
8
|
autorequire:
|
|
10
9
|
bindir: bin
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
11
|
+
date: 2014-08-21 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
14
|
name: cucumber
|
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
|
17
|
-
none: false
|
|
18
16
|
requirements:
|
|
19
|
-
- -
|
|
17
|
+
- - ">="
|
|
20
18
|
- !ruby/object:Gem::Version
|
|
21
19
|
version: '0'
|
|
22
20
|
type: :development
|
|
23
21
|
prerelease: false
|
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
-
none: false
|
|
26
23
|
requirements:
|
|
27
|
-
- -
|
|
24
|
+
- - ">="
|
|
28
25
|
- !ruby/object:Gem::Version
|
|
29
26
|
version: '0'
|
|
30
27
|
- !ruby/object:Gem::Dependency
|
|
31
28
|
name: rake
|
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
|
33
|
-
none: false
|
|
34
30
|
requirements:
|
|
35
|
-
- -
|
|
31
|
+
- - ">="
|
|
36
32
|
- !ruby/object:Gem::Version
|
|
37
33
|
version: '0'
|
|
38
34
|
type: :development
|
|
39
35
|
prerelease: false
|
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
41
|
-
none: false
|
|
42
37
|
requirements:
|
|
43
|
-
- -
|
|
38
|
+
- - ">="
|
|
44
39
|
- !ruby/object:Gem::Version
|
|
45
40
|
version: '0'
|
|
46
41
|
description: Format dates and times based on human-friendly examples, not arcane strftime
|
|
47
42
|
directives.
|
|
48
43
|
email:
|
|
49
|
-
- jeremy@
|
|
44
|
+
- jeremy@terriblelabs.com
|
|
50
45
|
executables: []
|
|
51
46
|
extensions: []
|
|
52
47
|
extra_rdoc_files: []
|
|
53
48
|
files:
|
|
54
|
-
- .gitignore
|
|
55
|
-
- .travis.yml
|
|
56
|
-
- .yardopts
|
|
49
|
+
- ".gitignore"
|
|
50
|
+
- ".travis.yml"
|
|
51
|
+
- ".yardopts"
|
|
57
52
|
- CONTRIBUTING.md
|
|
58
53
|
- Gemfile
|
|
59
54
|
- LICENSE.txt
|
|
@@ -64,7 +59,9 @@ files:
|
|
|
64
59
|
- features/step_definitions/stamp_steps.rb
|
|
65
60
|
- features/support/env.rb
|
|
66
61
|
- lib/stamp.rb
|
|
62
|
+
- lib/stamp/disambiguator.rb
|
|
67
63
|
- lib/stamp/emitters/am_pm.rb
|
|
64
|
+
- lib/stamp/emitters/ambiguous.rb
|
|
68
65
|
- lib/stamp/emitters/composite.rb
|
|
69
66
|
- lib/stamp/emitters/delegate.rb
|
|
70
67
|
- lib/stamp/emitters/lookup.rb
|
|
@@ -77,33 +74,26 @@ files:
|
|
|
77
74
|
- stamp.gemspec
|
|
78
75
|
homepage: https://github.com/jeremyw/stamp
|
|
79
76
|
licenses: []
|
|
77
|
+
metadata: {}
|
|
80
78
|
post_install_message:
|
|
81
79
|
rdoc_options: []
|
|
82
80
|
require_paths:
|
|
83
81
|
- lib
|
|
84
82
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
|
-
none: false
|
|
86
83
|
requirements:
|
|
87
|
-
- -
|
|
84
|
+
- - ">="
|
|
88
85
|
- !ruby/object:Gem::Version
|
|
89
86
|
version: '0'
|
|
90
|
-
segments:
|
|
91
|
-
- 0
|
|
92
|
-
hash: 167698609462982419
|
|
93
87
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
|
-
none: false
|
|
95
88
|
requirements:
|
|
96
|
-
- -
|
|
89
|
+
- - ">="
|
|
97
90
|
- !ruby/object:Gem::Version
|
|
98
91
|
version: '0'
|
|
99
|
-
segments:
|
|
100
|
-
- 0
|
|
101
|
-
hash: 167698609462982419
|
|
102
92
|
requirements: []
|
|
103
93
|
rubyforge_project:
|
|
104
|
-
rubygems_version:
|
|
94
|
+
rubygems_version: 2.2.2
|
|
105
95
|
signing_key:
|
|
106
|
-
specification_version:
|
|
96
|
+
specification_version: 4
|
|
107
97
|
summary: Date and time formatting for humans.
|
|
108
98
|
test_files:
|
|
109
99
|
- features/stamp.feature
|