Index: openacs-4/packages/ref-timezones/sql/postgresql/ref-timezones-create.sql
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/ref-timezones/sql/postgresql/ref-timezones-create.sql,v
diff -u -r1.1 -r1.2
--- openacs-4/packages/ref-timezones/sql/postgresql/ref-timezones-create.sql	4 Sep 2001 04:41:30 -0000	1.1
+++ openacs-4/packages/ref-timezones/sql/postgresql/ref-timezones-create.sql	15 Oct 2001 19:33:38 -0000	1.2
@@ -5,10 +5,47 @@
 -- a combination of the NIH timezone database and the Unix zoneinfo
 -- database (conversion rules).
 --
--- @author jon@jongriffin.com
+-- @author jon@jongriffin.com, dhogaza@pacifier.com
 -- @creation-date 2001-09-02
 -- @cvs-id $Id$
 
+-- DRB: PostgreSQL has its own ideas about timezones and input/output conversions.
+-- It natively supports a subset of the Unix timezone database, and external
+-- representations are always in server-local time unless overridden by explicit
+-- time zone information on input or converted to varchar with an "at timezone"
+-- suffix in a select statement.
+
+-- While useful for applications that can live with the restrictions, it's not
+-- quite general enough for our usage.  This package provides the generality
+-- we need in a style that's very close to that of its Oracle equivalent.
+
+-- PG stores all dates shifted to UTC and does all computations in Julian
+-- dates.  This package provides some very simple utilities:
+
+-- timezone__convert_to_utc(timezone, input_string) returns timestamp
+--   Takes an input string (which must NOT have any explicit timezone
+--   information embedded) and converts it to a timestamp, shifting it
+--   to UTC using the timezone information.   In other words, input_string
+--   is a date/time local to the given timezone while the returned timestamp
+--   is the same date/time shifted to UTC.
+
+-- timezone__get_date(timezone, timestamp, format string) returns varchar
+--   Converts the timestamp to a pretty date in the given timezone using "to_char"
+--   and appends the timezone abbreviation.
+
+-- timezone__get_offset(timezone, timestamp) returns interval
+--   Returns a PostgreSQL interval (which can be added or substracted from
+--   a UTC timestamp) for the timestamp in the given timezone.
+
+-- timezone__get_rawoffset(timezone, timestamp) returns interval
+--   Returns the raw (i.e. not adjusted for daylight savings time) offset
+--   for the timestamp in the timezone (those reading the code for the first
+--   time may think these definitions are backwards, but they're not)
+
+-- Currently if timezone can't be found UTC is assumed.  Server local time
+-- might make more sense but the Oracle version assumes UTC so we'll use that
+-- for now...
+
 create sequence timezone_seq;
 
 -- Primary table for storing timezone names and standard offsets
@@ -17,27 +54,20 @@
     tz_id	   integer
 		   constraint timezones_tz_id_pk primary key,
     -- Unix-style TZ environment variable string, e.g. 'America/Los_Angeles'
-    tz		   varchar2(100) not null,
+    tz		   varchar(100) not null,
     -- the standard time offset from UTC as (+-)hhmiss
-    gmt_offset	   char(7) not null
+    gmt_offset	   text not null
 );
 
 -- add this table into the reference repository
 
-declare
-    v_id integer;
-begin
-    v_id := acs_reference.new(
-        table_name     => 'TIMEZONES',
-        package_name   => 'TIMEZONE',
-        source         => 'National Institute of Health (USA)',
-        source_url     => 'ftp://elsie.nci.nih.gov/pub',
-        last_update    => to_date('2000-08-21','YYYY-MM-DD'),
-        effective_date => sysdate
+select acs_reference__new(
+          'TIMEZONES',
+          '2000-08-21',
+          'National Institute of Health (USA)',
+          'ftp://elsie.nci.nih.gov/pub',
+          now()
     );
-commit;
-end;
-/
 
 -- The following table stores the rules for converting between
 -- local and UTC time. Each rule is specified by timezone, its
@@ -49,19 +79,17 @@
 			constraint timezone_rules_tz_id_fk references timezones
 			on delete cascade,
     -- abbreviation for local time, e.g. EST, EDT
-    abbrev		varchar2(10),
+    abbrev		varchar(10) not null,
     -- UTC start/end time of this rule
-    utc_start		date,
-    utc_end		date,
+    utc_start		timestamp not null,
+    utc_end		timestamp not null,
     -- local start/end time of this rule
-    local_start		date,
-    local_end		date,
-    -- GMT offset in fractions of day (UTC + gmt_offset = local)
-    gmt_offset		number,
+    local_start		timestamp not null,
+    local_end		timestamp not null,
+    -- GMT offset in seconds
+    gmt_offset		text not null,
     -- is Daylight Savings Time in effect for this rule?
-    isdst		char(1) 
-			constraint timezone_rules_isdist_ck
-			check (isdst in ('t','f'))
+    isdst_p		boolean
 );
 
 create index timezone_rules_idx1 on timezone_rules(tz_id, utc_start,   utc_end);
