incomplete_date 0.1.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 60bcab9d810f4b939ecf3b9ff1e5df23f8b04482947110dedd527766df9d6fb3
4
+ data.tar.gz: 0a271f6433f699854bf2939ad1eb5d46cdbab7783430554185e1bedcf3090a31
5
+ SHA512:
6
+ metadata.gz: a725a7381bb110165ce22e002e62c616a7d95155c5fdd14f7f5e7b44212e0909266081ae59b1b51c04337e7175c5aea67703b8360a1a259eddabbfd81a050f1b
7
+ data.tar.gz: 7e7fd53527085104b73e7dba177a32d9bf22f597c23e4095e7109c089b194435623f0ba3f27b1945e771cd4d11419ebe1b7b003163dae831b4ac512f7496173f
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Ernesto García
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.
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the incomplete_date plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the incomplete_date plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'IncompleteDate'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
@@ -0,0 +1,296 @@
1
+ require 'incomplete_date/date'
2
+ require 'incomplete_date/active_record'
3
+ require 'incomplete_date/form_builder'
4
+
5
+ class IncompleteDate
6
+ attr_reader :day, :month, :year
7
+ attr_accessor :circa
8
+ alias mday day
9
+
10
+ def initialize(value)
11
+ @circa = false
12
+
13
+ case value
14
+ when Integer
15
+ @circa = (value < 0)
16
+ num = value.abs
17
+ num, @day = num.divmod(100)
18
+ @year, @month = num.divmod(100)
19
+ when Hash
20
+ @day, @month, @year = value[:day], value[:month], value[:year]
21
+ @circa = value.fetch(:circa, false)
22
+ when Date
23
+ @day, @month, @year = value.mday, value.month, value.year
24
+ when IncompleteDate
25
+ @day, @month, @year, @circa = value.day, value.month, value.year, value.circa
26
+ when nil #invaluable for existing databases!
27
+ @day, @month, @year, @circa = 0, 0, 0, false
28
+ else
29
+ raise ArgumentError, "Invalid #{self.class.name} specification"
30
+ end
31
+
32
+ @day = nil if @day == 0
33
+ @month = nil if @month == 0
34
+ @year = nil if @year == 0
35
+ end
36
+
37
+ #--
38
+ # Core attributes handling
39
+ #++
40
+
41
+ #
42
+ # Returns +true+ if the given year is a leap year, +false+ otherwise.
43
+ #
44
+ def self.leap_year?(year)
45
+ raise ArgumentError, "year cannot be null or zero" if year.nil? or year.zero?
46
+ (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0)
47
+ end
48
+
49
+ def self.max_month_days(month = nil, year = nil)
50
+ year ||= 2000 # Assume a leap year if year is not known
51
+ case month
52
+ when nil,1,3,5,7,8,10,12 then 31
53
+ when 4,6,9,11 then 30
54
+ when 2 then leap_year?(year) ? 29 : 28
55
+ else raise ArgumentError, "invalid month"
56
+ end
57
+ end
58
+
59
+ def valid_day?(day, month = nil, year = nil)
60
+ return true if day.nil? || day.zero?
61
+ max = IncompleteDate.max_month_days(month || self.month, year || self.year)
62
+ (1..max).include?(day)
63
+ end
64
+
65
+ def valid_month?(month)
66
+ return true if month.nil? || month.zero?
67
+ (1..12).include?(month) && valid_day?(self.day, month)
68
+ end
69
+
70
+ def valid_year?(year, month = nil, day = nil)
71
+ return true if year.nil? || year.zero?
72
+ day ||= self.day
73
+ month ||= self.month
74
+ !(!IncompleteDate.leap_year?(year) && (day == 29) && (month == 2))
75
+ end
76
+
77
+ def day=(value)
78
+ value = (value == 0) ? nil : value
79
+ raise ArgumentError, "invalid day" unless valid_day?(value)
80
+ @day = value
81
+ end
82
+
83
+ def month=(value)
84
+ value = (value == 0) ? nil : value
85
+ raise ArgumentError, "invalid month" unless valid_month?(value)
86
+ @month = value
87
+ end
88
+
89
+ def year=(value)
90
+ value = (value == 0) ? nil : value
91
+ raise ArgumentError, "invalid year" unless valid_year?(value)
92
+ @year = value
93
+ end
94
+
95
+ def circa?
96
+ @circa
97
+ end
98
+
99
+ VALID_DATE_PARTS = [:year, :month, :day, :circa]
100
+
101
+ def [](part_name)
102
+ raise ArgumentError, "Invalid date part #{part_name}" unless VALID_DATE_PARTS.include?(part_name)
103
+ self.send(part_name)
104
+ end
105
+
106
+ def []=(part_name, value)
107
+ raise ArgumentError, "Invalid date part #{part_name}" unless VALID_DATE_PARTS.include?(part_name)
108
+ self.send("#{part_name}=", value)
109
+ end
110
+
111
+ #--
112
+ # Testing completeness
113
+ #++
114
+
115
+ def incomplete?
116
+ @day.nil? || @month.nil? || @year.nil?
117
+ end
118
+
119
+ def complete?
120
+ !incomplete?
121
+ end
122
+
123
+ def exact?
124
+ complete? && !circa?
125
+ end
126
+
127
+ def empty?
128
+ @day.nil? && @month.nil? && @year.nil?
129
+ end
130
+
131
+ def has?(*parts)
132
+ parts.collect!(&:to_sym)
133
+ parts.inject(true) { |total,part| total && !self.send(part).nil? }
134
+ end
135
+
136
+ def has_day?
137
+ !@day.nil?
138
+ end
139
+
140
+ def has_month?
141
+ !@month.nil?
142
+ end
143
+
144
+ def has_year?
145
+ !@year.nil?
146
+ end
147
+
148
+ def defines_birthday?
149
+ has_day? && has_month?
150
+ end
151
+
152
+ def defined_parts
153
+ VALID_DATE_PARTS.reject { |part| (part == :circa) || self[part].nil? }
154
+ end
155
+
156
+ #++
157
+ # Relation to complete dates
158
+ #--
159
+
160
+ #
161
+ # Returns +true+ if the given date is included in the possible set of complete
162
+ # dates that match this incomplete date.
163
+ #
164
+ # For instance 2003-xx-xx includes 2003-12-24 (or any date within the year
165
+ # 2003) but it does not include 1999-12-14, for instance.
166
+ #
167
+ # The argument can be either a Date instance or an IncompleteDate instance.
168
+ #
169
+ def include?(date)
170
+ (self.year.nil? || (date.year == self.year)) &&
171
+ (self.month.nil? || (date.month == self.month)) &&
172
+ (self.day.nil? || (date.mday == self.mday))
173
+ end
174
+
175
+ #
176
+ # Returns the lowest possible date that matches this incomplete date.
177
+ #
178
+ # Only defined if this incomplete date has the year part defined. Otherwise it
179
+ # returns +nil+.
180
+ #
181
+ # see #highest
182
+ #
183
+ def lowest
184
+ return nil unless has_year?
185
+ Date.civil(self.year, self.month || 1, self.day || 1)
186
+ end
187
+ alias min lowest
188
+ alias first lowest
189
+
190
+ #
191
+ # Returns the highest possible date that matches this incomplete date.
192
+ #
193
+ # Only defined if this incomplete date has the year part defined. Otherwise it
194
+ # returns +nil+.
195
+ #
196
+ # see #lowest
197
+ #
198
+ def highest
199
+ return nil unless has_year?
200
+ max_month = self.month || 12
201
+ max_day = self.day || IncompleteDate.max_month_days(max_month, self.year)
202
+ Date.civil(self.year, max_month, max_day)
203
+ end
204
+ alias max highest
205
+ alias last highest
206
+
207
+ #--
208
+ # Conversions
209
+ #++
210
+
211
+ #
212
+ # Returns an incomplete date which only has the birthday parts (month and day)
213
+ # taken from this incomplete date. If the needed date parts are not defined,
214
+ # it returns +nil+.
215
+ #
216
+ def to_birthday
217
+ return nil unless defines_birthday?
218
+ IncompleteDate.new(:month => month, :day => day)
219
+ end
220
+
221
+ #
222
+ # Converts this incomplete date to a standard date value. Any missing date
223
+ # parts can be provided by the hash argument, or else are taken from today's
224
+ # date, or a reference date that be given as an optional argument with key
225
+ # +:ref+. In the case of the day missing and not explicitely provided, it
226
+ # defaults to 1, and not to the reference date.
227
+ #
228
+ def to_date(opts = {})
229
+ ref = opts.fetch(:ref, Date.today)
230
+ Date.civil(
231
+ (self.year || opts[:year] || ref.year).to_i,
232
+ (self.month || opts[:month] || ref.month).to_i,
233
+ (self.day || opts[:day] || 1).to_i)
234
+ end
235
+
236
+ def to_incomplete_date
237
+ self
238
+ end
239
+
240
+ #
241
+ # Converts this incomplete date to its equivalent integer representation
242
+ # suitable for the database layer.
243
+ #
244
+ # The two less significant digits in the decimal representation of the number
245
+ # are the day of the month. The next two less significant digits represent the
246
+ # month (numbered from 01 to 12) and the rest of the digits represent the
247
+ # year. In any case a value of zero means that that particular part of the
248
+ # date is not known. Finally, the sign of the number represents wether the
249
+ # date is considered to be uncertain (negative) or certain (positive).
250
+ #
251
+ # This representation relies on the fact there was no year 0 in the Gregorian
252
+ # or Julian calendars (see http://en.wikipedia.org/wiki/Year_zero).
253
+ #
254
+ # The integer value zero represents a date that is completely unknown, so it
255
+ # is somehow equivalent to +nil+ in terms of meaning, and it is questionable
256
+ # that we allow instances of this class with such a configuration.
257
+ #
258
+ def to_i
259
+ y,m,d = [year,month,day].collect(&:to_i) # converts +nil+ values to zero
260
+ (circa ? -1 : 1) * (d + m*100 + y*10000)
261
+ end
262
+
263
+ def to_s
264
+ dt = to_date
265
+ ord = has_day? ? dt.day.ordinalize : nil
266
+ result = case defined_parts
267
+ when [:year, :month, :day] then dt.strftime("%B #{ord}, %Y")
268
+ when [:year, :month] then dt.strftime("%B %Y")
269
+ when [:month, :day] then dt.strftime("%B #{ord}")
270
+ when [:year, :day] then dt.strftime("#{ord} of the month, %Y")
271
+ when [:year] then dt.strftime("%Y")
272
+ when [:month] then dt.strftime("%B")
273
+ when [:day] then "#{ord} of the month"
274
+ else return "unknown"
275
+ end
276
+ circa ? "c. #{result}" : result
277
+ end
278
+
279
+ #
280
+ # Returns the range of dates that are included or match with this incomplete
281
+ # date.
282
+ #
283
+ # Uses #lowest and #highest to determine the extremes of the range. Returns
284
+ # +nil+ if the year is not defined.
285
+ #
286
+ def to_range
287
+ has_year? ? (min..max) : nil
288
+ end
289
+
290
+ def to_hash
291
+ result = {}
292
+ VALID_DATE_PARTS.each { |part| result[part] = self.send(part) }
293
+ result
294
+ end
295
+
296
+ end
@@ -0,0 +1,55 @@
1
+ class IncompleteDate
2
+ module IncompleteDateAttr
3
+ extend ActiveSupport::Concern
4
+ #
5
+ # Defines a new IncompleteDate virtual attribute with name +attr_name+
6
+ # corresponding to a real integer attribute named +raw_name+ that holds the
7
+ # raw date value in the database.
8
+ #
9
+ class_methods do
10
+
11
+ def incomplete_date_attr(attr_name, raw_name)
12
+ instance_var = "@#{attr_name}"
13
+
14
+ # getter
15
+ define_method "#{attr_name}" do
16
+ value = instance_variable_get(instance_var)
17
+ value ||= IncompleteDate.new(read_attribute(raw_name))
18
+ instance_variable_set(instance_var, value)
19
+ value
20
+ end
21
+
22
+ # setter
23
+ define_method "#{attr_name}=" do |value|
24
+ #begin
25
+ value = IncompleteDate.new(value)
26
+ instance_variable_set(instance_var, value)
27
+ write_attribute(raw_name, value.to_i)
28
+ #rescue ArgumentError
29
+ # errors.add(raw_name)
30
+ #end
31
+ end
32
+ end
33
+
34
+ #
35
+ # Defines several virtual attributes at once for raw real attributes
36
+ #
37
+ def incomplete_date_attrs(*names)
38
+ options = names.extract_options!
39
+ prefix = options.fetch(:prefix, 'raw')
40
+ names.each do |name|
41
+ raw_name = "#{prefix}_#{name}"
42
+ incomplete_date_attr name, raw_name
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+ end
50
+
51
+ class ApplicationRecord < ActiveRecord::Base
52
+ include IncompleteDate::IncompleteDateAttr
53
+
54
+ self.abstract_class = true
55
+ end
@@ -0,0 +1,22 @@
1
+ class Date
2
+ def circa
3
+ false
4
+ end
5
+ alias circa? circa
6
+
7
+ def to_birthday
8
+ IncompleteDate.new(:month => month, :day => day)
9
+ end
10
+
11
+ def to_i
12
+ year*10000 + month*100 + day
13
+ end
14
+
15
+ def to_incomplete_date
16
+ IncompleteDate.new(self)
17
+ end
18
+
19
+ def matches?(incomplete_date)
20
+ incomplete_date.include?(self)
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ module ActionView::Helpers
2
+ class FormBuilder
3
+ def incomplete_date_select(method, options = {}, html_options = {})
4
+ date = @object.send(method) || (Date.today - 50.years)
5
+ date = date.to_incomplete_date
6
+ options.merge!(:prefix => @object_name, :include_blank => true)
7
+ # TODO: The +year+ and +circa+ tags don't have the standard +id+ according to its names.
8
+ @template.text_field_tag("#{@object_name}[#{method}][year]", date.year, :size => 5, :maxlength => 4) + ' ' +
9
+ @template.select_month(date, options.merge(:field_name => "#{method}][month")) + ' ' +
10
+ @template.select_day(date, options.merge(:field_name => "#{method}][day")) + ' ' +
11
+ @template.check_box_tag("#{@object_name}[#{method}][circa]", '1', date.circa) + ' circa'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ class IncompleteDate
2
+ VERSION = '0.1.2'
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :incomplete_date do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: incomplete_date
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - gnapse
8
+ - ErCollao
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2019-05-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: sqlite3
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 1.3.6
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 1.3.6
42
+ description: This plugin creates a Value Object class that controls the logic of incomplete
43
+ dates. It also creates a class method incomplete_date_attr to hook onto Active Record
44
+ objects, so some of the attributes are stored in the database as an integer instead
45
+ of a date
46
+ email:
47
+ - gnapse@gmail.com
48
+ - daniel@collado-ruiz.es
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - MIT-LICENSE
54
+ - Rakefile
55
+ - lib/incomplete_date.rb
56
+ - lib/incomplete_date/active_record.rb
57
+ - lib/incomplete_date/date.rb
58
+ - lib/incomplete_date/form_builder.rb
59
+ - lib/incomplete_date/version.rb
60
+ - lib/tasks/incomplete_date_tasks.rake
61
+ homepage: https://github.com/gnapse/incomplete_date
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubygems_version: 3.0.1
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: This plugin allows a Rails application to store and manage incomplete date
84
+ data, which is very common in historical archives, particularly in genealogy.
85
+ test_files: []