stamp 0.5.0 → 0.7.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/.github/workflows/ci.yml +18 -0
- data/Gemfile +1 -1
- data/LICENSE.txt +1 -1
- data/README.md +40 -3
- data/Rakefile +6 -6
- data/lib/stamp/core.rb +73 -0
- data/lib/stamp/disambiguator.rb +25 -0
- data/lib/stamp/emitters/am_pm.rb +10 -8
- data/lib/stamp/emitters/ambiguous.rb +24 -0
- data/lib/stamp/emitters/composite.rb +31 -10
- data/lib/stamp/emitters/delegate.rb +12 -8
- data/lib/stamp/emitters/lookup.rb +12 -11
- data/lib/stamp/emitters/ordinal.rb +10 -2
- data/lib/stamp/emitters/string.rb +6 -0
- data/lib/stamp/emitters/twelve_hour.rb +38 -0
- data/lib/stamp/emitters/two_digit.rb +18 -9
- data/lib/stamp/emitters/utc_offset.rb +27 -0
- data/lib/stamp/refinement.rb +21 -0
- data/lib/stamp/translator.rb +63 -71
- data/lib/stamp/version.rb +1 -1
- data/lib/stamp.rb +3 -49
- data/stamp.gemspec +11 -5
- metadata +28 -40
- data/.travis.yml +0 -8
- data/cucumber.yml +0 -3
- data/features/stamp.feature +0 -131
- data/features/step_definitions/stamp_steps.rb +0 -30
- data/features/support/env.rb +0 -14
- data/lib/stamp/emitters/modifiable.rb +0 -9
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f41c8f35a8e2cd599d4cff288999cadec0daecd942e963617a97c30fe2b6af16
|
|
4
|
+
data.tar.gz: 7f2da591b6b8c66fc6862b93bf93db1e86aac7212f4a043abfde0e6e0d3d4a1e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1cf409a95607824e5758705c2f152270e9294549bafcbb32e74f877575bed567310899170c9ae88c5c4ab411ac666c7f54bef133b67d3375fc5822e938b9b1e7
|
|
7
|
+
data.tar.gz: 9755ca6a5e9d1ff3c8a48e0adede18577699187985240580e77ba93c8912e5feb1ebf6197b31a3451394f1800e344ad3dac248fbdbcc5fec7a18be681d6004ae
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
strategy:
|
|
9
|
+
fail-fast: false
|
|
10
|
+
matrix:
|
|
11
|
+
ruby: ["3.1", "3.2", "3.3", "3.4"]
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: ruby/setup-ruby@v1
|
|
15
|
+
with:
|
|
16
|
+
ruby-version: ${{ matrix.ruby }}
|
|
17
|
+
bundler-cache: true
|
|
18
|
+
- run: bundle exec rake
|
data/Gemfile
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Format dates and times based on human-friendly examples, not arcane
|
|
4
4
|
[strftime](http://strfti.me) directives.
|
|
5
5
|
|
|
6
|
-
[](https://github.com/jeremyw/stamp/actions/workflows/ci.yml)
|
|
7
7
|
|
|
8
8
|
## Installation
|
|
9
9
|
|
|
@@ -58,6 +58,44 @@ time.stamp("Jan 1 at 01:00 AM") #=> "Jun 9 at 08:52 PM"
|
|
|
58
58
|
time.stamp("23:59 UTC") #=> "20:52 PST"
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
UTC offsets are recognized too, in both `+HHMM` and `+HH:MM` styles:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
time.stamp("23:59 +00:00") #=> "20:52 -05:00"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Without monkey patches
|
|
68
|
+
|
|
69
|
+
If you'd rather not patch `Date` and `Time`, use the module function:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
require "stamp/core" # instead of "stamp"
|
|
73
|
+
|
|
74
|
+
Stamp.format(Date.new(2011, 6, 9), "Jan 1, 1999") #=> "Jun 9, 2011"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Or a refinement, scoped to the files that opt in:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
require "stamp/refinement"
|
|
81
|
+
using Stamp::Refinement
|
|
82
|
+
|
|
83
|
+
Date.new(2011, 6, 9).stamp("Jan 1, 1999") #=> "Jun 9, 2011"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Converting examples to strftime formats
|
|
87
|
+
|
|
88
|
+
When you need the equivalent strftime string — for `Time::DATE_FORMATS`,
|
|
89
|
+
I18n configs, or APIs that want a format rather than a formatted value:
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
Stamp.strftime_format("Jan 1, 1999") #=> "%b %-d, %Y"
|
|
93
|
+
Stamp.strftime_format("23:59:59") #=> "%H:%M:%S"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Ordinal days (e.g. "1st") have no strftime equivalent and raise an
|
|
97
|
+
`ArgumentError`.
|
|
98
|
+
|
|
61
99
|
## Features
|
|
62
100
|
|
|
63
101
|
* Abbreviated and full names of months and weekdays are recognized.
|
|
@@ -102,5 +140,4 @@ Time.now.to_s(:military) #=> "16 July 15:35"
|
|
|
102
140
|
|
|
103
141
|
## Copyright
|
|
104
142
|
|
|
105
|
-
Copyright (c) 2011 Jeremy Weiskotten
|
|
106
|
-
further details.
|
|
143
|
+
Copyright (c) 2011-2026 Jeremy Weiskotten. See LICENSE.txt for further details.
|
data/Rakefile
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
require 'bundler/gem_tasks'
|
|
2
|
-
require '
|
|
2
|
+
require 'rake/testtask'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
t.
|
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
|
5
|
+
t.libs << "test"
|
|
6
|
+
t.libs << "lib"
|
|
7
|
+
t.pattern = "test/**/*_test.rb"
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
task :default => :
|
|
10
|
+
task :default => :test
|
data/lib/stamp/core.rb
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require "date"
|
|
2
|
+
require "time"
|
|
3
|
+
|
|
4
|
+
require "stamp/emitters/am_pm"
|
|
5
|
+
require "stamp/emitters/ambiguous"
|
|
6
|
+
require "stamp/emitters/composite"
|
|
7
|
+
require "stamp/emitters/delegate"
|
|
8
|
+
require "stamp/emitters/lookup"
|
|
9
|
+
require "stamp/emitters/ordinal"
|
|
10
|
+
require "stamp/emitters/string"
|
|
11
|
+
require "stamp/emitters/twelve_hour"
|
|
12
|
+
require "stamp/emitters/two_digit"
|
|
13
|
+
require "stamp/emitters/utc_offset"
|
|
14
|
+
require "stamp/disambiguator"
|
|
15
|
+
require "stamp/translator"
|
|
16
|
+
require "stamp/version"
|
|
17
|
+
|
|
18
|
+
module Stamp
|
|
19
|
+
# Formats a date/time using a human-friendly example as a template,
|
|
20
|
+
# without monkey patching Date or Time.
|
|
21
|
+
#
|
|
22
|
+
# @param [Date|Time|DateTime] target the date/time to format
|
|
23
|
+
# @param [String] example a human-friendly date/time example
|
|
24
|
+
#
|
|
25
|
+
# @return [String] the formatted date or time
|
|
26
|
+
#
|
|
27
|
+
# @example
|
|
28
|
+
# Stamp.format(Date.new(2012, 12, 21), "Jan 1, 1999") #=> "Dec 21, 2012"
|
|
29
|
+
def self.format(target, example)
|
|
30
|
+
emitters(example).format(target)
|
|
31
|
+
rescue NoMethodError => e
|
|
32
|
+
raise e if target.respond_to?(e.name)
|
|
33
|
+
raise ArgumentError,
|
|
34
|
+
"#{example.inspect} requires ##{e.name}, which #{target.class} doesn't implement (e.g. a Date can't format time-of-day examples)"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Translates a human-friendly example into a strftime format string.
|
|
38
|
+
#
|
|
39
|
+
# @param [String] example a human-friendly date/time example
|
|
40
|
+
#
|
|
41
|
+
# @return [String] the equivalent strftime format
|
|
42
|
+
#
|
|
43
|
+
# @raise [ArgumentError] if the example uses a feature strftime can't
|
|
44
|
+
# express (e.g. ordinal days like "1st")
|
|
45
|
+
#
|
|
46
|
+
# @example
|
|
47
|
+
# Stamp.strftime_format("Jan 1, 1999") #=> "%b %-d, %Y"
|
|
48
|
+
def self.strftime_format(example)
|
|
49
|
+
emitters(example).strftime_format
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Memoizes the emitters for each example to improve performance.
|
|
53
|
+
def self.emitters(example)
|
|
54
|
+
cache = (@emitters ||= {})
|
|
55
|
+
cache.clear if cache.size >= 1000 # ponytail: crude cap to bound memory; LRU if it ever matters
|
|
56
|
+
cache[example] ||= Disambiguator.new(Translator.new.translate(example)).disambiguate!
|
|
57
|
+
end
|
|
58
|
+
private_class_method :emitters
|
|
59
|
+
|
|
60
|
+
# Formats a date/time using a human-friendly example as a template.
|
|
61
|
+
#
|
|
62
|
+
# @param [String] example a human-friendly date/time example
|
|
63
|
+
#
|
|
64
|
+
# @return [String] the formatted date or time
|
|
65
|
+
#
|
|
66
|
+
# @example
|
|
67
|
+
# Date.new(2012, 12, 21).stamp("Jan 1, 1999") #=> "Dec 21, 2012"
|
|
68
|
+
def stamp(example)
|
|
69
|
+
Stamp.format(self, example)
|
|
70
|
+
end
|
|
71
|
+
alias :stamp_like :stamp
|
|
72
|
+
alias :format_like :stamp
|
|
73
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Stamp
|
|
2
|
+
class Disambiguator
|
|
3
|
+
attr_reader :emitters
|
|
4
|
+
|
|
5
|
+
def initialize(emitters)
|
|
6
|
+
@emitters = emitters
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def disambiguate!
|
|
10
|
+
emitters.replace_each! do |emitter|
|
|
11
|
+
disambiguate(emitter)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def disambiguate(emitter)
|
|
18
|
+
if emitter.respond_to?(:disambiguate)
|
|
19
|
+
emitter.disambiguate(emitters)
|
|
20
|
+
else
|
|
21
|
+
emitter
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/stamp/emitters/am_pm.rb
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
module Stamp
|
|
2
2
|
module Emitters
|
|
3
3
|
class AmPm
|
|
4
|
-
|
|
4
|
+
attr_reader :strftime_directive
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@modifier = block
|
|
6
|
+
# @param [Array<String>]values for am pm text
|
|
7
|
+
def initialize(values, strftime_directive)
|
|
8
|
+
@values = values
|
|
9
|
+
@strftime_directive = strftime_directive
|
|
11
10
|
end
|
|
12
11
|
|
|
13
12
|
def format(target)
|
|
14
|
-
|
|
13
|
+
@values[target.hour < 12 ? 0 : 1]
|
|
15
14
|
end
|
|
16
15
|
|
|
17
16
|
def field
|
|
18
17
|
nil
|
|
19
18
|
end
|
|
19
|
+
|
|
20
|
+
UPPERCASE = new(%w(AM PM), "%p")
|
|
21
|
+
LOWERCASE = new(%w(am pm), "%P")
|
|
20
22
|
end
|
|
21
23
|
end
|
|
22
|
-
end
|
|
24
|
+
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,50 @@ 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
|
|
|
20
|
+
# Builds the equivalent strftime format string.
|
|
21
|
+
#
|
|
22
|
+
# @raise [ArgumentError] if any emitter has no strftime equivalent
|
|
23
|
+
def strftime_format
|
|
24
|
+
emitters.map do |e|
|
|
25
|
+
e.strftime_directive ||
|
|
26
|
+
raise(ArgumentError, "#{e.class.name.split('::').last} (e.g. \"1st\") has no strftime equivalent")
|
|
27
|
+
end.join
|
|
28
|
+
end
|
|
29
|
+
|
|
18
30
|
def <<(emitter)
|
|
19
|
-
|
|
20
|
-
emitter.each { |e| @emitters << e }
|
|
21
|
-
else
|
|
22
|
-
@emitters << emitter
|
|
23
|
-
end
|
|
31
|
+
Array(emitter).each { |e| emitters << e }
|
|
24
32
|
end
|
|
25
33
|
|
|
26
34
|
def each(&block)
|
|
27
|
-
|
|
35
|
+
emitters.each(&block)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def -(others)
|
|
39
|
+
emitters - Array(others)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Replace each element as we iterate with the result of the given block.
|
|
43
|
+
def replace_each!
|
|
44
|
+
emitters.each_with_index do |emitter, index|
|
|
45
|
+
emitters[index] = yield(emitter)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
self
|
|
28
49
|
end
|
|
29
50
|
end
|
|
30
51
|
end
|
|
31
|
-
end
|
|
52
|
+
end
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
module Stamp
|
|
2
2
|
module Emitters
|
|
3
3
|
class Delegate
|
|
4
|
-
|
|
4
|
+
attr_reader :field, :strftime_directive
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
# @param [field] the field to be formatted (e.g. +:month+, +:year+)
|
|
9
|
-
def initialize(field, &block)
|
|
6
|
+
# @param [Symbol|String] field the field to be formatted (e.g. +:month+, +:year+)
|
|
7
|
+
def initialize(field, strftime_directive)
|
|
10
8
|
@field = field
|
|
11
|
-
@
|
|
9
|
+
@strftime_directive = strftime_directive
|
|
12
10
|
end
|
|
13
11
|
|
|
12
|
+
# @param [Date|Time|DateTime] target the date to be formatted
|
|
14
13
|
def format(target)
|
|
15
|
-
|
|
14
|
+
target.send(field)
|
|
16
15
|
end
|
|
16
|
+
|
|
17
|
+
ZONE = new(:zone, "%Z")
|
|
18
|
+
YEAR = new(:year, "%Y")
|
|
19
|
+
MONTH = new(:month, "%-m")
|
|
20
|
+
DAY = new(:day, "%-d")
|
|
17
21
|
end
|
|
18
22
|
end
|
|
19
|
-
end
|
|
23
|
+
end
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
module Stamp
|
|
2
2
|
module Emitters
|
|
3
3
|
class Lookup
|
|
4
|
-
attr_reader :field
|
|
4
|
+
attr_reader :field, :strftime_directive
|
|
5
5
|
|
|
6
|
-
# @param [
|
|
7
|
-
# @param [
|
|
8
|
-
|
|
9
|
-
def initialize(field, lookup=nil)
|
|
6
|
+
# @param [Symbol|String] field the field to be formatted (e.g. +:month+, +:year+)
|
|
7
|
+
# @param [Array<String>] lookup an array of the string values to be formatted (e.g. +Date::DAYNAMES+)
|
|
8
|
+
def initialize(field, lookup, strftime_directive)
|
|
10
9
|
@field = field
|
|
11
10
|
@lookup = lookup
|
|
11
|
+
@strftime_directive = strftime_directive
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def format(target)
|
|
@@ -16,12 +16,13 @@ module Stamp
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def lookup(value)
|
|
19
|
-
|
|
20
|
-
@lookup.call(value)
|
|
21
|
-
else
|
|
22
|
-
@lookup[value]
|
|
23
|
-
end
|
|
19
|
+
@lookup[value]
|
|
24
20
|
end
|
|
21
|
+
|
|
22
|
+
MONTH = new(:month, Date::MONTHNAMES, "%B")
|
|
23
|
+
ABBR_MONTH = new(:month, Date::ABBR_MONTHNAMES, "%b")
|
|
24
|
+
DAY = new(:wday, Date::DAYNAMES, "%A")
|
|
25
|
+
ABBR_DAY = new(:wday, Date::ABBR_DAYNAMES, "%a")
|
|
25
26
|
end
|
|
26
27
|
end
|
|
27
|
-
end
|
|
28
|
+
end
|
|
@@ -3,11 +3,12 @@ module Stamp
|
|
|
3
3
|
class Ordinal
|
|
4
4
|
attr_reader :field
|
|
5
5
|
|
|
6
|
-
# @param [
|
|
6
|
+
# @param [Symbol|String] field the field to be formatted (e.g. +:month+, +:year+)
|
|
7
7
|
def initialize(field)
|
|
8
8
|
@field = field
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
# @param [Date|Time|DateTime] target the date to be formatted
|
|
11
12
|
def format(target)
|
|
12
13
|
ordinalize(target.send(field))
|
|
13
14
|
end
|
|
@@ -26,6 +27,13 @@ module Stamp
|
|
|
26
27
|
end
|
|
27
28
|
end
|
|
28
29
|
end
|
|
30
|
+
|
|
31
|
+
# strftime has no directive for ordinals ("1st", "22nd", ...)
|
|
32
|
+
def strftime_directive
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
DAY = new(:day)
|
|
29
37
|
end
|
|
30
38
|
end
|
|
31
|
-
end
|
|
39
|
+
end
|
|
@@ -15,6 +15,12 @@ module Stamp
|
|
|
15
15
|
value << emitter.value
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
# Literal text, with % escaped so strftime passes it through verbatim.
|
|
19
|
+
def strftime_directive
|
|
20
|
+
# value can be nil (unmatched optional capture groups become tokens)
|
|
21
|
+
value.to_s.gsub("%", "%%")
|
|
22
|
+
end
|
|
23
|
+
|
|
18
24
|
def field
|
|
19
25
|
nil
|
|
20
26
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Stamp
|
|
2
|
+
module Emitters
|
|
3
|
+
# Emits hours as a two-digit number with a leading
|
|
4
|
+
# zero if necessary.
|
|
5
|
+
class TwelveHour
|
|
6
|
+
attr_reader :leading_zero
|
|
7
|
+
|
|
8
|
+
# @param [String|Symbol] field the field to be formatted (e.g. +:month+, +:year+)
|
|
9
|
+
# @param [Hash] options the options for output
|
|
10
|
+
# @option options [String] :leading_zero (default: false) pass true add a leading 0 to output
|
|
11
|
+
def initialize(options = {})
|
|
12
|
+
@leading_zero = options[:leading_zero]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @param [Date|Time|DateTime] target the date to be formatted
|
|
16
|
+
def format(target)
|
|
17
|
+
value = target.hour
|
|
18
|
+
value = ((value - 1) % 12) + 1
|
|
19
|
+
if leading_zero && (value < 10)
|
|
20
|
+
"0#{value}"
|
|
21
|
+
else
|
|
22
|
+
value
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def field
|
|
27
|
+
:hour
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def strftime_directive
|
|
31
|
+
leading_zero ? "%I" : "%-I"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
LEADING_ZERO = new(leading_zero: true)
|
|
35
|
+
NON_LEADING_ZERO = new(leading_zero: false)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -3,20 +3,29 @@ module Stamp
|
|
|
3
3
|
# Emits the given field as a two-digit number with a leading
|
|
4
4
|
# zero if necessary.
|
|
5
5
|
class TwoDigit
|
|
6
|
-
|
|
6
|
+
attr_reader :field, :strftime_directive
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
# @param [field] the field to be formatted (e.g. +:month+, +:year+)
|
|
11
|
-
def initialize(field, &block)
|
|
8
|
+
# @param [String|Symbol] field the field to be formatted (e.g. +:month+, +:year+)
|
|
9
|
+
def initialize(field, strftime_directive)
|
|
12
10
|
@field = field
|
|
13
|
-
@
|
|
11
|
+
@strftime_directive = strftime_directive
|
|
14
12
|
end
|
|
15
13
|
|
|
16
14
|
def format(target)
|
|
17
|
-
value =
|
|
18
|
-
|
|
15
|
+
value = target.send(field) % 100
|
|
16
|
+
# by definition, leading_zero option is true
|
|
17
|
+
if value < 10
|
|
18
|
+
"0#{value}"
|
|
19
|
+
else
|
|
20
|
+
value
|
|
21
|
+
end
|
|
19
22
|
end
|
|
23
|
+
YEAR = new(:year, "%y")
|
|
24
|
+
MONTH = new(:month, "%m")
|
|
25
|
+
DAY = new(:day, "%d")
|
|
26
|
+
HOUR = new(:hour, "%H")
|
|
27
|
+
MIN = new(:min, "%M")
|
|
28
|
+
SEC = new(:sec, "%S")
|
|
20
29
|
end
|
|
21
30
|
end
|
|
22
|
-
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Stamp
|
|
2
|
+
module Emitters
|
|
3
|
+
# Emits the target's UTC offset, e.g. "-0500" or "-05:00".
|
|
4
|
+
class UtcOffset
|
|
5
|
+
# @param [String] directive the strftime directive to delegate to
|
|
6
|
+
# (+"%z"+ or +"%:z"+)
|
|
7
|
+
def initialize(directive)
|
|
8
|
+
@directive = directive
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def format(target)
|
|
12
|
+
target.strftime(@directive)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def strftime_directive
|
|
16
|
+
@directive
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def field
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
PLAIN = new("%z")
|
|
24
|
+
COLON = new("%:z")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require "stamp/core"
|
|
2
|
+
|
|
3
|
+
module Stamp
|
|
4
|
+
# An alternative to the default Date/Time monkey patches:
|
|
5
|
+
#
|
|
6
|
+
# require "stamp/refinement"
|
|
7
|
+
# using Stamp::Refinement
|
|
8
|
+
#
|
|
9
|
+
# Date.today.stamp("Jan 1, 1999")
|
|
10
|
+
module Refinement
|
|
11
|
+
[Date, Time].each do |klass|
|
|
12
|
+
refine klass do
|
|
13
|
+
def stamp(example)
|
|
14
|
+
Stamp.format(self, example)
|
|
15
|
+
end
|
|
16
|
+
alias_method :stamp_like, :stamp
|
|
17
|
+
alias_method :format_like, :stamp
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/stamp/translator.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Stamp
|
|
|
5
5
|
# http://en.wikipedia.org/wiki/List_of_time_zone_abbreviations
|
|
6
6
|
TIME_ZONE_ABBREVIATIONS = %w{
|
|
7
7
|
ACDT ACST ACT ADT AEDT AEST AFT AKDT AKST AMST AMT ART AST AWDT AWST AZOST AZT
|
|
8
|
-
BDT BIOT BIT BOT BRT BST
|
|
8
|
+
BDT BIOT BIT BOT BRT BST BTT CAT CCT CDT CEDT CEST CET CHADT CHAST CHOT ChST CHUT
|
|
9
9
|
CIST CIT CKT CLST CLT COST COT CST CT CVT CWST CXT DAVT DDUT DFT EASST EAST EAT
|
|
10
10
|
ECT EDT EEDT EEST EET EGST EGT EIT EST FET FJT FKST FKT FNT GALT GAMT GET GFT
|
|
11
11
|
GILT GIT GMT GST GYT HADT HAEC HAST HKT HMT HOVT HST ICT IDT IOT IRDT IRKT IRST
|
|
@@ -28,54 +28,46 @@ module Stamp
|
|
|
28
28
|
|
|
29
29
|
TIME_REGEXP = /(\d{1,2})(:)(\d{2})(\s*)(:)?(\d{2})?(\s*)?([ap]m)?/i
|
|
30
30
|
|
|
31
|
+
# UTC offsets like "+05:00" or "-0500". The lookbehind/lookahead keep
|
|
32
|
+
# date fragments like "12-31-1999" from matching; hours are capped at 14.
|
|
33
|
+
UTC_OFFSET_REGEXP = /(?<![-\d])[+-](?:0\d|1[0-4]):?[0-5]\d(?!\d)/
|
|
34
|
+
|
|
31
35
|
MERIDIAN_LOWER_REGEXP = /^(a|p)m$/
|
|
32
36
|
MERIDIAN_UPPER_REGEXP = /^(A|P)M$/
|
|
33
37
|
|
|
34
38
|
ORDINAL_DAY_REGEXP = /^(\d{1,2})(st|nd|rd|th)$/
|
|
35
39
|
|
|
36
40
|
# Disambiguate based on value
|
|
37
|
-
OBVIOUS_YEARS = 60..99
|
|
38
|
-
OBVIOUS_MONTHS = 12
|
|
39
|
-
OBVIOUS_DAYS = 13..31
|
|
40
41
|
OBVIOUS_24_HOUR = 13..23
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
TWO_DIGIT_MONTH_EMITTER = Emitters::TwoDigit.new(:month)
|
|
44
|
-
TWO_DIGIT_DAY_EMITTER = Emitters::TwoDigit.new(:day)
|
|
45
|
-
HOUR_TO_12_HOUR = lambda { |h| ((h - 1) % 12) + 1 }
|
|
46
|
-
|
|
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
|
-
}
|
|
42
|
+
OBVIOUS_DAY = 13..31
|
|
43
|
+
OBVIOUS_YEAR = 32..99
|
|
63
44
|
|
|
64
45
|
def translate(example)
|
|
46
|
+
# extract any substrings that look like UTC offsets, like "+05:00" or
|
|
47
|
+
# "-0500", before the time logic can mistake them for clock times
|
|
48
|
+
before, offset_example, after = example.partition(UTC_OFFSET_REGEXP)
|
|
49
|
+
unless offset_example.empty?
|
|
50
|
+
emitters = Emitters::Composite.new
|
|
51
|
+
emitters << translate(before) unless before.empty?
|
|
52
|
+
emitters << (offset_example.include?(":") ? Emitters::UtcOffset::COLON : Emitters::UtcOffset::PLAIN)
|
|
53
|
+
emitters << translate(after) unless after.empty?
|
|
54
|
+
return emitters
|
|
55
|
+
end
|
|
56
|
+
|
|
65
57
|
# extract any substrings that look like times, like "23:59" or "8:37 am"
|
|
66
58
|
before, time_example, after = example.partition(TIME_REGEXP)
|
|
67
59
|
|
|
68
60
|
# build emitters from the example date
|
|
69
61
|
emitters = Emitters::Composite.new
|
|
70
|
-
emitters << build_emitters(before.split(/\b/)) do |token
|
|
71
|
-
date_emitter(token
|
|
62
|
+
emitters << build_emitters(before.split(/\b/)) do |token|
|
|
63
|
+
date_emitter(token)
|
|
72
64
|
end
|
|
73
65
|
|
|
74
66
|
# build emitters from the example time
|
|
75
67
|
unless time_example.empty?
|
|
76
68
|
time_parts = time_example.scan(TIME_REGEXP).first
|
|
77
|
-
emitters << build_emitters(time_parts) do |token
|
|
78
|
-
time_emitter(token
|
|
69
|
+
emitters << build_emitters(time_parts) do |token|
|
|
70
|
+
time_emitter(token)
|
|
79
71
|
end
|
|
80
72
|
end
|
|
81
73
|
|
|
@@ -86,83 +78,83 @@ module Stamp
|
|
|
86
78
|
|
|
87
79
|
# Transforms tokens that look like date/time parts to emitter objects.
|
|
88
80
|
def build_emitters(tokens)
|
|
89
|
-
previous_part = nil
|
|
90
81
|
tokens.map do |token|
|
|
91
|
-
|
|
92
|
-
previous_part = emitter.field unless emitter.nil?
|
|
93
|
-
|
|
94
|
-
emitter || Emitters::String.new(token)
|
|
82
|
+
yield(token) || Emitters::String.new(token)
|
|
95
83
|
end
|
|
96
84
|
end
|
|
97
85
|
|
|
98
|
-
def time_emitter(token
|
|
86
|
+
def time_emitter(token)
|
|
99
87
|
case token
|
|
100
88
|
when MERIDIAN_LOWER_REGEXP
|
|
101
|
-
Emitters::AmPm
|
|
89
|
+
Emitters::AmPm::LOWERCASE
|
|
102
90
|
|
|
103
91
|
when MERIDIAN_UPPER_REGEXP
|
|
104
|
-
Emitters::AmPm
|
|
92
|
+
Emitters::AmPm::UPPERCASE
|
|
105
93
|
|
|
106
94
|
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
|
|
95
|
+
Emitters::Ambiguous.new(
|
|
96
|
+
two_digit_hour_emitter(token),
|
|
97
|
+
Emitters::TwoDigit::MIN,
|
|
98
|
+
Emitters::TwoDigit::SEC)
|
|
116
99
|
|
|
117
100
|
when ONE_DIGIT_REGEXP
|
|
118
101
|
# 12-hour clock without leading zero
|
|
119
|
-
Emitters::
|
|
102
|
+
Emitters::TwelveHour::NON_LEADING_ZERO
|
|
120
103
|
end
|
|
121
104
|
end
|
|
122
105
|
|
|
123
|
-
def
|
|
106
|
+
def two_digit_hour_emitter(token)
|
|
107
|
+
case token.to_i
|
|
108
|
+
when OBVIOUS_24_HOUR
|
|
109
|
+
# 24-hour clock
|
|
110
|
+
Emitters::TwoDigit::HOUR
|
|
111
|
+
else
|
|
112
|
+
# 12-hour clock with leading zero
|
|
113
|
+
Emitters::TwelveHour::LEADING_ZERO
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def date_emitter(token)
|
|
124
118
|
case token
|
|
125
119
|
when MONTHNAMES_REGEXP
|
|
126
|
-
Emitters::Lookup
|
|
120
|
+
Emitters::Lookup::MONTH
|
|
127
121
|
|
|
128
122
|
when ABBR_MONTHNAMES_REGEXP
|
|
129
|
-
Emitters::Lookup
|
|
123
|
+
Emitters::Lookup::ABBR_MONTH
|
|
130
124
|
|
|
131
125
|
when DAYNAMES_REGEXP
|
|
132
|
-
Emitters::Lookup
|
|
126
|
+
Emitters::Lookup::DAY
|
|
133
127
|
|
|
134
128
|
when ABBR_DAYNAMES_REGEXP
|
|
135
|
-
Emitters::Lookup
|
|
129
|
+
Emitters::Lookup::ABBR_DAY
|
|
136
130
|
|
|
137
131
|
when TIMEZONE_REGEXP
|
|
138
|
-
Emitters::Delegate
|
|
132
|
+
Emitters::Delegate::ZONE
|
|
139
133
|
|
|
140
134
|
when FOUR_DIGIT_REGEXP
|
|
141
|
-
Emitters::Delegate
|
|
135
|
+
Emitters::Delegate::YEAR
|
|
142
136
|
|
|
143
137
|
when ORDINAL_DAY_REGEXP
|
|
144
|
-
Emitters::Ordinal
|
|
138
|
+
Emitters::Ordinal::DAY
|
|
145
139
|
|
|
146
140
|
when TWO_DIGIT_REGEXP
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
141
|
+
case token.to_i
|
|
142
|
+
when OBVIOUS_DAY
|
|
143
|
+
Emitters::TwoDigit::DAY
|
|
144
|
+
when OBVIOUS_YEAR
|
|
145
|
+
Emitters::TwoDigit::YEAR
|
|
146
|
+
else
|
|
147
|
+
Emitters::Ambiguous.new(
|
|
148
|
+
Emitters::TwoDigit::MONTH,
|
|
149
|
+
Emitters::TwoDigit::DAY,
|
|
150
|
+
Emitters::TwoDigit::YEAR)
|
|
154
151
|
end
|
|
155
152
|
|
|
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
153
|
when ONE_DIGIT_REGEXP
|
|
163
|
-
Emitters::
|
|
154
|
+
Emitters::Ambiguous.new(
|
|
155
|
+
Emitters::Delegate::MONTH,
|
|
156
|
+
Emitters::Delegate::DAY)
|
|
164
157
|
end
|
|
165
158
|
end
|
|
166
|
-
|
|
167
159
|
end
|
|
168
160
|
end
|
data/lib/stamp/version.rb
CHANGED
data/lib/stamp.rb
CHANGED
|
@@ -1,50 +1,4 @@
|
|
|
1
|
-
require "
|
|
2
|
-
require "time"
|
|
1
|
+
require "stamp/core"
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
require "stamp/emitters/composite"
|
|
7
|
-
require "stamp/emitters/delegate"
|
|
8
|
-
require "stamp/emitters/lookup"
|
|
9
|
-
require "stamp/emitters/ordinal"
|
|
10
|
-
require "stamp/emitters/string"
|
|
11
|
-
require "stamp/emitters/two_digit"
|
|
12
|
-
require "stamp/translator"
|
|
13
|
-
require "stamp/version"
|
|
14
|
-
|
|
15
|
-
module Stamp
|
|
16
|
-
# Limits the number of formats that we memoize to prevent unbounded
|
|
17
|
-
# memory consumption.
|
|
18
|
-
MEMOIZATION_CAP = 999
|
|
19
|
-
|
|
20
|
-
# Formats a date/time using a human-friendly example as a template.
|
|
21
|
-
#
|
|
22
|
-
# @param [String] example a human-friendly date/time example
|
|
23
|
-
# @param [Hash] options
|
|
24
|
-
# @option options [Boolean] :memoize (true)
|
|
25
|
-
#
|
|
26
|
-
# @return [String] the formatted date or time
|
|
27
|
-
#
|
|
28
|
-
# @example
|
|
29
|
-
# Date.new(2012, 12, 21).stamp("Jan 1, 1999") #=> "Dec 21, 2012"
|
|
30
|
-
def stamp(example)
|
|
31
|
-
memoize_stamp_emitters(example).format(self)
|
|
32
|
-
end
|
|
33
|
-
alias :stamp_like :stamp
|
|
34
|
-
alias :format_like :stamp
|
|
35
|
-
|
|
36
|
-
# Memoizes the set of emitter objects for the given +example+ in
|
|
37
|
-
# order to improve performance.
|
|
38
|
-
def memoize_stamp_emitters(example)
|
|
39
|
-
@@memoized_stamp_emitters ||= {}
|
|
40
|
-
@@memoized_stamp_emitters.clear if @@memoized_stamp_emitters.size > MEMOIZATION_CAP
|
|
41
|
-
@@memoized_stamp_emitters[example] ||= stamp_emitters(example)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def stamp_emitters(example)
|
|
45
|
-
Translator.new.translate(example)
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
Date.send(:include, ::Stamp)
|
|
50
|
-
Time.send(:include, ::Stamp)
|
|
3
|
+
Date.include(::Stamp)
|
|
4
|
+
Time.include(::Stamp)
|
data/stamp.gemspec
CHANGED
|
@@ -6,16 +6,22 @@ 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.}
|
|
13
|
+
s.license = "MIT"
|
|
13
14
|
|
|
14
|
-
s.
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
s.metadata = {
|
|
16
|
+
"source_code_uri" => "https://github.com/jeremyw/stamp",
|
|
17
|
+
"bug_tracker_uri" => "https://github.com/jeremyw/stamp/issues"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
s.required_ruby_version = ">= 3.1"
|
|
21
|
+
|
|
22
|
+
s.files = `git ls-files`.split("\n").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
17
23
|
s.require_paths = ["lib"]
|
|
18
24
|
|
|
19
|
-
s.add_development_dependency '
|
|
25
|
+
s.add_development_dependency 'minitest'
|
|
20
26
|
s.add_development_dependency 'rake'
|
|
21
27
|
end
|
metadata
CHANGED
|
@@ -1,111 +1,99 @@
|
|
|
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.7.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: 2026-08-02 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
|
-
name:
|
|
14
|
+
name: minitest
|
|
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
|
-
- .
|
|
55
|
-
- .
|
|
56
|
-
- .yardopts
|
|
49
|
+
- ".github/workflows/ci.yml"
|
|
50
|
+
- ".gitignore"
|
|
51
|
+
- ".yardopts"
|
|
57
52
|
- CONTRIBUTING.md
|
|
58
53
|
- Gemfile
|
|
59
54
|
- LICENSE.txt
|
|
60
55
|
- README.md
|
|
61
56
|
- Rakefile
|
|
62
|
-
- cucumber.yml
|
|
63
|
-
- features/stamp.feature
|
|
64
|
-
- features/step_definitions/stamp_steps.rb
|
|
65
|
-
- features/support/env.rb
|
|
66
57
|
- lib/stamp.rb
|
|
58
|
+
- lib/stamp/core.rb
|
|
59
|
+
- lib/stamp/disambiguator.rb
|
|
67
60
|
- lib/stamp/emitters/am_pm.rb
|
|
61
|
+
- lib/stamp/emitters/ambiguous.rb
|
|
68
62
|
- lib/stamp/emitters/composite.rb
|
|
69
63
|
- lib/stamp/emitters/delegate.rb
|
|
70
64
|
- lib/stamp/emitters/lookup.rb
|
|
71
|
-
- lib/stamp/emitters/modifiable.rb
|
|
72
65
|
- lib/stamp/emitters/ordinal.rb
|
|
73
66
|
- lib/stamp/emitters/string.rb
|
|
67
|
+
- lib/stamp/emitters/twelve_hour.rb
|
|
74
68
|
- lib/stamp/emitters/two_digit.rb
|
|
69
|
+
- lib/stamp/emitters/utc_offset.rb
|
|
70
|
+
- lib/stamp/refinement.rb
|
|
75
71
|
- lib/stamp/translator.rb
|
|
76
72
|
- lib/stamp/version.rb
|
|
77
73
|
- stamp.gemspec
|
|
78
74
|
homepage: https://github.com/jeremyw/stamp
|
|
79
|
-
licenses:
|
|
75
|
+
licenses:
|
|
76
|
+
- MIT
|
|
77
|
+
metadata:
|
|
78
|
+
source_code_uri: https://github.com/jeremyw/stamp
|
|
79
|
+
bug_tracker_uri: https://github.com/jeremyw/stamp/issues
|
|
80
80
|
post_install_message:
|
|
81
81
|
rdoc_options: []
|
|
82
82
|
require_paths:
|
|
83
83
|
- lib
|
|
84
84
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
|
-
none: false
|
|
86
85
|
requirements:
|
|
87
|
-
- -
|
|
86
|
+
- - ">="
|
|
88
87
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '
|
|
90
|
-
segments:
|
|
91
|
-
- 0
|
|
92
|
-
hash: 167698609462982419
|
|
88
|
+
version: '3.1'
|
|
93
89
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
|
-
none: false
|
|
95
90
|
requirements:
|
|
96
|
-
- -
|
|
91
|
+
- - ">="
|
|
97
92
|
- !ruby/object:Gem::Version
|
|
98
93
|
version: '0'
|
|
99
|
-
segments:
|
|
100
|
-
- 0
|
|
101
|
-
hash: 167698609462982419
|
|
102
94
|
requirements: []
|
|
103
|
-
|
|
104
|
-
rubygems_version: 1.8.23
|
|
95
|
+
rubygems_version: 3.0.3.1
|
|
105
96
|
signing_key:
|
|
106
|
-
specification_version:
|
|
97
|
+
specification_version: 4
|
|
107
98
|
summary: Date and time formatting for humans.
|
|
108
|
-
test_files:
|
|
109
|
-
- features/stamp.feature
|
|
110
|
-
- features/step_definitions/stamp_steps.rb
|
|
111
|
-
- features/support/env.rb
|
|
99
|
+
test_files: []
|
data/.travis.yml
DELETED
data/cucumber.yml
DELETED
data/features/stamp.feature
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
@stamp
|
|
2
|
-
Feature: Stamping a date
|
|
3
|
-
In order to format dates in a more programmer-friendly way
|
|
4
|
-
the stamp method
|
|
5
|
-
formats a date given a human-readable example.
|
|
6
|
-
|
|
7
|
-
@date
|
|
8
|
-
Scenario Outline: Formatting dates by example
|
|
9
|
-
Given the date September 8, 2011
|
|
10
|
-
When I stamp the example "<example>"
|
|
11
|
-
Then I produce "<output>"
|
|
12
|
-
|
|
13
|
-
Examples:
|
|
14
|
-
| example | output |
|
|
15
|
-
| January | September |
|
|
16
|
-
| Jan | Sep |
|
|
17
|
-
| Jan 1 | Sep 8 |
|
|
18
|
-
| Jan 01 | Sep 08 |
|
|
19
|
-
| Jan 10 | Sep 08 |
|
|
20
|
-
| Jan 1, 1999 | Sep 8, 2011 |
|
|
21
|
-
| Jan 12, 1999 | Sep 08, 2011 |
|
|
22
|
-
| 13 January 1999 | 08 September 2011 |
|
|
23
|
-
| Monday | Thursday |
|
|
24
|
-
| Tue, Jan 1 | Thu, Sep 8 |
|
|
25
|
-
| Tuesday, January 1, 1999 | Thursday, September 8, 2011 |
|
|
26
|
-
| 01/1999 | 09/2011 |
|
|
27
|
-
| 01/01 | 09/08 |
|
|
28
|
-
| 01/31 | 09/08 |
|
|
29
|
-
| 01/99 | 09/11 |
|
|
30
|
-
| 01/01/1999 | 09/08/2011 |
|
|
31
|
-
| 12/31/99 | 09/08/11 |
|
|
32
|
-
| 31/12 | 08/09 |
|
|
33
|
-
| 31/12/99 | 08/09/11 |
|
|
34
|
-
| 31-Jan-1999 | 08-Sep-2011 |
|
|
35
|
-
| 1999-12-31 | 2011-09-08 |
|
|
36
|
-
| DOB: 12-31-1999 | DOB: 09-08-2011 |
|
|
37
|
-
|
|
38
|
-
@date
|
|
39
|
-
Scenario Outline: Formatting dates with ordinal days
|
|
40
|
-
Given the date <date>
|
|
41
|
-
When I stamp the example "<example>"
|
|
42
|
-
Then I produce "<output>"
|
|
43
|
-
|
|
44
|
-
Examples:
|
|
45
|
-
| date | example | output |
|
|
46
|
-
| Jan 1, 1999 | July 4th | January 1st |
|
|
47
|
-
| Jan 2, 1999 | Dec 3rd | Jan 2nd |
|
|
48
|
-
| Jan 3, 1999 | Dec 2nd | Jan 3rd |
|
|
49
|
-
| Jan 4, 1999 | Jul 1st | Jan 4th |
|
|
50
|
-
| Jan 5, 1999 | Dec 1st | Jan 5th |
|
|
51
|
-
| Jan 6, 1999 | Dec 1st | Jan 6th |
|
|
52
|
-
| Jan 7, 1999 | Dec 1st | Jan 7th |
|
|
53
|
-
| Jan 8, 1999 | Dec 1st | Jan 8th |
|
|
54
|
-
| Jan 9, 1999 | Dec 1st | Jan 9th |
|
|
55
|
-
| Jan 10, 1999 | Dec 1st | Jan 10th |
|
|
56
|
-
| Jan 11, 1999 | Dec 1st | Jan 11th |
|
|
57
|
-
| Jan 12, 1999 | Dec 1st | Jan 12th |
|
|
58
|
-
| Jan 13, 1999 | Dec 1st | Jan 13th |
|
|
59
|
-
| Jan 14, 1999 | Dec 1st | Jan 14th |
|
|
60
|
-
| Jan 20, 1999 | Dec 1st | Jan 20th |
|
|
61
|
-
| Jan 21, 1999 | Dec 1st | Jan 21st |
|
|
62
|
-
| Jan 22, 1999 | Dec 1st | Jan 22nd |
|
|
63
|
-
| Jan 23, 1999 | Dec 1st | Jan 23rd |
|
|
64
|
-
| Jan 24, 1999 | Dec 1st | Jan 24th |
|
|
65
|
-
| Jan 1, 1999 | 4th of July | 1st of January |
|
|
66
|
-
| Jan 1, 1999 | 4th of July, 1999 | 1st of January, 1999 |
|
|
67
|
-
|
|
68
|
-
@time
|
|
69
|
-
Scenario Outline: Formatting times by example
|
|
70
|
-
Given the time zone is "EST"
|
|
71
|
-
And the time February 8, 2011 at 13:31:27
|
|
72
|
-
When I stamp the example "<example>"
|
|
73
|
-
Then I produce "<output>"
|
|
74
|
-
|
|
75
|
-
Examples:
|
|
76
|
-
| example | output |
|
|
77
|
-
| 8:59 am | 1:31 pm |
|
|
78
|
-
| 8:59am | 1:31pm |
|
|
79
|
-
| 08:59 AM | 01:31 PM |
|
|
80
|
-
| 08:59 PM | 01:31 PM |
|
|
81
|
-
| 23:59 | 13:31 |
|
|
82
|
-
| 8:59:59 am | 1:31:27 pm |
|
|
83
|
-
| 08:59:59 AM | 01:31:27 PM |
|
|
84
|
-
| 08:59:59 PM | 01:31:27 PM |
|
|
85
|
-
| 23:59:59 | 13:31:27 |
|
|
86
|
-
| 8:59 PST | 1:31 EST |
|
|
87
|
-
|
|
88
|
-
@date
|
|
89
|
-
@time
|
|
90
|
-
Scenario Outline: Formatting dates and times by example
|
|
91
|
-
Given the time September 8, 2011 at 13:31:27
|
|
92
|
-
When I stamp the example "<example>"
|
|
93
|
-
Then I produce "<output>"
|
|
94
|
-
|
|
95
|
-
Examples:
|
|
96
|
-
| example | output |
|
|
97
|
-
| Jan 1, 1999 8:59 am | Sep 8, 2011 1:31 pm |
|
|
98
|
-
| 08:59 AM 1999-12-31 | 01:31 PM 2011-09-08 |
|
|
99
|
-
| Date: Jan 1, 1999 Time: 8:59 am | Date: Sep 8, 2011 Time: 1:31 pm |
|
|
100
|
-
|
|
101
|
-
Scenario: strftime directives just get passed through
|
|
102
|
-
Given the date December 21, 2012
|
|
103
|
-
When I stamp the example "John Cusack was in a movie about Jan (%-m) %e, %Y, but it wasn't very good."
|
|
104
|
-
Then I produce "John Cusack was in a movie about Dec (%-m) %e, %Y, but it wasn't very good."
|
|
105
|
-
|
|
106
|
-
Scenario: Plain text just gets passed through
|
|
107
|
-
Given the date June 1, 1926
|
|
108
|
-
When I stamp the example "Marilyn Monroe was born on January 9, 1999."
|
|
109
|
-
Then I produce "Marilyn Monroe was born on June 1, 1926."
|
|
110
|
-
|
|
111
|
-
Scenario Outline: Aliases for the stamp method
|
|
112
|
-
Given the date December 9, 2011
|
|
113
|
-
When I call "<alias>" with "1999-01-31"
|
|
114
|
-
Then I produce "2011-12-09"
|
|
115
|
-
|
|
116
|
-
Examples:
|
|
117
|
-
| alias |
|
|
118
|
-
| stamp_like |
|
|
119
|
-
| 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 |
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
module StampStepHelpers
|
|
2
|
-
def month(month_name)
|
|
3
|
-
Date::MONTHNAMES.index(month_name) || Date::ABBR_MONTHNAMES.index(month_name)
|
|
4
|
-
end
|
|
5
|
-
end
|
|
6
|
-
World(StampStepHelpers)
|
|
7
|
-
|
|
8
|
-
Given /^the date (\w+) (\d+), (\d{4})$/ do |month_name, day, year|
|
|
9
|
-
@target = Date.new(year.to_i, month(month_name), day.to_i)
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
Given /^the time (\w+) (\d+), (\d+) at (\d{2}):(\d{2}):(\d{2})$/ do |month_name, day, year, hours, minutes, seconds|
|
|
13
|
-
@target = Time.local(year.to_i, month(month_name), day.to_i, hours.to_i, minutes.to_i, seconds.to_i)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
Given /^the time zone is "(.*?)"$/ do |zone|
|
|
17
|
-
ENV['TZ'] = zone
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
When /^I stamp the example "([^"]*)"$/ do |example|
|
|
21
|
-
@stamped = @target.stamp(example)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
Then /^I produce "([^"]*)"$/ do |expected|
|
|
25
|
-
assert_equal expected.strip, @stamped.strip
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
When /^I call "([^"]*)" with "([^"]*)"$/ do |method, arg|
|
|
29
|
-
@stamped = @target.send(method, arg)
|
|
30
|
-
end
|
data/features/support/env.rb
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
require 'bundler'
|
|
2
|
-
begin
|
|
3
|
-
Bundler.setup(:default, :development)
|
|
4
|
-
rescue Bundler::BundlerError => e
|
|
5
|
-
$stderr.puts e.message
|
|
6
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
|
7
|
-
exit e.status_code
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
|
|
11
|
-
require 'stamp'
|
|
12
|
-
|
|
13
|
-
require 'test/unit/assertions'
|
|
14
|
-
World(Test::Unit::Assertions)
|