tediparse 2.0.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.
Files changed (176) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +319 -0
  3. data/LICENSE +26 -0
  4. data/README.md +308 -0
  5. data/Rakefile +33 -0
  6. data/bin/tediparse +102 -0
  7. data/doc/Defining.md +97 -0
  8. data/doc/Generating-Grammars.md +241 -0
  9. data/doc/Generating.md +332 -0
  10. data/doc/Navigating.md +670 -0
  11. data/doc/Parsing.md +133 -0
  12. data/doc/README.md +51 -0
  13. data/doc/Serializing.md +100 -0
  14. data/doc/Tokenizing.md +136 -0
  15. data/doc/Validating.md +140 -0
  16. data/lib/ruby/array.rb +226 -0
  17. data/lib/ruby/blank.rb +52 -0
  18. data/lib/ruby/exception.rb +14 -0
  19. data/lib/ruby/hash.rb +14 -0
  20. data/lib/ruby/module.rb +60 -0
  21. data/lib/ruby/object.rb +56 -0
  22. data/lib/ruby/string.rb +89 -0
  23. data/lib/ruby/to_d.rb +82 -0
  24. data/lib/ruby/to_date.rb +26 -0
  25. data/lib/ruby/to_time.rb +21 -0
  26. data/lib/ruby/try.rb +46 -0
  27. data/lib/stupidedi/builder.rb +6 -0
  28. data/lib/stupidedi/color.rb +94 -0
  29. data/lib/stupidedi/config/code_list_config.rb +45 -0
  30. data/lib/stupidedi/config/functional_group_config.rb +72 -0
  31. data/lib/stupidedi/config/interchange_config.rb +86 -0
  32. data/lib/stupidedi/config/transaction_set_config.rb +73 -0
  33. data/lib/stupidedi/config.rb +100 -0
  34. data/lib/stupidedi/either.rb +286 -0
  35. data/lib/stupidedi/exceptions/invalid_element_error.rb +7 -0
  36. data/lib/stupidedi/exceptions/invalid_schema_error.rb +7 -0
  37. data/lib/stupidedi/exceptions/missing_grammar_error.rb +50 -0
  38. data/lib/stupidedi/exceptions/output_error.rb +7 -0
  39. data/lib/stupidedi/exceptions/parse_error.rb +7 -0
  40. data/lib/stupidedi/exceptions/stupidedi_error.rb +7 -0
  41. data/lib/stupidedi/exceptions/tokenize_error.rb +7 -0
  42. data/lib/stupidedi/exceptions/zipper_error.rb +7 -0
  43. data/lib/stupidedi/exceptions.rb +13 -0
  44. data/lib/stupidedi/inspect.rb +26 -0
  45. data/lib/stupidedi/interchanges/element_types/separator_val.rb +79 -0
  46. data/lib/stupidedi/interchanges/element_types/special_val.rb +48 -0
  47. data/lib/stupidedi/interchanges/element_types.rb +11 -0
  48. data/lib/stupidedi/interchanges.rb +21 -0
  49. data/lib/stupidedi/parser/builder_dsl.rb +292 -0
  50. data/lib/stupidedi/parser/constraint_table.rb +566 -0
  51. data/lib/stupidedi/parser/generation.rb +147 -0
  52. data/lib/stupidedi/parser/identifier_stack.rb +248 -0
  53. data/lib/stupidedi/parser/instruction.rb +112 -0
  54. data/lib/stupidedi/parser/instruction_table.rb +222 -0
  55. data/lib/stupidedi/parser/navigation.rb +786 -0
  56. data/lib/stupidedi/parser/state_machine.rb +62 -0
  57. data/lib/stupidedi/parser/states/abstract_state.rb +385 -0
  58. data/lib/stupidedi/parser/states/failure_state.rb +69 -0
  59. data/lib/stupidedi/parser/states/functional_group_state.rb +101 -0
  60. data/lib/stupidedi/parser/states/initial_state.rb +62 -0
  61. data/lib/stupidedi/parser/states/interchange_state.rb +96 -0
  62. data/lib/stupidedi/parser/states/loop_state.rb +74 -0
  63. data/lib/stupidedi/parser/states/table_state.rb +90 -0
  64. data/lib/stupidedi/parser/states/transaction_set_state.rb +120 -0
  65. data/lib/stupidedi/parser/states/transmission_state.rb +59 -0
  66. data/lib/stupidedi/parser/tokenization.rb +195 -0
  67. data/lib/stupidedi/parser.rb +32 -0
  68. data/lib/stupidedi/reader/input/abstract_input.rb +137 -0
  69. data/lib/stupidedi/reader/input/delegated_input.rb +112 -0
  70. data/lib/stupidedi/reader/input/file_input.rb +157 -0
  71. data/lib/stupidedi/reader/input.rb +31 -0
  72. data/lib/stupidedi/reader/position.rb +78 -0
  73. data/lib/stupidedi/reader/result.rb +172 -0
  74. data/lib/stupidedi/reader/segment_dict.rb +176 -0
  75. data/lib/stupidedi/reader/separators.rb +90 -0
  76. data/lib/stupidedi/reader/stream_reader.rb +173 -0
  77. data/lib/stupidedi/reader/token_reader.rb +465 -0
  78. data/lib/stupidedi/reader/tokens/component_element_tok.rb +71 -0
  79. data/lib/stupidedi/reader/tokens/composite_element_tok.rb +85 -0
  80. data/lib/stupidedi/reader/tokens/repeated_element_tok.rb +74 -0
  81. data/lib/stupidedi/reader/tokens/segment_tok.rb +74 -0
  82. data/lib/stupidedi/reader/tokens/simple_element_tok.rb +76 -0
  83. data/lib/stupidedi/reader.rb +121 -0
  84. data/lib/stupidedi/schema/abstract_def.rb +76 -0
  85. data/lib/stupidedi/schema/abstract_element_def.rb +35 -0
  86. data/lib/stupidedi/schema/abstract_element_use.rb +47 -0
  87. data/lib/stupidedi/schema/abstract_use.rb +79 -0
  88. data/lib/stupidedi/schema/code_list.rb +99 -0
  89. data/lib/stupidedi/schema/component_element_use.rb +76 -0
  90. data/lib/stupidedi/schema/composite_element_def.rb +103 -0
  91. data/lib/stupidedi/schema/composite_element_use.rb +78 -0
  92. data/lib/stupidedi/schema/element_req.rb +57 -0
  93. data/lib/stupidedi/schema/functional_group_def.rb +124 -0
  94. data/lib/stupidedi/schema/generation/definition_generator.rb +139 -0
  95. data/lib/stupidedi/schema/generation/element_generator.rb +221 -0
  96. data/lib/stupidedi/schema/generation/flat_file_reader.rb +551 -0
  97. data/lib/stupidedi/schema/generation/functional_group_generator.rb +64 -0
  98. data/lib/stupidedi/schema/generation/interchange_generator.rb +145 -0
  99. data/lib/stupidedi/schema/generation/master_loader_generator.rb +121 -0
  100. data/lib/stupidedi/schema/generation/models.rb +85 -0
  101. data/lib/stupidedi/schema/generation/module_loader_generator.rb +64 -0
  102. data/lib/stupidedi/schema/generation/registration_generator.rb +230 -0
  103. data/lib/stupidedi/schema/generation/runner.rb +161 -0
  104. data/lib/stupidedi/schema/generation/segment_generator.rb +130 -0
  105. data/lib/stupidedi/schema/generation/support.rb +78 -0
  106. data/lib/stupidedi/schema/generation/support_modules_generator.rb +126 -0
  107. data/lib/stupidedi/schema/generation/version_modules.rb +35 -0
  108. data/lib/stupidedi/schema/generation.rb +94 -0
  109. data/lib/stupidedi/schema/interchange_def.rb +103 -0
  110. data/lib/stupidedi/schema/loop_def.rb +156 -0
  111. data/lib/stupidedi/schema/repeat_count.rb +86 -0
  112. data/lib/stupidedi/schema/segment_def.rb +122 -0
  113. data/lib/stupidedi/schema/segment_req.rb +46 -0
  114. data/lib/stupidedi/schema/segment_use.rb +99 -0
  115. data/lib/stupidedi/schema/simple_element_def.rb +51 -0
  116. data/lib/stupidedi/schema/simple_element_use.rb +83 -0
  117. data/lib/stupidedi/schema/syntax_note.rb +52 -0
  118. data/lib/stupidedi/schema/table_def.rb +178 -0
  119. data/lib/stupidedi/schema/transaction_set_def.rb +125 -0
  120. data/lib/stupidedi/schema.rb +30 -0
  121. data/lib/stupidedi/sets.rb +42 -0
  122. data/lib/stupidedi/transaction_sets/builder/dsl.rb +192 -0
  123. data/lib/stupidedi/transaction_sets/builder.rb +188 -0
  124. data/lib/stupidedi/transaction_sets/common/implementations/element_reqs.rb +37 -0
  125. data/lib/stupidedi/transaction_sets/common/implementations/segment_reqs.rb +31 -0
  126. data/lib/stupidedi/transaction_sets/common/implementations.rb +11 -0
  127. data/lib/stupidedi/transaction_sets/common.rb +8 -0
  128. data/lib/stupidedi/transaction_sets/validation/ambiguity.rb +395 -0
  129. data/lib/stupidedi/transaction_sets/validation/implementation.rb +12 -0
  130. data/lib/stupidedi/transaction_sets/validation.rb +9 -0
  131. data/lib/stupidedi/transaction_sets.rb +25 -0
  132. data/lib/stupidedi/values/abstract_element_val.rb +19 -0
  133. data/lib/stupidedi/values/abstract_val.rb +133 -0
  134. data/lib/stupidedi/values/composite_element_val.rb +102 -0
  135. data/lib/stupidedi/values/functional_group_val.rb +105 -0
  136. data/lib/stupidedi/values/interchange_val.rb +102 -0
  137. data/lib/stupidedi/values/invalid_envelope_val.rb +61 -0
  138. data/lib/stupidedi/values/invalid_segment_val.rb +89 -0
  139. data/lib/stupidedi/values/loop_val.rb +73 -0
  140. data/lib/stupidedi/values/repeated_element_val.rb +113 -0
  141. data/lib/stupidedi/values/segment_val.rb +105 -0
  142. data/lib/stupidedi/values/segment_val_group.rb +20 -0
  143. data/lib/stupidedi/values/simple_element_val.rb +80 -0
  144. data/lib/stupidedi/values/table_val.rb +69 -0
  145. data/lib/stupidedi/values/transaction_set_val.rb +69 -0
  146. data/lib/stupidedi/values/transmission_val.rb +56 -0
  147. data/lib/stupidedi/values.rb +22 -0
  148. data/lib/stupidedi/version.rb +4 -0
  149. data/lib/stupidedi/versions/common/element_reqs.rb +13 -0
  150. data/lib/stupidedi/versions/common/element_types/an.rb +386 -0
  151. data/lib/stupidedi/versions/common/element_types/dt.rb +572 -0
  152. data/lib/stupidedi/versions/common/element_types/id.rb +304 -0
  153. data/lib/stupidedi/versions/common/element_types/nn.rb +312 -0
  154. data/lib/stupidedi/versions/common/element_types/operators.rb +128 -0
  155. data/lib/stupidedi/versions/common/element_types/r.rb +342 -0
  156. data/lib/stupidedi/versions/common/element_types/simple_element.rb +73 -0
  157. data/lib/stupidedi/versions/common/element_types/tm.rb +347 -0
  158. data/lib/stupidedi/versions/common/element_types.rb +29 -0
  159. data/lib/stupidedi/versions/common/segment_reqs.rb +15 -0
  160. data/lib/stupidedi/versions/common/syntax_notes.rb +172 -0
  161. data/lib/stupidedi/versions/common.rb +11 -0
  162. data/lib/stupidedi/versions.rb +25 -0
  163. data/lib/stupidedi/writer/claredi.rb +178 -0
  164. data/lib/stupidedi/writer/default.rb +119 -0
  165. data/lib/stupidedi/writer.rb +7 -0
  166. data/lib/stupidedi/zipper/abstract_cursor.rb +351 -0
  167. data/lib/stupidedi/zipper/dangling_cursor.rb +103 -0
  168. data/lib/stupidedi/zipper/edited_cursor.rb +157 -0
  169. data/lib/stupidedi/zipper/memoized_cursor.rb +133 -0
  170. data/lib/stupidedi/zipper/path.rb +132 -0
  171. data/lib/stupidedi/zipper/root_cursor.rb +120 -0
  172. data/lib/stupidedi/zipper/stack_cursor.rb +107 -0
  173. data/lib/stupidedi/zipper.rb +45 -0
  174. data/lib/stupidedi.rb +69 -0
  175. data/lib/tediparse.rb +1 -0
  176. metadata +249 -0
