roo 2.3.0 → 2.10.1

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 (95) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +17 -0
  3. data/.github/issue_template.md +16 -0
  4. data/.github/pull_request_template.md +14 -0
  5. data/.github/workflows/pull-request.yml +15 -0
  6. data/.github/workflows/ruby.yml +34 -0
  7. data/.gitignore +4 -0
  8. data/.rubocop.yml +186 -0
  9. data/CHANGELOG.md +148 -0
  10. data/Gemfile +4 -4
  11. data/LICENSE +2 -0
  12. data/README.md +84 -27
  13. data/Rakefile +1 -1
  14. data/lib/roo/base.rb +111 -237
  15. data/lib/roo/constants.rb +5 -3
  16. data/lib/roo/csv.rb +106 -85
  17. data/lib/roo/errors.rb +2 -0
  18. data/lib/roo/excelx/cell/base.rb +26 -12
  19. data/lib/roo/excelx/cell/boolean.rb +9 -6
  20. data/lib/roo/excelx/cell/date.rb +7 -7
  21. data/lib/roo/excelx/cell/datetime.rb +50 -44
  22. data/lib/roo/excelx/cell/empty.rb +3 -2
  23. data/lib/roo/excelx/cell/number.rb +60 -47
  24. data/lib/roo/excelx/cell/string.rb +3 -3
  25. data/lib/roo/excelx/cell/time.rb +17 -16
  26. data/lib/roo/excelx/cell.rb +11 -7
  27. data/lib/roo/excelx/comments.rb +3 -3
  28. data/lib/roo/excelx/coordinate.rb +11 -4
  29. data/lib/roo/excelx/extractor.rb +20 -3
  30. data/lib/roo/excelx/format.rb +38 -31
  31. data/lib/roo/excelx/images.rb +26 -0
  32. data/lib/roo/excelx/relationships.rb +12 -4
  33. data/lib/roo/excelx/shared.rb +10 -3
  34. data/lib/roo/excelx/shared_strings.rb +113 -9
  35. data/lib/roo/excelx/sheet.rb +49 -10
  36. data/lib/roo/excelx/sheet_doc.rb +101 -48
  37. data/lib/roo/excelx/styles.rb +4 -4
  38. data/lib/roo/excelx/workbook.rb +8 -3
  39. data/lib/roo/excelx.rb +85 -42
  40. data/lib/roo/formatters/base.rb +15 -0
  41. data/lib/roo/formatters/csv.rb +84 -0
  42. data/lib/roo/formatters/matrix.rb +23 -0
  43. data/lib/roo/formatters/xml.rb +31 -0
  44. data/lib/roo/formatters/yaml.rb +40 -0
  45. data/lib/roo/helpers/default_attr_reader.rb +20 -0
  46. data/lib/roo/helpers/weak_instance_cache.rb +41 -0
  47. data/lib/roo/open_office.rb +41 -27
  48. data/lib/roo/spreadsheet.rb +8 -2
  49. data/lib/roo/tempdir.rb +24 -0
  50. data/lib/roo/utils.rb +76 -26
  51. data/lib/roo/version.rb +1 -1
  52. data/lib/roo.rb +5 -0
  53. data/roo.gemspec +22 -12
  54. data/spec/lib/roo/base_spec.rb +65 -3
  55. data/spec/lib/roo/csv_spec.rb +19 -0
  56. data/spec/lib/roo/excelx/cell/time_spec.rb +15 -0
  57. data/spec/lib/roo/excelx/relationships_spec.rb +43 -0
  58. data/spec/lib/roo/excelx/sheet_doc_spec.rb +11 -0
  59. data/spec/lib/roo/excelx_spec.rb +237 -5
  60. data/spec/lib/roo/openoffice_spec.rb +2 -2
  61. data/spec/lib/roo/spreadsheet_spec.rb +1 -1
  62. data/spec/lib/roo/strict_spec.rb +43 -0
  63. data/spec/lib/roo/utils_spec.rb +22 -9
  64. data/spec/lib/roo/weak_instance_cache_spec.rb +92 -0
  65. data/spec/lib/roo_spec.rb +0 -0
  66. data/spec/spec_helper.rb +2 -7
  67. data/test/excelx/cell/test_attr_reader_default.rb +72 -0
  68. data/test/excelx/cell/test_base.rb +6 -2
  69. data/test/excelx/cell/test_boolean.rb +1 -3
  70. data/test/excelx/cell/test_date.rb +1 -6
  71. data/test/excelx/cell/test_datetime.rb +7 -10
  72. data/test/excelx/cell/test_empty.rb +12 -2
  73. data/test/excelx/cell/test_number.rb +28 -4
  74. data/test/excelx/cell/test_string.rb +21 -3
  75. data/test/excelx/cell/test_time.rb +7 -10
  76. data/test/excelx/test_coordinate.rb +51 -0
  77. data/test/formatters/test_csv.rb +136 -0
  78. data/test/formatters/test_matrix.rb +76 -0
  79. data/test/formatters/test_xml.rb +78 -0
  80. data/test/formatters/test_yaml.rb +20 -0
  81. data/test/helpers/test_accessing_files.rb +81 -0
  82. data/test/helpers/test_comments.rb +43 -0
  83. data/test/helpers/test_formulas.rb +9 -0
  84. data/test/helpers/test_labels.rb +103 -0
  85. data/test/helpers/test_sheets.rb +55 -0
  86. data/test/helpers/test_styles.rb +62 -0
  87. data/test/roo/test_base.rb +182 -0
  88. data/test/roo/test_csv.rb +88 -0
  89. data/test/roo/test_excelx.rb +360 -0
  90. data/test/roo/test_libre_office.rb +9 -0
  91. data/test/roo/test_open_office.rb +289 -0
  92. data/test/test_helper.rb +129 -14
  93. data/test/test_roo.rb +60 -1765
  94. metadata +91 -21
  95. data/.travis.yml +0 -14
