Dates and Timestamps
转自:http://www.heysky.net/cgi-bin/mt/mt-tb.cgi/408
1.Datetime Datatypes
DATE
存储日期、时间,没有时区,精确到秒,是 9i 之前唯一的 datetime datatype。
TIMESTAMP [(precision)]
除了最多精确到 billionth of a second,其他和 DATE 一样。
TIMESTAMP [(precision)] WITH TIME ZONE
TIMESTAMP 的基础上,保存时区信息。在内部存储时,Oracle 将时间转换为 UTC 格式进行保存,比如:2002-02-06 20:00:00:00.00 -5:00。
TIMESTAMP [(precision)] WITH LOCAL TIME ZONE
不保存时区信息,但会将时间转换为数据库的时区(如果是保存到数据库表的列中)或者 session 的时区(如果保存为 PL/SQL variables)。数据在不同时区间传输时,会进行转换,但不保存时区信息。
其中 precision 表示秒的小数部分保留的位数,范围为:0~9。
如何选择 Datetime Datatype
1.如果你要精确到秒的小数位,那么使用 TIMESTAMP
2.如果要保留 datetime 值的时区,那么使用 TIMESTAMP WITH TIME ZONE
3.可以用 TIMESTAMP(0) 代替 DATE,但是两者的日期算法是不同的
4.为了兼容 TIMESTAMP 出现之前的应用程序,那么使用 DATE
5.PL/SQL 代码中的类型应该和数据库表中的类型相一致,比如将 TIMESTAMP WITH TIME ZONE 的类型存放到 DATE 类型的列中,时区就会丢失
6.使用 9i 以前的版本,那么只能使用 DATE
7.将 ADD_MONTHS 这种传统操作 DATE 类型的函数应用到新的 TIMESTAMP 类型上,会产生很大的不同
2.获得现在的时间
Function Time zone Datatype returned
CURRENT_DATE Session DATE
CURRENT_TIMESTAMP Session TIMESTAMP WITH TIME ZONE
LOCALTIMESTAMP Session TIMESTAMP
SYSDATE Server DATE
SYSTIMESTAMP Server TIMESTAMP WITH TIME ZONE
注:返回的都是数据库服务器端的当时时间,单前三者会转换为 session 的时区,可以用 alter session set time_zone 改变
9i 之前只有 SYSDATE
3.Interval Datatypes
INTERVAL YEAR TO MONTH
Allows you to define an interval of time in terms of years and months.
INTERVAL YEAR [(year_precision)] TO MONTH
year_precision:year 的位数,范围:0~4,默认:2
INTERVAL DAY TO SECOND
Allows you to define an interval of time in terms of days, hours, minutes, and seconds (including fractional seconds).
INTERVAL DAY [(day_precision)] TO SECOND [(frac_sec_prec)]
day_precision:day 的位数,范围:0~9,默认:2
frac_sec_prec:秒小数部分保留的位数,范围:0~9,默认:6
Month、hour、minute、second 的精度是不用设置的,Oracle 会保证他们的范围分别在 0~11、0~23、0~59、0~59
4.Datetime Conversions
Date 范围:
4712-01-01 B.C. —— 9999-12-31 A.D.
From Strings to Datetimes
隐式:
根据 NLS_DATE_FORMAT 的格式写 String 的值,Oracle 会隐式转换,如果和 NLS_DATE_FORMAT 不匹配,不能转换。
显式(使用内建函数):
TO_DATE( string[, format_mask[, nls_language]])
TO_DATE( number[, format_mask[, nls_language]])
用数字表示 Julian date 转换为 Date 类型,此时 format_mask = 'J',number 表示从 January 1, 4712 B.C. 开始的天数,由于 Oracle 中最大日期是 December 31, 9999 A.D.,所以 number 的范围为:1 ~ 5373484
TO_TIMESTAMP( string[, format_mask[, nls_language]])
TO_TIMESTAMP_TZ( string[, format_mask[, nls_language]])
此函数用于将 string 转换为 TIMESTAMP WITH TIME ZONE、TIMESTAMP WITH LOCAL TIME ZONE
format_mask:
默认为 NLS_DATE_FORMAT、NLS_TIMESTAMP_FORMAT、NLS_TIMESTAMP_TZ_FORMAT(分别对应 TO_DATE、TO_TIMESTAMP、TO_TIMESTAMP_TZ)
对于 TIMESTAMP 类型,秒的小数部分可以用 '.FF' 或者 'XFF' 表示,比如:'mm/dd/yyyy hh:mi:ss.ff AM TZD' or 'mm/dd/yyyy hh:mi:ssxff AM TZD',其中 'X' 由 NLS_NUMERIC_CHARACTERS 的第一个字符决定。
nls_language:
Optionally specifies the language to be used to interpret the names and abbreviations of both months and days in the string.
几个限制:
1.传给 TO_DATE 中的 string 长度不能超过 220 个字符。
2.format mask 中 Julian date element (J) 和 the day of year element (DDD) 不能同时出现。
3.format mask 中 date/time 的某一个部分不能重复出现,比如:'YYYY-YYY-DD-MM'
4.format mask 中 HH24 不能和 am/pm 同时出现
From Datetimes to Strings
使用 TO_CHAR,默认格式 'DD-MON-RR'(9 位),可以用 NLS_DATE_FORMAT 覆盖。
对于 TIMESTAMP 类型,秒的小数部分可以用 FF1 ~ FF9 来表示保留几位(自动四舍五入)。
不能将用于 TIMESTAMP 的 format_mask 用于 DATE 类型,否则会报 ORA-01821,反过来可以。
Working with Time Zones
明确表示某一个时区,应该联合使用 TZH TZM 或者 TZR TZD
TZH:与 UTC 之间 HOUR 的偏移
TZM:与 UTC 之间 MINUTE 的偏移
TZR:The time zone region
TZD:The abbreviated time zone name
注:后两者可以查看 V$TIMEZONE_NAMES 获得
对于时区,存入的是什么信息,显示的也是什么信息,比如用 TZH:TZM 存入和 UTC 之间的偏移,就只能显示类似 +08:00 的时区偏移,而无法显示具体哪个 time zone region
二位数年份的处理
使用 RR/RRRR 时,Oracle 自动根据现在的年份辨别输入的二位数年份:
1.如果现在是前半世纪(0~49)
如果输入的是前半世纪(0~49),那么返回本世纪的年份
如果输入的是后半世纪(50~99),那么返回上世纪的年份
2.如果现在是后半世纪(50~99)
如果输入的是前半世纪(0~49),那么返回下世纪的年份
如果输入的是后半世纪(50~99),那么返回本世纪的年份
使用 YY/YYYY 时,不管输入的是什么,都返回本世纪的年份
注:这种自动转换只适用于 String -> Date 的转换,如果是 Date -> String,那就按照 Date 存储的值来转换,此时再用 RR/RRRR 已经没有意义了,因为 Oracle 内部存储的年份是四位数的。
5.Date and Timestamp Literals
这是 9i 之后 引入的 ISO SQL standard 格式,格式是固定的,不能更改,也不受环境变量影响,因此可以作为常量来使用
DATE 'YYYY-MM-DD'
TIMESTAMP 'YYYY-MM-DD HH:MI:SS[.FFFFFFFFF] [{+|-}HH:MI]' (HH 必须是 24 小时制的,FFFFFFFFF 可选 1~9 位,也可以没有,时区也可以使用 time zone region(EST),但这只是 Oracle 提供的格式,不是 ANSI/ISO standards)
例子:
DATE '2002-02-19'
TIMESTAMP '2002-02-19 14:00:00.000000000 -5:00';
6.Interval Conversions
Numbers to Intervals
NUMTOYMINTERVAL ( n , 'char_expr' )
NUMTODSINTERVAL ( n , 'char_expr' )
char_expr:
Name Description
YEAR Some number of years, ranging from 1 through 999,999,999
MONTH Some number of months, ranging from 0 through 11
DAY Some number of days, ranging from 0 to 999,999,999
HOUR Some number of hours, ranging from 0 through 23
MINUTE Some number of minutes, ranging from 0 through 59
SECOND Some number of seconds, ranging from 0 through 59.999999999
以上不区分大小写
Strings to Intervals
TO_YMINTERVAL('Y-M')
TO_DSINTERVAL('D HH:MI:SS')
格式是固定的,不能缺少任何一个部分
7.Interval Literals
语法: INTERVAL 'character_representation' start_element TO end_element
character_representation 不需要指明所有 datetime 的元素,但必须指明从 start_element 到 end_element 的所有连续元素,如果只有 start_element 只需要一个元素就行了,但 start_element 和 end_element 不能跨越 month 和 day。
注:由于 bug 的问题,在 9i Releases 1 and 2 和 10g Release 1 中在 pl/sql 中指明部分元素会出错,比如 INTERVAL '1:02' HOUR TO MINUTE,但在 SQL 中不会。
Oracle 自动会将 high-end value 规格化,比如:INTERVAL '72:15' HOUR TO MINUTE 会规格化为 +03 00:15:00.000000,但 INTERVAL '72:75' HOUR TO MINUTE 会报错,只有 high-end value(这里的 HOUR)会自动转换
8.CAST and EXTRACT
两者都是 standard SQL functions
CAST(Oracle8 开始)
语法:
CAST(var as type)
注:在 SQL 中,type 可以指定长度,比如:varchar2(40),但在 pl/sql 中不能指定长度
可以在 string、datetime(DATE、TIMESTAMP)之间互相转换
由于无法指定 format,所以取决于 NLS_DATE_FORMAT、NLS_TIMESTAMP_FORMAT、NLS_TIMESTAMP_TZ_FORMAT 环境变量
EXTRACT(Oracle9i 开始)
语法:
EXTRACT (component_name, FROM {datetime | interval})
component_name(不区分大小写):
Component name Return datatype
YEAR NUMBER
MONTH NUMBER
DAY NUMBER
HOUR NUMBER
MINUTE NUMBER
SECOND NUMBER
TIMEZONE_HOUR NUMBER
TIMEZONE_MINUTE NUMBER
TIMEZONE_REGION VARCHAR2
TIMEZONE_ABBR VARCHAR2
由于用 to_char 无法格式化 interval 的显示格式(格式是固定的),所以唯一的方法就是使用 EXTRACT 来格式化
9.Datetime Arithmetic
Adding and Subtracting Intervals to/from Datetimes
1.和 Intervals 做 +- 操作。在和 INTERVAL DAY TO SECOND 进行运算时,不用考虑任何问题,但和 INTERVAL YEAR TO MONTH 做运算时,由于它是直接 +- 原始 datetimes 的年和月,所以可能够产生 ORA-01839 错误,应该有相关的 exception 处理语句。
2.直接和 number 做 +- 操作。number 表示天数,可以用分数小数表示:
Value Expression Represents
1/24 1/24 One hour
1/1440 1/24/60 One minute
1/86400 1/24/60/60 One second
注:不建议进行约分等操作,这样使得程序一目了然。
3.使用 ADD_MONTHS 函数。
1.如果输入的日期是输入月份中的最后一天,那么结果也是结果月份的最后一天
比如:
add_months(Date '2006-02-28',1) --> 2006-03-31
2.如果结果月份的最后一天小于输入月份的日子,那么结果日期是结果月份的最后一天
比如:
add_months(Date '2006-03-30',-1) --> 2006-02-28
可以构建一个自定义函数,解决 1 的问题,让他不返回月份的最后一天
FUNCTION my_add_months (
date_in IN DATE, months_shift IN NUMBER)
RETURN DATE IS
date_out DATE;
day_in NUMBER;
day_out NUMBER;
BEGIN
date_out := ADD_MONTHS(date_in, months_shift);
day_in := TO_NUMBER(TO_CHAR(date_in,'DD'));
day_out := TO_NUMBER(TO_CHAR(date_out,'DD'));
IF day_out > day_in
THEN
date_out := date_out - (day_out - day_in);
END IF;
RETURN date_out;
END;
建议:
虽然以上三者 DATE TIMESTAMP 都可以操作,但建议 TIMESTAMP 使用 1,DATE 可以使用 2、3
Computing the Interval Between Two Datetimes
两个 TIMESTAMP 相减返回的永远是 INTERVAL DAY TO SECOND
两个 DATE 相减返回的是天数,如果有小数部分,意思是 hours, minutes, and seconds 转换为天的结果
MONTHS_BETWEEN 函数:
定义:
FUNCTION MONTHS_BETWEEN (date1 IN DATE, date2 IN DATE)
RETURN NUMBER
尝试了一下也可以使用 TIMESTAMP,但还是建议不使用,不知道会出现什么问题。
1.date1 > date2 返回正数,小于返回负数,相等为0
2.如果在同一年的同一月中,结果的范围是 > -1 and < 1,1 和 -1 都是不会到达的,一月按照 31 天计算,TIME 部分也要计算
3.如果 date1 date2 分别是相应月份中的第一天或者最后一天,则返回整数,且忽略 TIME 部分的值
4.如果 date1 date2 在不同的月份中,且至少有一个不是该月的第一天或者最后一天,那么返回小数。小数是基于 31 天为一个月计算的,且要计算 TIME 部分。
Mixing DATEs and TIMESTAMPs
由前面可知:TIMESTAMPs 相减返回 INTERVAL DAY TO SECOND,DATEs 相减返回 numeric value
那么:
1.如果 DATEs 相减想返回 INTERVAL DAY TO SECOND 需要用 CAST 显示地将 DATEs 转换为 TIMESTAMPs
CAST(a AS TIMESTAMP) - CAST(b AS TIMESTAMP)
2.如果 DATE 和 TIMESTAMP 混合使用,Oracle 隐式地将 DATE 转换为 TIMESTAMP,因此返回的是 INTERVAL DAY TO SECOND
Adding and Subtracting Intervals
INTERVAL 相加减必须满足类型相同:两个 INTERVAL DAY TO SECOND 相加减,或者两个 INTERVAL YEAR TO MONTH 相加减,不能混合使用。
Multiplying and Dividing Intervals
DATETIMEs 是不能进行乘除运算的,但是 Intervals 可以。进行乘除运算时,每一个元素都会进行运算,如果超过该元素的范围就往上一级元素进位,如果出现小数就将小数部分转化为下一级元素,秒除外。
Using Unconstrained INTERVAL Types
由于在函数、过程中,参数不能指定精度,因此当 INTERVAL 传入时如果精度大于默认精度:YEAR(2),DAY(2),SECOND(6),会返回错误 ORA-01873: the leading precision of the interval is too small
因此,引入两个特殊类型:
YMINTERVAL_UNCONSTRAINED
接受任何精度的 INTERVAL YEAR TO MONTH
DSINTERVAL_UNCONSTRAINED
接受任何精度的 INTERVAL DAY TO SECOND
10.Date/Time Functions
对于传统的 DATE 函数,比如 ADD_MONTHS 建议不要用于 TIMESTAMP,Oracle 会将他们隐式地转为 DATE,这样会丢失秒的小数部分,还会将时区修改为 SESSION 的时区。因此,对于 TIMESTAMP 应该使用 INTERVAL 来操作。