-- -- The Postgres specific hacks for emulating oracle date/char conversion -- functions. -- -- Uses/requires pltcl. configure postgres using --with-tcl and copy pltcl.so -- to /usr/local/pgsql/lib. create function pltcl_call_handler() returns opaque as '/usr/local/pgsql/lib/pltcl.so' language 'C'; create trusted procedural language 'pltcl' handler pltcl_call_handler lancompiler 'PL/Tcl'; create function trunc(datetime,varchar) returns datetime as ' declare date_in alias for $1; part alias for $2; begin if lower(part) = ''yyyy'' then return date_trunc(''year'',date_in); end if; return date_trunc(part,date_in); end; ' language 'plpgsql'; create function trunc(timespan) returns varchar as ' declare inspan alias for $1; begin return date_part(''day'',inspan); end; ' language 'plpgsql'; create function last_day(datetime) returns datetime as ' begin return date_trunc(''month'',(date_trunc(''month'',$1) + 31)::datetime) - 1; end; ' language 'plpgsql'; create function calc_days(varchar) returns integer as ' set span $1 if { ![regexp {([0\-9]+) [0\-9][0\-9]:[0\-9][0\-9]:[0\-9][0\-9]} $span {} days] } { return 0 } return $days ' language 'pltcl'; create function round(timespan) returns integer as ' declare delta alias for $1; invar varchar; begin invar := delta; return calc_days(invar); end; ' language 'plpgsql'; create function calc_months(varchar) returns integer as ' set timespan $1 if { ![regexp {([0\-9]+) years?} $timespan {} yr] } { set yr 0 } if { ![regexp {([0\-9]+) mons?} $timespan {} mn] } { set mn 0 } return [expr ($yr * 12) + $mn] ' language 'pltcl'; create function months_between(datetime, datetime) returns integer as ' declare date1 alias for $1; date2 alias for $2; delta varchar; begin delta := age(date1,date2); return calc_months(delta); end; ' language 'plpgsql'; create function sign(timespan) returns integer as ' if [regexp {ago} $1 {}] { return -1 } else { return 1 } ' language 'pltcl'; create function day_of_the_year(datetime) returns varchar as ' declare the_date alias for $1; new_date datetime; delta varchar(80); pos_blank integer; begin if date_trunc(''day'',the_date) = date_trunc(''year'',the_date) then return ''1''; else new_date := date_trunc(''day'',the_date + 1); delta := new_date \- date_trunc(''year'',the_date); pos_blank := position('' '' in delta); return substring(delta from 0 for pos_blank); end if; end; ' language 'plpgsql'; -- I think the julian date starts in 4713BC, but this -- date gives the correct answer. I verified it by using two seperate -- julian date calculators that I found by searching on the net. create function julian_zero_date() returns datetime as ' begin return ''4714-11-25 BC''::datetime; end; ' language 'plpgsql'; create function julian_date(datetime) returns varchar as ' declare indate alias for $1; begin return trunc(indate - julian_zero_date()); end; ' language 'plpgsql'; -- drop function to_char_work(datetime,varchar,varchar,varchar,varchar); create function to_char_work(datetime,varchar,varchar,varchar,varchar) returns varchar as ' set indate $1 set pattern [string tolower $2] set day_of_year $3 set julian_date $4 set day_of_week $5 set month "" set day "" # pull apart the date/time string regexp {^([0\-9][0\-9]([0\-9][0\-9]))\-([0\-9][0\-9])\-([0\-9][0\-9]) ([0\-9][0\-9]):([0\-9][0\-9]):([0\-9][0\-9])} $indate full_date year part_year month day hour minute second # calculate the quarter switch \-regexp $month { "0[1-3]" { set qtr 1 } "0[4-6]" { set qtr 2 } "0[7-9]" { set qtr 3 } "1[0-2]" { set qtr 4 } default { set qtr 0 } } switch $month { "01" { set p_month January } "02" { set p_month February } "03" { set p_month March } "04" { set p_month April } "05" { set p_month May } "06" { set p_month June } "07" { set p_month July } "08" { set p_month August } "09" { set p_month September } "10" { set p_month October } "11" { set p_month November } "12" { set p_month December } default { set p_month void } } switch $day_of_week { "0" { set p_day Sunday } "1" { set p_day Monday } "2" { set p_day Tuesday } "3" { set p_day Wednesday } "4" { set p_day Thursday } "5" { set p_day Friday } "6" { set p_day Saturday } default { set p_day void } } # check for truncated year patterns if { [regsub {yyyy} $pattern $year pattern] == 0 && [regexp {yy} $pattern] } { regsub {yy} $pattern $part_year pattern } # handle all of the standard date/time patterns regsub {fmdd} $pattern $day pattern regsub {dd} $pattern $day pattern if { [regsub {fmday} $pattern $p_day pattern] == 0 } { regsub {d} $pattern $day pattern } regsub {j} $pattern $julian_date pattern regsub {fmmonth} $pattern $p_month pattern regsub {mon(th)?} $pattern $month pattern regsub {mm} $pattern $month pattern regsub {hh24} $pattern $hour pattern regsub {mi} $pattern $minute pattern regsub {ss} $pattern $second pattern regsub {q} $pattern $qtr pattern return $pattern ' language 'pltcl'; drop function to_char(datetime,char); create function to_char(datetime,varchar) returns varchar as ' declare indate alias for $1; pattern alias for $2; doy varchar; j_date varchar; dow varchar; begin j_date := julian_date(indate); doy := day_of_the_year(indate); dow := date_part(''dow'',indate); return to_char_work(indate,pattern,doy,j_date,dow); end; ' language 'plpgsql'; -- special case for inserting commas into numbers (e.g. 1,239 from 1239) create function to_char(integer,varchar) returns varchar as ' set l [split $1 ""] set nl [list] # reverse the string foreach c $l { set nl [linsert $nl 0 $c] } # convert the string back to its original direction and insert commas set l [list] set i 0 foreach c $nl { incr i set l [linsert $l 0 $c] if { [expr $i % 3] == 0 } {set l [linsert $l 0 ","]} } # return the new string with commas inserted return [join $l ""] ' language 'pltcl'; create function to_date_work(varchar,varchar) returns datetime as ' set date_in $1 set pattern [string tolower $2] set month "" if ![regexp \-indices {yyyy} $pattern year_long] { if [regexp \-indices {yy} $pattern year_short] { set year "20[string range $date_in [lindex $year_short 0] [lindex $year_short 1]]" } else { set year "2000" } } else { set year [string range $date_in [lindex $year_long 0] [lindex $year_long 1]] } if [regexp \-indices {mm} $pattern month] { set month [string range $date_in [lindex $month 0] [lindex $month 1]] } else { set month "01" } if [regexp \-indices {dd} $pattern day] { set day [string range $date_in [lindex $day 0] [lindex $day 1]] } else { set day "01" } if [regexp \-indices {hh} $pattern hour] { set hour [string range $date_in [lindex $hour 0] [lindex $hour 1]] } else { set hour "00" } if [regexp \-indices {mi} $pattern minute] { set minute [string range $date_in [lindex $minute 0] [lindex $minute 1]] } else { set minute "00" } if [regexp \-indices {ss} $pattern second] { set second [string range $date_in [lindex $second 0] [lindex $second 1]] } else { set second "00" } return "$year-$month-$day $hour:$minute:$second+00" ' language 'pltcl'; -- drop function sanitize_date(varchar); create function sanitize_date(varchar) returns varchar as ' set date_in [string tolower $1] set fancy_months(january) 01 set fancy_months(february) 02 set fancy_months(march) 03 set fancy_months(april) 04 set fancy_months(may) 05 set fancy_months(june) 06 set fancy_months(july) 07 set fancy_months(august) 08 set fancy_months(september) 09 set fancy_months(october) 10 set fancy_months(november) 11 set fancy_months(december) 12 regexp {(january|february|march|april|may|june|july|august|september|october|november|december)} $date_in match if [info exists match] { set month $fancy_months($match) regsub $match $date_in $month date_in } return $date_in ' language 'pltcl'; -- drop function to_date(varchar,varchar); create function to_date(varchar,varchar) returns datetime as ' declare date_string alias for $1; pattern alias for $2; clean_string varchar; begin clean_string := sanitize_date(date_string); return to_date_work(clean_string,pattern); end; ' language 'plpgsql'; -- drop function to_date(datetime,varhchar); create function to_date(datetime,varchar) returns datetime as ' declare date_string alias for $1; pattern alias for $2; clean_string varchar; begin clean_string := date_string; return to_date_work(clean_string,pattern); end; ' language 'plpgsql'; create function to_date(varchar) returns datetime as ' declare date_string alias for $1; begin return date_string; end; ' language 'plpgsql'; create function to_date(integer,varchar) returns datetime as ' declare jdate alias for $1; pattern alias for $2; begin return julian_zero_date() + jdate; end; ' language 'plpgsql'; -- drop function delta_days(integer,varchar); create function delta_days(integer,varchar) returns varchar as ' set day_in $1 set pattern [string toupper $2] switch \-regexp $pattern { "SUN(DAY)?" { set the_day 0 } "MON(DAY)?" { set the_day 1 } "TUES(DAY)?" { set the_day 2 } "WED(NESDAY)?" { set the_day 3 } "THURS(DAY)?" { set the_day 4 } "FRI(DAY)?" { set the_day 5 } "SAT(URDAY)?" { set the_day 6 } } set d_days [expr (($the_day - $day_in) + 7) % 7] return "$d_days day" ' language 'pltcl'; -- drop function next_day(datetime,varchar); create function next_day(datetime,varchar) returns datetime as ' declare the_date alias for $1; pattern alias for $2; dow integer; begin dow := date_part(''dow'',the_date); return the_date + delta_days(dow,pattern)::timespan; end; ' language 'plpgsql';