SAS日付関連の関数

Last-modified: 2010-03-03 (水) 15:30:08

日付や時間に関連した処理をするいくつかの関数が用意されています.これらは,関数の値,もしくは,引数にSAS日付値,SAS時間値,SAS日時値をとります.SAS日付値などの概念については,SASの時間データの概念を参照してください.

現在日付などを得る TODAY,DATE,TIME,DATETIME

現在の日付や時刻をSAS日付値,SAS時間値,SAS日時値で取り出す関数群です.引数はとりません.

data;
 date=TODAY(); put date date.; /*現在日をSAS日付値で返す date()とtoday()は同じ*/
 date=DATE(); put date date.;  /*現在日をSAS日付値で返す date()とtoday()は同じ*/
 time=TIME(); put time time.;  /*現在時刻をSAS時間値で返す*/
 datetime=DATETIME(); put datetime datetime.; /*現在日時をSAS日付値で返す*/
run;
/*
02JUN08
02JUN08
22:34:07
02JUN08:22:34:07
 */

日付時間要素を取り出す DAY,MONTH,YEAR,QTR,WEEKDAY,WEEK,HOUR,MINUTE,SECOND,DATEPART,TIMEPART

SAS日付値から,年,月,日などを取り出します.

data;
 date="28may08"d;
 day=DAY(date);put day;		/*SAS日付値から日を取り出す*/
 jul=JULDATE(date);put jul;		/*SAS日付値からジュリアンデートを取り出す*/
 jul7=JULDATE7(date);put jul7;		/*SAS日付値から7桁の年つきジュリアンデートを取り出す*/
 mon=MONTH(date);put mon;		/*SAS日付値から月を取り出す*/
 qtr=QTR(date);put qtr;		/*SAS日付値から4半期を取り出す*/
 weekday=WEEKDAY(date);put weekday;		/*SAS日付値から曜日を取り出す*/
 year=YEAR(date);put year;		/*SAS日付値から年を取り出す*/
 week=WEEK(date,"U");put week;		/*SAS日付値から週番号を取り出す*/
run;
/*
28
8149
2008149
5
2
4
2008
21
 */

SAS時間値から,時刻,分,秒を取り出します.

data;
time="12:34:56"t;
h=HOUR(time);put h;	/*SAS時間値またはSAS日時値から時間を取り出す*/
m=MINUTE(time);put m;	/*SAS時間値またはSAS日時値から分を取り出す*/
s=SECOND(time);put s;	/*SAS時間値またはSAS日時値から秒を取り出す*/
run;
/*
12
34
56
 */

SAS日時値から,SAS日付値やSAS時間値を取り出します.

data;
 datetime="28may2008:12:34:56"dt;
 date=DATEPART(datetime);put date date.;	/*SAS日時値から日付を取り出しSAS日付値として返す*/
 time=TIMEPART(datetime);put time time.;	/*SAS日時値から時間を取り出しSAS時間値として返す*/
run;
/*
28MAY08
12:34:56
 */

日付時間要素からSAS日付などを作る MDY,DATEJUL,YYQ,HMS,DHMS

年,月などの日付や時間を構成する要素から,SAS日付値,SAS時間値,SAS日時値に変換します.

data;
 date=MDY(5,28,08);put date date.;   /*月,日,年からSAS日付値を返す*/
 date=DATEJUL(08149);put date date.; /*ジュリアンデートをSAS日付値にする*/
 date=YYQ(08,1);put date date.;      /*年と4半期からSAS日付を返す*/
 time=HMS(12,34,56);put time time.;  /*時刻,分,秒からSAS時間値を返す*/
 datetime=DHMS("28mar08"d,12,34,56);put datetime datetime.; /*SAS日付値,時刻,分,秒からSAS日時値を返す*/
run;
/*
28MAY08
28MAY08
01JAN08
12:34:56
28MAR08:12:34:56
 */

2つのSAS日付値の時間間隔を返す DATDIF,YRDIF