data/lib/roo/csv.rb CHANGED
@@ -1,5 +1,7 @@
1
- require 'csv'
2
- require 'time'
1
+ # frozen_string_literal: true
2
+
3
+ require "csv"
4
+ require "time"
3
5
 
4
6
  # The CSV class can read csv files (must be separated with commas) which then
5
7
  # can be handled like spreadsheets. This means you can access cells like A5
@@ -9,112 +11,131 @@ require 'time'
9
11
  #
10
12
  # You can pass options to the underlying CSV parse operation, via the
11
13
  # :csv_options option.
12
- #
14
+ module Roo
15
+ class CSV < Roo::Base
16
+ attr_reader :filename
17
+
18
+ # Returns an array with the names of the sheets. In CSV class there is only
19
+ # one dummy sheet, because a csv file cannot have more than one sheet.
20
+ def sheets
21
+ ["default"]
22
+ end
13
23
 
14
- class Roo::CSV < Roo::Base
24
+ def cell(row, col, sheet = nil)
25
+ sheet ||= default_sheet
26
+ read_cells(sheet)
27
+ @cell[normalize(row, col)]
28
+ end
15
29
 
16
- attr_reader :filename
30
+ def celltype(row, col, sheet = nil)
31
+ sheet ||= default_sheet
32
+ read_cells(sheet)
33
+ @cell_type[normalize(row, col)]
34
+ end
17
35
 
18
- # Returns an array with the names of the sheets. In CSV class there is only
19
- # one dummy sheet, because a csv file cannot have more than one sheet.
20
- def sheets
21
- ['default']
22
- end
36
+ def cell_postprocessing(_row, _col, value)
37
+ value
38
+ end
23
39
 
24
- def cell(row, col, sheet=nil)
25
- sheet ||= default_sheet
26
- read_cells(sheet)
27
- @cell[normalize(row,col)]
28
- end
40
+ def csv_options
41
+ @options[:csv_options] || {}
42
+ end
29
43
 
