class Date

Extension for Date to calculate a forward date with compressed syntax

Copyright 2021 Stephan Wenzel <stephan.wenzel@drwpatent.de>

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

Public Instance Methods

adjust( epoch, pos, ref=self ) click to toggle source

calculate time adjustmentfor pos in epoch (_F_irst, _M_id, _L_ast, _W_orking day) epoch: D - day: W - Monday epoch: W - week: F - Monday, M - Wednesday, L - Friday, W - Monday epoch: M - month: F - 1st, M - 15th, L - last, W - Monday epoch: Y - year: F - 01/01, M - 06/30, L - 12/31, W - Monday epoch: q - quarter: W - Monday

# File lib/date_helper/date.rb, line 169
def adjust( epoch, pos, ref=self )

  case epoch
  when "D"
    case pos
    when "W"
      # if saturday or sunday, fall back to last monday, then add one week for monday coming up
      (wday % 6) != 0 ? self : monday.advance(:days => 7)
    else
      self
    end #case
    
  when "W", "C"
    # week
    case pos
    when "F"
      # Monday
      monday < ref ? advance( :weeks => 1).monday : monday
      
    when "M"
      # Wednesday = Monday + 2 days
      monday.advance(:days => 2) < ref ? advance( :weeks => 1).monday.advance(:days => 2) : monday.advance(:days => 2)
      
    when "L"
      # Friday = Monday + 4 days
      monday.advance(:days => 4) < ref ? advance( :weeks => 1).monday.advance(:days => 4) : monday.advance(:days => 4)
      
    when "W"
    # if saturday or sunday, fall back to last monday, then add one week for monday coming up
      (wday % 6) != 0 ? self : monday.advance(:days => 7)
      
    else
      self
    end #case
    
  when "M"
    # month
    case pos
    when "F"
      # 1st
      change(:day => 1) < ref ? advance( :months => 1).beginning_of_month.change(:day => 1) : change(:day => 1)
      
    when "M"
      # 15th
      change(:day => 15) < ref ? advance( :months => 1).beginning_of_month.change(:day => 15) : change(:day => 15)
      
    when "L"
      # last day
      end_of_month
      
    when "W"
    # if saturday or sunday, fall back to last monday, then add one week for monday coming up
      (wday % 6) != 0 ? self : monday.advance(:days => 7)
    else
      self
    end #case
    
  when "Y"
    # year
    case pos
    when "F"
      # Jan. 1st
      beginning_of_year < ref ? advanve(:years => 1).beginning_of_year : beginning_of_year
      
    when "M"
      # Jun. 30th
      change(:month => 5, :day => 30) < ref ? advance(:years => 1).change(:month => 5, :day => 30) : change(:month => 5, :day => 30)
      
    when "L"
      # Dec. 31st
      change(:month => 12, :day => 31)
      
    when "W"
    # if saturday or sunday, fall back to last monday, then add one week for monday coming up
      (wday % 6) != 0 ? self : monday.advance(:days => 7)
    else
      self
    end #case
    
  when "q"
    case pos
    when "F"
      # Jan. 1st
      beginning_of_quarter < ref ? advance( :months => 3).beginning_of_quarter : beginning_of_quarter
      
    when "M"
      # mid quarter
      beginning_of_quarter.advance(:months => 1).advance(:days => 14) < ref ? advance(:quarters => 1).beginning_of_quarter.advance(:months => 1).advance(:days => 14) : beginning_of_quarter.advance(:months => 1).advance(:days => 14)
      
    when "L"
      # end quarter
      end_of_quarter
      
    when "W"
    # if saturday or sunday, fall back to last monday, then add one week for monday coming up
      (wday % 6) != 0 ? self : monday.advance(:days => 7)
    else
      self
    end #case
    
  else
    self
  end #case epoch
end
calc( rule ) click to toggle source

calc

# File lib/date_helper/date.rb, line 84
def calc( rule )

  new_date = nil
  new_rule = nil
  
  if rule.present?
  
    m = parse_rule( rule )
    
    if m['mockswitch']
    
      # mockswitch does not calculate anything
      # mockswitch is removed from new_rule, however
      new_rule = unmock( rule, m )
      
    elsif self <= Date.today || m['force']
    
      if( m['epoch'] && m['num'] )
      
        new_date = move(   m['epoch'], m['num'] )
        new_date = new_date.adjust( m['epoch'], m['pos'], self )
        new_date = new_date.adjust("D", "W", new_date) if m["workingday"] # adjust working day
        
        if m['killswitch']
          new_rule  = "" 
        else
          new_rule  = rule
        end
        
      end #if
    end #if
  end #if
  
  [new_date, new_rule]
  
end
forward( rule ) click to toggle source

forward

# File lib/date_helper/date.rb, line 124
def forward( rule )
  calc( rule ).first
end
move( epoch, num ) click to toggle source

calculate num times advance of epoch epoch: D - n days epoch: W - n weeks epoch: M - n months epoch: Y - n years

epoch: C - n calendar weeks (absolute, within this year, not relative) epoch: m - n mondays epoch: q - q quarters

# File lib/date_helper/date.rb, line 139
def move( epoch, num )
  case epoch  
  when "D"
      self + num.to_i
  when "W"
      self + num.to_i * 7
  when "M"
      self >> num.to_i
  when "Y"
      self >> num.to_i * 12
  when "m"
      (self + num.to_i * 7).monday 
  when "q"
      (self >> num.to_i * 3).beginning_of_quarter
  when "C"
      # calendar week 1 is the week containing Jan. 4th
      change(:month => 1, :day => 4).advance( :weeks => (num.to_i - 1))
  else
    self
  end #case
end

Private Instance Methods

parse_rule(rule) click to toggle source

parse_rule

# File lib/date_helper/date.rb, line 283
def parse_rule(rule)

  m = {}
  matches = /(?<epoch>[DWMYCmq])(?<num>[0-9]+)(?<pos>[XFMLW]?)(?<kfmw>[-!W\*]*)(?<trailing_rest>.*)/.match(rule)
  
  if matches.present?
    m.merge!(Hash[ matches.names.zip( matches.captures ) ])
    m.merge!('killswitch' => m['kfmw'].match(/-/).to_s.presence)
    m.merge!('force'      => m['kfmw'].match(/!/).to_s.presence)
    m.merge!('mockswitch' => m['kfmw'].match(/\*/).to_s.presence)
    m.merge!('workingday' => m['kfmw'].match(/W/).to_s.presence)
  end
  m.compact
end
unmock( rule, m=nil ) click to toggle source

parse_rule

# File lib/date_helper/date.rb, line 301
def unmock( rule, m=nil )
  m = parse_rule( rule ) unless m
  if m['mockswitch'].present?
    "#{m['epoch']}#{m['num']}#{m['pos']}#{m['killswitch']}#{m['force']}#{m['trailing_rest']}"
  else
    nil
  end
end