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.
- checksums.yaml +5 -5
- data/.codeclimate.yml +17 -0
- data/.github/issue_template.md +16 -0
- data/.github/pull_request_template.md +14 -0
- data/.github/workflows/pull-request.yml +15 -0
- data/.github/workflows/ruby.yml +34 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +186 -0
- data/CHANGELOG.md +148 -0
- data/Gemfile +4 -4
- data/LICENSE +2 -0
- data/README.md +84 -27
- data/Rakefile +1 -1
- data/lib/roo/base.rb +111 -237
- data/lib/roo/constants.rb +5 -3
- data/lib/roo/csv.rb +106 -85
- data/lib/roo/errors.rb +2 -0
- data/lib/roo/excelx/cell/base.rb +26 -12
- data/lib/roo/excelx/cell/boolean.rb +9 -6
- data/lib/roo/excelx/cell/date.rb +7 -7
- data/lib/roo/excelx/cell/datetime.rb +50 -44
- data/lib/roo/excelx/cell/empty.rb +3 -2
- data/lib/roo/excelx/cell/number.rb +60 -47
- data/lib/roo/excelx/cell/string.rb +3 -3
- data/lib/roo/excelx/cell/time.rb +17 -16
- data/lib/roo/excelx/cell.rb +11 -7
- data/lib/roo/excelx/comments.rb +3 -3
- data/lib/roo/excelx/coordinate.rb +11 -4
- data/lib/roo/excelx/extractor.rb +20 -3
- data/lib/roo/excelx/format.rb +38 -31
- data/lib/roo/excelx/images.rb +26 -0
- data/lib/roo/excelx/relationships.rb +12 -4
- data/lib/roo/excelx/shared.rb +10 -3
- data/lib/roo/excelx/shared_strings.rb +113 -9
- data/lib/roo/excelx/sheet.rb +49 -10
- data/lib/roo/excelx/sheet_doc.rb +101 -48
- data/lib/roo/excelx/styles.rb +4 -4
- data/lib/roo/excelx/workbook.rb +8 -3
- data/lib/roo/excelx.rb +85 -42
- data/lib/roo/formatters/base.rb +15 -0
- data/lib/roo/formatters/csv.rb +84 -0
- data/lib/roo/formatters/matrix.rb +23 -0
- data/lib/roo/formatters/xml.rb +31 -0
- data/lib/roo/formatters/yaml.rb +40 -0
- data/lib/roo/helpers/default_attr_reader.rb +20 -0
- data/lib/roo/helpers/weak_instance_cache.rb +41 -0
- data/lib/roo/open_office.rb +41 -27
- data/lib/roo/spreadsheet.rb +8 -2
- data/lib/roo/tempdir.rb +24 -0
- data/lib/roo/utils.rb +76 -26
- data/lib/roo/version.rb +1 -1
- data/lib/roo.rb +5 -0
- data/roo.gemspec +22 -12
- data/spec/lib/roo/base_spec.rb +65 -3
- data/spec/lib/roo/csv_spec.rb +19 -0
- data/spec/lib/roo/excelx/cell/time_spec.rb +15 -0
- data/spec/lib/roo/excelx/relationships_spec.rb +43 -0
- data/spec/lib/roo/excelx/sheet_doc_spec.rb +11 -0
- data/spec/lib/roo/excelx_spec.rb +237 -5
- data/spec/lib/roo/openoffice_spec.rb +2 -2
- data/spec/lib/roo/spreadsheet_spec.rb +1 -1
- data/spec/lib/roo/strict_spec.rb +43 -0
- data/spec/lib/roo/utils_spec.rb +22 -9
- data/spec/lib/roo/weak_instance_cache_spec.rb +92 -0
- data/spec/lib/roo_spec.rb +0 -0
- data/spec/spec_helper.rb +2 -7
- data/test/excelx/cell/test_attr_reader_default.rb +72 -0
- data/test/excelx/cell/test_base.rb +6 -2
- data/test/excelx/cell/test_boolean.rb +1 -3
- data/test/excelx/cell/test_date.rb +1 -6
- data/test/excelx/cell/test_datetime.rb +7 -10
- data/test/excelx/cell/test_empty.rb +12 -2
- data/test/excelx/cell/test_number.rb +28 -4
- data/test/excelx/cell/test_string.rb +21 -3
- data/test/excelx/cell/test_time.rb +7 -10
- data/test/excelx/test_coordinate.rb +51 -0
- data/test/formatters/test_csv.rb +136 -0
- data/test/formatters/test_matrix.rb +76 -0
- data/test/formatters/test_xml.rb +78 -0
- data/test/formatters/test_yaml.rb +20 -0
- data/test/helpers/test_accessing_files.rb +81 -0
- data/test/helpers/test_comments.rb +43 -0
- data/test/helpers/test_formulas.rb +9 -0
- data/test/helpers/test_labels.rb +103 -0
- data/test/helpers/test_sheets.rb +55 -0
- data/test/helpers/test_styles.rb +62 -0
- data/test/roo/test_base.rb +182 -0
- data/test/roo/test_csv.rb +88 -0
- data/test/roo/test_excelx.rb +360 -0
- data/test/roo/test_libre_office.rb +9 -0
- data/test/roo/test_open_office.rb +289 -0
- data/test/test_helper.rb +129 -14
- data/test/test_roo.rb +60 -1765
- metadata +91 -21
- data/.travis.yml +0 -14
    
        data/lib/roo/csv.rb
    CHANGED
    
    | @@ -1,5 +1,7 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 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 | 
            -
             | 