30
- def celltype(row, col, sheet=nil)
31
- sheet ||= default_sheet
32
- read_cells(sheet)
33
- @cell_type[normalize(row,col)]
34
- end
44
+ def set_value(row, col, value, _sheet)
45
+ @cell[[row, col]] = value
46
+ end
35
47
 
36
- def cell_postprocessing(row,col,value)
37
- value
38
- end
48
+ def set_type(row, col, type, _sheet)
49
+ @cell_type[[row, col]] = type
50
+ end
39
51
 
40
- def csv_options
41
- @options[:csv_options] || {}
42
- end
52
+ private
43
53
 
44
- private
54
+ TYPE_MAP = {
55
+ String => :string,
56
+ Float => :float,
57
+ Date => :date,
58
+ DateTime => :datetime,
59
+ }
45
60
 
46
- TYPE_MAP = {
47
- String => :string,
48
- Float => :float,
49
- Date => :date,
50
- DateTime => :datetime,
51
- }
61
+ def celltype_class(value)
62
+ TYPE_MAP[value.class]
63
+ end
52
64
 
53
- def celltype_class(value)
54
- TYPE_MAP[value.class]
55
- end
65
+ def read_cells(sheet = default_sheet)
66
+ sheet ||= default_sheet
67
+ return if @cells_read[sheet]
68
+ row_num = 0
69
+ max_col_num = 0
70
+
71
+ each_row csv_options do |row|
72
+ row_num += 1
73
+ col_num = 0
74
+
75
+ row.each do |elem|
76
+ col_num += 1
77
+ coordinate = [row_num, col_num]
78
+ @cell[coordinate] = elem
79
+ @cell_type[coordinate] = celltype_class(elem)
80
+ end
56
81
 
57
- def each_row(options, &block)
58
- if uri?(filename)
59
- make_tmpdir do |tmpdir|
60
- tmp_filename = download_uri(filename, tmpdir)
61
- CSV.foreach(tmp_filename, options, &block)
82
+ max_col_num = col_num if col_num > max_col_num
62
83
  end
63
- else
64
- CSV.foreach(filename, options, &block)
84
+
85
+ set_row_count(sheet, row_num)
86
+ set_column_count(sheet, max_col_num)
87
+ @cells_read[sheet] = true
65
88
  end
66
- end
67
89
 
68
- def read_cells(sheet = default_sheet)
69
- sheet ||= default_sheet
70
- return if @cells_read[sheet]
71
- @first_row[sheet] = 1
72
- @last_row[sheet] = 0
73
- @first_column[sheet] = 1
74
- @last_column[sheet] = 1
75
- rownum = 1
76
- each_row csv_options do |row|
77
- row.each_with_index do |elem,i|
78
- @cell[[rownum,i+1]] = cell_postprocessing rownum,i+1, elem
79
- @cell_type[[rownum,i+1]] = celltype_class @cell[[rownum,i+1]]
80
- if i+1 > @last_column[sheet]
81
- @last_column[sheet] += 1
82
- end
90
+ def each_row(options, &block)
91
+ if uri?(filename)
92
+ each_row_using_tempdir(options, &block)
93
+ else
94
+ csv_foreach(filename_or_stream, options, &block)
83
95
  end
84
- rownum += 1
85
- @last_row[sheet] += 1
86
96
  end
87
- @cells_read[sheet] = true
88
- #-- adjust @first_row if neccessary
89
- while !row(@first_row[sheet]).any? and @first_row[sheet] < @last_row[sheet]
90
- @first_row[sheet] += 1
97
+
98
+ def each_row_using_tempdir(options, &block)
99
+ ::Dir.mktmpdir(Roo::TEMP_PREFIX, ENV["ROO_TMP"]) do |tmpdir|
100
+ tmp_filename = download_uri(filename, tmpdir)
101
+ csv_foreach(tmp_filename, options, &block)
102
+ end
91
103
  end