@@ -0,0 +1,572 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ using Refinements
4
+
5
+ module Versions
6
+ module Common
7
+ module ElementTypes
8
+ #
9
+ # Simple element definition for date elements
10
+ #
11
+ # @see DateVal
12
+ # @see X222.pdf B.1.1.3.1.5 Date
13
+ #
14
+ class DT < SimpleElementDef
15
+ def initialize(id, name, min_length, max_length, description = nil, parent = nil)
16
+ super(id, name, min_length, max_length, description, parent)
17
+
18
+ unless min_length == 6 or min_length == 8
19
+ raise Exceptions::InvalidSchemaError,
20
+ "min_length must be either 6 or 8"
21
+ end
22
+
23
+ unless max_length == 6 or max_length == 8
24
+ raise Exceptions::InvalidSchemaError,
25
+ "max_length must be either 6 or 8"
26
+ end
27
+ end
28
+
29
+ def companion
30
+ DateVal
31
+ end
32
+ end
33
+
34
+ #
35
+ # @see X222.pdf B.1.1.3.1.5 Date
36
+ #
37
+ class DateVal < Values::SimpleElementVal
38
+ def date?
39
+ true
40
+ end
41
+
42
+ def proper?
43
+ false
44
+ end
45
+
46
+ def too_long?
47
+ false
48
+ end
49
+
50
+ def too_short?
51
+ false
52
+ end
53
+
54
+ #
55
+ # Models invalid dates, either with a bad format or a date that
56
+ # does not exist (eg Febuary 30th 2000)
57
+ #
58
+ class Invalid < DateVal
59
+ # @return [Object]
60
+ attr_reader :value
61
+
62
+ def initialize(value, usage, position)
63
+ super(usage, position)
64
+ @value = value
65
+ end
66
+
67
+ def valid?
68
+ false
69
+ end
70
+
71
+ def empty?
72
+ false
73
+ end
74
+
75
+ # @return [DateVal]
76
+ def map
77
+ self
78
+ end
79
+
80
+ # @return [String]
81
+ # :nocov:
82
+ def inspect
83
+ id = definition.bind do |d|
84
+ "[#{"% 5s" % d.id}: #{d.name}]".bind do |s|
85
+ if usage.forbidden?
86
+ ansi.forbidden(s)
87
+ elsif usage.required?
88
+ ansi.required(s)
89
+ else
90
+ ansi.optional(s)
91
+ end
92
+ end
93
+ end
94
+
95
+ ansi.element("DT.invalid#{id}") + "(#{ansi.invalid(@value.inspect)})"
96
+ end
97
+ # :nocov:
98
+
99
+ # @return [String]
100
+ def to_s
101
+ ""
102
+ end
103
+
104
+ # @return [String]
105
+ def to_x12(truncate = true)
106
+ ""
107
+ end
108
+
109
+ # @return [Boolean]
110
+ def ==(other)
111
+ eql?(other)
112
+ end
113
+
114
+ # @return [Invalid]
115
+ def copy(changes = {})
116
+ self
117
+ end
118
+ end
119
+
120
+ class Valid < DateVal
121
+ def valid?
122
+ true
123
+ end
124
+
125
+ # @return [DateVal]
126
+ def map
127
+ DateVal.value(yield(value), usage, position)
128
+ end
129
+
130
+ # @return [DateVal]
131
+ def copy(changes = {})
132
+ DateVal.value \
133
+ changes.fetch(:value, value),
134
+ changes.fetch(:usage, usage),
135
+ changes.fetch(:position, position)
136
+ end
137
+
138
+ def coerce(other)
139
+ return DateVal.value(other, usage, position), self
140
+ end
141
+
142
+ def ==(other)
143
+ other = DateVal.value(other, usage, position)
144
+ other.valid? and other.value == value
145
+ end
146
+ end
147
+
148
+ #
149
+ # Empty date value. Shouldn't be directly instantiated -- instead,
150
+ # use the {DateVal.empty} constructor.
151
+ #
152
+ class Empty < Valid
153
+ def value
154
+ nil
155
+ end
156
+
157
+ def empty?
158
+ true
159
+ end
160
+
161
+ # @return [String]
162
+ # :nocov:
163
+ def inspect
164
+ id = definition.bind do |d|
165
+ "[#{"% 5s" % d.id}: #{d.name}]".bind do |s|
166
+ if usage.forbidden?
167
+ ansi.forbidden(s)
168
+ elsif usage.required?
169
+ ansi.required(s)
170
+ else
171
+ ansi.optional(s)
172
+ end
173
+ end
174
+ end
175
+
176
+ ansi.element("DT.empty#{id}")
177
+ end
178
+ # :nocov:
179
+
180
+ # @return [String]
181
+ def to_s
182
+ ""
183
+ end
184
+
185
+ # @return [String]
186
+ def to_x12(truncate = true)
187
+ ""
188
+ end
189
+ end
190
+
191
+ #
192
+ # Date with a fully-specified year (with century). Shouldn't be
193
+ # directly instantiated -- instead use the {DateVal.value} constructor
194
+ #
195
+ class Proper < Valid
196
+ def_delegators :@value, :year, :month, :day, :cwday, :cweek,
197
+ :downto, :upto, :step, :httpdate, :to_s, :to_i, :strftime,
198
+ :iso8601, :rfc2822, :rfc3339, :rfc822, :leap?, :julian?,
199
+ :gregorian?, :mday, :mon, :to_datetime, :to_int, :to_r, :to_c,
200
+ :wday, :xmlschema, :yday, :start
201
+
202
+ extend Operators::Wrappers
203
+ wrappers :+, :<<, :>>, :next_day, :next_month, :next_year,
204
+ :prev_day, :prev_month, :prev_year
205
+
206
+ extend Operators::Unary
207
+ unary_operators :next, :succ, :prev
208
+
209
+ extend Operators::Relational
210
+ relational_operators :<, :>, :<=, :>=, :<=>, :-, :coerce => :to_date
211
+
212
+ attr_reader :value
213
+
214
+ def initialize(value, usage, position)
215
+ @value = value
216
+
217
+ # Check that date is reasonably valid
218
+ unless @value.year.between?(0, 9999) and @value.month.between?(1, 12) and @value.day.between?(1, 31)
219
+ raise Exceptions::InvalidElementError,
220
+ "invalid date: year(#{year}) month(#{month}) day(#{day})"
221
+ end
222
+
223
+ super(usage, position)
224
+ end
225
+
226
+ def empty?
227
+ false
228
+ end
229
+
230
+ def proper?
231
+ true
232
+ end
233
+
234
+ # @return [Date]
235
+ def to_date
236
+ @value
237
+ end
238
+
239
+ # @return [Time]
240
+ def to_time(hour = nil, minute = nil, second = nil)
241
+ if minute.nil? and second.nil? and hour.respond_to?(:hour)
242
+ if hour.respond_to?(:minute) and hour.respond_to?(:second)
243
+ hour, minute, second = hour.hour, hour.minute, hour.second
244
+ elsif hour.respond_to?(:min) and hour.respond_to?(:sec)
245
+ hour, minute, second = hour.hour, hour.min, hour.sec
246
+ end
247
+ end
248
+
249
+ if not second.nil?
250
+ Time.utc(year, month, day, hour, minute, second)
251
+ elsif not minute.nil?
252
+ Time.utc(year, month, day, hour, minute)
253
+ elsif not hour.nil?
254
+ Time.utc(year, month, day, hour)
255
+ else
256
+ Time.utc(year, month, day)
257
+ end
258
+ end
259
+
260
+ # @return [Proper] self
261
+ def oldest(date)
262
+ self
263
+ end
264
+
265
+ # @return [Proper] self
266
+ def newest(date)
267
+ self
268
+ end
269
+
270
+ # @return [Proper] self
271
+ def future
272
+ self
273
+ end
274
+
275
+ # @return [Proper] self
276
+ def past
277
+ self
278
+ end
279
+
280
+ # @return [String]
281
+ # :nocov:
282
+ def inspect
283
+ id = definition.bind do |d|
284
+ "[#{"% 5s" % d.id}: #{d.name}]".bind do |s|
285
+ if usage.forbidden?
286
+ ansi.forbidden(s)
287
+ elsif usage.required?
288
+ ansi.required(s)
289
+ else
290
+ ansi.optional(s)
291
+ end
292
+ end
293
+ end
294
+
295
+ ansi.element("DT.value#{id}") + "(#{"%04d-%02d-%02d" % [year, month, day]})"
296
+ end
297
+ # :nocov:
298
+
299
+ # @return [String]
300
+ def to_x12(truncate = true)
301
+ x12 =
302
+ if definition.max_length < 8
303
+ "%02d%02d%02d" % [year % 100, month, day]
304
+ else
305
+ "%04d%02d%02d" % [year % 10000, month, day]
306
+ end
307
+
308
+ if truncate
309
+ x12.slice(-definition.max_length..-1)
310
+ else
311
+ x12
312
+ end
313
+ end
314
+
315
+ def too_long?
316
+ if definition.max_length < 8
317
+ definition.max_length - 2 < year.to_s.length
318
+ else
319
+ definition.max_length - 4 < year.to_s.length
320
+ end
321
+ end
322
+ end
323
+
324
+ #
325
+ # Date with a partially-specified year (two digits, missing century).
326
+ # Shouldn't be directly instantiated -- instead, use the constuctor
327
+ # method {DateVal.value}
328
+ #
329
+ class Improper < Valid
330
+ # @return [Integer]
331
+ attr_reader :year
332
+
333
+ # @return [Integer]
334
+ attr_reader :month
335
+
336
+ # @return [Integer]
337
+ attr_reader :day
338
+
339
+ def initialize(year, month, day, usage, position)
340
+ @year, @month, @day = year, month, day
341
+
342
+ # Check that date is reasonably valid
343
+ unless @year.between?(0, 99) and @month.between?(1, 12) and @day.between?(1, 31)
344
+ raise Exceptions::InvalidElementError,
345
+ "invalid date: year(#{year}) month(#{month}) day(#{day})"
346
+ end
347
+
348
+ super(usage, position)
349
+ end
350
+
351
+ def value
352
+ [@year, @month, @day]
353
+ end
354
+
355
+ def copy(changes = {})
356
+ if [:year, :month, :day].any?{|k| changes.include?(k) }
357
+ changes[:value] = [changes.fetch(:year, @year),
358
+ changes.fetch(:month, @month),
359
+ changes.fetch(:day, @day)]
360
+ end
361
+
362
+ super(changes)
363
+ end
364
+
365
+ def empty?
366
+ false
367
+ end
368
+
369
+ def proper?
370
+ false
371
+ end
372
+
373
+ def too_short?
374
+ # Less than a 4-digit year means our max length is 7, but in
375
+ # practice the definition min/max lengths are either 6 or 8
376
+ definition.min_length > 6
377
+ end
378
+
379
+ def too_long?
380
+ # We know month and day occupy four characters, but year *could*
381
+ # occupy either three or two characters. If the max_length can't
382
+ # accomodate a three-digit year, make sure we don't have one
383
+ definition.max_length < 7 and @year > 99
384
+ end
385
+
386
+ # Create a proper date using the given century `cc`
387
+ #
388
+ # @example
389
+ # DateVal.value("501015").century(19) #=> DateVal.value("19501230")
390
+ #
391
+ # @return [Proper]
392
+ def century(cc)
393
+ date = ::Date.civil(100 * cc + @year, @month, @day)
394
+ Proper.new(date, usage, position)
395
+ end
396
+
397
+ # Create a proper date which cannot be older than the given `date`
398
+ # and cannot be newer than one year after the given `date`.
399
+ #
400
+ # @example
401
+ # DateVal.value("501015").oldest(Date.civil(1950, 10, 20)) #=> DateVal.value("20501015")
402
+ # DateVal.value("501015").oldest(Date.civil(1950, 10, 15)) #=> DateVal.value("19501015")
403
+ # DateVal.value("501015").oldest(Date.civil(1950, 10, 10)) #=> DateVal.value("19501015")
404
+ #
405
+ # @return [Proper]
406
+ def oldest(date)
407
+ cc, yy = date.year.divmod(100)
408
+
409
+ if @year < yy
410
+ century(cc + 1)
411
+ elsif @year > yy
412
+ century(cc)
413
+ else
414
+ if @month < date.month
415
+ century(cc + 1)
416
+ elsif @month > date.month
417
+ century(cc)
418
+ else
419
+ if @day < date.day
420
+ century(cc + 1)
421
+ else
422
+ century(cc)
423
+ end
424
+ end
425
+ end
426
+ end
427
+
428
+ # Create a proper date which cannot be newer than the given `date`
429
+ # and cannot be older than one year before the given `date`.
430
+ #
431
+ # @example
432
+ # DateVal.value("501015").newest(Date.civil(1950, 10, 20)) #=> DateVal.value("19501015")
433
+ # DateVal.value("501015").newest(Date.civil(1950, 10, 15)) #=> DateVal.value("19501015")
434
+ # DateVal.value("501015").newest(Date.civil(1950, 10, 10)) #=> DateVal.value("18501015")
435
+ #
436
+ # @return [Proper]
437
+ def newest(date)
438
+ cc, yy = date.year.divmod(100)
439
+
440
+ if @year < yy
441
+ century(cc)
442
+ elsif @year > yy
443
+ century(cc - 1)
444
+ else
445
+ if @month < date.month
446
+ century(cc)
447
+ elsif @month > date.month
448
+ century(cc - 1)
449
+ else
450
+ if @day <= date.day
451
+ century(cc)
452
+ else
453
+ century(cc - 1)
454
+ end
455
+ end
456
+ end
457
+ end
458
+
459
+ # Create a proper date which cannot be newer than the current date
460
+ #
461
+ # @return [Proper]
462
+ def past
463
+ newest(Date.today)
464
+ end
465
+
466
+ # Create a proper date which cannot be older than the current date
467
+ #
468
+ # @return [Proper]
469
+ def future
470
+ oldest(Date.today)
471
+ end
472
+
473
+ # @return [String]
474
+ # :nocov:
475
+ def inspect
476
+ id = definition.bind do |d|
477
+ "[#{"% 5s" % d.id}: #{d.name}]".bind do |s|
478
+ if usage.forbidden?
479
+ ansi.forbidden(s)
480
+ elsif usage.required?
481
+ ansi.required(s)
482
+ else
483
+ ansi.optional(s)
484
+ end
485
+ end
486
+ end
487
+
488
+ ansi.element("DT.value#{id}") + "(XX#{"%02d-%02d-%02d" % [@year, @month, @day]})"
489
+ end
490
+ # :nocov:
491
+
492
+ # @return [String]
493
+ def to_s
494
+ "XX%02d%02d%02d" % [@year, @month, @day]
495
+ end
496
+
497
+ # @return [String]
498
+ def to_x12(truncate = true)
499
+ "%02d%02d%02d" % [@year, @month, @day]
500
+ end
501
+ end
502
+ end
503
+
504
+ class << DateVal
505
+ # @group Constructors
506
+ ###################################################################
507
+
508
+ # @return [DateVal::Empty]
509
+ def empty(usage, position)
510
+ self::Empty.new(usage, position)
511
+ end
512
+
513
+ # @return [DateVal]
514
+ def value(object, usage, position)
515
+ if object.is_a?(DateVal)
516
+ object#.copy(:usage => usage, :position => position)
517
+ elsif object.blank?
518
+ self::Empty.new(usage, position)
519
+ elsif object.is_a?(String) or object.is_a?(StringVal)
520
+ string = object.to_s
521
+
522
+ if string.length < 6
523
+ self::Invalid.new(object, usage, position)
524
+ else
525
+ day = string.slice(-2, 2).to_i
526
+ month = string.slice(-4, 2).to_i
527
+ year = string.slice( 0..-5)
528
+
529
+ if year.length < 4
530
+ self::Improper.new(year.to_i, month, day, usage, position)
531
+ else
532
+ self::Proper.new(date(year, month, day), usage, position)
533
+ end
534
+ end
535
+ elsif object.respond_to?(:year) and object.respond_to?(:month) and object.respond_to?(:day)
536
+ self::Proper.new(date(object.year, object.month, object.day), usage, position)
537
+ elsif object.is_a?(Array) and object.length == 3
538
+ if object[0] <= 99
539
+ self::Improper.new(*object, usage, position)
540
+ else
541
+ self::Proper.new(date(*object), usage, position)
542
+ end
543
+ else
544
+ self::Invalid.new(object, usage, position)
545
+ end
546
+ rescue Exceptions::InvalidElementError
547
+ self::Invalid.new(object, usage, position)
548
+ end
549
+
550
+ # @endgroup
551
+ ###################################################################
552
+
553
+ private
554
+
555
+ def date(year, month, day)
556
+ ::Date.civil(year.to_i, month.to_i, day.to_i)
557
+ rescue
558
+ raise Exceptions::InvalidElementError,
559
+ "invalid year(#{year}), month(#{month}), day(#{day})"
560
+ end
561
+ end
562
+
563
+ # Prevent direct instantiation of abstract class DateVal
564
+ DateVal.eigenclass.send(:protected, :new)
565
+ DateVal::Empty.eigenclass.send(:public, :new)
566
+ DateVal::Proper.eigenclass.send(:public, :new)
567
+ DateVal::Invalid.eigenclass.send(:public, :new)
568
+ DateVal::Improper.eigenclass.send(:public, :new)
569
+ end
570
+ end
571
+ end
572
+ end