@@ -71,440 +99,241 @@
 -- TimeZone package
 -------------------------------------------------------------------------------
 
-create or replace package timezone
-as
-    procedure new (
-        tz         in timezones.tz%TYPE,
-        gmt_offset in timezones.gmt_offset%type
-    );
+create function rdbms_date(varchar) returns timestamp as '
+declare
+  p_raw_date alias for $1;
+begin
+  return timestamp (p_raw_date || ''+00'');
+end;' language 'plpgsql';
 
-    procedure delete (
-	 tz_id	   in timezones.tz_id%TYPE
-    );
+create function timezone__new (varchar, varchar) returns integer as '
+declare
+  p_tz alias for $1;
+  p_gmt_offset alias for $2;
+begin
+  insert into timezones
+    (tz_id, tz, gmt_offset)
+  values
+    (nextval(''timezone_seq''), p_tz, gmt_offset);
+  return 0;
+end;' language 'plpgsql';
+	 
+create function timezone__delete (integer) returns integer as '
+declare
+  p_tz_id alias for $1;
+begin
+  delete from timezone_rules where tz_id = p_tz_id;
+  delete from timezones      where tz_id = p_tz_id;
+  return 0;
+end;' language 'plpgsql';
 
-    function get_id (
-	 -- Gets the ID number of the given timezone
-	 tz in timezones.tz%TYPE
-    ) return integer;
+-- private function for looking up timezone id's
 
-    procedure add_rule (
-	 -- Adds a new conversion rule to the timezone_rules database
-	 tz		in timezones.tz%TYPE,
-	 abbrev		in timezone_rules.abbrev%TYPE,
-	 isdst		in integer,
-	 gmt_offset	in integer,
-	 utc_start	in varchar,
-	 utc_end	in varchar,
-	 local_start	in varchar,
-	 local_end	in varchar
-    );
+create function timezone__get_id (varchar) returns integer as '
+declare
+  p_tz alias for $1;
+  v_tz_id integer;
+begin
+  select tz_id into v_tz_id 
+  from   timezones
+  where  tz = p_tz;
+  return v_tz_id;
+end;' language 'plpgsql';
 
-    -- The following are the primary time conversion functions
+create function timezone__add_rule (varchar, varchar, integer, varchar, varchar, varchar, varchar, varchar) returns integer as '
+declare
+  p_tz alias for $1;
+  p_abbrev alias for $2;
+  p_isdst_p alias for $3;
+  p_gmt_offset alias for $4;
+  p_utc_start alias for $5;
+  p_utc_end alias for $6;
+  p_local_start alias for $7;
+  p_local_end alias for $8;
+begin
+  insert into timezone_rules
+    (tz_id, abbrev, utc_start, utc_end, local_start, local_end, gmt_offset, isdst_p)
+  select timezone__get_id(tz), abbrev, rdbms_date(utc_start),
+    rdbms_date(utc_end), rdbms_date(local_start),
+    to_date(local_end),
+    gmt_offset,
+    case isdst_p isdst_p when 0 then ''f'' else ''t''end;
+end;' language 'plpgsql';
 
-    function utc_to_local (
-	 -- Returns utc_time converted to local time
-	 tz        in timezones.tz%TYPE,
-	 utc_time  in date
-    ) return date;
+create function timezone__convert_to_utc (integer, varchar) returns timestamp as '
+declare
+  p_tz_id alias for $1;
+  p_local_varchar alias for $2;
+  v_base_time timestamp;
+foo varchar;
+begin
 