2つのSAS日付値の日数間隔を返します.実日数間隔で計算するか,1月分を30日,1年分を360日とみなして計算するかを引数でわたします.

data;
  /*2つのSAS日付値の日付間隔を返す*/
  days=DATDIF("01jan08"d,"28may08"d,"ACT/ACT");put days;	/*実日数で計算*/
  days=DATDIF("01jan08"d,"28may08"d,"30/360");put days;	/*1月を30日,1年を360日として計算*/
run;
/*
148
147
 */

2つのSAS日付値の年数間隔を返します.実日数間隔で計算するか,1月分を30日,1年分を360日とみなすかなどの計算方式(4パターン)を引数でわたします.

data;
/*2つのSAS日付値の年間隔を返す*/
  years=YRDIF("01jan08"d,"28may08"d,"ACT/ACT");put years;	/*実日数を実年日数(2008は閏年)で割る*/
  years=YRDIF("01jan08"d,"28may08"d,"30/360"); put years;	/*1月を30日で計算し1年を360日として割る*/
  years=YRDIF("01jan08"d,"28may08"d,"ACT/360");put years;	/*実日数を1年を360日として割る*/
  years=YRDIF("01jan08"d,"28may08"d,"ACT/365");put years;	/*実日数を1年を365日として割る*/
run;
/*
0.4043715847   <= 148/366
0.4083333333   <= 147/360
0.4111111111   <= 148/360
0.4054794521   <= 148/365
 */

時間間隔指定子に従って,SAS日付などの時間間隔を求めたり,時間をすすめたりする INTCK,INTNX

INTCK関数とINTNX関数は,ともに与えられた時間間隔指定子(DAY,MONTH,YEARなど)にしたがって,SAS日付値の間隔を求めたり,SAS日付値を指定された間隔の数分すすめたりするものです.SAS時間値,SAS日時値にも使えます.機能は少々豊富なので,基本的な使い方を先に示し,その後で時間間隔指定子の応用的な説明をします.

INTCK関数の基本 時間間隔指定子に従って,2つのSAS日付値(またはSAS時間値,SAS日時値)の間隔を返す

INTCK関数は,DATDIF関数やYRDIF関数のように,2つのSAS日付値の時間間隔を返しますが,計算方法などに違いがあります.
まずINTCK関数では,時間間隔が日や年だけではなく,時間間隔指定子を使って,4半期や月,週などの間隔の単位をいろいろと選べることです.
2点目の違いは,DATDIF関数やYRDIF関数が,基本的に2つのSAS日付値の実日数差をベースに計算しているのに対し,INTCK関数では,2つのSAS日付値の所属する時間間隔を特定しその間隔単位の差を数えます.すなわち,2007年12月31日と2008年1月1日は,実日数で1日差ですが,INTCK関数で,年を時間間隔として指定すると,12月31日は2007年に属し,1月1日は2008年に属するので,その差は1年と計算されます.一方,2007年1月31日と2007年12月31日では,実日数で364日差ありますが,これは同じ2007年に属しているので0年差ということになります.下記プログラム結果のようにYRDIF関数とは,まったく違う値になります.

data;
  int=intck("year","31dec07"d,"1jan08"d);
  yrs=YRDIF("31dec07"d,"1jan08"d,"ACT/ACT");
  put int= yrs=;
  int=intck("year","01jan07"d,"31dec07"d);
  yrs=YRDIF("01jan07"d,"31dec07"d,"ACT/ACT");
  put int= yrs=;
run;
/*
int=1 yrs=0.002739726
int=0 yrs=0.997260274
 */

以下によく使いそうな基本的な時間間隔指定子の例を示します.

data;
  int=intck("day",  "31may08"d,"01jun08"d);  put int; /*日にち単位なので実日数差1となる*/
  int=intck("week", "31may08"d,"01jun08"d);  put int; /*日曜~土曜が週の単位.土日をはさんでいるので1週差*/
  int=intck("month","31may08"d,"01jun08"d);  put int; /*5月と6月なので,1ヶ月差となる*/
  int=intck("year", "31may08"d,"01jun08"d);  put int; /*同じ年なので0年差となる*/