92
- #-- adjust @last_row if neccessary
93
- while !row(@last_row[sheet]).any? and @last_row[sheet] and
94
- @last_row[sheet] > @first_row[sheet]
95
- @last_row[sheet] -= 1
104
+
105
+ def csv_foreach(path_or_io, options, &block)
106
+ if is_stream?(path_or_io)
107
+ ::CSV.new(path_or_io, **options).each(&block)
108
+ else
109
+ ::CSV.foreach(path_or_io, **options, &block)
110
+ end
96
111
  end
97
- #-- adjust @first_column if neccessary
98
- while !column(@first_column[sheet]).any? and
99
- @first_column[sheet] and
100
- @first_column[sheet] < @last_column[sheet]
101
- @first_column[sheet] += 1
112
+
113
+ def set_row_count(sheet, last_row)
114
+ @first_row[sheet] = 1
115
+ @last_row[sheet] = last_row
116
+ @last_row[sheet] = @first_row[sheet] if @last_row[sheet].zero?
117
+
118
+ nil
102
119
  end
103
- #-- adjust @last_column if neccessary
104
- while !column(@last_column[sheet]).any? and
105
- @last_column[sheet] and
106
- @last_column[sheet] > @first_column[sheet]
107
- @last_column[sheet] -= 1
120
+
121
+ def set_column_count(sheet, last_col)
122
+ @first_column[sheet] = 1
123
+ @last_column[sheet] = last_col
124
+ @last_column[sheet] = @first_column[sheet] if @last_column[sheet].zero?
125
+
126
+ nil
108
127
  end
109
- end
110
128
 
111
- def clean_sheet(sheet)
112
- read_cells(sheet)
129
+ def clean_sheet(sheet)
130
+ read_cells(sheet)
131
+
132
+ @cell.each_pair do |coord, value|
133
+ @cell[coord] = sanitize_value(value) if value.is_a?(::String)
134
+ end
113
135
 
114
- @cell.each_pair do |coord, value|
115
- @cell[coord] = sanitize_value(value) if value.is_a?(::String)
136
+ @cleaned[sheet] = true
116
137
  end
117
138
 
118
- @cleaned[sheet] = true
139
+ alias_method :filename_or_stream, :filename
119
140
  end
120
141
  end
data/lib/roo/errors.rb CHANGED
@@ -6,4 +6,6 @@ module Roo
6
6
  # Raised when Roo cannot find a header row that matches the given column
7
7
  # name(s).
8
8
  class HeaderRowNotFoundError < Error; end
9
+
10
+ class FileNotFound < Error; end
9
11
  end
@@ -1,13 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roo/helpers/default_attr_reader"
4
+
1
5
  module Roo
2
6
  class Excelx
3
7
  class Cell
4
8
  class Base
9
+ extend Roo::Helpers::DefaultAttrReader
5
10
  attr_reader :cell_type, :cell_value, :value
6
11
 
7
12
  # FIXME: I think style should be deprecated. Having a style attribute
8
13
  # for a cell doesn't really accomplish much. It seems to be used
9
14
  # when you want to export to excelx.
10
- attr_reader :style
15
+ attr_reader_with_default default_type: :base, style: 1
11
16
 
12
17
 
13
18
  # FIXME: Updating a cell's value should be able tochange the cell's type,
@@ -34,14 +39,12 @@ module Roo
34
39
  attr_writer :value
35
40
 
36
41
  def initialize(value, formula, excelx_type, style, link, coordinate)
37
- @link = !!link
38
42
  @cell_value = value
39
- @cell_type = excelx_type
40
- @formula = formula
41
- @style = style
43
+ @cell_type = excelx_type if excelx_type
44
+ @formula = formula if formula
45
+ @style = style unless style == 1
42
46
  @coordinate = coordinate
43
- @type = :base
44
- @value = link? ? Roo::Link.new(link, value) : value
47
+ @value = link ? Roo::Link.new(link, value) : value
45
48
  end
46
49
 
47
50
  def type
@@ -50,16 +53,16 @@ module Roo
50
53
  elsif link?
51
54
  :link
52
55
  else
53
- @type
56
+ default_type
54
57
  end
55
58
  end
56
59
 
57
60
  def formula?