-    function utc_to_local (
-	 -- Returns utc_time converted to local time
-	 tz_id     in timezones.tz_id%TYPE,
-	 utc_time  in date
-    ) return date;
+  select timestamp (p_local_varchar || substr(gmt_offset,1,5)) into v_base_time
+  from timezones
+  where tz_id = p_tz_id;
 
-    function local_to_utc (
-	 tz_id      in timezones.tz_id%TYPE,
-	 local_time in date
-    ) return date;
+  if not found then
+    return timestamp (p_local_varchar || ''+00'');
+  end if;
 
-    function local_to_utc (
-	 tz         in timezones.tz%TYPE,
-	 local_time in date
-    ) return date;
+  return timestamp (p_local_varchar || ''+00'') - interval (gmt_offset || ''seconds'')
+  from   timezone_rules
+  where  tz_id = p_tz_id and v_base_time between utc_start and utc_end;
 
-    -- The following provide access to the current offset information
+end;' language 'plpgsql';
 
-    function get_offset (
-	 -- Gets the timezone offset in seconds, for the current date,
-	 -- modified in case of DST.
-	 tz_id      in timezones.tz_id%TYPE,
-	 local_time in date default sysdate
-    ) return integer;
 
-    function get_offset (
-	 tz         in timezones.tz%TYPE,
-	 local_time in date default sysdate
-    ) return integer;
+create function timezone__convert_to_utc (varchar, varchar) returns timestamp as '
+declare
+  p_tz alias for $1;
+  p_time alias for $2;
+begin
+  return timezone__local_to_utc(timezone__get_id(p_tz), p_time);
+end;' language 'plpgsql';
 
+create function timezone__get_offset (integer, timestamp) returns interval as '
+declare
+  p_tz_id alias for $1;
+  p_time alias for $2;
+  v_offset integer;
+begin
+  v_offset := ''0'';
 
-    function get_rawoffset (
-	 -- Gets the timezone offset NOT modified for DST
-	 tz_id      in timezones.tz_id%TYPE,
-	 local_time in date default sysdate
-    ) return integer;
+  select gmt_offset into v_offset
+  from timezone_rules
+  where  tz_id = p_tz_id and p_time between utc_start and utc_end;
 
-    function get_rawoffset (
-	 -- Gets the timezone offset NOT modified for DST
-	 tz         in timezones.tz%TYPE,
-	 local_time in date default sysdate
-    ) return integer;
+  return interval (v_offset || ''seconds'');
+end;' language 'plpgsql';
 
-    function get_abbrev (
-	 -- Returns abbreviation for the coversion rule
-	 tz_id	     in timezones.tz_id%TYPE,
-	 local_time  in date default sysdate
-    ) return varchar;
+create function timezone__get_offset (varchar, timestamp) returns interval as '
+declare
+  p_tz alias for $1;
+  p_time alias for $2;
+begin
+  return timezone__get_offset(timezone__get_id(p_tz), p_time);
+end;' language 'plpgsql';
+    
+create function timezone__get_rawoffset (integer, timestamp) returns interval as '
+declare
+  p_tz_id alias for $1;
+  p_time alias for $2;
+  v_offset varchar;
+begin
+  v_offset := ''0'';
 
-    function get_abbrev (
-	 -- Returns abbreviation for the coversion rule
-	 tz	     in timezones.tz%TYPE,
-	 local_time  in date default sysdate
-    ) return varchar;
+  select
+    case isdst_p
+    when ''t'' then interval (gmt_offset || ''seconds'') - ''3600 seconds''
+    else interval (gmt_offset || ''seconds'')
+    end
+  into v_offset
+  from   timezone_rules
+  where  tz_id  = p_tz_id and p_time between utc_start and utc_end;
 
-    function get_zone_offset (
-    	 -- Returns the relative offset between two zones at a
-	 -- particular UTC time. 
-	 tz_this     in timezones.tz%TYPE,
-	 tz_other    in timezones.tz%TYPE,
-	 utc_time    in date default sysdate
-    ) return integer;
+  return v_offset;
+end;' language 'plpgsql';
 
-    -- Access to flags
+create function timezone__get_rawoffset (varchar, timestamp) returns interval as '
+declare
+  p_tz alias for $1;
+  p_time for $2;
+begin
+  return timezone__get_rawoffset(timezone__get_id(p_tz), p_time);
+end;' language 'plpgsql';
 