run;
/*
1
1
1
0
 */

INTNX関数の基本 時間間隔指定子に従って,SAS日付値(またはSAS時間値,SAS日時値)をすすめる

INTNX関数は,INTCK関数でも使う月や年などの時間間隔指定子に従い,SAS日付値を与えられた間隔数分すすめたSAS日付値を返します.注意すべき点は,INTCK関数にも共通することですが,時間間隔の数え方にあります.たとえば,週単位に2週分日付をすすめる場合,単純に7x2=14日後になるわけではありません.時間間隔指定子の週は,日曜日から翌土曜日を1単位としており,元のSAS日付が属する週から2つ先の週の最初の日(日曜日)が,INTNX関数の結果として返されます.


data;
  next=intnx("week","31may08"d,-1); put next weekdate.; /**/
  next=intnx("week","31may08"d,0);  put next weekdate.; /**/
  next=intnx("week","31may08"d,1);  put next weekdate.; /**/
  next=intnx("week","31may08"d,2);  put next weekdate.; /**/
run;
/*
         Sunday, May 18, 2008
         Sunday, May 25, 2008
         Sunday, June 1, 2008
         Sunday, June 8, 2008
 */

なお,すすめた時間間隔の最初の日ではなく,その時間間隔の期末などを返すようにアライメントを指定することもできます.

data;
  next=intnx("month","03jun08"d,1,"beginning");  put next date.; /*月初*/
  next=intnx("month","03jun08"d,1,"middle");     put next date.; /*月の中日*/
  next=intnx("month","03jun08"d,1,"end");        put next date.; /*月末*/
  next=intnx("month","03jun08"d,1,"sameday");    put next date.; /*同日*/
run;
/*
01JUL08
16JUL08
31JUL08
03JUL08
 */

時間間隔指定子の種類

INTCK関数,INTNX関数の間隔の数え方がわかりにくいと感じる場合は,これらの関数の時間間隔の考え方を誤解しているかもしれません.間隔と考えるより,区間の個数ととらえるとよいです.すなわち,

INTNX("MONTH","28may08"d,4)

は,「5/28から4ヶ月後」ではなく,「5/28の属する区間から4区間先」と考えるわけです.
以下に時間間隔指定子とその区間の定義を示します.

時間間隔指定子間隔間隔の単位となる区間,備考
【SAS日付用】
YEAR1-12月
SEMIYEAR半期1-6月,7-12月
QTR4半期1-3月,4-6月,7-9月,10-12月
MONTH1日-月末
SEMIMONTH半月1-15日,16日-月末
TENDAY10日1-10日,11-20日,21日-月末
WEEK日曜から土曜までの7日
WEEKDAY営業日平日(土日は数えない)
WEEKDAY17Wなど営業日(休日の曜日を任意に指定,1=日,2=火,..)
DAY日(0時から23時)
【SAS日時用】
DTYEARなど同上同上,DTをつけるとSAS日時値用になる
【SAS時間用】
HOUR0分0秒-59分59秒
MINUTE0秒-59秒
SECOND0秒-0.99..秒

WEEK時間間隔指定子では,2つの日付の間隔は次のように計算されます.

data;
  start="01jun2008"d;
  do sdate=start to start+20;
  do edate=start to start+20;
  i=intck("week",sdate,edate);
  output;
  end;end;
 run;

 proc tabulate data=_last_ formchar="           " missing noseps format=2.;
   class sdate edate;
   var i;
   table sdate,edate*i=""*mean=""/rts=20;
   format sdate edate weekdate16.;