58
- !!@formula
61
+ !!(defined?(@formula) && @formula)
59
62
  end
60
63
 
61
64
  def link?
62
- !!@link
65
+ Roo::Link === @value
63
66
  end
64
67
 
65
68
  alias_method :formatted_value, :value
@@ -68,9 +71,16 @@ module Roo
68
71
  formatted_value
69
72
  end
70
73
 
71
- # DEPRECATED: Please use link instead.
74
+ # DEPRECATED: Please use link? instead.
72
75
  def hyperlink
73
- warn '[DEPRECATION] `hyperlink` is deprecated. Please use `link` instead.'
76
+ warn '[DEPRECATION] `hyperlink` is deprecated. Please use `link?` instead.'
77
+ link?
78
+ end
79
+
80
+ # DEPRECATED: Please use link? instead.
81
+ def link
82
+ warn '[DEPRECATION] `link` is deprecated. Please use `link?` instead.'
83
+ link?
74
84
  end
75
85
 
76
86
  # DEPRECATED: Please use cell_value instead.
@@ -88,6 +98,10 @@ module Roo
88
98
  def empty?
89
99
  false
90
100
  end
101
+
102
+ def presence
103
+ empty? ? nil : self
104
+ end
91
105
  end
92
106
  end
93
107
  end
@@ -1,17 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Roo
2
4
  class Excelx
3
5
  class Cell
4
6
  class Boolean < Cell::Base
5
- attr_reader :value, :formula, :format, :cell_type, :cell_value, :link, :coordinate
7
+ attr_reader :value, :formula, :format, :cell_value, :coordinate
8
+
9
+ attr_reader_with_default default_type: :boolean, cell_type: :boolean
6
10
 
7
11
  def initialize(value, formula, style, link, coordinate)
8
- super(value, formula, nil, style, link, coordinate)
9
- @type = @cell_type = :boolean
10
- @value = link? ? Roo::Link.new(link, value) : create_boolean(value)
12
+ super(value, formula, nil, style, nil, coordinate)
13
+ @value = link ? Roo::Link.new(link, value) : create_boolean(value)
11
14
  end
12
15
 
13
16
  def formatted_value
14
- value ? 'TRUE'.freeze : 'FALSE'.freeze
17
+ value ? 'TRUE' : 'FALSE'
15
18
  end
16
19
 
17
20
  private
@@ -19,7 +22,7 @@ module Roo
19
22
  def create_boolean(value)
20
23
  # FIXME: Using a boolean will cause methods like Base#to_csv to fail.
21
24
  # Roo is using some method to ignore false/nil values.
22
- value.to_i == 1 ? true : false
25
+ value.to_i == 1
23
26
  end
24
27
  end
25
28
  end
@@ -4,23 +4,23 @@ module Roo
4
4
  class Excelx
5
5
  class Cell
6
6
  class Date < Roo::Excelx::Cell::DateTime
7
- attr_reader :value, :formula, :format, :cell_type, :cell_value, :link, :coordinate
7
+ attr_reader :value, :formula, :format, :cell_type, :cell_value, :coordinate
8
+
9
+ attr_reader_with_default default_type: :date
8
10
 
9
11
  def initialize(value, formula, excelx_type, style, link, base_date, coordinate)
10
12
  # NOTE: Pass all arguments to the parent class, DateTime.
11
13
  super
12
- @type = :date
13
14
  @format = excelx_type.last
14
- @value = link? ? Roo::Link.new(link, value) : create_date(base_date, value)
15
+ @value = link ? Roo::Link.new(link, value) : create_date(base_date, value)
15
16
  end
16
17
 
17
18
  private
18
19
 
19
- def create_date(base_date, value)
20
- date = base_date + value.to_i
21
- yyyy, mm, dd = date.strftime('%Y-%m-%d').split('-')
20
+ def create_datetime(_,_); end
22
21
 
23
- ::Date.new(yyyy.to_i, mm.to_i, dd.to_i)
22
+ def create_date(base_date, value)
23
+ base_date + value.to_i
24
24
  end
25
25
  end
26
26
  end
