Permalink
Browse files

decouple from strftime

  • Loading branch information...
kbrock authored and Jeremy Weiskotten committed Dec 9, 2012
1 parent 9e1032d commit 72af9edba74a04bfdd9adf12b6a4231b4063c10b
View
@@ -0,0 +1,11 @@
+## Contributing to stamp
+
+* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
+* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
+* Fork the project
+* Run `bundle install`
+* Run `rake` to execute the cucumber specs and make sure they all pass
+* Start a feature/bugfix branch
+* Commit and push until you are happy with your contribution
+* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
+* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
View
@@ -1,7 +1,7 @@
# stamp
Format dates and times based on human-friendly examples, not arcane
-strftime directives.
+[strftime](http://strfti.me) directives.
[![Build Status](https://secure.travis-ci.org/jeremyw/stamp.png)](http://travis-ci.org/jeremyw/stamp)
@@ -15,7 +15,7 @@ Your Ruby dates and times get a powerful new method: `stamp`.
You might be concerned that "stamp" isn't descriptive enough for developers
reading your code who aren't familiar with this gem. If that's the case, the
-following aliases are available:
+following aliases are provided:
* `stamp_like`
* `format_like`
@@ -27,11 +27,11 @@ and weekday parts you'd like, and your date will be formatted accordingly:
```ruby
date = Date.new(2011, 6, 9)
-date.stamp("March 1, 1999") #=> "June 9, 2011"
-date.stamp("Jan 1, 1999") #=> "Jun 9, 2011"
+date.stamp("March 1, 1999") #=> "June 9, 2011"
+date.stamp("Jan 1, 1999") #=> "Jun 9, 2011"
date.stamp("Jan 01") #=> "Jun 09"
-date.stamp("Sunday, May 1, 2000") #=> "Thursday, June 9, 2011"
-date.stamp("Sun Aug 5") #=> "Thu Jun 9"
+date.stamp("Sunday, May 1, 2000") #=> "Thursday, June 9, 2011"
+date.stamp("Sun Aug 5") #=> "Thu Jun 9"
date.stamp("12/31/99") #=> "06/09/11"
date.stamp("DOB: 12/31/2000") #=> "DOB: 06/09/2011"
```
@@ -50,11 +50,11 @@ hours, minutes, and seconds when it sees colon-separated values.
```ruby
time = Time.utc(2011, 6, 9, 20, 52, 30)
-time.stamp("3:00 AM") #=> " 8:52 PM"
+time.stamp("3:00 AM") #=> "8:52 PM"
time.stamp("01:00:00 AM") #=> "08:52:30 PM"
time.stamp("23:59") #=> "20:52"
time.stamp("23:59:59") #=> "20:52:30"
-time.stamp("Jan 1 at 01:00 AM") #=> "Jun 9 at 08:52 PM"
+time.stamp("Jan 1 at 01:00 AM") #=> "Jun 9 at 08:52 PM"
time.stamp("23:59 UTC") #=> "20:52 PST"
```
@@ -76,53 +76,29 @@ For example, "01/09" could refer to January 9, September 1, or
January 2009. More explicit examples include "12/31", "31/12", and "12/99".
Using unambiguous values will also help people who read the code in the
-future understand your intent.
+future, including yourself, understand your intent.
### Rails Integration
-Stamp makes it easy to configure your application's common date and time
-formats in a more self-documenting way with the `strftime_format` method:
+Stamp makes it easy to configure your Rails application's common date and time
+formats in a more self-documenting way with `DATE_FORMATS`:
```ruby
-# config/initializers/time_formats.rb
-Date::DATE_FORMATS[:short] = Stamp.strftime_format("Mon Jan 1")
-Time::DATE_FORMATS[:military] = Stamp.strftime_format("23:59")
+# config/initializers/date_formats.rb
+Date::DATE_FORMATS[:short] = Proc.new { |date| date.stamp("Sun Jan 5") }
+Time::DATE_FORMATS[:military] = Proc.new { |time| time.stamp("5 January 23:59") }
```
To use your formats:
```ruby
Date.today.to_s(:short) #=> "Sat Jul 16"
-Time.now.to_s(:military) #=> "15:35"
+Time.now.to_s(:military) #=> "16 July 15:35"
```
### Limitations
-* DateTime should inherit stamp behavior from Date, but it hasn't been thoroughly tested. Patches welcome!
-
-### Advanced Usage
-
-If you need more obscure formatting options, you can include any valid
-[strftime](http://strfti.me) directives in your example string, and they'll
-just be passed along:
-
-```ruby
-Date.today.stamp("Week #%U, 1999") #=> "Week #23, 2011"
-```
-
-Check out [http://strfti.me](http://strfti.me) for more ideas.
-
-## Contributing to stamp
-
-* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
-* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
-* Fork the project
-* Run `bundle install`
-* Run `rake` to execute the cucumber specs and make sure they all pass
-* Start a feature/bugfix branch
-* Commit and push until you are happy with your contribution
-* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
-* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
+* `DateTime` should inherit stamp behavior from `Date`, but it hasn't been thoroughly tested. Patches welcome!
## Copyright
View
@@ -14,15 +14,15 @@ Feature: Stamping a date
| example | output |
| January | September |
| Jan | Sep |
- | Jan 1 | Sep 8 |
+ | Jan 1 | Sep 8 |
| Jan 01 | Sep 08 |
| Jan 10 | Sep 08 |
- | Jan 1, 1999 | Sep 8, 2011 |
+ | Jan 1, 1999 | Sep 8, 2011 |
| Jan 12, 1999 | Sep 08, 2011 |
| 13 January 1999 | 08 September 2011 |
| Monday | Thursday |
- | Tue, Jan 1 | Thu, Sep 8 |
- | Tuesday, January 1, 1999 | Thursday, September 8, 2011 |
+ | Tue, Jan 1 | Thu, Sep 8 |
+ | Tuesday, January 1, 1999 | Thursday, September 8, 2011 |
| 01/1999 | 09/2011 |
| 01/01 | 09/08 |
| 01/31 | 09/08 |
@@ -94,19 +94,19 @@ Feature: Stamping a date
Examples:
| example | output |
- | Jan 1, 1999 8:59 am | Sep 8, 2011 1:31 pm |
+ | Jan 1, 1999 8:59 am | Sep 8, 2011 1:31 pm |
| 08:59 AM 1999-12-31 | 01:31 PM 2011-09-08 |
- | Date: Jan 1, 1999 Time: 8:59 am | Date: Sep 8, 2011 Time: 1:31 pm |
+ | Date: Jan 1, 1999 Time: 8:59 am | Date: Sep 8, 2011 Time: 1:31 pm |
Scenario: strftime directives just get passed through
Given the date December 21, 2012
- When I stamp the example "John Cusack was in a movie about %b %d, %Y, but it wasn't very good."
- Then I produce "John Cusack was in a movie about Dec 21, 2012, but it wasn't very good."
+ When I stamp the example "John Cusack was in a movie about Jan (%-m) %e, %Y, but it wasn't very good."
+ Then I produce "John Cusack was in a movie about Dec (%-m) %e, %Y, but it wasn't very good."
Scenario: Plain text just gets passed through
Given the date June 1, 1926
- When I stamp the example "Marilyn Monroe was born on January 1, 1999."
- Then I produce "Marilyn Monroe was born on June 1, 1926."
+ When I stamp the example "Marilyn Monroe was born on January 9, 1999."
+ Then I produce "Marilyn Monroe was born on June 1, 1926."
Scenario Outline: Aliases for the stamp method
Given the date December 9, 2011
View
@@ -1,53 +1,50 @@
require "date"
require "time"
+require "stamp/emitters/modifiable"
+require "stamp/emitters/am_pm"
+require "stamp/emitters/composite"
+require "stamp/emitters/delegate"
+require "stamp/emitters/lookup"
+require "stamp/emitters/ordinal"
+require "stamp/emitters/string"
+require "stamp/emitters/two_digit"
require "stamp/translator"
require "stamp/version"
module Stamp
-
- # Transforms the given example dates/time format to a format string
- # suitable for strftime.
- #
- # @param [String] example a human-friendly date/time example
- # @param [#strftime] the Date or Time to be formatted. Optional, but may
- # be used to support certain edge cases
- # @return [String] a strftime-friendly format
- #
- # @example
- # Stamp.strftime_format("Jan 1, 1999") #=> "%b %e, %Y"
- def self.strftime_format(example, target=nil)
- Stamp::StrftimeTranslator.new(target).translate(example)
- end
+ # Limits the number of formats that we memoize to prevent unbounded
+ # memory consumption.
+ MEMOIZATION_CAP = 999
# Formats a date/time using a human-friendly example as a template.
#
- # @param [String] example a human-friendly date/time example
+ # @param [String] example a human-friendly date/time example
+ # @param [Hash] options
+ # @option options [Boolean] :memoize (true)
+ #
# @return [String] the formatted date or time
#
# @example
# Date.new(2012, 12, 21).stamp("Jan 1, 1999") #=> "Dec 21, 2012"
def stamp(example)
- strftime(strftime_format(example))
+ memoize_stamp_emitters(example).format(self)
end
alias :stamp_like :stamp
alias :format_like :stamp
- # Transforms the given example date/time format to a format string
- # suitable for strftime.
- #
- # @param [String] example a human-friendly date/time example
- # @return [String] a strftime-friendly format
- #
- # @example
- # Date.today.strftime_format("Jan 1, 1999") #=> "%b %e, %Y"
- def strftime_format(example)
- # delegate to the class method, providing self as a target value to
- # support certain edge cases
- Stamp.strftime_format(example, self)
+ # Memoizes the set of emitter objects for the given +example+ in
+ # order to improve performance.
+ def memoize_stamp_emitters(example)
+ @@memoized_stamp_emitters ||= {}
+ @@memoized_stamp_emitters.clear if @@memoized_stamp_emitters.size > MEMOIZATION_CAP
+ @@memoized_stamp_emitters[example] ||= stamp_emitters(example)
end
+ def stamp_emitters(example)
+ Translator.new.translate(example)
+ end
end
Date.send(:include, ::Stamp)
-Time.send(:include, ::Stamp)
+Time.send(:include, ::Stamp)
@@ -0,0 +1,22 @@
+module Stamp
+ module Emitters
+ class AmPm
+ include Modifiable
+
+ AM = 'am'
+ PM = 'pm'
+
+ def initialize(&block)
+ @modifier = block
+ end
+
+ def format(target)
+ modify(target.hour < 12 ? AM : PM)
+ end
+
+ def field
+ nil
+ end
+ end
+ end
+end
@@ -0,0 +1,31 @@
+module Stamp
+ module Emitters
+ class Composite
+ include Enumerable
+
+ def initialize
+ @emitters = []
+ end
+
+ def format(target)
+ # NOTE using #each to build string because benchmarking shows
+ # that it's ~20% faster than .map.join('')
+ result = ''
+ @emitters.each { |e| result << e.format(target).to_s }
+ result
+ end
+
+ def <<(emitter)
+ if emitter.is_a?(Enumerable)
+ emitter.each { |e| @emitters << e }
+ else
+ @emitters << emitter
+ end
+ end
+
+ def each(&block)
+ @emitters.each(&block)
+ end
+ end
+ end
+end
@@ -0,0 +1,19 @@
+module Stamp
+ module Emitters
+ class Delegate
+ include Modifiable
+
+ attr_reader :field
+
+ # @param [field] the field to be formatted (e.g. +:month+, +:year+)
+ def initialize(field, &block)
+ @field = field
+ @modifier = block
+ end
+
+ def format(target)
+ modify(target.send(field))
+ end
+ end
+ end
+end
@@ -0,0 +1,27 @@
+module Stamp
+ module Emitters
+ class Lookup
+ attr_reader :field
+
+ # @param [field] the field to be formatted (e.g. +:month+, +:year+)
+ # @param [lookup] an array of the string values to be formatted (e.g. +Date::DAYNAMES+)
+ # or a +call+able that returns the formatted value
+ def initialize(field, lookup=nil)
+ @field = field
+ @lookup = lookup
+ end
+
+ def format(target)
+ lookup(target.send(field))
+ end
+
+ def lookup(value)
+ if @lookup.respond_to?(:call)
+ @lookup.call(value)
+ else
+ @lookup[value]
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,9 @@
+module Modifiable
+ def modify(value)
+ if @modifier
+ @modifier.call(value)
+ else
+ value
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit 72af9ed

Please sign in to comment.