run;
/*
                                               T- W- T- F- S- S- M- T- W- T- F- S-
                    S- M- T- W- T- F- S- S- M- u- e- h- r- a- u- o- u- e- h- r- a-
                    u- o- u- e- h- r- a- u- o- e, d, u, i, t, n, n, e, d, u, i, t,
                    n, n, e, d, u, i, t, n, n, J- J- J- J- J- J- J- J- J- J- J- J-
                    J- J- J- J- J- J- J- J- J- un un un un un un un un un un un un
                    un un un un un un un un un 1- 1- 1- 1- 1- 1- 1- 1- 1- 1- 2- 2-
                    1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1,
                    08 08 08 08 08 08 08 08 08 08 08 08 08 08 08 08 08 08 08 08 08

 sdate
 Sun, Jun 1, 08      0  0  0  0  0  0  0  1  1  1  1  1  1  1  2  2  2  2  2  2  2
 Mon, Jun 2, 08      0  0  0  0  0  0  0  1  1  1  1  1  1  1  2  2  2  2  2  2  2
 Tue, Jun 3, 08      0  0  0  0  0  0  0  1  1  1  1  1  1  1  2  2  2  2  2  2  2
 Wed, Jun 4, 08      0  0  0  0  0  0  0  1  1  1  1  1  1  1  2  2  2  2  2  2  2
 Thu, Jun 5, 08      0  0  0  0  0  0  0  1  1  1  1  1  1  1  2  2  2  2  2  2  2
 Fri, Jun 6, 08      0  0  0  0  0  0  0  1  1  1  1  1  1  1  2  2  2  2  2  2  2
 Sat, Jun 7, 08      0  0  0  0  0  0  0  1  1  1  1  1  1  1  2  2  2  2  2  2  2
 Sun, Jun 8, 08     -1 -1 -1 -1 -1 -1 -1  0  0  0  0  0  0  0  1  1  1  1  1  1  1
 Mon, Jun 9, 08     -1 -1 -1 -1 -1 -1 -1  0  0  0  0  0  0  0  1  1  1  1  1  1  1
 Tue, Jun 10, 08    -1 -1 -1 -1 -1 -1 -1  0  0  0  0  0  0  0  1  1  1  1  1  1  1
 Wed, Jun 11, 08    -1 -1 -1 -1 -1 -1 -1  0  0  0  0  0  0  0  1  1  1  1  1  1  1
 Thu, Jun 12, 08    -1 -1 -1 -1 -1 -1 -1  0  0  0  0  0  0  0  1  1  1  1  1  1  1
 Fri, Jun 13, 08    -1 -1 -1 -1 -1 -1 -1  0  0  0  0  0  0  0  1  1  1  1  1  1  1
 Sat, Jun 14, 08    -1 -1 -1 -1 -1 -1 -1  0  0  0  0  0  0  0  1  1  1  1  1  1  1
 Sun, Jun 15, 08    -2 -2 -2 -2 -2 -2 -2 -1 -1 -1 -1 -1 -1 -1  0  0  0  0  0  0  0
 Mon, Jun 16, 08    -2 -2 -2 -2 -2 -2 -2 -1 -1 -1 -1 -1 -1 -1  0  0  0  0  0  0  0
 Tue, Jun 17, 08    -2 -2 -2 -2 -2 -2 -2 -1 -1 -1 -1 -1 -1 -1  0  0  0  0  0  0  0
 Wed, Jun 18, 08    -2 -2 -2 -2 -2 -2 -2 -1 -1 -1 -1 -1 -1 -1  0  0  0  0  0  0  0
 Thu, Jun 19, 08    -2 -2 -2 -2 -2 -2 -2 -1 -1 -1 -1 -1 -1 -1  0  0  0  0  0  0  0
 Fri, Jun 20, 08    -2 -2 -2 -2 -2 -2 -2 -1 -1 -1 -1 -1 -1 -1  0  0  0  0  0  0  0
 Sat, Jun 21, 08    -2 -2 -2 -2 -2 -2 -2 -1 -1 -1 -1 -1 -1 -1  0  0  0  0  0  0  0
 */

時間間隔指定子の修飾子

時間間隔指定子は,2つの修飾子を伴うことができ,さらに応用的な間隔の数え上げを行うことができます.

時間間隔指定子名m.p
  • m:多重化数 間隔をm個分まとめて,1区間とします.1がデフォルトです.
  • s:シフト位置 区間の開始位置をずらします.1がデフォルトのまま,2で,1つすすめます.すすめる単位は指定子によって違います(後述).