@@ -1,16 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'date'
2
4
 
3
5
  module Roo
4
6
  class Excelx
5
7
  class Cell
6
8
  class DateTime < Cell::Base
7
- attr_reader :value, :formula, :format, :cell_value, :link, :coordinate
9
+ SECONDS_IN_DAY = 60 * 60 * 24
10
+
11
+ attr_reader :value, :formula, :format, :cell_value, :coordinate
8
12
 
9
- def initialize(value, formula, excelx_type, style, link, base_date, coordinate)
10
- super(value, formula, excelx_type, style, link, coordinate)
11
- @type = :datetime
13
+ attr_reader_with_default default_type: :datetime
14
+
15
+ def initialize(value, formula, excelx_type, style, link, base_timestamp, coordinate)
16
+ super(value, formula, excelx_type, style, nil, coordinate)
12
17
  @format = excelx_type.last
13
- @value = link? ? Roo::Link.new(link, value) : create_datetime(base_date, value)
18
+ @value = link ? Roo::Link.new(link, value) : create_datetime(base_timestamp, value)
14
19
  end
15
20
 
16
21
  # Public: Returns formatted value for a datetime. Format's can be an
@@ -32,14 +37,9 @@ module Roo
32
37
  #
33
38
  # Returns a String representation of a cell's value.
34
39
  def formatted_value
35
- date_regex = /(?<date>[dmy]+[\-\/][dmy]+([\-\/][dmy]+)?)/
36
- time_regex = /(?<time>(\[?[h]\]?+:)?[m]+(:?ss|:?s)?)/
37
-
38
40
  formatter = @format.downcase.split(' ').map do |part|
