[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
SF.net SVN: ledger-smb:[5728] trunk/sql/modules/Business_Dates.sql
- Subject: SF.net SVN: ledger-smb:[5728] trunk/sql/modules/Business_Dates.sql
- From: ..hidden..
- Date: Tue, 30 Apr 2013 19:35:07 +0000
Revision: 5728
http://sourceforge.net/p/ledger-smb/code/5728
Author: ehuelsmann
Date: 2013-04-30 19:35:06 +0000 (Tue, 30 Apr 2013)
Log Message:
-----------
Add business day convention calculations, day count convention
calculations and recurrance pattern calculations as building
blocks for later development.
Added Paths:
-----------
trunk/sql/modules/Business_Dates.sql
Added: trunk/sql/modules/Business_Dates.sql
===================================================================
--- trunk/sql/modules/Business_Dates.sql (rev 0)
+++ trunk/sql/modules/Business_Dates.sql 2013-04-30 19:35:06 UTC (rev 5728)
@@ -0,0 +1,337 @@
+
+-- Copyright (C) 2012, the LedgerSMB Core Team. This file may be re-used under
+-- the GNU GPL version 2 or at your option any future version. Please see the
+-- accompanying LICENSE file for details.
+
+
+--
+-- Content
+--
+-- * Business day conventions
+-- * Day count conventions
+-- * Recurrence patterns
+--
+
+
+--
+-- Business day conventions
+--
+--
+
+
+
+create or replace
+function lsmb__is_workday(in_date date, in_calendar integer)
+returns boolean
+language sql
+as $$
+ select extract(isodow from $1) <= 5;
+$$;
+
+
+create table holidays (
+ calendar integer,
+ holidate date,
+ primary key (calendar, holidate)
+);
+
+
+create or replace
+function lsmb__is_holiday(in_date date, in_calendar integer)
+returns boolean
+language plpgsql
+as $$
+begin
+ if exists(select *
+ from holidays
+ where holidate = in_date
+ and calendar = in_calendar) then
+ return true;
+ else
+ return false;
+ end if;
+end;
+$$;
+
+create or replace
+function lsmb__next_business_day(in_date date, in_calendar integer,
+ in_direction integer)
+returns date
+language plpgsql
+as $$
+declare
+ t_date date;
+begin
+ t_date := in_date;
+ loop
+ exit when lsmb__is_workday(t_date, in_calendar)
+ and (not lsmb__is_holiday(t_date, in_calendar));
+
+ t_date := t_date + in_direction;
+ end loop;
+ return t_date;
+end;
+$$;
+
+create or replace
+function lsmb__next_business_day_modified(in_date date, in_calendar integer,
+ in_direction integer)
+returns date
+language plpgsql
+as $$
+declare
+ t_date date;
+begin
+ t_date := lsmb__next_business_day(in_date, in_calendar, in_direction);
+
+ if extract(month from in_date) != extract(month from t_date) then
+ t_date := lsmb__next_business_day(in_date, in_calendar, -1 * in_direction);
+ end if;
+
+ return t_date;
+end;
+$$;
+
+
+create or replace
+function lsmb__closest_business_day(in_date date, in_calendar integer,
+ in_type integer)
+returns date
+language plpgsql
+as $$
+declare
+ t_date date;
+begin
+ if in_type = 1 then -- no adjustment
+ t_date := in_date;
+ elseif in_type = 2 then -- following
+ t_date := lsmb__next_business_day(in_date, in_calendar, 1);
+ elseif in_type = 3 then -- modified following
+ t_date := lsmb__next_business_day_modified(in_date, in_calendar, 1);
+ elseif in_type = 4 then -- previous
+ t_date := lsmb__next_businss_day(in_date, in_calendar, -1);
+ elseif in_type = 5 then -- modified previous
+ t_date := lsmb__next_business_day_modified(in_date, in_calendar, -1);
+ else
+ raise exception 'Unknown business day convention (%)', in_type;
+ end if;
+
+ return t_date;
+end;
+$$;
+
+--
+-- Day count conventions
+--
+--
+
+-- Calculations implemented as described
+-- on http://en.wikipedia.org/wiki/Day_count_convention
+-- Algorithms written to allow multi-year time spans
+
+
+-- Definitions
+--
+-- Start date: start of the day-counting period (exclusive)
+-- Presumably, this is the last invoicing date
+-- End date: end date of the day-counting period (inclusive)
+-- Coupon date: coupon or invoice date
+-- Maturity date: end date of the contract
+
+create or replace
+function lsmb__daycount_30e_360(in_start_date, in_end_date)
+returns number
+language plpgsql
+as $$
+declare
+ d1, d2 integer;
+ m1, m2 integer;
+ y1, y2 integer;
+begin
+ y1 := extract(YEAR from in_start_date);
+ m1 := extract(MONTH from in_start_date);
+ d1 := extract(DAY from in_start_date);
+
+ y2 := extract(YEAR from in_end_date);
+ m2 := extract(YEAR from in_end_date);
+ d2 := extract(YEAR from in_end_date);
+
+ if d1 = 31 then
+ d1 := 30;
+ end if
+
+ if d2 = 31 then
+ d2 := 30;
+ end if
+
+ return ((y2 - y1) * 360 + (m2 - m1) * 30 + (d2 - d1)) / 360
+$$;
+
+create or replace
+function lsmb__daycount_30e_360_isda(in_start date, in_end date,
+ in_maturity date)
+returns number
+language plpgsql
+as $$
+declare
+ d1, d2 integer;
+ m1, m2 integer;
+ y1, y2 integer;
+ t_date date;
+begin
+ y1 := extract(YEAR from in_start_date);
+ m1 := extract(MONTH from in_start_date);
+ d1 := extract(DAY from in_start_date);
+
+ y2 := extract(YEAR from in_end_date);
+ m2 := extract(YEAR from in_end_date);
+ d2 := extract(YEAR from in_end_date);
+
+ if extract(MONTH from in_start) <> extract(MONTH from (in_start+1)) then
+ d1 := 30;
+ end if
+
+ if in_end <> in_maturity
+ AND extract(MONTH from in_end) <> extract(MONTH from (in_end+1)) then
+ d2 := 30;
+ end if
+
+ return ((y2 - y1) * 360 + (m2 - m1) * 30 + (d2 - d1)) / 360
+$$;
+
+
+create or replace
+function lsmb__daycount_act_act_isda(in_start date, in_end date)
+returns number
+language plpgsql
+as $$
+declare
+ t_start date;
+ t_end date;
+ t_factor number;
+ t_denom integer;
+begin
+ t_start := in_start;
+ t_end := min(in_end, (extract(YEAR from t_start) || '-12-31')::date);
+
+ loop
+ if is_leapyear(t_end) then
+ t_denom := 366;
+ else
+ t_denom := 365;
+ end if;
+
+ t_factor := t_factor + (t_end - t_start) / t_denom;
+ t_start := t_end;
+ t_end := min(in_end, t_end + '1 year'::interval);
+
+ exit when t_start = in_end;
+ end loop;
+
+ return t_factor;
+end;
+$$;
+
+create or replace
+function lsmb__daycount_act_365fixed(in_start date, in_end date)
+returns number
+language sql
+as $$
+ select (in_end - in_start) / 365;
+$$;
+
+create or replace
+function lsmb__daycount_act_360(in_start date, in_end date)
+returns number
+language sql
+as $$
+ select (in_end - in_start) / 360;
+$$;
+
+create or replace
+function lsmb__daycount_act_364(in_start date, in_end date)
+returns number
+language sql
+as $$
+ select (in_end - in_start) / 364;
+$$;
+
+
+-- can't implement Actual/Actual (icma), because it requires
+-- knowing the couponing dates up to and including the first couponing
+-- date beyond in_end, as well as the couponing frequency
+
+
+create or replace
+function lsmb__daycount_factor(in_name text, in_start date, in_end date,
+ in_maturity date)
+returns number
+language plpgsql
+as $$
+begin
+ if in_name = '30e/360' then
+ return lsmb__daycount_30e_360(in_start, in_end);
+ elseif in_name = '30e/360 (isda)' then
+ return lsmb__daycount_30e_360_isda(in_start, in_end, in_maturity);
+ elseif in_name = 'act/act (isda)' then
+ return lsmb__daycount_act_act_isda(in_start, in_end);
+ elseif in_name = 'act/365 fixed' then
+ return lsmb__daycount_act_365(in_start, in_end);
+ elseif in_name = 'act/360' then
+ return lsmb__daycount_act_360(in_start, in_end);
+ elseif in_name = 'act/364' then
+ return lsmb__daycount_act_364(in_start, in_end);
+ else
+ raise exception 'Unknown day count convention (%)', in_name;
+ end if
+end;
+$$;
+
+comment on function lsmb__daycount_factor(in_name text, in_start date,
+ in_end date, in_maturity date) IS
+$$This function returns the factor designating the weight of the days
+between in_start (exclusive) and in_end (inclusive) using the day weighting
+procedure indicated by in_name. A full year may end up with a weight
+higher than 1 in all cases marked with '[*]' below.
+
+The argument in_name can be any of these values:
+
+ * '30e/360'
+ * '30e/360 (isda)'
+ * 'act/act (isda)'
+(* 'act/act (icma)' not implemented through this interface)
+ * 'act/365 fixed' [*]
+ * 'act/360' [*]
+ * 'act/364' [*]
+
+The in_maturity argument designates the end of the contract period
+and is used in case of '30e/360 (isda)' only.
+$$;
+
+
+--
+-- Recurrence patterns
+--
+--
+
+
+create or replace
+function lsmb__next_interval_date(in_start_date date, in_repeat integer,
+ in_interval interval, in_eom boolean)
+returns date
+language plpgsql
+as $$
+declare
+ t_end_date date;
+begin
+ select in_start_date + in_repeat * in_interval into t_end_date;
+
+ if in_eom then
+ select date_trunc('month', t_end_date)
+ + '1 month'::interval - '1 day'::interval
+ into t_end_date;
+ end if
+
+ return t_end_date;
+end;
+$$;
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.