たとえば,WEEKは,日曜日から翌土曜日までの7日間を1区間としますが,WEEK2.3は,3-1=2日ずらして,火曜日から翌々月曜日までの14日間を1区間とします.

 

また,多重化数を使って,間隔をまとめる場合に,まとめをはじめる開始基準日は,

  • WEEKの場合,1959年12月27日(日曜)0時ちょうどが開始基準日
  • それ以外の場合,1960年1月1日0時ちょうどが開始基準日

となります.シフト位置の指定があれば,この開始基準日はずらされます.MONTH2などと2月分を1間隔としてまとめるなら,1-2月,3-4月,などど開始基準日などを定めなくても,一見自然に定義できるように感じるかもしれませんが,MONTH7などを考えた場合,今年は,1-7月としても,次が8-翌2月,翌年が,3-10月というふうに,年でまとめる月が変って行きます.それを一意に定めるために,開始規準日が必要になります.WEEKのみ開始基準日が違うのは,1960年1月1日が,残念ながら日曜日ではなかったからです.

 

次にシフト位置の示すシフトさせる単位は,以下のとおりです.

  • MONTH,QTR,SEMIYEAR,YEARに対しては,1月単位.
  • SEMIMONTHに対しては,SEMIMONTH単位.(実15日単位ではなく,1-15日,16-月末の単位です)
  • TENDAYに対しては,TENDAY単位.(実10日単位ではなく,1-10日,11-20日,21-月末の単位です)
  • DAY,WEEK,WEEKDAYに対しては,1日単位.
  • HOURに対しては,時間単位.
  • MINUTEに対しては,分単位.
  • SECONDに対しては,秒単位.

シフト単位のルールは,きっと実用性を考えてこのように決められているのだと思いますが,指定子の単位に一致しているものとそうでないものが混在していて,わかりにくいと感じるかもしれません.たとえば,m日単位をまとめて,開始位置をs-1日ずらしたいなら,DAYm.sとなりますが,m年単位をまとめて,開始位置をs-1年ずらしたいなら,YEARm.sではなく,t=(s-1)*12+1として,YEARm.tとなります.

 

以下に幾つかの応用例を示します.

  • 会計年度が,2007/11から開始している場合,YEAR.11は,会計年度に一致した時間間隔指定子になります.
  • 11/1から始まる4半期単位の時間間隔は,QTR.2または,MONTH3.2となります.
  • DAY7.3は,WEEK(=WEEK1.1)と一致します.(WEEKが7日単位で,1960年1月3日が日曜日だから)
  • 朝9時開始の6時間シフト勤務のシフト間隔は,HOUR6.3となります.毎日の最初の開始が朝3時になるからです.
data;
  do i=0,1,2,3;
  nx=intnx("year.11","01jan2008"d,i);put i= nx= date.;
  end;
run;
/*
i=0 nx=01NOV07
i=1 nx=01NOV08
i=2 nx=01NOV09
i=3 nx=01NOV10
 */

SAS日付などを形式ディスクリプタで返す NLDATE,NLTIME,NLDATM

NLDATE関数は,与えられたSAS日付値を,指定された日付形式ディスクリプタにしたがって,LOCALEシステムオプションの表記で表示されます.SAS時間値,SAS日時値には,それぞれ,NLTIME関数,NLDATM関数を使います.
用意されているディスクリプタの修飾子は以下のとおりです.これらと任意の文字を組み合わせて,文字列値として第2引数に与えます.大文字と小文字では意味が違いますので注意してください.

ディスクリプタ

SAS日付値(NLDATE),SAS日時値(NLDATM)用

  • %y 2桁数字表示(ゼロ詰め)
  • %Y 4桁数字表示(1970年から2069年までの範囲で使用可能)

  • %b 省略形表示
  • %B フルスペル表示
  • %C フルフルスペル表示(空白詰め)
  • %m 数字表示(ゼロ詰め)
  • %o 数字表示(空白詰め)

  • %d 2桁数字表示(ゼロ詰め)
  • %e 2桁数字表示(空白詰め)