39
- if part[date_regex] == part
40
- part.gsub(/#{DATE_FORMATS.keys.join('|')}/, DATE_FORMATS)
41
- elsif part[time_regex]
42
- part.gsub(/#{TIME_FORMATS.keys.join('|')}/, TIME_FORMATS)
41
+ if (parsed_format = parse_date_or_time_format(part))
42
+ parsed_format
43
43
  else
44
44
  warn 'Unable to parse custom format. Using "YYYY-mm-dd HH:MM:SS" format.'
45
45
  return @value.strftime('%F %T')
@@ -51,49 +51,55 @@ module Roo
51
51
 
52
52
  private
53
53
 
54
+ def parse_date_or_time_format(part)
55
+ date_regex = /(?<date>[dmy]+[\-\/][dmy]+([\-\/][dmy]+)?)/
56
+ time_regex = /(?<time>(\[?[h]\]?+:)?[m]+(:?ss|:?s)?)/
57
+
58
+ if part[date_regex] == part
59
+ formats = DATE_FORMATS
60
+ elsif part[time_regex]
61
+ formats = TIME_FORMATS
62
+ else
63
+ return false
64
+ end
65
+
66
+ part.gsub(/#{formats.keys.join('|')}/, formats)
67
+ end
68
+
54
69
  DATE_FORMATS = {
55
- 'yyyy'.freeze => '%Y'.freeze, # Year: 2000
56
- 'yy'.freeze => '%y'.freeze, # Year: 00
70
+ 'yyyy' => '%Y', # Year: 2000
71
+ 'yy' => '%y', # Year: 00
57
72
  # mmmmm => J-D
58
- 'mmmm'.freeze => '%B'.freeze, # Month: January
59
- 'mmm'.freeze => '%^b'.freeze, # Month: JAN
60
- 'mm'.freeze => '%m'.freeze, # Month: 01
61
- 'm'.freeze => '%-m'.freeze, # Month: 1
62
- 'dddd'.freeze => '%A'.freeze, # Day of the Week: Sunday
63
- 'ddd'.freeze => '%^a'.freeze, # Day of the Week: SUN
64
- 'dd'.freeze => '%d'.freeze, # Day of the Month: 01
65
- 'd'.freeze => '%-d'.freeze, # Day of the Month: 1
73
+ 'mmmm' => '%B', # Month: January
74
+ 'mmm' => '%^b', # Month: JAN
75
+ 'mm' => '%m', # Month: 01
76
+ 'm' => '%-m', # Month: 1
77
+ 'dddd' => '%A', # Day of the Week: Sunday
78
+ 'ddd' => '%^a', # Day of the Week: SUN
79
+ 'dd' => '%d', # Day of the Month: 01
80
+ 'd' => '%-d' # Day of the Month: 1
66
81
  # '\\\\'.freeze => ''.freeze, # NOTE: Fixes a custom format's output.
67
82
  }
68
83
 
69
84
  TIME_FORMATS = {
70
- 'hh'.freeze => '%H'.freeze, # Hour (24): 01
71
- 'h'.freeze => '%-k'.freeze, # Hour (24): 1
85
+ 'hh' => '%H', # Hour (24): 01
86
+ 'h' => '%-k', # Hour (24): 1
72
87
  # 'hh'.freeze => '%I'.freeze, # Hour (12): 08
73
88
  # 'h'.freeze => '%-l'.freeze, # Hour (12): 8
74
- 'mm'.freeze => '%M'.freeze, # Minute: 01
89
+ 'mm' => '%M', # Minute: 01
75
90
  # FIXME: is this used? Seems like 'm' is used for month, not minute.
76
- 'm'.freeze => '%-M'.freeze, # Minute: 1
77
- 'ss'.freeze => '%S'.freeze, # Seconds: 01
78
- 's'.freeze => '%-S'.freeze, # Seconds: 1
79
- 'am/pm'.freeze => '%p'.freeze, # Meridian: AM
80
- '000'.freeze => '%3N'.freeze, # Fractional Seconds: thousandth.
81
- '00'.freeze => '%2N'.freeze, # Fractional Seconds: hundredth.
82
- '0'.freeze => '%1N'.freeze, # Fractional Seconds: tenths.
91
+ 'm' => '%-M', # Minute: 1
92
+ 'ss' => '%S', # Seconds: 01
93
+ 's' => '%-S', # Seconds: 1
94
+ 'am/pm' => '%p', # Meridian: AM
95
+ '000' => '%3N', # Fractional Seconds: thousandth.
96
+ '00' => '%2N', # Fractional Seconds: hundredth.
97
+ '0' => '%1N' # Fractional Seconds: tenths.
83
98
  }
84
99
 
85
- def create_datetime(base_date, value)
86
- date = base_date + value.to_f.round(6)
87
- datetime_string = date.strftime('%Y-%m-%d %H:%M:%S.%N')
88
- t = round_datetime(datetime_string)
89
-
90
- ::DateTime.civil(t.year, t.month, t.day, t.hour, t.min, t.sec)
91
- end
92
-
93
- def round_datetime(datetime_string)
94
- /(?<yyyy>\d+)-(?<mm>\d+)-(?<dd>\d+) (?<hh>\d+):(?<mi>\d+):(?<ss>\d+.\d+)/ =~ datetime_string
95
-
96
- ::Time.new(yyyy.to_i, mm.to_i, dd.to_i, hh.to_i, mi.to_i, ss.to_r).round(0)
100
+ def create_datetime(base_timestamp, value)
101
+ timestamp = (base_timestamp + (value.to_f.round(6) * SECONDS_IN_DAY)).round(0)
102
+ ::Time.at(timestamp).utc.to_datetime
97
103
  end
98
104
  end
99
105
  end
@@ -3,10 +3,11 @@ module Roo
3
3
  class Excelx
4
4
  class Cell
5
5
  class Empty < Cell::Base
6
- attr_reader :value, :formula, :format, :cell_type, :cell_value, :hyperlink, :coordinate
6
+ attr_reader :value, :formula, :format, :cell_type, :cell_value, :coordinate
7
+
8
+ attr_reader_with_default default_type: nil, style: nil
7
9
 
8
10
  def initialize(coordinate)
9
- @value = @formula = @format = @cell_type = @cell_value = @hyperlink = nil
10
11
  @coordinate = coordinate
11
12
  end
12
13