## Copyright (C) 2019 Anthony Morast
##
## 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 3 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, see <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn {Function File} {} daysdif (@var{d1}, @var{d2})
## @deftypefnx {Function File} {} daysdif (@var{d1}, @var{d2}, @var{basis})
##
## Calculates the number of days between two dates for any day-count basis.
## The variables @var{d1} and @var{d2} can either be strings or an @var{n}-row 
## string matrix. If both @var{d1} and @var{d2} are string matrices, then the 
## number of rows must match. 
##
## The day-count basis is specified as an integer between 0 and 13 or an 
## @var{n}-row vector of integers between 0 and 13. The valid day-count
## basis are:
##
## @itemize 
## @item
## 0 = Actual/Actual (default)
## @item
## 1 = 30/360 SIA
## @item
## 2 = Actual/360
## @item
## 3 = Actual/365
## @item
## 4 = 30/360 PSA
## @item
## 5 = 30/360 ISDA
## @item
## 6 = 30/360 European
## @item
## 7 = Act/365 Japanese
## @item
## 8 = Act/Act ISMA
## @item
## 9 = Act/360 ISMA
## @item
## 10 = Act/365 ISMA
## @item
## 11 = 30/360 ISMA
## @item
## 12 = Act/365 ISDA
## @item
## 13 = Bus/252
## @end itemize
##
## @seealso{datenum}
## @end deftypefn

function days = daysdif(d1, d2, basis=0)
  if (nargin < 2)
	print_usage();
	return
  endif
	
  nr1 = size (d1, 1);
  nr2 = size (d2, 1);
  if (nr1 != nr2 && nr1 != 1 && nr2 != 1)
    error ("daysdif: date size mismatch");
  endif

  if (size(basis, 2) > size(basis,1)) 
	# if basis vector is a row vector transpose it since row vectors of strings
	# function as one long string.
    basis = transpose(basis);
  endif
  bn = size(basis, 1);
  if ((bn != nr2 && nr2 != 1 && bn != 1) || (bn != nr1 && nr1 != 1 && bn != 1))
	error ("daysdif: basis size mismatch");
  endif

  days = 0;
  for i = 1:max(max(nr1, nr2), bn)
    tb = basis(1);
    if (nr1 > 1) 
      td1 = datenum(d1(i, :));
    else
      td1 = datenum(d1);
    endif
    if (nr2 > 1)
      td2 = datenum(d2(i,:));
    else 
      td2 = datenum(d2);
    endif
    if (bn > 1)
      tb = basis(i);
    endif

	d1vec = datevec(td1);
	d2vec = datevec(td2);

	d1leap = (mod(d1vec(1), 4) == 0 && mod(d1vec(1), 100) != 0) || mod(d1vec(1), 400) == 0;
	d2leap = (mod(d2vec(1), 4) == 0 && mod(d2vec(1), 100) != 0) || mod(d2vec(1), 400) == 0;	
	d1lstfeb = d1vec(2) == 2 && ((d1vec(3) == 28 && !d1leap) || (d1vec(3) == 29 && d1leap));
	d2lstfeb = d2vec(2) == 2 && ((d2vec(3) == 28 && !d2leap) || (d2vec(3) == 29 && d2leap));

	leapcount = 0;
	for yr=d1vec(1):d2vec(1)
      if ((mod(yr, 4) == 0 && mod(yr, 100) != 0) || mod(yr, 400) == 0)
	    leapcount += 1;
	  endif
	endfor

    switch (tb)
	  case { 0, 2, 3, 8, 9, 10, 12 }
	  	td = daysact(d1vec, d2vec);
	  case 1
		if (d1lstfeb && d2lstfeb)
		  d2vec(3) = 30;
		endif
		if (d1lstfeb || d1vec(3) == 31) 
		  d1vec(3) = 30;
		endif
		if (d1vec(3) == 30 && d2vec(3) == 31)
		  d2vec(3) = 30;
		endif
		td = ((360 * (d2vec(1) - d1vec(1))) + (30 * (d2vec(2) - d1vec(2))) + (d2vec(3) - d1vec(3)));
	  case 4
		if (d1lstfeb || d1vec(3) == 31) 
		  d1vec(3) = 30;
		endif
		if (d1vec(3) == 30 && d2vec(3) == 31) 
		  d2vec(3) = 30;
		endif
		td = ((360 * (d2vec(1) - d1vec(1))) + (30 * (d2vec(2) - d1vec(2))) + (d2vec(3) - d1vec(3)));
	  case 5
		if (d1vec(3) == 31) 
		  d1vec(3) = 30;
		endif
		if (d1vec(3) == 30 && d2vec(3) == 31) 
		  d2vec(3) = 30;
		endif
		td = ((360 * (d2vec(1) - d1vec(1))) + (30 * (d2vec(2) - d1vec(2))) + (d2vec(3) - d1vec(3)));
	  case { 6, 11 }
		if (d1vec(3) == 31) 
		  d1vec(3) = 30;
		endif
		if (d2vec(3) == 31)
		  d2vec(3) = 30;
		endif		  
	    td = ((360 * (d2vec(1) - d1vec(1))) + (30 * (d2vec(2) - d1vec(2))) + (d2vec(3) - d1vec(3)));
	  case 7
	    td = daysact(d1vec, d2vec);
		td -= leapcount;
	  case 13
		td = 0;
		if (td1 < td2) 
		  for (currdate=td1:td2-1) 
		    if (isbusday(currdate))	
			  td += 1;
			endif
		  endfor
		else
		  for (currdate=td2-1:td1)
		    if (isbusday(currdate))
			  td -= 1;
			endif
		  endfor
		endif
	  otherwise
	    error ("daysdif: invalid basis value")
	endswitch

	if (td1 == td2)
	  td = 0;
	endif
	if (days == 0)
	  days = [td];
	else
	  days = [days; td];
	endif
  endfor 