-    function isdst_p (
-	 -- Returns 't' if timezone is currently using DST
-	 tz_id	     in timezones.tz_id%TYPE,
-	 local_time  in date default sysdate
-    ) return char;	 
+create function timezone__get_abbrev (integer, timestamp) returns varchar as '
+declare
+  p_tz_id alias for $1;
+  p_time for $2;
+  v_abbrev timezone_rules.abbrev%TYPE;
+begin
+  v_abbrev := ''GMT'';
 
-    function isdst_p (
-	 -- Returns 't' if timezone is currently using DST
-	 tz	     in timezones.tz%TYPE,
-	 local_time  in date default sysdate
-    ) return char;	 
-
-
-    -- Special formatting functions
-
-    function get_date (
-	 -- Returns a formatted date with timezone info appended  
-	 tz_id	     in timezones.tz_id%TYPE,
-	 local_time  in date,
-	 format	     in varchar default 'yyyy-mm-ss hh24:mi:ss'
-    ) return varchar;
-
-    function get_date (
-	 -- Returns a formatted date with timezone info appended  
-	 tz	     in timezones.tz%TYPE,
-	 local_time  in date,
-	 format	     in varchar default 'yyyy-mm-ss hh24:mi:ss'
-    ) return varchar;
-
-
-end timezone;
-/
-show errors
-
---
---
---
-
-create or replace package body timezone
-as
-    procedure new (
-	 tz         in timezones.tz%TYPE,
-	 gmt_offset in timezones.gmt_offset%type
-    ) 
-    is
-    begin
-	 insert into timezones
-	      (tz_id, tz, gmt_offset)
-	 values
-	      (timezone_seq.nextval, tz, gmt_offset);
-    end;
+  select abbrev into v_abbrev
+  from   timezone_rules
+  where  tz_id = p_tz_id and p_time between local_start and local_end;
 	 
-    procedure delete (
-	 tz_id	   in timezones.tz_id%TYPE
-    )
-    is
-    begin
-	 delete from timezone_rules where tz_id = tz_id;
-	 delete from timezones      where tz_id = tz_id;
-    end;
+  return v_abbrev;
+end;' language 'plpgsql';
 
-    -- private function for looking up timezone id's
+create function timezone__get_abbrev (varchar, timestamp) returns varchar as '
+declare
+  p_tz alias for $1;
+  p_time alias for $2;
+begin
+  return timezone__get_abbrev(timezone__get_id(p_tz), p_time);
+end;' language 'plpgsql';
 
-    function get_id (
-	 tz in timezones.tz%TYPE
-    ) return integer
-    is
-	 tz_id integer;
-    begin
-	 select tz_id into tz_id 
-	 from   timezones
-	 where  tz = get_id.tz;
+-- Returns a formatted date with timezone info appended  
 
-	 return tz_id;
-    end;
+create function timezone__get_date (integer, timestamp, varchar) returns varchar as '
+declare
+  p_tz_id alias for $1;
+  p_timestamp alias for $2;
+  p_format alias for $3;
+  v_timezone_offset interval;
+  v_date text;
+begin
 
-    procedure add_rule (
-	 tz		in timezones.tz%TYPE,
-	 abbrev		in timezone_rules.abbrev%TYPE,
-	 isdst		in integer,
-	 gmt_offset	in integer,
-	 utc_start	in varchar,
-	 utc_end	in varchar,
-	 local_start	in varchar,
-	 local_end	in varchar
-    )
-    is
-    begin
-	 insert into timezone_rules
-	      (tz_id,
-	       abbrev,
-	       utc_start,
-               utc_end,
-	       local_start,
-	       local_end,
-	       gmt_offset,
-	       isdst)
-	 values
-	      (get_id(tz),
-	       abbrev,
-	       to_date(utc_start,'Mon dd hh24:mi:ss yyyy'),
-	       to_date(utc_end,  'Mon dd hh24:mi:ss yyyy'),
-	       to_date(local_start,'Mon dd hh24:mi:ss yyyy'),
-	       to_date(local_end,'Mon dd hh24:mi:ss yyyy'),
-	       gmt_offset / 86400,
-	       decode(isdst,0,'f',1,'t'));
-    end;
+  select to_char(p_timestamp + interval (extract(tz from p_timestamp)|| ''seconds'') +
+         interval (gmt_offset || ''seconds''), p_format) || '' '' || abbrev
+    into v_date 
+  from   timezone_rules
+  where  tz_id = p_tz_id and p_timestamp between utc_start and utc_end;
 
