flex_date 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +95 -0
- data/Rakefile +84 -0
- data/lib/flex_date.rb +120 -0
- data/test/flex_date_test.rb +62 -0
- metadata +50 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d8c5b1d1dd3cb06b8f3fc0c101dc59f00c5c809c
|
4
|
+
data.tar.gz: 6286c685d1e746ecd0c5259bd94ec3e657a7437d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1fcf79ccd39d5da8ffb6afc8bda0f1f6342d05f09e2f3e81cac52c275920a33ca93d09365f712e842601d6056d60b40ac2a0df18b7deecc8ec09aab31d48411b
|
7
|
+
data.tar.gz: 01bc8e5ae933677cb05ba694d61dbd175ae18016e22c2fa15da36d14447b36bd6083d3724fce061567823f16df8cdc0d5d10d6c84caba072b1f7467b81716d96
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
= FlexDate
|
2
|
+
|
3
|
+
Flexible dates for Ruby.
|
4
|
+
|
5
|
+
<b>FlexDate is currently compatible with Rails 2.x and Rails 3.</b>
|
6
|
+
|
7
|
+
|
8
|
+
== The Problem
|
9
|
+
|
10
|
+
You are collecting dates of historic events but you don't always have <i>exact</i> dates. For example, you might know that an event occurred in March of 1862 but you don't know the exact day. You could store this as a Ruby Date object that looks like '1862-03-01' but, really, you don't want to specify a day at all. You want to <b>explicitly not store</b> the day part of the date.
|
11
|
+
|
12
|
+
|
13
|
+
== The Solution
|
14
|
+
|
15
|
+
FlexDate addresses this problem by extending Ruby's included Date class. At the moment the following methods are provided to create partial dates:
|
16
|
+
|
17
|
+
FlexDate.new(year = nil, month = nil, date = nil)
|
18
|
+
year=(int)
|
19
|
+
month=(int)
|
20
|
+
day=(int)
|
21
|
+
|
22
|
+
Where a full date (year, month, and day) is specified, a FlexDate behaves exactly like a normal Ruby Date. When a partial date is specified, only the following methods will work:
|
23
|
+
|
24
|
+
year
|
25
|
+
month
|
26
|
+
day
|
27
|
+
to_s
|
28
|
+
strftime
|
29
|
+
|
30
|
+
Comparison operators also (== and <=>) work and I hope to have date arithmetic operators (+ and -) up and running soon.
|
31
|
+
|
32
|
+
|
33
|
+
== Installation
|
34
|
+
|
35
|
+
To install FlexDate, use the standard Rails plugin installation script:
|
36
|
+
|
37
|
+
script/plugin install git://github.com/alexreisner/flex_date.git
|
38
|
+
|
39
|
+
|
40
|
+
== Integration with Rails/ActiveRecord
|
41
|
+
|
42
|
+
The MultipartDate module facilitates integration of FlexDate with ActiveRecord models. It requires that you store each date in three separate columns, each with a _y, _m, or _d suffix. So, if you have a Person model that stores birthdays, the columns required would be (all integers):
|
43
|
+
|
44
|
+
birthday_y
|
45
|
+
birthday_m
|
46
|
+
birthday_d
|
47
|
+
|
48
|
+
Then, simply put this in your Person model (you can list multiple attributes if needed):
|
49
|
+
|
50
|
+
multipart_date :birthday
|
51
|
+
|
52
|
+
and you'll be able to do things like this:
|
53
|
+
|
54
|
+
# Initialize a new Person object (don't know year they were born).
|
55
|
+
p = Person.new(:birthday_m => 8, :birthday_d => 23)
|
56
|
+
|
57
|
+
# See if their birthday is set.
|
58
|
+
p.birthday?
|
59
|
+
=> true
|
60
|
+
|
61
|
+
# Get their birthday as a FlexDate object.
|
62
|
+
b = p.birthday
|
63
|
+
=> #<FlexDate:0xb78b542c ...>
|
64
|
+
|
65
|
+
# Display their birthday nicely.
|
66
|
+
p.birthday.to_s(:long)
|
67
|
+
=> "August 23"
|
68
|
+
|
69
|
+
# Add the year when you find out.
|
70
|
+
p.birthday_y = 1964
|
71
|
+
p.birthday.to_s(:long)
|
72
|
+
=> "August 23, 1964"
|
73
|
+
|
74
|
+
|
75
|
+
I've also written another Rails plugin called Informant which provides a full-featured FormBuilder class, and includes a <tt>multipart_date_select</tt> field type which is a good compliment to MultipartDate in your Rails application. Install Informant from my Git repository:
|
76
|
+
|
77
|
+
script/plugin install git://github.com/alexreisner/informant.git
|
78
|
+
|
79
|
+
|
80
|
+
== Code Discussion
|
81
|
+
|
82
|
+
You might expect FlexDate to be a subclass of Date, but this is not the case. That implementation would be far more complicated because the instance methods of Date require certain instance variables to be set, and to meet certain requirements for validity. Massaging the Date class to accept an invalid date seems like the wrong approach for the modest goals of FlexDate, which is simply to allow storing of partial dates, and duplicate the behavior of the Date class <b>when a date is complete</b>.
|
83
|
+
|
84
|
+
So instead, FlexDate is a very simple class that inherits from nothing, provides some (hopefully) useful methods for partial dates, and uses <tt>method_missing</tt> to fall back to Date when a FlexDate object is complete (specifies year, month, and day).
|
85
|
+
|
86
|
+
Any comments or suggestions regarding this design are welcome.
|
87
|
+
|
88
|
+
|
89
|
+
== To-do List
|
90
|
+
|
91
|
+
* <tt>MultipartDate::multipart_date</tt> should also generate a setter method.
|
92
|
+
* Make date arithmetic work (Date minus FlexDate works, but neither FlexDate minus Date or FlexDate minus FlexDate works).
|
93
|
+
|
94
|
+
|
95
|
+
Copyright (c) 2008 Alex Reisner (alex@alexreisner.com), released under the MIT license.
|
data/Rakefile
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require "rubygems"
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rdoc/task'
|
5
|
+
require "rubygems/package_task"
|
6
|
+
|
7
|
+
desc 'Default: run unit tests.'
|
8
|
+
task :default => :test
|
9
|
+
|
10
|
+
require "rake/testtask"
|
11
|
+
desc 'Test the flex_date plugin.'
|
12
|
+
Rake::TestTask.new(:test) do |t|
|
13
|
+
t.libs << "test"
|
14
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
15
|
+
t.verbose = true
|
16
|
+
end
|
17
|
+
task :default => ["test"]
|
18
|
+
|
19
|
+
# This builds the actual gem. For details of what all these options
|
20
|
+
# mean, and other ones you can add, check the documentation here:
|
21
|
+
#
|
22
|
+
# http://rubygems.org/read/chapter/20
|
23
|
+
#
|
24
|
+
spec = Gem::Specification.new do |s|
|
25
|
+
|
26
|
+
# Change these as appropriate
|
27
|
+
s.name = "flex_date"
|
28
|
+
s.version = "0.1.0"
|
29
|
+
s.summary = "Flexible dates for Ruby"
|
30
|
+
s.author = "Sean Gregory"
|
31
|
+
s.email = "seangregory@gmail.com"
|
32
|
+
s.homepage = "https://github.com/skinnyjames/flex_date"
|
33
|
+
|
34
|
+
s.has_rdoc = true
|
35
|
+
s.extra_rdoc_files = %w(README.rdoc)
|
36
|
+
s.rdoc_options = %w(--main README.rdoc)
|
37
|
+
|
38
|
+
# Add any extra files to include in the gem
|
39
|
+
s.files = %w(README.rdoc Rakefile MIT-LICENSE) + Dir.glob("{test,lib}/**/*")
|
40
|
+
s.require_paths = ["lib"]
|
41
|
+
|
42
|
+
# If you want to depend on other gems, add them here, along with any
|
43
|
+
# relevant versions
|
44
|
+
# s.add_dependency("some_other_gem", "~> 0.1.0")
|
45
|
+
|
46
|
+
# If your tests use any gems, include them here
|
47
|
+
# s.add_development_dependency("mocha") # for example
|
48
|
+
end
|
49
|
+
|
50
|
+
# This task actually builds the gem. We also regenerate a static
|
51
|
+
# .gemspec file, which is useful if something (i.e. GitHub) will
|
52
|
+
# be automatically building a gem for this project. If you're not
|
53
|
+
# using GitHub, edit as appropriate.
|
54
|
+
#
|
55
|
+
# To publish your gem online, install the 'gemcutter' gem; Read more
|
56
|
+
# about that here: http://gemcutter.org/pages/gem_docs
|
57
|
+
Gem::PackageTask.new(spec) do |pkg|
|
58
|
+
pkg.gem_spec = spec
|
59
|
+
end
|
60
|
+
|
61
|
+
desc "Build the gemspec file #{spec.name}.gemspec"
|
62
|
+
task :gemspec do
|
63
|
+
file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
|
64
|
+
File.open(file, "w") {|f| f << spec.to_ruby }
|
65
|
+
end
|
66
|
+
|
67
|
+
# If you don't want to generate the .gemspec file, just remove this line. Reasons
|
68
|
+
# why you might want to generate a gemspec:
|
69
|
+
# - using bundler with a git source
|
70
|
+
# - building the gem without rake (i.e. gem build blah.gemspec)
|
71
|
+
# - maybe others?
|
72
|
+
task :package => :gemspec
|
73
|
+
|
74
|
+
# Generate documentation
|
75
|
+
RDoc::Task.new do |rd|
|
76
|
+
rd.main = "README.rdoc"
|
77
|
+
rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
78
|
+
rd.rdoc_dir = "rdoc"
|
79
|
+
end
|
80
|
+
|
81
|
+
desc 'Clear out RDoc and generated packages'
|
82
|
+
task :clean => [:clobber_rdoc, :clobber_package] do
|
83
|
+
rm "#{spec.name}.gemspec"
|
84
|
+
end
|
data/lib/flex_date.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'date'
|
2
|
+
class FlexDate
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
def <=>(other)
|
6
|
+
spaceship = 0 # start assuming equal
|
7
|
+
%w(year month day).each do |p|
|
8
|
+
a = eval("self.#{p}") || 0
|
9
|
+
b = eval("other.#{p}") || 0
|
10
|
+
break if (spaceship = a <=> b) != 0
|
11
|
+
end
|
12
|
+
spaceship
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
[year,month,day] == [other.year, other.month, other.day]
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_accessor :year, :month, :day
|
20
|
+
|
21
|
+
##
|
22
|
+
# Initialize the FlexDate with whichever are available: year, month, and day.
|
23
|
+
#
|
24
|
+
def initialize(year = nil, month = nil, day = nil)
|
25
|
+
@year, @month, @day = year, month, day
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Define some preset date formats.
|
30
|
+
#
|
31
|
+
DATE_FORMATS = {
|
32
|
+
:short => "%b %e",
|
33
|
+
:medium => "%e %b %Y",
|
34
|
+
:long => "%B %e, %Y",
|
35
|
+
:db => "%Y-%m-%d",
|
36
|
+
:number => "%Y%m%d",
|
37
|
+
:rfc822 => "%e %b %Y"
|
38
|
+
}
|
39
|
+
|
40
|
+
##
|
41
|
+
# String representation of a date. Pass a <tt>strftime</tt>-format string
|
42
|
+
# or a symbol which is a key in the DATE_FORMATS hash.
|
43
|
+
#
|
44
|
+
def to_s(format = :medium)
|
45
|
+
format = DATE_FORMATS[format] if format.is_a?(Symbol)
|
46
|
+
s = strftime(format)
|
47
|
+
|
48
|
+
# Do some rough cleanup (this isn't very "programmatic").
|
49
|
+
replacements = [
|
50
|
+
[/^[ ,]+/, ''], # leading whitespace and commas
|
51
|
+
[/[ ,]+$/, ''], # trailing whitespace and commas
|
52
|
+
[/ +, +/, ' '], # commas and whitespace in middle
|
53
|
+
]
|
54
|
+
s.strip!
|
55
|
+
replacements.each do |r|
|
56
|
+
s.gsub!(r[0], r[1])
|
57
|
+
end
|
58
|
+
s
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Does this FlexDate specify a year, month, and day?
|
63
|
+
#
|
64
|
+
def complete?
|
65
|
+
not (@year and @month and @day).nil?
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Same as Date#strftime but gracefully removes missing parts.
|
70
|
+
#
|
71
|
+
def strftime(format = '%F')
|
72
|
+
# If we have a complete date, just let Date handle it.
|
73
|
+
return real_date.strftime(format) if complete?
|
74
|
+
|
75
|
+
# If we have a partial date, define token dependencies.
|
76
|
+
dependencies = {
|
77
|
+
:year => %w(C c D F G g u V v w x Y y),
|
78
|
+
:month => %w(B b c D F h m u V v w x),
|
79
|
+
:day => %w(A a c D d e F j u V v w x),
|
80
|
+
:time => %w(H I k L M N P p Q R r S s T X Z)
|
81
|
+
}
|
82
|
+
|
83
|
+
# Remove tokens that refer to missing parts.
|
84
|
+
format = format.gsub(/%([-_0^#]+)?(\d+)?[EO]?(:{1,3}z|.)/m) do |m|
|
85
|
+
s, w, c = $1, $2, $3
|
86
|
+
ok = true
|
87
|
+
dependencies.each do |attr,tokens|
|
88
|
+
missing = instance_variable_get("@#{attr}").nil?
|
89
|
+
ok = false if (tokens.include?(c) and missing)
|
90
|
+
end
|
91
|
+
ok ? m : ''
|
92
|
+
end
|
93
|
+
phony_date.strftime(format)
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Pass unimplemented methods along to a regular Date object if possible.
|
98
|
+
#
|
99
|
+
def method_missing(name, *args)
|
100
|
+
real_date.send(name, *args) if complete?
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
private # -------------------------------------------------------------------
|
105
|
+
|
106
|
+
##
|
107
|
+
# Get a Date object based on the current values of @year, @month, and @day.
|
108
|
+
# Returns nil unless all three are known.
|
109
|
+
#
|
110
|
+
def real_date
|
111
|
+
Date.new(@year, @month, @day) if complete?
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Get a Date object with "1" set for any unknown parts.
|
116
|
+
#
|
117
|
+
def phony_date
|
118
|
+
Date.new((@year || 1), (@month || 1), (@day || 1))
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
$:.unshift File.join(File.dirname(__FILE__), '../lib')
|
3
|
+
require 'flex_date'
|
4
|
+
|
5
|
+
class FlexDateTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def test_creation
|
8
|
+
assert FlexDate.new(1953, 11, 2).is_a?(FlexDate)
|
9
|
+
assert FlexDate.new(1953, 11).is_a?(FlexDate)
|
10
|
+
assert FlexDate.new(1953).is_a?(FlexDate)
|
11
|
+
assert FlexDate.new(nil, 11).is_a?(FlexDate)
|
12
|
+
assert FlexDate.new(nil, 11, 2).is_a?(FlexDate)
|
13
|
+
assert FlexDate.new(nil, nil, 2).is_a?(FlexDate)
|
14
|
+
assert FlexDate.new.is_a?(FlexDate)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_accessors
|
18
|
+
f = FlexDate.new
|
19
|
+
|
20
|
+
assert !f.complete?
|
21
|
+
assert f.year.nil?
|
22
|
+
f.year = 1794
|
23
|
+
assert_equal 1794, f.year
|
24
|
+
|
25
|
+
assert !f.complete?
|
26
|
+
assert f.month.nil?
|
27
|
+
f.month = 9
|
28
|
+
assert_equal 9, f.month
|
29
|
+
|
30
|
+
assert !f.complete?
|
31
|
+
assert f.day.nil?
|
32
|
+
f.day = 4
|
33
|
+
assert_equal 4, f.day
|
34
|
+
|
35
|
+
assert f.complete?
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_formatting
|
39
|
+
f = FlexDate.new
|
40
|
+
assert_equal "", f.strftime("%Y")
|
41
|
+
f.year = 2087
|
42
|
+
assert_equal "2087", f.strftime("%Y")
|
43
|
+
assert_equal "2087-", f.strftime("%Y-%m")
|
44
|
+
assert_equal "2087", f.to_s("%Y")
|
45
|
+
assert_equal "2087", f.to_s(:long)
|
46
|
+
f.month = 8
|
47
|
+
assert_equal "Aug 2087", f.to_s("%b %e, %Y")
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_comparison_and_equality
|
51
|
+
a = FlexDate.new(2008,6,4)
|
52
|
+
b = FlexDate.new(2004,9,30)
|
53
|
+
assert_equal 0, a <=> a
|
54
|
+
assert a == a
|
55
|
+
assert a != b
|
56
|
+
assert_equal 1, a <=> b
|
57
|
+
c = FlexDate.new(2004)
|
58
|
+
d = FlexDate.new(1980,8,3)
|
59
|
+
e = FlexDate.new(1945,6)
|
60
|
+
assert_equal [e,d,c,b,a], [b,e,a,d,c].sort
|
61
|
+
end
|
62
|
+
end
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: flex_date
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sean Gregory
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-18 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email: seangregory@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files:
|
18
|
+
- README.rdoc
|
19
|
+
files:
|
20
|
+
- MIT-LICENSE
|
21
|
+
- README.rdoc
|
22
|
+
- Rakefile
|
23
|
+
- lib/flex_date.rb
|
24
|
+
- test/flex_date_test.rb
|
25
|
+
homepage: https://github.com/skinnyjames/flex_date
|
26
|
+
licenses: []
|
27
|
+
metadata: {}
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options:
|
30
|
+
- "--main"
|
31
|
+
- README.rdoc
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project:
|
46
|
+
rubygems_version: 2.5.2
|
47
|
+
signing_key:
|
48
|
+
specification_version: 4
|
49
|
+
summary: Flexible dates for Ruby
|
50
|
+
test_files: []
|