SpeciesFileGroup/taxonworks

View on GitHub
lib/utilities/dates.rb

Summary

Maintainability
F
1 wk
Test Coverage
# TODO: Extract to gem
#  - remove reference to `blank?` (no Rails methods)
#  - use && not `and` ?
#
#  In Ruby
#    Time.now.strftime("%Y-%m-%d") gets the commonly used yyyy-mm-dd
module Utilities::Dates

  EARLIEST_DATE = '1700/01/10'.freeze # Reconcile with in-app

  LONG_MONTHS = %w{january february march april may june july august september october november december}.freeze
  SHORT_MONTHS = %w{jan feb mar apr may jun jul aug sep oct nov dec}.freeze
  ROMAN_MONTHS = %i{i ii iii iv v vi vii viii ix x xi xii}.freeze

  MONTHS_FOR_SELECT = LONG_MONTHS.collect {|m| [m.capitalize, LONG_MONTHS.index(m) + 1]}

  # This following is the better long term approach than using
  # a preset LEGAL_MONTHS, as it depends on extending
  # SHORT_MONTH_FILTER correctly.
  #    SHORT_MONTHS.include?(SHORT_MONTH_FILTER[value].to_s)
  LEGAL_MONTHS = (1..12).to_a +
    (1..12).to_a.collect {|d| d.to_s} +
    ROMAN_MONTHS.map(&:to_s) +
    ROMAN_MONTHS.map(&:to_s).map(&:upcase) +
    SHORT_MONTHS +
    SHORT_MONTHS.map(&:capitalize) +
    SHORT_MONTHS.map(&:upcase) +
    SHORT_MONTHS.map(&:to_sym) +
    LONG_MONTHS +
    LONG_MONTHS.map(&:capitalize) +
    LONG_MONTHS.map(&:upcase)
  LONG_MONTHS.map(&:to_sym)

  # TODO: Write unit tests
  # Concept from from http://www.rdoc.info/github/inukshuk/bibtex-ruby/master/BibTeX/Entry
  # Converts integers, month names, or roman numerals, regardless of class to three letter symbols.
  #   SHORT_MONTH_FILTER[1]         # => :jan
  #   SHORT_MONTH_FILTER['JANUARY'] # => :jan
  #   SHORT_MONTH_FILTER['i']       # => :jan
  #   SHORT_MONTH_FILTER['I']       # => :jan
  #   SHORT_MONTH_FILTER['foo']     # => 'foo':
  SHORT_MONTH_FILTER =
    Hash.new do |h, k|
      v = k.to_s.strip
      if v =~ /^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i
        h[k] = v[0, 3].downcase.to_sym
      else
        i = nil
        if v =~ /^\d\d?$/
          i = v.to_i
        elsif ROMAN_MONTHS.include?(v.downcase.to_sym)
          i = ROMAN_MONTHS.index(v.downcase.to_sym) + 1
        end

        if !i.nil? && i > 0 && i < 13
          h[k] = DateTime.new(1, i, 1).strftime('%b').downcase.to_sym
        else # return the value passed if it doesn't match
          k
        end
      end
    end

  REGEXP_DATES = {
      month_dd_yyyy_2: {reg: /(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)\.?[\s,\/]\s?(\d\d?)[\.;,]?[\s\.,\/](\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})\s?[-\u2013\/]\s?(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)\.?[\s,\/]\s?(\d\d?)[\.;,]?[\s,\/]\s?(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})/i,
                        hlp: 'June 27 1946 - July 1 1947',
                        hdr: 'mody2'},

      # dd_month_yyyy_2: {reg: /(\d\d?)[\.,\/]?\s?(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)\s?\.?[\s,\/]?\s?(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})\s?[-\.,\/]?\s?(\d\d?)[\.,\/]?\s?(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)\s?\.?[\s,\/]?\s?(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})/i,
      dd_month_yyyy_2: {reg: /(\d\d?)\s*[-\.,\/]?\s*(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)\s*[-\.\s,\/]?\s*(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})\s?[-\.,\/]?\s?(\d\d?)\s*[-\.,\/]?\s*(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)\s*[-\.\s,\/]?\s*(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})/i,
                        hlp: '27 June 1946 - 1 July 1947',
                        hdr: 'dmoy2'},

      mm_dd_yyyy_2: {reg: /(\d\d?)[\s,\.\/]\s?(\d\d?)[\.,]?[\s\.,\/](\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\?\d{2})\s?[-\u2013\/]\s?(\d\d?)[\s,\.\/]\s?(\d\d?)[\.,]?[\s,\/]\s?(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})/i,
                     hlp: '5 27 1946 - 6 1 1947',
                     hdr: 'mdy2'},

      month_dd_month_dd_yyyy: {reg: /(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)\.?[\s,\/]?\s?(\d\d?)\s?[-\u2013\/]\s?(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)\.?[\s,\/]?\s?(\d\d?)[\s\.;,\/]\s?(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})/i,
                               hlp: 'June 27 - July 1 1947',
                               hdr: 'modmody2'},

      dd_month_dd_month_yyyy: {reg: /(\d\d?)\s?[\.\/,\u2013-]?\s?(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)\.?\s?[-\u2013\/]\s?(\d\d?)\s?[-\s\u2013_\.,\/]?\s?(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)\.?\s?[-,\u2013\/]?\s?(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})/i,
                               hlp: '27 June - 1 July 1947',
                               hdr: 'dmodmoy2'},

      dd_mm_dd_mm_yyyy: {reg: /(\d\d?)[\s\.,\/]\s?(\d\d?)\s?[-\u2013\/]\s?(\d\d?)[\s\.,\/]\s?(\d\d?)[\s\.,\/]\s?(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})/i,
                         hlp: '14.6-17.6.1994',
                         hdr: 'dmdmy2'},

      mm_dd_mm_dd_yyyy: {reg: /(\d\d?)[\s\.,\/]\s?(\d\d?)\s?[-\u2013\/]\s?(\d\d?)[\s\.,\/]\s?(\d\d?)[\s\.,\/]\s?(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})/i,
                         hlp: '5 27 - 6 1 1947 | 5/ 26- 6/ 26 1944',
                         hdr: 'mdmdy2'},

      month_dd_dd_yyyy: {reg: /(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)[\.,]?[-\s\u2013,\/]?(\d\d?)\s?[-\u2013\+\/]\s?(\d\d?)[\.,]?[-\s\.\u2013,\/]\s?(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})/i,
                         hlp: 'June 27-29 1947',
                         hdr: 'moddy2'},

      dd_dd_month_yyyy: {reg: /(\d\d?)\s?[-\u2013\+\/]\s?(\d\d?)[\s\.,\/-]\s?(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)\.?[-\s\u2013,\/]?\s?(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})/i,
                         hlp: '27-29 June 1947',
                         hdr: 'ddmoy2'},

      # mm_dd_mm_dd_yyyy:    /(\d\d?)[\s\.,\/]\s?(\d\d?)\s?[-\u2013\/]\s?(\d\d?)[\s\.,\/]\s?(\d\d?)[\s\.,\/]\s?(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})/i,
      # mm_dd_dd_yyyy:     /(\d\d?)\s?[-\u2013\s\.,\/]\s?(\d\d?)\s?[-\u2013\+\/]\s?(\d\d?)[\.,;]?\s?[-\s\u2013,\/](\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})/i,
      #  THIS REGEX HAS ISSUES WITH THE FIRST EXAMPLE BUT ERRONEOUSLY WORKS FOR THE SECOND
      # (\d\d?)\s?[-\u2013\s\.,\/]\s?(\d\d?)\s?[-\u2013\+\/]\s?(\d\d?)[\.,;]?\s?[-\u2013\+\/]\s?(\d\d?)[\.,;]?\s?[-\s\u2013,\/](\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2,4})

      mm_dd_dd_yyyy: {reg: /(\d\d?)\s?[-\u2013\s\.,\/]\s?(\d\d?)\s?[-\u2013\+\/]\s?(\d\d?)[\.,;]?\s?[-\u2013\+\/]?\s?[\.,;]?\s?[-\s\u2013,\/]?(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2,4})/i,
                      hlp: ' 5 27-29 1947',
                      hdr: 'mddy2'},

      month_dd_yyy: {reg: /(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)\.?\s?[-\u2013_,\/]?\s?(\d\d?)[\.;,]?\s?[-\s\u2013_\/\.\u0027,]\s?(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})/i,
                     hlp: "Jun 29 1947 | Jun 29, 1947 | June 29 1947 | June 29, 1947 | VI-29-1947 | X.25.2000 | Jun 29, '47 | June 29, '47 | VI-4-08 | Jun 29, '47",
                     hdr: 'mody'},

      dd_month_yyy: {reg: /(\d\d?)\s?[-\u2013_\.,\/]?\s?(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)[\.,]?\s?[-,\u2013_\/]?\s?(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})/i,
                     hlp: "29 Jun 1947 | 29 June 1947 | 2 June, 1983 | 29 VI 1947 | 29-VI-1947 | 25.X.2000 | 25X2000 | 29 June '47 | 29 Jun '47",
                     hdr: 'dmoy'},

      mm_dd_yyyy: {reg: /(\d\d?)[-\s\u2013_\.,\/]\s?(\d\d?)[-\s\u2013_\.,\/]\s?(\d{4})/i,
                   hlp: '6/29/1947 | 6-29-1947 | 6-15 1985 | 10.25 2000 | 7.10.1994',
                   hdr: 'mdy'},

      mm_dd_yy: {reg: /(\d\d?)[-\s\u2013_\.,\/]\s?(\d\d?)[-\s\u2013_\.,\/]\s?([\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})/i,
                 hlp: "6/29/47 | 6/29/'47 | 7.10.94 | 5-17-97",
                 hdr: 'mdy'},

      dd_mm_yy: {reg: /(\d\d?)[-\s\u2013_\.,\/]\s?(\d\d?)[-\s\u2013_\.,\/]\s?([\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})/i,
                 hlp: "29/9/47 | 29/9/'47 | 7.10.94 | 15-07-97",
                 hdr: 'dmy'},

      yyyy_mm_dd: {reg: /(\d{4})[-\s\u2013_\.,\/]\s?(\d\d?)[-\s\u2013_\.,\/]\s?(\d\d?)/i,
                   hlp: '1994, 4.16 | 1902-04-24',
                   hdr: 'yyymd'},

      yyy_mm_dd: {reg: /(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})[-\s\u2013_\.,\/]\s?(\d\d?)[-\s\u2013_\.,\/]\s?(\d\d?)/i,
                  hlp: "1994, 4.16 | '02-04-24 | 02-04-24",
                   hdr: 'ymd'},

      yyyy_month_dd: {reg: /(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})[-\s\u2013_\.,\/]?\s*(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)[-\s\u2013_\.,\/]?\s*(\d\d?)/i,
                      hlp: '1994 JULY 17 | 2003 June 15 -  2004 July 04 | 2002-IV-27 - 2008-JUL-04',
                      hdr: 'ymod'},

      yyyy_mm_dd_mm_dd: {reg: /(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})[-\s\u2013_\.,\/]\s?(\d\d?)[-\s\u2013_\.,\/]\s?(\d\d?)[-\s\u2013_\.,\/]\s?(\d\d?)[-\s\u2013_\.,\/]\s?(\d\d?)/i,
                         hlp: '1994, 6.14-6.17',
                         hdr: 'ymdmd'},

      yyyy_month_dd_month_dd: {reg: /(\d{4}|[\u0027´`\u02B9\u02BC\u02CA]?\s?\d{2})[-\s\u2013_\.,\/]?\s*(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)[-\s\u2013_\.,\/]?\s*(\d\d?)\s?[-\s\u2013_\.,\/]?\s*(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)[-\s\u2013_\.,\/]?\s*(\d\d?)/i,
                               hlp: '1994 june 14 -JULY 17 | 2002-IV-27 - JUL-04',
                               hdr: 'ymodmod'}
  }.freeze

  class DateFromLabel
    attr_reader(:verbatim_label, :date)

    # @param [String] label
    def initialize(label)
      @verbatim_label = label
      @date = Utilities::Dates.date_regex_from_verbatim_label(label)
    end
  end


  # @param [String]
  # @param [Integer]
  # @param [Symbol]
  # @return [Integer]
  #    return the month index
  def self.month_index(month_value)
    return nil if month_value.blank?
    month_test = SHORT_MONTH_FILTER[month_value]
    return nil if month_test.to_s.to_i > 11 # this is to filter out numeric values > 11, symbols => 0
    retval = SHORT_MONTHS.index(month_test.to_s) + 1
    return nil if retval > 12
    retval
  end

  # @param date [Date]
  # @return [String]
  #   yyyy-mm-dd
  def self.date_string(date)
    "#{'%4d' % date.year}-#{'%02d' % date.month}-#{'%02d' % date.day}" unless date.nil?
  end

  # @return String
  #   A date string unambiguosly formatted in yyyy/mm/dd.  Always includes all parts indicating
  #   not available parts with '?'
  def self.from_parts(y, m, d)
    return '????/??/??' if y.nil? && m.nil? && d.nil?
    [y ? "#{y}" : '????',
     m ? "#{'%02d' % m}" : '??',
     d ? "#{'%02d' % d}" : '??'
    ].join('/')
  end

  # @params [year: integer]
  # @params [month: integer, nil]
  # @params [day: integer, nil]
  # @return[Time] a UTC time (Uses Time instead of Date so that it can be saved as a UTC object -
  #   See http://www.ruby-doc.org/core-2.0.0/Time.html)
  #   Returns nomenclature_date based on computation of the values of :year, :month, :day.
  #    if :year is empty, return nil
  #    if :month is empty, returns 12/31/:year
  #    if :day is empty, returns the last day of the month
  #
  # Use self.month_index to convert months prior to handling them here
  #
  def self.nomenclature_date(day = nil, month = nil, year = nil)
    if year.nil?
      nil
    elsif month.nil?
      Time.utc(year.to_i, 12, 31)
    elsif day.nil?
      tmp = Time.utc(year.to_i, month.to_i)
      if tmp.month == 12 # want the last day of december
        Time.utc(year.to_i, 12, 31)
      else # time + 1 month - 1 day (60 sec * 60 min *24 hours)
        Time.utc(year.to_i, tmp.month + 1) - 86400
      end
    else
      Time.utc(year.to_i, month.to_i, day.to_i)
    end
  end

  # !#!@# Deprecate this! It's handled in CollectingEVent date filter code
  # @return [String] of sql to test dates
  # @param [Hash] params
  # TODO: still needs more work for some date combinations
  def self.date_sql_from_params(params)
    st_date, end_date = params['st_datepicker'], params['en_datepicker']
    # processing start date data
    st_year, st_month, st_day = params['start_date_year'], params['start_date_month'], params['start_date_day']
    unless st_date.blank?
      parts = st_date.split('/')
      st_year, st_month, st_day = parts[2], parts[0], parts[1]
    end
    st_my = (!st_month.blank? and !st_year.blank?)
    st_m = (!st_month.blank? and st_year.blank?)
    st_y = (st_month.blank? and !st_year.blank?)
    st_blank = (st_year.blank? and st_month.blank? and st_day.blank?)
    # st_full = (!st_year.blank? and !st_month.blank? and !st_day.blank?)

    # st_partial = (!st_blank and (st_year.blank? or st_month.blank? or st_day.blank?))
    # start_time = fix_time(st_year, st_month, st_day) if st_full

    # processing end date data
    end_year, end_month, end_day = params['end_date_year'], params['end_date_month'], params['end_date_day']
    unless end_date.blank?
      parts = end_date.split('/')
      end_year, end_month, end_day = parts[2], parts[0], parts[1]
    end
    end_my = (!end_month.blank? and !end_year.blank?)
    end_m = (!end_month.blank? and end_year.blank?)
    end_y = (end_month.blank? and !end_year.blank?)
    end_blank = (end_year.blank? and end_month.blank? and end_day.blank?)

    # end_full = (!end_year.blank? and !end_month.blank? and !end_day.blank?)
    # end_partial = (!end_blank and (end_year.blank? or end_month.blank? or end_day.blank?))
    # end_time = fix_time(end_year, end_month, end_day) if end_full

    sql_string = ''
    # if all the date information is blank, skip the date testing
    unless st_blank and end_blank
      # only start and end year
      if st_y and end_y
        # start and end year may be different, or the same
        # we ignore all records which have a null start year,
        # but include all records for the end year test
        sql_string += "(start_date_year >= #{st_year} and (end_date_year is null or end_date_year <= #{end_year}))"
      end

      # only start month and end month
      if st_m and end_m
        # todo: This case really needs additional consideration
        # maybe build a string of included month and use an 'in ()' construct
        sql_string += "(start_date_month between #{st_month} and #{end_month})"
      end

      if end_blank # !st_blank = st_partial
        # if we have only a start date there are three cases: d/m/y, m/y, y
        if st_year.blank?
          sql_string = add_st_month(sql_string, st_month)
        else
          sql_string = add_st_day(sql_string, st_day)
          sql_string = add_st_month(sql_string, st_month)
          sql_string = add_st_year(sql_string, st_year)
        end
      else
        # end date only, don't do anything
      end

      if ((st_y or st_my) and (end_y or end_my)) and not (st_y and end_y)
        # we have two dates of some kind, complete with years
        # three specific cases:
        #   case 1: start year, (start month, (start day)) forward
        #   case 2: end year, (end month, (end day)) backward
        #   case 3: any intervening year(s) complete
        if st_year
        end
      end
    end
    sql_string
  end

  # Pass integers
  # @param [Integer] hour
  # @param [Integer] minute
  # @param [Integer] second
  # @return [String]
  def self.format_to_hours_minutes_seconds(hour, minute, second)
    h, m, s = nil, nil, nil
    h = ('%02d' % hour) if hour
    m = ('%02d' % minute) if minute
    s = ('%02d' % second) if second
    [h, m, s].compact.join(':')
  end

  # Parse an ISO-8601 date string or interval into an array of OpenStructs objects
  #
  # The format may be yyyy-mm-dd or yyyy-mm-dd/yyyy-mm-dd.
  # The second date may omit higher-order elements that are the same as the first date, like yyyy-mm-dd/dd or yyyy-mm-dd/mm-dd
  # See https://en.wikipedia.org/wiki/ISO_8601#Time_intervals for more details.
  #
  # @param [String] date_str The date string to parse
  # @return [Array<OpenStruct>] Array of Date objects, with year, mmonth, day, hour, minute, and second properties
  def self.parse_iso_date_str(date_str)

    full_pattern = %r{^
          (?<year>[0-9]{4})(-(?<month>[0-9]{1,2}))?(-(?<day>[0-9]{1,2}))?  # Date in these formats: YYYY | YYYY-M(M)? | YYYY-M(M)?-D(D)?
          (
            T(?<hour>[0-9]{2}):(?<minute>[0-9]{2})(:(?<second>[0-9]{2}))?(Z)? # Optional time, only THH:MM:SS(Z)? and THH:MM(Z)? allowed.
          )?
        $}x.freeze

    if date_str.include? "/"
      first_date_str, second_date_str = date_str.split('/', 2)

      first_date_hash = first_date_str.match(full_pattern)&.named_captures&.transform_values! { |v| v&.to_i }

      return nil unless first_date_hash

      if date_str.include? "T"
        return nil unless date_str.count(':') == 4
      end

      # Split date on separators, then work backwards inserting numbers for non-null values from first date
      begin
        second_date_values = second_date_str.split(/[-T:Z]/).map { |x| Integer(x, 10) }
      rescue
        return nil
      end

      # keep non-nil values in first date string
      present_date_hash = first_date_hash.reject { |_,v| v.nil? }

      # Sort the keys present in the first date, smallest increment first
      date_order = [:second, :minute, :hour, :day, :month, :year]
      present_keys = present_date_hash.sort_by {|key, _| date_order.index(key.to_sym)}.map{|pair| pair[0]}

      # zip keys with values from second date and drop the extra keys
      new_values = present_keys.zip(second_date_values.reverse).to_h.reject { |_,v| v.nil? }

      # make new date from first, updating with values from second
      second_date_hsh = first_date_hash.clone.update(new_values)

      [OpenStruct.new(first_date_hash), OpenStruct.new(second_date_hsh)]

    else
      named_captures = date_str.match(full_pattern)&.named_captures&.transform_values! { |v| v&.to_i } # Not (&:to_i) because it would replace nil with 0

      return nil unless named_captures
      [OpenStruct.new(named_captures)]
    end
  end

  private

  # @param [String] sql
  # @param [Integer] st_year
  # @return [String] of sql
  def self.add_st_year(sql, st_year)
    unless st_year.blank?
      prefix = sql.blank? ? '' : ' and '
      sql += "#{prefix}(start_date_year = #{st_year})"
    end
    sql
  end

  # @param [String] sql
  # @param [Integer] st_month
  # @return [String] of sql
  def self.add_st_month(sql, st_month)
    unless st_month.blank?
      prefix = sql.blank? ? '' : ' and '
      sql += "#{prefix}(start_date_month = #{st_month})"
    end
    sql
  end

  # @param [String] sql
  # @param [Integer] st_day
  # @return [String] of sql
  def self.add_st_day(sql, st_day)
    unless st_day.blank?
      prefix = sql.blank? ? '' : ' and '
      sql += "#{prefix}(start_date_day = #{st_day})"
    end
    sql
  end

  # @param [Integer] year
  # @param [Integer] month
  # @param [Integer] day
  # @return [Time]
  def self.fix_time(year, month, day)
    start = Time.new(1970, 1, 1)
    if year.blank?
      year = start.year
    end
    if month.blank?
      month = start.month
    end
    if day.blank?
      day = start.day
    end
    Time.new(year, month, day)
  end

  # @param [String] start_date in the form of 'yyyy*mm*dd'
  # @param [String] end_date in the form of 'yyyy*mm*dd'
  # @return [String, String] start_date, end_date
  #   - orders dates from start to end (if they were passed incorrectly)
  #   - creates a one-day range (duplicates the only date) if only one date is provided
  #   - creates a date range from EARLIEST_DATE to Today if none provided
  def self.normalize_and_order_dates(start_date, end_date)
    if start_date.blank? && end_date.blank? # set entire range
      [ EARLIEST_DATE, today] # 1700-01-01 - Today
    else
      end_date = start_date if end_date.blank?
      start_date = end_date if start_date.blank?
      order_dates(start_date, end_date)
    end
  end

  # @return [String]
  def self.today
    Time.zone.today.strftime('%Y-%m-%d')
  end

  # @param [Array] label of strings
  # @param [Array] pieces of strings
  # @return [Array] of strings
  def self.make_verbatim_date_piece(label, pieces)
    left = label.index(pieces[0])
    right = left + pieces[0].length - 1 #
    unless pieces[1].blank?
      right = label.index(pieces[1]) + pieces[1].length - 1
    end
    label[left..right]
  end

  # @param [String] start_date in the form of 'yyyy-mm-dd'
  # @param [String] end_date in the form of 'yyyy-mm-dd'
  # @return [String, String] start_date, end_date in proper order
  def self.order_dates(start_date, end_date)
    if Date.parse(start_date) > Date.parse(end_date) # need to swap s and e?
      start_date, end_date = end_date, start_date
    end
    return start_date, end_date
  end

  # @param [Array] of 0-2 dates
  # @return [String, nil]
  #   a sentence spelling out the date range
  def self.date_range_sentence_tag(date_range)
    format = '%d-%b-%Y'
    date_range.compact!
    date_range.pop if date_range[0] == date_range[1]
    if date_range.empty?
      nil
    else
      if date_range.size == 2
        'Records exist in the date range ' + date_range.collect {|d| d.strftime(format)}.join(' to ') + '.'
      elsif date_range.size == 1
        "A record exists from #{date_range.first.strftime(format)}."
      else
        'Hmm, that is a curious date range!'
      end
    end
  end

  # @param [String] year
  # @return [String]
  def self.fix_2_digit_year(year)
    if year.length < 4
      year = year.gsub("'", '')
      if year.length < 3
        tny = Time.now.year
        if year.to_i > tny%100
          year = ((tny/100) - 1).to_s + year
        else
          year = (tny/100).to_s + year
        end
      end
    end
    year
  end

  # @param [String] label
  # @param [Array] filters of regexes
  # @return [Array] of possible interpretations of dates
  def self.hunt_dates(label, filters = REGEXP_DATES.keys)
    trials = {}
    filters.each_with_index {|kee, dex|
      trials[kee] = {}
      matches = label.to_enum(:scan, REGEXP_DATES[kee][:reg]).map {Regexp.last_match}
      unless matches.blank?
        trials[kee][:method] = kee
        trials[kee][:piece] = {}
        trial = extract_dates(trials[kee], matches)
        if invalid_month_day(trial)
          trials[kee] = {}
        end
      end
    }
    trials
  end

  # @param [Hash] trial of date strings
  # @return [Boolean]
  # check for as invalid day number per month for start and end date
  def self.invalid_month_day(trial)
    retval = false
    if trial[:start_date_day].to_i > 31 or trial[:end_date_day].to_i > 31
      retval = true
    end
    if trial[:start_date_day].to_i < 1 or (trial[:end_date_day].to_i < 1 and !trial[:end_date_day].blank?)
      retval = true
    end
    if trial[:start_date_month].to_i > 12 or trial[:end_date_month].to_i > 12
      retval = true
    end
    if trial[:start_date_month].to_i < 1 or (trial[:end_date_month].to_i < 1 and !trial[:end_date_month].blank?)
      retval = true
    end
    retval
  end

  # rubocop:disable Metrics/MethodLength
  # @param [Hash] trial
  # @param [Array] match_data of Regex results
  # @return [Hash]
  def self.extract_dates(trial, match_data)
    end_date_year, end_date_month, end_date_day = 0, 0, 0
    case trial[:method].downcase.to_sym
      when :month_dd_yyyy_2
        start_date_year = 3
        start_date_month = 1
        start_date_day = 2
        end_date_year = 6
        end_date_month = 4
        end_date_day = 5
      when :dd_month_yyyy_2
        start_date_year = 3
        start_date_month = 2
        start_date_day = 1
        end_date_year = 6
        end_date_month = 5
        end_date_day = 4
      when :mm_dd_yyyy_2
        start_date_year = 3
        start_date_month = 1
        start_date_day = 2
        end_date_year = 6
        end_date_month = 4
        end_date_day = 5
      when :month_dd_month_dd_yyyy
        start_date_year = 5
        start_date_month = 1
        start_date_day = 2
        end_date_year = 5
        end_date_month = 3
        end_date_day = 4
      when :dd_month_dd_month_yyyy
        start_date_year = 5
        start_date_month = 2
        start_date_day = 1
        end_date_year = 5
        end_date_month = 4
        end_date_day = 3
      when :dd_mm_dd_mm_yyyy
        start_date_year = 5
        start_date_month = 2
        start_date_day = 1
        end_date_year = 5
        end_date_month = 4
        end_date_day = 3
      when :mm_dd_mm_dd_yyyy
        start_date_year = 5
        start_date_month = 1
        start_date_day = 2
        end_date_year = 5
        end_date_month = 3
        end_date_day = 4
      when :month_dd_dd_yyyy
        start_date_year = 4
        start_date_month = 1
        start_date_day = 2
        end_date_year = 4
        end_date_month = 1
        end_date_day = 3
      when :dd_dd_month_yyyy
        start_date_year = 4
        start_date_month = 3
        start_date_day = 1
        end_date_year = 4
        end_date_month = 3
        end_date_day = 2
      when :mm_dd_dd_yyyy
        start_date_year = 4
        start_date_month = 1
        start_date_day = 2
        end_date_year = 4
        end_date_month = 1
        end_date_day = 3
      when :month_dd_yyy
        start_date_year = 3
        start_date_month = 1
        start_date_day = 2
        if match_data[1]
          end_date_year = 3
          end_date_month = 1
          end_date_day = 2
        end
      when :dd_month_yyy # done for yyyy
        start_date_year = 3
        start_date_month = 2
        start_date_day = 1
        if match_data[1]
          end_date_year = 3
          end_date_month = 2
          end_date_day = 1
        end
      when :mm_dd_yyyy
        start_date_year = 3
        start_date_month = 1
        start_date_day = 2
        if match_data[1]
          end_date_year = 3
          end_date_month = 1
          end_date_day = 2
        end
      when :mm_dd_yy
        start_date_year = 3
        start_date_month = 1
        start_date_day = 2
        if match_data[1]
          end_date_year = 3
          end_date_month = 1
          end_date_day = 2
        end
      when :dd_mm_yy
        start_date_year = 3
        start_date_month = 2
        start_date_day = 1
        if match_data[1]
          end_date_year = 3
          end_date_month = 2
          end_date_day = 1
        end
      when :yyyy_mm_dd
        start_date_year = 1
        start_date_month = 2
        start_date_day = 3
        if match_data[1]
          end_date_year = 1
          end_date_month = 2
          end_date_day = 3
        end
      when :yyy_mm_dd
        start_date_year = 1
        start_date_month = 2
        start_date_day = 3
        if match_data[1]
          end_date_year = 1
          end_date_month = 2
          end_date_day = 3
        end
      when :yyyy_month_dd
        start_date_year = 1
        start_date_month = 2
        start_date_day = 3
        if match_data[1]
          end_date_year = 1
          end_date_month = 2
          end_date_day = 3
        end
      when :yyyy_mm_dd_mm_dd
        start_date_year = 1
        start_date_month = 2
        start_date_day = 3
        end_date_year = 1
        end_date_month = 4
        end_date_day = 5
      when :yyyy_month_dd_month_dd
        start_date_year = 1
        start_date_month = 2
        start_date_day = 3
        end_date_year = 1
        end_date_month = 4
        end_date_day = 5
    end
    trial[:piece][0] = match_data[0][0]
    trial[:start_date_year] = fix_2_digit_year(match_data[0][start_date_year])
    trial[:start_date_month] = month_index(match_data[0][start_date_month]).to_s
    trial[:start_date_day] = match_data[0][start_date_day]
    trial[:end_date_year] = '' #  match_data[3]
    trial[:end_date_month] = '' #  month_index(match_data[2]).to_s
    trial[:end_date_day] = '' #  match_data[1]
    which_data = 0
    unless match_data[1].blank?
      which_data = 1
    end
    trial[:start_date] = trial[:start_date_year] + ' ' + trial[:start_date_month] + ' ' + trial[:start_date_day]
    trial[:end_date] = ''

    if (end_date_year > 0)
      trial[:piece][1] = match_data[which_data][0] unless which_data == 0
      trial[:end_date_year] = fix_2_digit_year(match_data[which_data][end_date_year])
      trial[:end_date_month] = month_index(match_data[which_data][end_date_month]).to_s
      trial[:end_date_day] = match_data[which_data][end_date_day]
      trial[:end_date] = trial[:end_date_year] + ' ' + trial[:end_date_month] + ' ' + trial[:end_date_day]
    end
    trial
  end

  # @return [Hash]
  # date from the label parsed to elements
  def self.date_regex_from_verbatim_label(text)
    return nil if text.blank?
    text = ' ' + text.downcase.squish + ' '

    date = {}
    # June 27 1946 - July 1 1947
    if matchdata1 = text.match(/\W(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xi|xii|x|iii|ii|i)\.?[-\s,\/]\s?(\d\d?)[\.;,]?[-\s\.,\/](\d{4}|['´`ʹʼˊ]?\s?\d{2})\s?[-–—]\s?(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xi|xii|x|iii|ii|i)\.?[-\s,\/]\s?(\d\d?)[\.;,]?[-\s,\/]\s?(\d{4}|['´`ʹʼˊ]?\s?\d{2})\D/)
      date[:verbatim_date] = matchdata1[0].strip
      date[:start_date_day] = matchdata1[2]
      date[:start_date_month] = matchdata1[1]
      date[:start_date_year] = matchdata1[3]
      date[:end_date_day]  = matchdata1[5]
      date[:end_date_month] = matchdata1[4]
      date[:end_date_year] = matchdata1[6]
      # 27 June 1946 - 1 July 1947
    elsif matchdata1 = text.match(/\W(\d\d?)[\.,\/-]?\s?(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xi|xii|x|iii|ii|i)\.?[\s,\/-]?\s?(\d{4}|['´`ʹʼˊ]?\s?\d{2})\s?[-–—]\s?(\d\d?)[\.,\/-]?\s?(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xi|xii|x|iii|ii|i)\.?[\s,\/-]?\s?(\d{4}|['´`ʹʼˊ]?\s?\d{2})\D/)
      date[:verbatim_date] = matchdata1[0].strip
      date[:start_date_day] = matchdata1[1]
      date[:start_date_month] = matchdata1[2]
      date[:start_date_year] = matchdata1[3]
      date[:end_date_day]  = matchdata1[4]
      date[:end_date_month] = matchdata1[5]
      date[:end_date_year] = matchdata1[6]
      # 5 27 1946 - 6 1 1947
    elsif matchdata1 = text.match(/\W(\d\d?)[\s,\.\/]\s?(\d\d?)[\.,]?[\s\.,\/](\d{4}|'?\d{2})\s?[-–—]\s?(\d\d?)[\s,\.\/]\s?(\d\d?)[\.,]?[\s,\/]\s?(\d{4}|['´`ʹʼˊ]?\s?\d{2})\D/)
      date[:verbatim_date] = matchdata1[0].strip
      date[:start_date_day] = matchdata1[2]
      date[:start_date_month] = matchdata1[1]
      date[:start_date_year] = matchdata1[3]
      date[:end_date_day]  = matchdata1[5]
      date[:end_date_month] = matchdata1[4]
      date[:end_date_year] = matchdata1[6]
      # June 27 - July 1 1947
    elsif matchdata1 = text.match(/\W(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xi|xii|x|iii|ii|i)\.?[-–—\s,\/]?\s?(\d\d?)\s?[-–—]\s?(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xi|xii|x|iii|ii|i)\.?[-–—\s,\/]?\s?(\d\d?)[-–—\s\.;,\/]\s?(\d{4}|['´`ʹʼˊ]?\s?\d{2})\D/)
      date[:verbatim_date] = matchdata1[0].strip
      date[:start_date_day] = matchdata1[2]
      date[:start_date_month] = matchdata1[1]
      date[:start_date_year] = matchdata1[5]
      date[:end_date_day]  = matchdata1[4]
      date[:end_date_month] = matchdata1[3]
      date[:end_date_year] = matchdata1[5]
      # 27 June - 1 July 1947
    elsif matchdata1 = text.match(/\W(\d\d?)[-–—\.\/,]?\s?(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xi|xii|x|iii|ii|i)\.?\s?[-–—]\s?(\d\d?)[-–—\.\/,]?\s?(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xi|xii|x|iii|ii|i)\.?[-–—,\/]?\s?(\d{4}|['´`ʹʼˊ]?\s?\d{2})\D/)
      date[:verbatim_date] = matchdata1[0].strip
      date[:start_date_day] = matchdata1[1]
      date[:start_date_month] = matchdata1[2]
      date[:start_date_year] = matchdata1[5]
      date[:end_date_day]  = matchdata1[3]
      date[:end_date_month] = matchdata1[4]
      date[:end_date_year] = matchdata1[5]
      # 5 27 - 6 1 1947
    elsif matchdata1 = text.match(/\W(\d\d?)[\s\.,\/]\s?(\d\d?)\s?[-–—]\s?(\d\d?)[\s\.,\/]\s?(\d\d?)[\s\.,\/]\s?(\d{4}|['´`ʹʼˊ]?\s?\d{2})\D/)
      date[:verbatim_date] = matchdata1[0].strip
      date[:start_date_day] = matchdata1[2]
      date[:start_date_month] = matchdata1[1]
      date[:start_date_year] = matchdata1[5]
      date[:end_date_day]  = matchdata1[4]
      date[:end_date_month] = matchdata1[3]
      date[:end_date_year] = matchdata1[5]
      # June 27-29 1947
    elsif matchdata1 = text.match(/\W(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xi|xii|x|iii|ii|i)\.?[-–—\s\/,]?(\d\d?)\s?[-–—\+]\s?(\d\d?)[\.;,]?[-\s\.–,\/]\s?(\d{4}|['´`ʹʼˊ]?\s?\d{2})\D/)
      date[:verbatim_date] = matchdata1[0].strip
      date[:start_date_day] = matchdata1[2]
      date[:start_date_month] = matchdata1[1]
      date[:start_date_year] = matchdata1[4]
      date[:end_date_day]  = matchdata1[3]
      date[:end_date_month] = matchdata1[1]
      date[:end_date_year] = matchdata1[4]
      # 27-29 June 1947
    elsif matchdata1 = text.match(/\W(\d\d?)\.?\s?[-–—\+]\s?(\d\d?)[\s\.,\/-]\s?(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xi|xii|x|iii|ii|i)\.?[-–—\s\/,]?\s?(\d{4}|['´`ʹʼˊ]?\s?\d{2})\D/)
      date[:verbatim_date] = matchdata1[0].strip
      date[:start_date_day] = matchdata1[1]
      date[:start_date_month] = matchdata1[3]
      date[:start_date_year] = matchdata1[4]
      date[:end_date_day]  = matchdata1[2]
      date[:end_date_month] = matchdata1[3]
      date[:end_date_year] = matchdata1[4]
      #=begin
      # 27-29 12 1947 #### watch for false positives
    elsif matchdata1 = text.match(/\W(\d\d?)\s?[-–—\+]\s?(\d\d?)[\s\.,\/]\s?(\d\d)\.?[\s\/,\.](\d{4}|['´`ʹʼˊ]?\s?\d{2})\D/)
      date[:verbatim_date] = matchdata1[0].strip
      date[:start_date_day] = matchdata1[1]
      date[:start_date_month] = matchdata1[3]
      date[:start_date_year] = matchdata1[4]
      date[:end_date_day]  = matchdata1[2]
      date[:end_date_month] = matchdata1[3]
      date[:end_date_year] = matchdata1[4]
      #=end
      # 12 27-29 1947
    elsif matchdata1 = text.match(/\W(\d\d)[\s\.,\/]\s?(\d\d?)\s?[-–—\+]\s?(\d\d?)[\.,]?[\s,\/](\d{4}|['´`ʹʼˊ]?\s?\d{2})\D/)
      date[:verbatim_date] = matchdata1[0].strip
      date[:start_date_day] = matchdata1[2]
      date[:start_date_month] = matchdata1[1]
      date[:start_date_year] = matchdata1[4]
      date[:end_date_day]  = matchdata1[3]
      date[:end_date_month] = matchdata1[1]
      date[:end_date_year] = matchdata1[4]
      # June - July 1947
    elsif matchdata1 = text.match(/\W(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xi|xii|x|iii|ii|i)\.?\s?[-–—]\s?(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xi|xii|x|iii|ii|i)\.?[-–—,\/]?\s?(\d{4}|['´`ʹʼˊ]?\s?\d{2})\D/)
      date[:verbatim_date] = matchdata1[0].strip
      date[:start_date_month] = matchdata1[1]
      date[:start_date_year] = matchdata1[3]
      date[:end_date_month] = matchdata1[2]
      date[:end_date_year] = matchdata1[3]
      # Jun 29 1947     Jun 29, 1947    June 29, 1947    VI-29-1947   X.25.2000   Jun 29, '47   June 29, '47    VI-4-08
    elsif matchdata1 = text.match(/\W(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)\.?\s?[-–—_,\/]?\s?(\d\d?)[\.;,]?\s?[-–—_\/\.',\s]\s?(\d{4}|['´`ʹʼˊ]?\s?\d{2})\D/)
      date[:verbatim_date] = matchdata1[0].strip
      date[:start_date_day] = matchdata1[2]
      date[:start_date_month] = matchdata1[1]
      date[:start_date_year] = matchdata1[3]
      # 29 Jun 1947   29 June 1947   2 June, 1983   29 VI 1947   29-VI-1947   25.X.2000  25X2000  29 June '47   29 Jun '47
    elsif matchdata1 = text.match(/\W(\d\d?)\s?[-–—_\.,\/]?\s?(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)[\.,\/]?\s?[-–—,]?\s?(\d{4}|['´`ʹʼˊ]?\s?\d{2})\D/)
      date[:verbatim_date] = matchdata1[0].strip
      date[:start_date_day] = matchdata1[1]
      date[:start_date_month] = matchdata1[2]
      date[:start_date_year] = matchdata1[3]
      # 6/29/1947    6-29-1947    6-15 1985    10.25 2000    7.10.1994
    elsif matchdata1 = text.match(/\W(\d\d?)[-–—_\.,\/\s]\s?(\d\d?)[-–—_\.,\/\s]\s?(\d{4})\W/)
      date[:verbatim_date] = matchdata1[0].strip
      date[:start_date_day] = matchdata1[2]
      date[:start_date_month] = matchdata1[1]
      date[:start_date_year] = matchdata1[3]
      # 1947-6-29
    elsif matchdata1 = text.match(/\W(\d{4})[-–—_\.,\/\s]\s?(\d\d?)[-–—_\.,\/\s]\s?(\d\d?)\W/)
      date[:verbatim_date] = matchdata1[0].strip
      date[:start_date_day] = matchdata1[3]
      date[:start_date_month] = matchdata1[2]
      date[:start_date_year] = matchdata1[1]
      # 6/29/47    6/29/'47    7.10.94    5-17-97
    elsif matchdata1 = text.match(/\W(\d\d?)[-–—_\.,\/\s]\s?(\d\d?)[-–—_\.,\/\s]\s?(['´`ʹʼˊ]?\s?\d{2})\W/)
      date[:verbatim_date] = matchdata1[0].strip
      date[:start_date_day] = matchdata1[2]
      date[:start_date_month] = matchdata1[1]
      date[:start_date_year] = matchdata1[3]
      # Jun 1947   June 1947   VI 1947   VI-1947   X.2000
    elsif matchdata1 = text.match(/\W(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec|viii|vii|iv|vi|v|ix|xii|xi|x|iii|ii|i)[\.,]?\s?[-–—,]?\s?(\d{4}|['´`ʹʼˊ]?\s?\d{2})\D/)
      date[:verbatim_date] = matchdata1[0].strip
      date[:start_date_month] = matchdata1[1]
      date[:start_date_year] = matchdata1[2]
    end

    return {} if date[:verbatim_date].blank?

    date[:verbatim_date] = date[:verbatim_date][0..-2] if date[:verbatim_date] =~ /[;,:\)\.\/]$/

    date[:start_date_month] = date[:start_date_month].gsub('january', '1')
        .gsub('february', '2')
        .gsub('march', '3')
        .gsub('april', '4')
        .gsub('may', '5')
        .gsub('june', '6')
        .gsub('july', '7')
        .gsub('august', '8')
        .gsub('september', '9')
        .gsub('october', '10')
        .gsub('november', '11')
        .gsub('december', '12')
        .gsub('jan', '1')
        .gsub('feb', '2')
        .gsub('mar', '3')
        .gsub('apr', '4')
        .gsub('sept', '9')
        .gsub('jun', '6')
        .gsub('jul', '7')
        .gsub('aug', '8')
        .gsub('sep', '9')
        .gsub('oct', '10')
        .gsub('nov', '11')
        .gsub('dec', '12')
        .gsub('xii', '12')
        .gsub('xi', '11')
        .gsub('ix', '9')
        .gsub('x', '10')
        .gsub('viii', '8')
        .gsub('vii', '7')
        .gsub('vi', '6')
        .gsub('iv', '4')
        .gsub('v', '5')
        .gsub('iii', '3')
        .gsub('ii', '2')
        .gsub('i', '1') if date[:start_date_month]

    date[:end_date_month] = date[:end_date_month].gsub('january', '1')
        .gsub('february', '2')
        .gsub('march', '3')
        .gsub('april', '4')
        .gsub('may', '5')
        .gsub('june', '6')
        .gsub('july', '7')
        .gsub('august', '8')
        .gsub('september', '9')
        .gsub('october', '10')
        .gsub('november', '11')
        .gsub('december', '12')
        .gsub('jan', '1')
        .gsub('feb', '2')
        .gsub('mar', '3')
        .gsub('apr', '4')
        .gsub('sept', '9')
        .gsub('jun', '6')
        .gsub('jul', '7')
        .gsub('aug', '8')
        .gsub('sep', '9')
        .gsub('oct', '10')
        .gsub('nov', '11')
        .gsub('dec', '12')
        .gsub('xii', '12')
        .gsub('xi', '11')
        .gsub('ix', '9')
        .gsub('x', '10')
        .gsub('viii', '8')
        .gsub('vii', '7')
        .gsub('vi', '6')
        .gsub('iv', '4')
        .gsub('v', '5')
        .gsub('iii', '3')
        .gsub('ii', '2')
        .gsub('i', '1') if date[:end_date_month]

    date[:verbatim_date] = date[:verbatim_date].gsub('january', 'January')
        .gsub('february', 'February')
        .gsub('march', 'March')
        .gsub('april', 'April')
        .gsub('may', 'May')
        .gsub('june', 'June')
        .gsub('july', 'July')
        .gsub('august', 'August')
        .gsub('september', 'September')
        .gsub('october', 'October')
        .gsub('november', 'November')
        .gsub('december', 'December')
        .gsub('xii', 'XII')
        .gsub('xi', 'XI')
        .gsub('ix', 'IX')
        .gsub('x', 'X')
        .gsub('viii', 'VIII')
        .gsub('vii', 'VII')
        .gsub('vi', 'VI')
        .gsub('iv', 'IV')
        .gsub(' v', ' V')  # space needed not to replace in 'November'
        .gsub('iii', 'III')
        .gsub('ii', 'II')
        .gsub(' i', ' I') if date[:verbatim_date] # space needed not to replace in 'April'

    if date[:start_date_year] && date[:start_date_year].length >=2 && date[:start_date_year].length < 4
      y = date[:start_date_year].last(2)
      if y.to_i >= 90
        y = '18' + y
      else
        y = '19' + y
      end
      date[:start_date_year] = y
    end
    if date[:end_date_year] && date[:end_date_year].length >=2 && date[:end_date_year].length < 4
      y = date[:end_date_year].last(2)
      if y.to_i >= 90
        y = '18' + y
      else
        y = '19' + y
      end
      date[:end_date_year] = y
    end

    if Date.valid_date?(date[:start_date_year].to_i, date[:start_date_month].to_i, date[:start_date_day].to_i) &&
        Date.parse(date[:start_date_year].to_s + '-' + date[:start_date_month].to_s + '-' + date[:start_date_day].to_s) <= Date.today &&
        date[:start_date_year].to_s + '-' + date[:start_date_month].to_s + '-' + date[:start_date_day].to_s > '1700-01-01'
      if date[:end_date_year] && Date.valid_date?(date[:end_date_year].to_i, date[:end_date_month].to_i, date[:end_date_day].to_i) &&
          Date.parse(date[:end_date_year].to_s + '-' + date[:end_date_month].to_s + '-' + date[:end_date_day].to_s) <= Date.today &&
          Date.parse(date[:end_date_year].to_s + '-' + date[:end_date_month].to_s + '-' + date[:end_date_day].to_s) >= Date.parse(date[:start_date_year].to_s + '-' + date[:start_date_month].to_s + '-' + date[:start_date_day].to_s) &&
          date[:end_date_year].to_s + '-' + date[:end_date_month].to_s + '-' + date[:end_date_day].to_s > '1700-01-01'
        return date
      elsif date[:end_date_year].nil?
        return date
      else
        return {}
      end
    elsif date[:start_date_day].nil? &&
        Date.valid_date?(date[:start_date_year].to_i, date[:start_date_month].to_i, 1) &&
        Date.parse(date[:start_date_year].to_s + '-' + date[:start_date_month].to_s + '-1') <= Date.today &&
        date[:start_date_year].to_s + '-' + date[:start_date_month].to_s + '-1' > '1700-01-01'
      if date[:end_date_day].nil? && date[:end_date_year]
          Date.valid_date?(date[:end_date_year].to_i, date[:end_date_month].to_i, 1) &&
          Date.parse(date[:end_date_year].to_s + '-' + date[:end_date_month].to_s + '-1') <= Date.today &&
          Date.parse(date[:end_date_year].to_s + '-' + date[:end_date_month].to_s + '-1') >= Date.parse(date[:start_date_year].to_s + '-' + date[:start_date_month].to_s + '-1') &&
          date[:end_date_year].to_s + '-' + date[:end_date_month].to_s + '-1' > '1700-01-01'
        return date
      elsif date[:end_date_year].nil?
        return date
      else
        return {}
      end
    else
      return {}
    end

  end

end