# This class is meant to try to figure out what date is 
# represented by a passed in string.  It heavily uses regular 
# expressions to determine this, so it may not be passed, but 
# it gives a lot of freedom in allowing someone to enter a 
# date easily.  I chose not to throw errors, instead giving 
# nil results for dates that can not be understood (strings that
# do not represent a valid date.)

class DateInfo
  def initialize
  end

  # It takes as input a string.  If it determines that the string specifies a date, 
  # it returns that date as a Time object.  Otherwise it returns null.  It sees the following 
  # types of strings as dates:
  # 1. Of the format MM/DD/YY, MM-DD-YY, MM\DD\YY.  YY may be YYYY in any of the cases.  
  #    This is the trickiest pattern, since the user may format using Year/Month/Day or 
  #    Month/Day/Year.  In addition, only two digits for the year makes it difficult to 
  #    assume what the user meant.  I chose to use the Month/Day/Year form, unless the 
  #    user uses four digits to specify the year, in which point it is easy to figure out 
  #    what they meant.  Therefore, YYYY/MM/DD is also a valid format .  As for the year, if 
  #    four digits are not specified, then I assume that they are specifying the current 
  #    millineum (2000).
  # 2. Of the format "June 27, 1983", "Jun 27, 1983", or "June 27" (in which the current 
  #    year is implied).  Granted Junileropwf 27, 1983 would also be valid here, but I 
  #    didn't think such cases were important enough to detect and were instead a waste the 
  #    readibility of the expression.  In specifying the month, a minimum of 3 characters
  #    are required.  The comma is optional, but at least one space must separate the tokens.
  # 3. Of the format YYYYMMDD.
  # 4. Of the format YYYY.  The date is specified as Jan 1 of the year YYYY.
  # 5. Last or Next or Current Day of the Week (ie last Thu, next Thursday, or Thursday).  
  #    I used a minimum of three letters for the weekday name to avoid matching cases 
  #    I didn't want it to match.
  # 6. Yesterday, today, tomorrow.
  def interpretDate( input )
    input = input.to_s.downcase
    t = nil
    currentDate = Time.local(Time.now.year, Time.now.mon, Time.now.day)

    if( input =~ /\A(\d+)\s*(-|\/|\\)\s*(\d+)\s*((-|\/|\\)\s*(\d+))?/ )
      month = $1.to_i
      day = $3.to_i
      if( $6 )
        year = $6.to_i
      else
        year = Time.new.year
      end
      t = Time.local( year, month, day )
    elsif( input =~ /\A(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[^\s]* (\d+)(.*)/ )
      month = $1
      day = $2.to_i
      if ( $3 =~ /\s*(\,|)\s*(\d\d\d\d)/ )
        year = $2.to_i
      else
        year = Time.new.year
      end
      t = Time.gm( year, month, day )
    elsif( input =~ /\A(\d\d\d\d)(\d\d)(\d\d)\Z/ )
      month = $2.to_i
      day = $3.to_i
      year = $1.to_i
      t = Time.local( year, month, day )
    elsif( input =~ /\A(\d\d\d\d)\Z/ )
      year = $1.to_i
      t = Time.local( year, 1, 1 )
    elsif( input =~ /(this |last |next |)(sun|mon|tue|wed|thu|fri|sat)[^\s]*/ )
      t = currentDate
      wdays = %w(sun mon tue wed thu fri sat)
      if( $1 == "last " )
        #subtract one week from today
        t -= (86400*7)
      elsif( $1 == "next " )
        #add one week to today
        t += (86400*7)
      end
      distance = wdays.index( $2 ) - currentDate.wday
      distance += 7 if ( distance < 0 )
      t += (distance*86400)
    elsif( input =~ /today/ )
      t = Time.now
    elsif( input =~ /tomorrow/ )
      t = Time.now + 86400
    elsif( input =~ /yesterday/ )
      t = Time.now - 86400
    else
      return nil
    end

    return t
  end

  #If input is not given, the input is the today
  #Returns the start of the month specified by input as a 
  #        string in the format mm/dd/yy
  #Returns nil if the input does not represent a valid date
  def startMonth( input = nil )
    if( input == nil )
      t = Time.local(Time.now.year, Time.now.mon, Time.now.day)
    else
      t = interpretDate( input )
    end

    return nil if( t == nil )

    stringDate = t.strftime("%m/01/%y")

    return stringDate
  end

  #If input is not given, the input is the today
  #Returns the end of the month specified by input as a 
  #        string in the format mm/dd/yy
  #Returns nil if the input does not represent a valid date
  def endMonth( input = nil )
    if( input == nil )
      t = Time.local(Time.now.year, Time.now.mon, Time.now.day)
    else
      t = interpretDate( input )
    end

    return nil if( t == nil )

    curMonth = t.strftime("%m").to_i
    curYear = t.strftime("%y").to_i

    if( curMonth == 4 || curMonth == 6 || curMonth == 9 || curMonth == 11 )
      stringDate = t.strftime("%m/30/%y")
    elsif( curMonth == 2 && Date.gregorian_leap?( curYear ) )
      stringDate = t.strftime("%m/29/%y")
    elsif( curMonth == 2 && !Date.gregorian_leap?( curYear ) )
      stringDate = t.strftime("%m/28/%y")
    else
      stringDate = t.strftime("%m/31/%y")
    end

    return stringDate
  end

  #If input is not given, the date is today
  #If input represents a valid date, returns the date as an integer (YYYYMMDD)
  #and as a string (MM/DD/YY) 
  #Returns nil, nil if the input does not represent a valid date
  def getFormattedDates( input= nil )
    if( input == nil )
      t = Time.local(Time.now.year, Time.now.mon, Time.now.day)
    else
      t = interpretDate( input )
    end

    return nil, nil if( t == nil )

    # Print out a report for this date
    intDate = t.strftime("%Y%m%d").to_i
    stringDate = t.strftime("%m/%d/%y")

    return intDate, stringDate
  end

  #Returns the number of days between start and end date
  #Returns nil if startdate or enddate is invalid
  def getDaysBetween( startDate, endDate )
    startDate = interpretDate( startDate )
    endDate = interpretDate( endDate )
    return nil if( startDate == nil || endDate == nil )
    return (((endDate-startDate)/86400).to_i+1)
  end

  # It takes as input a string.  If it determines that the string specifies a date, 
  # the method returns a string with the date, the day of the week of the date, and the number 
  # of days away the date is from today.  It sees the following types of strings as dates:
  def dateReporter( input )
    if( input == nil )
      t = Time.local(Time.now.year, Time.now.mon, Time.now.day)
    else
      t = interpretDate( input )
    end
    return "That Date is no Recognized" if( t == nil )

    currentDate = Time.local(Time.now.year, Time.now.mon, Time.now.day)

    # Get information related to the specified date
    distance = ((t-currentDate)/86400).to_i

    # Print out a report for this date
    report = ( "You asked about "+t.strftime("%B %d, %Y")+"\n" )
    report += ( "This date is a "+t.strftime("%A")+".\n" )
    if( distance == 0 )
      report += ( "This is today's date." )
    elsif( distance == 1 )
      report += ( "This is tomorrow's date." )
    elsif( distance == -1 )
      report += ( "This was yesterday's date." )
    elsif( distance > 0 )
      report += ( "This date will occur in "+distance.to_s+" days." )
    elsif( distance < 0 )
      report += ( "This date occured "+(distance*-1).to_s+" days ago." )
    end

    return report
  end

end