要求:
1. 函数申明固定为:
create or replace function my_add_months(p_date_string varchar2,
p_months number)
return varchar2
不允许更改.
3. 不求代码的效率高效, 但也不能太低.
见 http://www.itpub.net/thread-977394-1-1.html 中提到1582年前使用的是凯撒时期制定的儒略历,4年一闰,这个属历史问题, 我们这里不考虑.
set serverout on
declare
ln number ;
ld date ;
ls1 varchar2 ( 8 );
ls2 varchar2 ( 8 );
lt number := dbms_utility.get_time;
ex exception ;
y number ;
m number ;
d number ;
j number ;
begin
for j in 0 .. 5000 loop
for y in 2000 .. 2001 loop
for m in 2 .. 4 loop
for d in 28 .. 31 loop
begin
ls1 := y || '0' || m || d;
begin
ld := to_date(ls1, 'yyyymmdd' );
exception
when others then
exit ; -- 过滤所有格式外的日期
end ;
-- 正的月份计算验证
ld := add_months(ld, j);
ls2 := to_char(ld, 'yyyymmdd' );
if nvl(my_add_months(ls1, j), '*' ) <> ls2 then
dbms_output.put_line( 'Sorry: stop at p_date_string=' || ls1 ||
',p_months=' || j);
dbms_output.put_line( 'my_add_months returned: ' ||
my_add_months(ls1, j));
dbms_output.put_line( 'add_months returned: ' || ls2);
raise ex;
end if ;
-- 负的月份计算验证
ls1 := to_char(add_months(ld, -j), 'yyyymmdd' );
if nvl(my_add_months(ls2, -j), '*' ) <> ls1 then
dbms_output.put_line( 'Sorry: stop at p_date_string=' || ls2 ||
',p_months=' || -j);
dbms_output.put_line( 'my_add_months returned: ' ||
my_add_months(ls2, -j));
dbms_output.put_line( 'add_months returned: ' || ls1);
raise ex;
end if ;
exception
when ex then
raise ;
when others then
raise ;
end ;
end loop ;
end loop ;
end loop ;
end loop ;
-- 计算函数的字符个数
ln := 0 ;
for c in ( select text
from user_source
where name = 'MY_ADD_MONTHS'
and type = 'FUNCTION' ) loop
ln := ln + nvl(lengthb(translate(c.text,
'*' || chr( 9 ) || chr( 10 ) || chr( 13 ) || chr( 32 ),
-- 除去所有的空格、回车、换行、 tab
'*' )),
0 );
end loop ;
lt := (dbms_utility.get_time - lt) / 100 ;
dbms_output.put_line( 'Congratulation ... Code Length: ' || ln ||
' Bytes. Times: ' ||
to_char(to_date(to_char(lt, 'fm00000' ), 'sssss' ),
'hh24:mi:ss' ));
exception
when ex then
null ;
end ;
/
CREATE OR REPLACE FUNCTION MY_ADD_MONTHS(P_DATE_STRING VARCHAR2 ,
P_MONTHS NUMBER )
RETURN VARCHAR2 IS
H INT := 100 ;
S INT := P_DATE_STRING;
Y INT := S/H/H;
M INT := S/H MOD H;
D INT := S MOD H;
PROCEDURE P IS
BEGIN
S := 28 + 3232332323030 / 10 **M MOD 10 ; -- 每个月最后一天
IF M= 2 AND (Y MOD 400 = 0 OR Y MOD 4 < Y MOD H / H) THEN -- 判断 Y 是否闰年
S := 29 ;
END IF ;
END ;
BEGIN
P;
D := D + D * INSTR(D,S); -- 若 D 为月末,则不需要这个 D ,取新月份的月末期
M := Y* 12 +M+P_MONTHS;
Y := M/ 12 -.55; -- 计算新年份 ( 保证不进位或退位 ) ,用 Y:=(M-7)/12 好理解一些
M := M-Y* 12 ; -- 计算新月份
P; -- 计算新月份的月末
RETURN Y*H*H + M*H + LEAST(D,S);
END ;
/
/**********************
nyfor:
**********************/
create or replace function my_add_months
(
p_date_string varchar2,
p_months number
) return varchar2 is
c int := 100;
a int := p_date_string;
y int := a / c / c;
m int := a / c - y * c;
t int;
procedure p is
begin
t := substr(525454554545, m, 1);
t := t + 26 + 1 / (t + mod(y, 4) + instr(0, mod(y, c)) * mod(y, 400));
end;
begin
p;
a := mod(a, c);
a := trunc(a / t) * c + a;
m := m + p_months + y * 12 - 7;
y := m / 12;
m := m - y * 12 + 7;
p;
return y * c * c + m * c + least(a, t);
end;
--如果要确保取出每一个隐式转换,包含精度的转换, 也会增加很多字节的. 我的转换后如下:
create or replace function my_add_months
(
p_date_string varchar2,
p_months number
) return varchar2 is
c int := 100;
a int := to_number(p_date_string);
y int := round(to_number(a) / c / c);
m int := round(to_number(a) / c - y * c);
t int;
procedure p is
begin
t := to_number(substr('525454554545', m, 1));
t := round(t + 26 + 1 / (t + mod(y, 4) + instr(0, mod(y, c)) * mod(y, 400)));
end;
begin
p;
a := mod(to_number(a), c);
a := trunc(to_number(a) / t) * c + to_number(a);
m := m + p_months + y * 12 - 7;
y := round(m / 12);
m := m - y * 12 + 7;
p;
return to_char(y * c * c + m * c + least(to_number(a), t));
end;
--加入所有的显示转换及精度转换后 475 Bytes.
DragonBill:
***********************/
create or replace function my_add_months(p_date_string varchar2,
p_months number)
return varchar2
AS
C INT := p_date_string;
H INT := 100;
Y INT := C/H/H;
M INT := MOD(C/H, H);
D INT := MOD(C, H);
Z INT := Y * 12 + M + p_months;
AS
BEGIN
C := 27 + SUBSTR(43434434342 - SIGN(MOD(Y, 16 - 4 * INSTR(Y/H,'.'))), 1 - M, 1);
END;
Y := (Z - M) / 12;
Z := C;
END;
create or replace function my_add_months(p_date_string varchar2,
p_months number)
return varchar2
AS
C INT := p_date_string;
H INT := 100;
Y INT := C/H/H;
M INT := MOD(C/H, H);
D INT := MOD(C, H);
Z INT := Y * 12 + M + p_months;
AS
BEGIN
C := 27 + SUBSTR(43434434342 - SIGN(MOD(Y, 16 - 4 * INSTR(Y/H,'.'))), 1 - M, 1);
END;
Y := (Z - M) / 12;
Z := D/C/2;
END;
判断闰年: 非百年看后两位能否被4整除, 百年看前两位能否被4整除, 再往前推, 百年能被100整除,
又 100 = 4 * 25, 故闰百年必能被16整除, 年/100, 能整除的无小数点, 是百年, 不能整除的有小数点,
凑巧小数点的位置为3, 综合以上, 故, 非百年 MOD 4 , 百年 MOD 16, 能整除的都是闰年.
这也就是为什么用 INSTR(Y/H,'.') 而不用 SIGN(MOD(Y, H)) 的原因, 虽然它们的长度是一样,
但 12 * {0, 1} 和 4 * {0, 3} 相比, 多了一个Byte
**********************/
create or replace function my_add_months(p_date_string varchar2,
p_months number)
return varchar2
AS
C INT := p_date_string;
H INT := 100;
Y INT := C/H/H;
M INT := C/H - Y * H;
D INT := MOD(C, H);
Z INT := Y * 12 + M + p_months;
AS
BEGIN
C := 27 + SUBSTR(43434434342 - SIGN(MOD(Y, 16 - 4 * INSTR(Y/H,'.'))), 1 - M, 1);
END;
M := Z - Y * 12;
Z := D/C/2;
END;
/
--Congratulation ... Code Length: 319 Bytes. Times: 00:00:09
create or replace function my_add_months(p_date_string varchar2,
p_months number)
return varchar2
AS
C INT := p_date_string;
H INT := 100;
Y INT := C/H/H;
M INT := C/H - Y * H;
D INT := C MOD H;
Z INT := Y * 12 + M + p_months;
AS
BEGIN
C := 27 + SUBSTR(43434434341 + 0 ** (Y MOD (4 + 12 * 0 ** (Y MOD H))), 1 - M, 1);
END;
M := Z - Y * 12;
Z := D/C/2;
END;
/
-The End-