date_as_string 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rspec +1 -0
- data/Gemfile +10 -0
- data/Rakefile +28 -0
- data/date_as_string.gemspec +22 -0
- data/lib/date_as_string.rb +222 -0
- data/lib/date_as_string/formats.rb +55 -0
- data/lib/date_as_string/version.rb +12 -0
- data/spec/date_as_string_spec.rb +80 -0
- data/spec/spec_helper.rb +15 -0
- metadata +68 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f8bde9b4c771dda4f6270bb5270de295af06337e4352a55f425e4aaaa277ac21
|
4
|
+
data.tar.gz: '087b9878efd66ca01a9e60a5aa74fd7d6dc057edb680c570f70eaefcf6508878'
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 44c6573aee04ef19c850c36daabf1093ce2684fd266e48850fd51bab1f055acb214721960071c5b6932eb38467f8f7f42f4a692276254f0b094cd2a8eaafa13c
|
7
|
+
data.tar.gz: b029b37c736ef35ecad4e3ffe4cf1ef67c12f7e37b21a025831198cbb7be137ab3281620f8651b645ab69a2fa601d99d4a71297ee12feb3309843ff13dd12b2f
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
#Override bundler to prevent releasing to RubyGems
|
4
|
+
module Bundler
|
5
|
+
class GemHelper
|
6
|
+
def release_gem
|
7
|
+
guard_clean
|
8
|
+
guard_already_tagged
|
9
|
+
built_gem_path = build_gem
|
10
|
+
tag_version {
|
11
|
+
git_push
|
12
|
+
}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
#http://asciicasts.com/episodes/245-new-gem-with-bundler
|
18
|
+
#$ rake -T
|
19
|
+
#rake build # Build mesav-#.#.#.gem into the pkg directory
|
20
|
+
#rake install # Build and install mesav-#.#.#.gem into system gems
|
21
|
+
#rake release # Create tag v#.#.#, build, and push
|
22
|
+
|
23
|
+
#RSPEC
|
24
|
+
require 'rspec/core/rake_task'
|
25
|
+
|
26
|
+
RSpec::Core::RakeTask.new('spec')
|
27
|
+
|
28
|
+
task :default => :spec
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "date_as_string/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "date_as_string"
|
7
|
+
s.version = DateAsString::VERSION::STRING
|
8
|
+
s.authors = ["Eric Sullivan"]
|
9
|
+
s.email = ["eric.sullivan@annkissam.com"]
|
10
|
+
s.homepage = "http://annkissam.com"
|
11
|
+
s.summary = %q{Convert from Date to String and vice versa by attaching _string suffix to an ActiveRecord field}
|
12
|
+
s.description = %q{Treat an ActiveRecord Date column as a String}
|
13
|
+
|
14
|
+
s.rubyforge_project = "date_as_string"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency "rspec"
|
22
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
require "date_as_string/version"
|
2
|
+
|
3
|
+
require "date_as_string/formats"
|
4
|
+
|
5
|
+
module DateAsString
|
6
|
+
def self.guess_century(year, cutoff=20)
|
7
|
+
century = get_century_from_year_range(year, cutoff)
|
8
|
+
|
9
|
+
century.present? ? century : raise_century_error
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
base.send(:extend, DateAsString::ClassMethods)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse_date(date)
|
17
|
+
date.respond_to?(:strftime) ? date.strftime('%m/%d/%Y') : nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.parse_string(date_string)
|
21
|
+
begin
|
22
|
+
case
|
23
|
+
when t_regex.match(date_string)
|
24
|
+
date_for_t_match(date_string)
|
25
|
+
when mmddyy_regex.match(date_string)
|
26
|
+
date_for_mmddyy_match(date_string)
|
27
|
+
when mmddyy_regex(with_slashes: true).match(date_string)
|
28
|
+
date_for_mmddyy_match(date_string, with_slashes: true)
|
29
|
+
when mmddyyyy_regex.match(date_string)
|
30
|
+
date_for_mmddyyyy_match(date_string)
|
31
|
+
when mmddyyyy_regex(with_slashes: true).match(date_string)
|
32
|
+
date_for_mmddyyyy_match(date_string, with_slashes: true)
|
33
|
+
else
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
rescue
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module ClassMethods
|
42
|
+
def date_as_string(*args)
|
43
|
+
defaults = {:input_formats => [:mm_dd_yyyy, :mm_dd_yyyy, :mmddyyyy, :mmddyy, :t_pmd],
|
44
|
+
:default_display_format => :mm_dd_yyyy,
|
45
|
+
:century_cutoff => 20,
|
46
|
+
:default => nil}
|
47
|
+
options = args.last.is_a?(Hash) ? defaults.merge(args.pop) : defaults
|
48
|
+
|
49
|
+
#We're current not doing anything with the options...
|
50
|
+
#We should support default formats, default value, and allowable formats...
|
51
|
+
|
52
|
+
unless self.respond_to?(:date_as_string_options)
|
53
|
+
cattr_accessor :date_as_string_options
|
54
|
+
self.date_as_string_options = {}
|
55
|
+
end
|
56
|
+
|
57
|
+
args.each do |attribute_name|
|
58
|
+
self.date_as_string_options[attribute_name] = options
|
59
|
+
|
60
|
+
validate :"#{attribute_name}_format"
|
61
|
+
|
62
|
+
define_method "#{attribute_name}_string" do |*display_format|
|
63
|
+
if instance_variable_get("@#{attribute_name}_string")
|
64
|
+
instance_variable_get("@#{attribute_name}_string")
|
65
|
+
else
|
66
|
+
if self.send("#{attribute_name}").nil?
|
67
|
+
if options[:default] && options[:default].is_a?(String)
|
68
|
+
eval(options[:default])
|
69
|
+
elsif options[:default] && options[:default].is_a?(Proc)
|
70
|
+
options[:default].call
|
71
|
+
else
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
else
|
75
|
+
#self.send("#{attribute_name}").strftime('%m/%d/%Y')
|
76
|
+
unless display_format.empty?
|
77
|
+
DateAsString::Formats.find_format(display_format[0]).strftime(self.send("#{attribute_name}"))
|
78
|
+
else
|
79
|
+
DateAsString::Formats.find_format(options[:default_display_format]).strftime(self.send("#{attribute_name}"))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
define_method "#{attribute_name}_string=" do |str|
|
86
|
+
instance_variable_set("@#{attribute_name}_string", str)
|
87
|
+
self.send("#{attribute_name}=", DateAsString.parse_string(str))
|
88
|
+
end
|
89
|
+
|
90
|
+
define_method "#{attribute_name}_format" do
|
91
|
+
re = /^(([tT]([\-\+]\d+)?)|(\d{6})|(\d{8})|([0-1]?\d\/[0-3]?\d\/\d\d)|([0-1]?\d\/[0-3]?\d\/(19|20)\d\d))$/
|
92
|
+
|
93
|
+
attribute_string = instance_variable_get("@#{attribute_name}_string")
|
94
|
+
|
95
|
+
#No string was entered - this clears the date
|
96
|
+
return if attribute_string.blank?
|
97
|
+
|
98
|
+
#the entered string did not match a regex...
|
99
|
+
if (attribute_string =~ re).nil?
|
100
|
+
errors.add("#{attribute_name}_string", "must be in the format of MM/DD/YY, MM/DD/YYYY, MMDDYY, MMDDYYYY or t, t+#, t-#")
|
101
|
+
return
|
102
|
+
end
|
103
|
+
|
104
|
+
#The mapping did not work for some reason...
|
105
|
+
if self.send("#{attribute_name}").nil?
|
106
|
+
errors.add("#{attribute_name}_string", :invalid)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def self.year_range(cutoff)
|
116
|
+
current_year = Time&.zone&.today&.year || Date.today.year
|
117
|
+
|
118
|
+
range_start = current_year - (99 - cutoff)
|
119
|
+
range_end = current_year + cutoff
|
120
|
+
|
121
|
+
range_start..range_end
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.get_century_from_year_range(year, cutoff=20)
|
125
|
+
year_range(cutoff).detect { |temp_year| temp_year % 100 == year }
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.raise_century_error
|
129
|
+
raise RangeError.new('Error converting year...')
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.t_regex
|
133
|
+
/^[tT]((?<operator>[\-\+])(?<days_count>\d+))?$/
|
134
|
+
end
|
135
|
+
|
136
|
+
#Allow MMDDYY or MM/DD/YY
|
137
|
+
def self.mmddyy_regex(options={})
|
138
|
+
with_slashes = options.fetch(:with_slashes, false)
|
139
|
+
|
140
|
+
slash_str = with_slashes ? '/' : nil
|
141
|
+
month_str = '(?<month>[0-1]?\d)'
|
142
|
+
day_str = '(?<day>[0-3]?\d)'
|
143
|
+
year_str = '(?<year>\d{2})'
|
144
|
+
|
145
|
+
date_tup = [month_str, day_str, year_str]
|
146
|
+
slashes = [slash_str]*2
|
147
|
+
|
148
|
+
inner_regex = date_tup.zip(slashes)
|
149
|
+
.flatten
|
150
|
+
.compact
|
151
|
+
.join
|
152
|
+
|
153
|
+
/^#{inner_regex}$/
|
154
|
+
end
|
155
|
+
|
156
|
+
#Allow MMDDYYYY or MM/DD/YYYY
|
157
|
+
def self.mmddyyyy_regex(options={})
|
158
|
+
with_slashes = options.fetch(:with_slashes, false)
|
159
|
+
|
160
|
+
slash_str = with_slashes ? '/' : nil
|
161
|
+
month_str = '(?<month>[0-1]?\d)'
|
162
|
+
day_str = '(?<day>[0-3]?\d)'
|
163
|
+
year_str = '(?<year>\d{4})'
|
164
|
+
|
165
|
+
date_tup = [month_str, day_str, year_str]
|
166
|
+
slashes = [slash_str]*2
|
167
|
+
|
168
|
+
inner_regex = date_tup.zip(slashes)
|
169
|
+
.flatten
|
170
|
+
.compact
|
171
|
+
.join
|
172
|
+
|
173
|
+
/^#{inner_regex}$/
|
174
|
+
end
|
175
|
+
|
176
|
+
# matches:
|
177
|
+
# e.g.)
|
178
|
+
# t => Date.today
|
179
|
+
# t+7 => Date.today + 1.week
|
180
|
+
# t-1 => Date.today - 1.day
|
181
|
+
def self.date_for_t_match(date_string)
|
182
|
+
today = Time&.zone&.today || Date.today
|
183
|
+
|
184
|
+
t_regex.match(date_string) do |match_data|
|
185
|
+
operator, days_count = *[match_data[:operator], match_data[:days_count]]
|
186
|
+
|
187
|
+
operator ? today.send(operator, days_count.to_i) : today
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.date_for_mmddyy_match(date_string, options={})
|
192
|
+
slashy_regex = options.fetch(:with_slashes, false)
|
193
|
+
month, day, year = nil, nil, nil
|
194
|
+
|
195
|
+
mmddyy_regex(with_slashes: slashy_regex).match(date_string) do |match_data|
|
196
|
+
month = match_data[:month] ? match_data[:month] : date_string[0..1]
|
197
|
+
day = match_data[:day] ? match_data[:day] : date_string[2..3]
|
198
|
+
year = match_data[:year] ? match_data[:year] : date_string[4..5]
|
199
|
+
|
200
|
+
century = DateAsString.guess_century(year.to_i)
|
201
|
+
|
202
|
+
Date.civil(century, month.to_i, day.to_i)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def self.date_for_mmddyyyy_match(date_string, options={})
|
207
|
+
slashy_regex = options.fetch(:with_slashes, false)
|
208
|
+
month, day, year = nil, nil, nil
|
209
|
+
|
210
|
+
mmddyyyy_regex(with_slashes: slashy_regex).match(date_string) do |match_data|
|
211
|
+
month = match_data[:month] ? match_data[:month] : date_string[0..1]
|
212
|
+
day = match_data[:day] ? match_data[:day] : date_string[2..3]
|
213
|
+
year = match_data[:year] ? match_data[:year] : date_string[4..5]
|
214
|
+
|
215
|
+
Date.civil(year.to_i, month.to_i, day.to_i)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
if Object.const_defined?("ActiveRecord")
|
221
|
+
ActiveRecord::Base.send(:include, DateAsString)
|
222
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module DateAsString
|
2
|
+
module Formats
|
3
|
+
def self.find_format(format_string)
|
4
|
+
case format_string
|
5
|
+
when :mm_dd_yyyy
|
6
|
+
Format1
|
7
|
+
when :mm_dd_yy
|
8
|
+
Format2
|
9
|
+
when :mmddyyyy
|
10
|
+
Format3
|
11
|
+
when :mmddyy
|
12
|
+
Format4
|
13
|
+
when :t_pmd
|
14
|
+
Format5
|
15
|
+
else
|
16
|
+
raise TypeError.new("Format does not exist: #{format_string}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
#mm_dd_yyyy
|
21
|
+
class Format1
|
22
|
+
def self.strftime(value)
|
23
|
+
value.strftime('%m/%d/%Y')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
#mm_dd_yy
|
28
|
+
class Format2
|
29
|
+
def self.strftime(value)
|
30
|
+
value.strftime('%m/%d/%y')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
#mmddyyyy
|
35
|
+
class Format3
|
36
|
+
def self.strftime(value)
|
37
|
+
value.strftime('%m%d%Y')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#mmddyy
|
42
|
+
class Format4
|
43
|
+
def self.strftime(value)
|
44
|
+
value.strftime('%m%d%y')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
#t_pmd
|
49
|
+
class Format5
|
50
|
+
def self.strftime(value)
|
51
|
+
raise NotImplementedError.new
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'DateAsString' do
|
4
|
+
let(:today) { Date.today }
|
5
|
+
let(:tomorrow) { today + 1.day }
|
6
|
+
let(:yesterday) { today - 1.day }
|
7
|
+
let(:new_years_day_1990) { Date.new(1990, 1, 1) }
|
8
|
+
|
9
|
+
describe '.parse_date(date)' do
|
10
|
+
subject { DateAsString.parse_date date_arg }
|
11
|
+
|
12
|
+
context 'when provided with a Date' do
|
13
|
+
let(:date_arg) { new_years_day_1990 }
|
14
|
+
|
15
|
+
it { is_expected.to eq "01/01/1990" }
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'handling invalid arguments' do
|
19
|
+
context "argument is a String" do
|
20
|
+
let(:date_arg) { 'abc' }
|
21
|
+
|
22
|
+
it { is_expected.to eq nil }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe ".parse_string(string)" do
|
28
|
+
subject { DateAsString.parse_string date_string_arg }
|
29
|
+
|
30
|
+
context "format -> 't+#'" do
|
31
|
+
let(:date_string_arg) { "t+1" }
|
32
|
+
|
33
|
+
it { is_expected.to eq tomorrow }
|
34
|
+
end
|
35
|
+
|
36
|
+
context "format -> 't-#'" do
|
37
|
+
let(:date_string_arg) { "t-1" }
|
38
|
+
|
39
|
+
it { is_expected.to eq yesterday }
|
40
|
+
end
|
41
|
+
|
42
|
+
context "format -> 't'" do
|
43
|
+
let(:date_string_arg) { "t" }
|
44
|
+
|
45
|
+
it { is_expected.to eq today }
|
46
|
+
end
|
47
|
+
|
48
|
+
context "format -> '######'" do
|
49
|
+
let(:date_string_arg) { "010190" }
|
50
|
+
|
51
|
+
it { is_expected.to eq new_years_day_1990 }
|
52
|
+
end
|
53
|
+
|
54
|
+
context "format -> '########'" do
|
55
|
+
let(:date_string_arg) { "01011990" }
|
56
|
+
|
57
|
+
it { is_expected.to eq new_years_day_1990 }
|
58
|
+
end
|
59
|
+
|
60
|
+
context "format -> '##/##/##'" do
|
61
|
+
let(:date_string_arg) { "01/01/90" }
|
62
|
+
|
63
|
+
it { is_expected.to eq new_years_day_1990 }
|
64
|
+
end
|
65
|
+
|
66
|
+
context "format -> '##/##/####'" do
|
67
|
+
let(:date_string_arg) { "01/01/1990" }
|
68
|
+
|
69
|
+
it { is_expected.to eq new_years_day_1990 }
|
70
|
+
end
|
71
|
+
|
72
|
+
describe 'handling invalid arguments' do
|
73
|
+
context "argument is a String" do
|
74
|
+
let(:date_string_arg) { 'abc' }
|
75
|
+
|
76
|
+
it { is_expected.to eq nil }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "date_as_string/version"
|
2
|
+
require "date_as_string/formats"
|
3
|
+
|
4
|
+
require 'active_support/all'
|
5
|
+
|
6
|
+
require 'bundler/setup'
|
7
|
+
Bundler.setup
|
8
|
+
|
9
|
+
require 'date_as_string'
|
10
|
+
|
11
|
+
require 'byebug'
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
# some (optional) config here
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: date_as_string
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.6
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eric Sullivan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-12-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Treat an ActiveRecord Date column as a String
|
28
|
+
email:
|
29
|
+
- eric.sullivan@annkissam.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".gitignore"
|
35
|
+
- ".rspec"
|
36
|
+
- Gemfile
|
37
|
+
- Rakefile
|
38
|
+
- date_as_string.gemspec
|
39
|
+
- lib/date_as_string.rb
|
40
|
+
- lib/date_as_string/formats.rb
|
41
|
+
- lib/date_as_string/version.rb
|
42
|
+
- spec/date_as_string_spec.rb
|
43
|
+
- spec/spec_helper.rb
|
44
|
+
homepage: http://annkissam.com
|
45
|
+
licenses: []
|
46
|
+
metadata: {}
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubyforge_project: date_as_string
|
63
|
+
rubygems_version: 2.7.6
|
64
|
+
signing_key:
|
65
|
+
specification_version: 4
|
66
|
+
summary: Convert from Date to String and vice versa by attaching _string suffix to
|
67
|
+
an ActiveRecord field
|
68
|
+
test_files: []
|