曜日

  • %a 省略形表示
  • %A フルスペル表示
  • %F フルフルスペル表示(空白詰め)
  • %u 数字表示 1=月曜
  • %w 数字表示 0=日曜

ジュリアンデート

  • %j 数字表示(ゼロ詰め)

週番号

  • %U WEEKUフォーマットと同じルールの週番号(ゼロ詰め)
  • %V WEEKVフォーマットと同じルールの週番号(ゼロ詰め)
  • %w WEEKWフォーマットと同じルールの週番号(ゼロ詰め)

SAS時間値(NLTIME),SAS日時値(NLDATM)用

  • %H 24時式表示(ゼロ詰め)
  • %I 12時式表示(ゼロ詰め)

午前午後

  • %P 午前,午後の表示

  • %M 分表示(ゼロ詰め)

  • %S 秒表示(ゼロ詰め)

SAS日付値(NLDATE),SAS時間値(NLTIME),SAS日時値(NLDATM)共通

%文字

  • %% %を表示させたい場合%を2回連続で書くこと

その他の文字

  • その他の文字 (その他の文字は書いたままに表示される)
options LOCALE=JAPANESE_JAPAN; *日本ではこれがロケールのデフォルト値.むやみに変えると用紙サイズなどにも影響するので要注意;
data;
  date=nldate("03jun08"d,'%y年%b月%e日(%a)第%U週');  put date $;
run;
options LOCALE=English_unitedstates;
data;
  date=nldate("03jun08"d,'%e.%b.%y(%a)W#:%U');  put date $;
run;
options LOCALE=JAPANESE_JAPAN;
/*
08年6月 3日(火)第22週
3.Jun.08(Tue)W#:22
 */

日付関連のフォーマット,インフォーマットを使って値を変換する PUT(,フォーマット),INPUT(,インフォーマット)

PUT関数,INPUT関数は,日付用の関数ではありませんが,日付関連のフォーマット,インフォーマットと一緒に使うことで,日付の形式や型を変えることができます.日付構成要素を取り出したい場合,MONTH関数などの代りに,PUT関数にMONTHフォーマットを与えても可能です.ただし,前者は数値型,後者は文字型の値になります.(see also SAS日付関連のフォーマット,インフォーマット)

data;
  date=0;
  chardate=put(date,yymmdd.);put chardate=;
  numdate=input(chardate,yymmdd8.);put numdate= date.;
run;
/*
chardate=60-01-01
numdate=01JAN60
 */

(Tip)
日付値をSAS日付値に変換する際,日付不明は,欠損値にしておきたいですが,注意しないと思わぬ結果になる場合もあります.
たとえば,下記のように,数字でyyyymmdd形式の日付をあらわしているデータをSAS日付値に直す処理で,元のデータが数値の欠損に対し,SAS日付でも欠損になってほしいところが,0 (1960/1/1)になってしまうケースがあります.

data _null_;
  yyyymmdd=.;
  sasdate=input(put(yyyymmdd,8.),anydtdte.);
  put yyyymmdd= sasdate=;

  yyyymmdd=.;
  sasdate2=input(put(yyyymmdd,8.),yymmdd.);
  put yyyymmdd= sasdate2=;

  yyyymmdd=.;
  if yyyymmdd=. then sasdate3=.;
  else sasdate3=input(put(yyyymmdd,8.),yymmdd.);
  put yyyymmdd= sasdate3=;
run;
/*
yyyymmdd=. sasdate=0
yyyymmdd=. sasdate2=.
yyyymmdd=. sasdate3=.
 */

yyyymmddが欠損だと
put(yyyymmdd,8.)が," ."(空白7個+ピリオドの8文字列)となりなぜか,input(,anydtdte)が,これを 0 に変換してしまう.この場合,形式がyyyymmddとわかっているなら,input(,yymmdd)で,欠損(.)として変換するか,事前にIFステートメントで欠損変換を回避すべきです.