+  if not found then
+    select to_char(p_timestamp, p_format) into v_date;
+  end if;
 
-    function utc_to_local (
-	 tz_id     in timezones.tz_id%TYPE,
-	 utc_time  in date
-    ) return date
-    is
-	 local_time date;
-    begin
-	 select utc_time + gmt_offset into local_time
-	 from   timezone_rules
-	 where  tz_id  = utc_to_local.tz_id
-	 and    utc_time between utc_start and utc_end
-	 and	rownum = 1;
+  return v_date;
 
-	 return local_time;
-    exception
-	 when no_data_found then
-	      return utc_time;
-    end utc_to_local;
+end;' language 'plpgsql';
+create function timezone__get_date (varchar, timestamp, varchar) returns varchar as '
+declare
+  p_tz alias for $1;
+  p_time alias for $2;
+  p_format alias for $3;
+begin
+  return timezone__get_date(timezone__get_id(p_tz), p_time, p_format);
+end;' language 'plpgsql';
 
-    function utc_to_local (
-	 tz        in timezones.tz%TYPE,
-	 utc_time  in date
-    ) return date
-    is
-    begin
-	 return utc_to_local(get_id(tz), utc_time);
-    end;
+-- Returns 't' if timezone is currently using DST
+create function timezone__isdst_p (integer, timestamp) returns boolean as '
+declare
+  p_tz_id alias for $1;
+  p_time alias for $2;
+  v_isdst_p boolean;
+begin
+  v_isdst_p := ''f'';
 
+  select isdst_p into v_isdst_p
+  from   timezone_rules
+  where  tz_id = p_tz_id and p_time between local_start and local_end;
 
+  return v_isdst_p;
+end;' language 'plpgsql';
 
-    function local_to_utc (
-	 tz_id      in timezones.tz_id%TYPE,
-	 local_time in date
-    ) return date
-    is
-	 utc_time date;
-    begin
-	 select local_time - gmt_offset into utc_time
-	 from   timezone_rules
-	 where  tz_id = local_to_utc.tz_id
-	 and    local_time between local_start and local_end
-	 and    rownum = 1;
+create function timezone__isdst_p (varchar, timestamp) returns boolean as '
+declare
+  p_tz alias for $1;
+  p_time alias for $2;
+begin
+  return timezone__isdst_p(timezone__get_id(p_tz), p_time);
+end;' language 'plpgsql';
 
-	 return utc_time;
-    exception
-	 when no_data_found then
-	      return local_time;
-    end;
-
-    function local_to_utc (
-	 tz         in timezones.tz%TYPE,
-	 local_time in date
-    ) return date
-    is
-    begin
-	 return local_to_utc(get_id(tz),local_time);
-    end;
-
-    function get_offset (
-	 tz_id      in timezones.tz_id%TYPE,
-	 local_time in date default sysdate
-    ) return integer
-    is
-	 v_offset integer;
-    begin
-	 select round(gmt_offset*86400,0) into v_offset
-	 from   timezone_rules
-	 where  tz_id = get_offset.tz_id
-	 and    local_time between local_start and local_end
-	 and    rownum = 1;
-
-	 return v_offset;
-    exception
-	 when no_data_found then
-	      return 0;
-    end;
-
-    function get_offset (
-	 tz         in timezones.tz%TYPE,
-	 local_time in date default sysdate
-    ) return integer
-    is
-    begin
-	 return get_offset(get_id(tz),local_time);
-    end;
+create function timezone__get_zone_offset (varchar, varchar, timestamp) returns interval as '
+declare
+  p_tz_this alias for $1;
+  p_tz_other alias for $2;
+  p_time alias for $3;
+begin
+  return timezone__get_offset(p_tz_this, timezone__utc_to_local(p_tz_this, p_time)) -
+	 timezone__get_offset(p_tz_other, timezone__utc_to_local(p_tz_other, p_time));
+end;' language 'plpgsql';
     