| 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 | 
            -
             | 
| 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 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
                ['default']
         | 
| 22 | 
            -
              end
         | 
| 36 | 
            +
                def cell_postprocessing(_row, _col, value)
         | 
| 37 | 
            +
                  value
         | 
| 38 | 
            +
                end
         | 
| 23 39 |  | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
                 | 
| 27 | 
            -
                @cell[normalize(row,col)]
         | 
| 28 | 
            -
              end
         | 
| 40 | 
            +
                def csv_options
         | 
| 41 | 
            +
                  @options[:csv_options] || {}
         | 
| 42 | 
            +
                end
         | 
| 29 43 |  | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
                 | 
| 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 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 48 | 
            +
                def set_type(row, col, type, _sheet)
         | 
| 49 | 
            +
                  @cell_type[[row, col]] = type
         | 
| 50 | 
            +
                end
         | 
| 39 51 |  | 
| 40 | 
            -
             | 
| 41 | 
            -
                @options[:csv_options] || {}
         | 
| 42 | 
            -
              end
         | 
| 52 | 
            +
                private
         | 
| 43 53 |  | 
| 44 | 
            -
             | 
| 54 | 
            +
                TYPE_MAP = {
         | 
| 55 | 
            +
                  String => :string,
         | 
| 56 | 
            +
                  Float => :float,
         | 
| 57 | 
            +
                  Date => :date,
         | 
| 58 | 
            +
                  DateTime => :datetime,
         | 
| 59 | 
            +
                }
         | 
| 45 60 |  | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
                 | 
| 49 | 
            -
                Date => :date,
         | 
| 50 | 
            -
                DateTime => :datetime,
         | 
| 51 | 
            -
              }
         | 
| 61 | 
            +
                def celltype_class(value)
         | 
| 62 | 
            +
                  TYPE_MAP[value.class]
         | 
| 63 | 
            +
                end
         | 
| 52 64 |  | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 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 | 
            -
             | 
| 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 | 
            -
             | 
| 64 | 
            -
                   | 
| 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 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 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 | 
            -
             | 
| 88 | 
            -
                 | 
| 89 | 
            -
             | 
| 90 | 
            -
             | 
| 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 | 
            -
             | 