endfunction

## tests
%!assert (daysdif ("01-Jan-2007", ["01-Jan-2008"; "01-Jan-2006"]), [365;-365])
%!assert (daysdif ("7-sep-2007", "07-Sep-2008"), 366)
%!assert (daysdif ("2018-05-01", ["2019-01-01"; "2019-02-02"], [1;2]), [240;277])
%!assert (daysdif ("2018-05-01", "2019-01-01", [1 12 13]), [240; 245; 170])
%!assert (daysdif ("2018-05-01", "2019-01-01", [1; 12; 13]), [240; 245; 170])
%!assert (daysdif ("2018-05-01", "2020-05-01", 0), 731)
%!assert (daysdif ("2018-05-01", "2020-05-01", 1), 720)
%!assert (daysdif ("2018-05-01", "2020-05-01", 2), 731)
%!assert (daysdif ("2018-05-01", "2020-05-01", 3), 731)
%!assert (daysdif ("2018-05-01", "2020-05-01", 4), 720)
%!assert (daysdif ("2018-05-01", "2020-05-01", 5), 720)
%!assert (daysdif ("2018-05-01", "2020-05-01", 6), 720)
%!assert (daysdif ("2018-05-01", "2020-05-01", 7), 730)
%!assert (daysdif ("2018-05-01", "2020-05-01", 8), 731)
%!assert (daysdif ("2018-05-01", "2020-05-01", 9), 731)
%!assert (daysdif ("2018-05-01", "2020-05-01", 10), 731)
%!assert (daysdif ("2018-05-01", "2020-05-01", 11), 720)
%!assert (daysdif ("2018-05-01", "2020-05-01", 12), 731)
%!assert (daysdif ("2018-05-01", "2020-05-01", 13), 505)
%!assert (daysdif ("2016-02-29", "2017-02-28", 1), 360)