-
-    function get_rawoffset (
-	 tz_id      in timezones.tz_id%TYPE,
-	 local_time in date default sysdate
-    ) return integer
-    is
-	 v_offset number;
-    begin
-	 select decode (isdst,'t', round(gmt_offset*86400,0) - 3600, 
-                              'f', round(gmt_offset*86400,0)) into v_offset
-	 from   timezone_rules
-	 where  tz_id  = get_rawoffset.tz_id
-	 and    local_time between local_start and local_end
-	 and    rownum = 1;
-
-	 return v_offset;
-    exception
-	 when no_data_found then
-	      return 0;
-    end;
-
-    function get_rawoffset (
-	 tz         in timezones.tz%TYPE,
-	 local_time in date default sysdate
-    ) return integer
-    is
-    begin
-	 return get_rawoffset(get_id(tz),local_time);
-    end;
-
-    function get_abbrev (
-	 tz_id	     in timezones.tz_id%TYPE,
-	 local_time  in date default sysdate
-    ) return varchar
-    is
-	 v_abbrev timezone_rules.abbrev%TYPE;
-    begin
-	 select abbrev into v_abbrev
-	 from   timezone_rules
-	 where  tz_id = get_abbrev.tz_id
-	 and    local_time between local_start and local_end;
-	 
-	 return v_abbrev;
-    exception
-	 when no_data_found then
-	      return 'GMT';
-    end;
-
-    function get_abbrev (
-	 tz	     in timezones.tz%TYPE,
-	 local_time  in date default sysdate
-    ) return varchar
-    is
-    begin
-	 return get_abbrev(get_id(tz),local_time);
-    end;
-
-    function get_date (
-	 -- Returns a formatted date with timezone info appended  
-	 tz_id	     in timezones.tz_id%TYPE,
-	 local_time  in date,
-	 format	     in varchar default 'yyyy-mm-ss hh24:mi:ss'
-    ) return varchar
-    is
-	 v_date varchar(1000);
-    begin
-	 select to_char(local_time,format) || ' ' || abbrev into v_date 
-	 from   timezone_rules
-	 where  tz_id = get_date.tz_id
-	 and    local_time between local_start and local_end
-	 and    rownum = 1;
-
-	 return v_date;
-    exception
-	 when no_data_found then
-	      select to_char(local_time,format) into v_date from dual;
-	      return v_date;
-    end;
-
-    function get_date (
-	 tz	     in timezones.tz%TYPE,
-	 local_time  in date,
-	 format	     in varchar default 'yyyy-mm-ss hh24:mi:ss'
-    ) return varchar
-    is
-    begin
-	 return get_date(get_id(tz),local_time,format);
-    end;
-
-
-    function isdst_p (
-	 -- Returns 't' if timezone is currently using DST
-	 tz_id	     in timezones.tz_id%TYPE,
-	 local_time  in date default sysdate
-    ) return char
-    is
-	 v_isdst char;
-    begin
-	 select isdst into v_isdst
-	 from   timezone_rules
-	 where  tz_id = isdst_p.tz_id
-	 and    local_time between local_start and local_end
-	 and    rownum = 1;
-
-	 return v_isdst;
-    exception
-	 when no_data_found then
-	      return 'f';
-    end;
-
-    function isdst_p (
-	 tz	     in timezones.tz%TYPE,
-	 local_time  in date default sysdate
-    ) return char
-    is
-    begin
-	 return isdst_p (get_id(tz),local_time);
-    end;
-
-    function get_zone_offset (
-	 tz_this     in timezones.tz%TYPE,
-	 tz_other    in timezones.tz%TYPE,
-	 utc_time    in date default sysdate
-    ) return integer
-    is
-    begin
-	 return get_offset(tz_this, utc_to_local(tz_this, utc_time)) -
-	        get_offset(tz_other,utc_to_local(tz_other,utc_time));
-    end;
-    
-end timezone; 
-/
-show errors
-
 -------------------------------------------------------------------------------
 -- TimeZone data
 -------------------------------------------------------------------------------
-/i ref-timezones-data.sql
-/i ../common/ref-timezone-rules.sql
-
-
+\i ../common/ref-timezones-data.sql
+\i ../common/ref-timezones-rules.sql