| 93 | 
            -
                 | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 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 | 
            -
             | 
| 98 | 
            -
                 | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 101 | 
            -
                  @ | 
| 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 | 
            -
             | 
| 104 | 
            -
                 | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
                  @last_column[sheet]  | 
| 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 | 
            -
             | 
| 112 | 
            -
             | 
| 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 | 
            -
             | 
| 115 | 
            -
                  @cell[coord] = sanitize_value(value) if value.is_a?(::String)
         | 
| 136 | 
            +
                  @cleaned[sheet] = true
         | 
| 116 137 | 
             
                end
         | 
| 117 138 |  | 
| 118 | 
            -
                 | 
| 139 | 
            +
                alias_method :filename_or_stream, :filename
         | 
| 119 140 | 
             
              end
         | 
| 120 141 | 
             
            end
         | 
    
        data/lib/roo/errors.rb
    CHANGED
    
    
    
        data/lib/roo/excelx/cell/base.rb
    CHANGED
    
    | @@ -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 | 
            -
                     | 
| 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 | 
            -
                      @ | 
| 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 | 
            -
                         | 
| 56 | 
            +
                        default_type
         | 
| 54 57 | 
             
                      end
         | 
| 55 58 | 
             
                    end
         | 
| 56 59 |  | 
| 57 60 | 
             
                    def formula?
         | 
| 58 | 
            -
                       | 
| 61 | 
            +
                      !!(defined?(@formula) && @formula)
         | 
| 59 62 | 
             
                    end
         | 
| 60 63 |  | 
| 61 64 | 
             
                    def link?
         | 
| 62 | 
            -
                       | 
| 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 | 
| 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, : | 
| 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,  | 
| 9 | 
            -
                      @ | 
| 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' | 
| 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 | 
| 25 | 
            +
                      value.to_i == 1
         | 
| 23 26 | 
             
                    end
         | 
| 24 27 | 
             
                  end
         | 
| 25 28 | 
             
                end
         | 
    
        data/lib/roo/excelx/cell/date.rb
    CHANGED
    
    | @@ -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, : | 
| 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 | 
| 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  | 
| 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 | 
            -
             | 
| 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 | 
            -
                     | 
| 9 | 
            +
                    SECONDS_IN_DAY = 60 * 60 * 24
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    attr_reader :value, :formula, :format, :cell_value, :coordinate
         | 
| 8 12 |  | 
| 9 | 
            -
                     | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 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 | 
| 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  | 
| 40 | 
            -
                           | 
| 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' | 
| 56 | 
            -
                      'yy' | 
| 70 | 
            +
                      'yyyy' => '%Y',  # Year: 2000
         | 
| 71 | 
            +
                      'yy' => '%y',    # Year: 00
         | 
| 57 72 | 
             
                      # mmmmm => J-D
         | 
| 58 | 
            -
                      'mmmm' | 
| 59 | 
            -
                      'mmm' | 
| 60 | 
            -
                      'mm' | 
| 61 | 
            -
                      'm' | 
| 62 | 
            -
                      'dddd' | 
| 63 | 
            -
                      'ddd' | 
| 64 | 
            -
                      'dd' | 
| 65 | 
            -
                      'd' | 
| 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' | 
| 71 | 
            -
                      'h' | 
| 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' | 
| 89 | 
            +
                      'mm' => '%M',    # Minute: 01
         | 
| 75 90 | 
             
                      # FIXME: is this used? Seems like 'm' is used for month, not minute.
         | 
| 76 | 
            -
                      'm' | 
| 77 | 
            -
                      'ss' | 
| 78 | 
            -
                      's' | 
| 79 | 
            -
                      'am/pm' | 
| 80 | 
            -
                      '000' | 
| 81 | 
            -
                      '00' | 
| 82 | 
            -
                      '0' | 
| 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( | 
| 86 | 
            -
                       | 
| 87 | 
            -
                       | 
| 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, : | 
| 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 |  |