From 9c61965d4b0c7a62c8ffc5914ff2435de394c749 Mon Sep 17 00:00:00 2001 From: munja Date: Tue, 1 Feb 2022 13:48:23 +0100 Subject: [PATCH 1/4] feat: new macro (mcf_getfmttype) to determine the type (DATE,DATETIME,TIME,CHAR,NUM) of a SAS format --- fcmp/mcf_getfmttype.sas | 110 ++++++++++++++++++++ tests/crossplatform/mcf_getfmttype.test.sas | 39 +++++++ 2 files changed, 149 insertions(+) create mode 100644 fcmp/mcf_getfmttype.sas create mode 100644 tests/crossplatform/mcf_getfmttype.test.sas diff --git a/fcmp/mcf_getfmttype.sas b/fcmp/mcf_getfmttype.sas new file mode 100644 index 0000000..56f9189 --- /dev/null +++ b/fcmp/mcf_getfmttype.sas @@ -0,0 +1,110 @@ +/** + @file + @brief Returns the type of the format + @details + Returns the type, eg DATE / DATETIME / TIME (based on hard-coded lookup) + else CHAR / NUM. + + This macro may be extended in the future to support custom formats - this + would necessitate a call to `dosubl()` for running a proc format with cntlout. + + The function itself takes the following (positional) parameters: + + | PARAMETER | DESCRIPTION | + |---|---| + | fmt | Format name to be tested. Can be with or without the w.d extension.| + + Usage: + + %mcf_getfmttype(wrap=YES, insert_cmplib=YES) + + data _null_; + fmt1=mcf_getfmttype('DATE9.'); + fmt2=mcf_getfmttype('DATETIME'); + put (fmt:)(=); + run; + %put fmt3=%sysfunc(mcf_getfmttype(TIME9.)); + + Returns: + + > fmt1=DATE fmt2=DATETIME + > fmt3=TIME + + @param [out] wrap= (NO) Choose YES to add the proc fcmp wrapper. + @param [out] insert_cmplib= (NO) Choose YES to insert the package into the + CMPLIB reference. + @param [out] lib= (work) The output library in which to create the catalog. + @param [out] cat= (sasjs) The output catalog in which to create the package. + @param [out] pkg= (utils) The output package in which to create the function. + Uses a 3 part format: libref.catalog.package + +

SAS Macros

+ @li mcf_init.sas + +

Related Programs

+ @li mcf_getfmttype.test.sas + @li mp_init.sas + + @todo "Custom Format Lookups" To enable site-specific formats, make + use of a set of SASJS_FMTLIST_(DATATYPE) global variables. + +**/ + +%macro mcf_getfmttype(wrap=NO + ,insert_cmplib=NO + ,lib=WORK + ,cat=SASJS + ,pkg=UTILS +)/*/STORE SOURCE*/; + +%if %mcf_init(mcf_getfmttype)=1 %then %return; + +%if &wrap=YES %then %do; + proc fcmp outlib=&lib..&cat..&pkg; +%end; + +function mcf_getfmttype(var $) $8; + if substr(var,1,1)='$' then return('CHAR'); + else do; + /* extract NAME */ + length fmt $32; + fmt=scan(var,1,'.'); + do while ( + substr(fmt,length(fmt),1) in ('1','2','3','4','5','6','7','8','9','0') + ); + if length(fmt)=1 then fmt='W'; + else fmt=substr(fmt,1,length(fmt)-1); + end; + + /* apply lookups */ + if cats(fmt) in ('DATETIME','B8601DN','B8601DN','B8601DT','B8601DT' + ,'B8601DZ','B8601DZ','DATEAMPM','DTDATE','DTMONYY','DTWKDATX','DTYEAR' + ,'DTYYQC','E8601DN','E8601DN','E8601DT','E8601DT','E8601DZ','E8601DZ') + then return('DATETIME'); + else if fmt in ('DATE','YYMMDD','B8601DA','B8601DA','DAY','DDMMYY' + ,'DDMMYYB','DDMMYYC','DDMMYYD','DDMMYYN','DDMMYYP','DDMMYYS','DDMMYYx' + ,'DOWNAME','E8601DA','E8601DA','JULDAY','JULIAN','MMDDYY','MMDDYYB' + ,'MMDDYYC','MMDDYYD','MMDDYYN','MMDDYYP','MMDDYYS','MMDDYYx','MMYY' + ,'MMYYC','MMYYD','MMYYN','MMYYP','MMYYS','MMYYx','MONNAME','MONTH' + ,'MONYY','PDJULG','PDJULI','QTR','QTRR','WEEKDATE','WEEKDATX','WEEKDAY' + ,'WEEKU','WEEKV','WEEKW','WORDDATE','WORDDATX','YEAR','YYMM','YYMMC' + ,'YYMMD','YYMMDDB','YYMMDDC','YYMMDDD','YYMMDDN','YYMMDDP','YYMMDDS' + ,'YYMMDDx','YYMMN','YYMMP','YYMMS','YYMMx','YYMON','YYQ','YYQC','YYQD' + ,'YYQN','YYQP','YYQR','YYQRC','YYQRD','YYQRN','YYQRP','YYQRS','YYQRx' + ,'YYQS','YYQx','YYQZ') then return('DATE'); + else if fmt in ('TIME','B8601LZ','B8601LZ','B8601TM','B8601TM','B8601TZ' + ,'B8601TZ','E8601LZ','E8601LZ','E8601TM','E8601TM','E8601TZ','E8601TZ' + ,'HHMM','HOUR','MMSS','TIMEAMPM','TOD') then return('TIME'); + else return('NUM'); + end; +endsub; + +%if &wrap=YES %then %do; + quit; +%end; + +%if &insert_cmplib=YES %then %do; + options insert=(CMPLIB=(&lib..&cat)); +%end; + +%mend mcf_getfmttype; \ No newline at end of file diff --git a/tests/crossplatform/mcf_getfmttype.test.sas b/tests/crossplatform/mcf_getfmttype.test.sas new file mode 100644 index 0000000..c63a47f --- /dev/null +++ b/tests/crossplatform/mcf_getfmttype.test.sas @@ -0,0 +1,39 @@ +/** + @file + @brief Testing mcf_getfmttype.sas macro + +

SAS Macros

+ @li mcf_getfmttype.sas + @li mp_assert.sas + @li mp_assertscope.sas + +**/ + +%mp_assertscope(SNAPSHOT) +%mcf_getfmttype(wrap=YES, insert_cmplib=YES) +%mp_assertscope(COMPARE,ignorelist=SASJS_FUNCTIONS) + +%mp_assert( + iftrue=(%sysfunc(mcf_getfmttype(DATE9.))=DATE), + desc=Check DATE format +) +%mp_assert( + iftrue=(%sysfunc(mcf_getfmttype($6))=CHAR), + desc=Check CHAR format +) +%mp_assert( + iftrue=(%sysfunc(mcf_getfmttype(8.))=NUM), + desc=Check NUM format +) +%mp_assert( + iftrue=(%sysfunc(mcf_getfmttype(E8601DT))=DATETIME), + desc=Check DATETIME format +) + +/* test 2 - compile again test for warnings */ +%mcf_getfmttype(wrap=YES, insert_cmplib=YES) + +%mp_assert( + iftrue=(&syscc=0), + desc=Check syscc=0 after re-initialisation +) \ No newline at end of file From cd8d16d09f3559bb8b11d4aa3c4cfdf3dff21a65 Mon Sep 17 00:00:00 2001 From: munja Date: Tue, 1 Feb 2022 13:48:48 +0100 Subject: [PATCH 2/4] chore: updating all.sas --- all.sas | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/all.sas b/all.sas index 769ee3b..2acb276 100644 --- a/all.sas +++ b/all.sas @@ -23556,6 +23556,115 @@ run; %mend ml_json; /** + @file + @brief Returns the type of the format + @details + Returns the type, eg DATE / DATETIME / TIME (based on hard-coded lookup) + else CHAR / NUM. + + This macro may be extended in the future to support custom formats - this + would necessitate a call to `dosubl()` for running a proc format with cntlout. + + The function itself takes the following (positional) parameters: + + | PARAMETER | DESCRIPTION | + |---|---| + | fmt | Format name to be tested. Can be with or without the w.d extension.| + + Usage: + + %mcf_getfmttype(wrap=YES, insert_cmplib=YES) + + data _null_; + fmt1=mcf_getfmttype('DATE9.'); + fmt2=mcf_getfmttype('DATETIME'); + put (fmt:)(=); + run; + %put fmt3=%sysfunc(mcf_getfmttype(TIME9.)); + + Returns: + + > fmt1=DATE fmt2=DATETIME + > fmt3=TIME + + @param [out] wrap= (NO) Choose YES to add the proc fcmp wrapper. + @param [out] insert_cmplib= (NO) Choose YES to insert the package into the + CMPLIB reference. + @param [out] lib= (work) The output library in which to create the catalog. + @param [out] cat= (sasjs) The output catalog in which to create the package. + @param [out] pkg= (utils) The output package in which to create the function. + Uses a 3 part format: libref.catalog.package + +

SAS Macros

+ @li mcf_init.sas + +

Related Programs

+ @li mcf_getfmttype.test.sas + @li mp_init.sas + + @todo "Custom Format Lookups" To enable site-specific formats, make + use of a set of SASJS_FMTLIST_(DATATYPE) global variables. + +**/ + +%macro mcf_getfmttype(wrap=NO + ,insert_cmplib=NO + ,lib=WORK + ,cat=SASJS + ,pkg=UTILS +)/*/STORE SOURCE*/; + +%if %mcf_init(mcf_getfmttype)=1 %then %return; + +%if &wrap=YES %then %do; + proc fcmp outlib=&lib..&cat..&pkg; +%end; + +function mcf_getfmttype(var $) $8; + if substr(var,1,1)='$' then return('CHAR'); + else do; + /* extract NAME */ + length fmt $32; + fmt=scan(var,1,'.'); + do while ( + substr(fmt,length(fmt),1) in ('1','2','3','4','5','6','7','8','9','0') + ); + if length(fmt)=1 then fmt='W'; + else fmt=substr(fmt,1,length(fmt)-1); + end; + + /* apply lookups */ + if cats(fmt) in ('DATETIME','B8601DN','B8601DN','B8601DT','B8601DT' + ,'B8601DZ','B8601DZ','DATEAMPM','DTDATE','DTMONYY','DTWKDATX','DTYEAR' + ,'DTYYQC','E8601DN','E8601DN','E8601DT','E8601DT','E8601DZ','E8601DZ') + then return('DATETIME'); + else if fmt in ('DATE','YYMMDD','B8601DA','B8601DA','DAY','DDMMYY' + ,'DDMMYYB','DDMMYYC','DDMMYYD','DDMMYYN','DDMMYYP','DDMMYYS','DDMMYYx' + ,'DOWNAME','E8601DA','E8601DA','JULDAY','JULIAN','MMDDYY','MMDDYYB' + ,'MMDDYYC','MMDDYYD','MMDDYYN','MMDDYYP','MMDDYYS','MMDDYYx','MMYY' + ,'MMYYC','MMYYD','MMYYN','MMYYP','MMYYS','MMYYx','MONNAME','MONTH' + ,'MONYY','PDJULG','PDJULI','QTR','QTRR','WEEKDATE','WEEKDATX','WEEKDAY' + ,'WEEKU','WEEKV','WEEKW','WORDDATE','WORDDATX','YEAR','YYMM','YYMMC' + ,'YYMMD','YYMMDDB','YYMMDDC','YYMMDDD','YYMMDDN','YYMMDDP','YYMMDDS' + ,'YYMMDDx','YYMMN','YYMMP','YYMMS','YYMMx','YYMON','YYQ','YYQC','YYQD' + ,'YYQN','YYQP','YYQR','YYQRC','YYQRD','YYQRN','YYQRP','YYQRS','YYQRx' + ,'YYQS','YYQx','YYQZ') then return('DATE'); + else if fmt in ('TIME','B8601LZ','B8601LZ','B8601TM','B8601TM','B8601TZ' + ,'B8601TZ','E8601LZ','E8601LZ','E8601TM','E8601TM','E8601TZ','E8601TZ' + ,'HHMM','HOUR','MMSS','TIMEAMPM','TOD') then return('TIME'); + else return('NUM'); + end; +endsub; + +%if &wrap=YES %then %do; + quit; +%end; + +%if &insert_cmplib=YES %then %do; + options insert=(CMPLIB=(&lib..&cat)); +%end; + +%mend mcf_getfmttype;/** @file @brief Sets up the mcf_xx functions @details From e62011d97e55a1d0165db8993599421f530f5051 Mon Sep 17 00:00:00 2001 From: munja Date: Tue, 1 Feb 2022 13:52:08 +0100 Subject: [PATCH 3/4] chore: updating variable name to fit doc header --- all.sas | 8 ++++---- fcmp/mcf_getfmttype.sas | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/all.sas b/all.sas index 2acb276..74f9a92 100644 --- a/all.sas +++ b/all.sas @@ -23569,7 +23569,7 @@ run; | PARAMETER | DESCRIPTION | |---|---| - | fmt | Format name to be tested. Can be with or without the w.d extension.| + |fmtnm| Format name to be tested. Can be with or without the w.d extension.| Usage: @@ -23620,12 +23620,12 @@ run; proc fcmp outlib=&lib..&cat..&pkg; %end; -function mcf_getfmttype(var $) $8; - if substr(var,1,1)='$' then return('CHAR'); +function mcf_getfmttype(fmtnm $) $8; + if substr(fmtnm,1,1)='$' then return('CHAR'); else do; /* extract NAME */ length fmt $32; - fmt=scan(var,1,'.'); + fmt=scan(fmtnm,1,'.'); do while ( substr(fmt,length(fmt),1) in ('1','2','3','4','5','6','7','8','9','0') ); diff --git a/fcmp/mcf_getfmttype.sas b/fcmp/mcf_getfmttype.sas index 56f9189..a0968a3 100644 --- a/fcmp/mcf_getfmttype.sas +++ b/fcmp/mcf_getfmttype.sas @@ -12,7 +12,7 @@ | PARAMETER | DESCRIPTION | |---|---| - | fmt | Format name to be tested. Can be with or without the w.d extension.| + |fmtnm| Format name to be tested. Can be with or without the w.d extension.| Usage: @@ -63,12 +63,12 @@ proc fcmp outlib=&lib..&cat..&pkg; %end; -function mcf_getfmttype(var $) $8; - if substr(var,1,1)='$' then return('CHAR'); +function mcf_getfmttype(fmtnm $) $8; + if substr(fmtnm,1,1)='$' then return('CHAR'); else do; /* extract NAME */ length fmt $32; - fmt=scan(var,1,'.'); + fmt=scan(fmtnm,1,'.'); do while ( substr(fmt,length(fmt),1) in ('1','2','3','4','5','6','7','8','9','0') ); From 2e790f69a107752444b9b7da59a4c6a7c06e2a53 Mon Sep 17 00:00:00 2001 From: munja Date: Tue, 1 Feb 2022 16:14:37 +0100 Subject: [PATCH 4/4] fix: removing view due to potential for vbufsize violations --- all.sas | 21 +++++++++------------ base/mp_jsonout.sas | 7 +++---- meta/mm_createwebservice.sas | 7 +++---- viya/mv_createwebservice.sas | 7 +++---- 4 files changed, 18 insertions(+), 24 deletions(-) diff --git a/all.sas b/all.sas index 74f9a92..bfab8d5 100644 --- a/all.sas +++ b/all.sas @@ -8137,7 +8137,7 @@ options %put &sysmacroname: Switching to DATASTEP engine; %goto datastep; %end; - data &tempds /view=&tempds;set &ds; + data &tempds;set &ds; %if &fmt=N %then format _numeric_ best32.;; /* PRETTY is necessary to avoid line truncation in large files */ proc json out=&jref pretty @@ -8188,7 +8188,7 @@ options %end; other = [best.]; - data &tempds/view=&tempds; + data &tempds; attrib _all_ label=''; %do i=1 %to &numcols; %if &&typelong&i=char or &fmt=Y %then %do; @@ -8250,8 +8250,7 @@ options %end; proc sql; - drop view &tempds; - drop table &colinfo; + drop table &colinfo, &tempds; %if &showmeta=YES %then %do; data _null_; file &jref encoding='utf-8' mod; @@ -13861,7 +13860,7 @@ data _null_; put ' %put &sysmacroname: Switching to DATASTEP engine; '; put ' %goto datastep; '; put ' %end; '; - put ' data &tempds /view=&tempds;set &ds; '; + put ' data &tempds;set &ds; '; put ' %if &fmt=N %then format _numeric_ best32.;; '; put ' /* PRETTY is necessary to avoid line truncation in large files */ '; put ' proc json out=&jref pretty '; @@ -13912,7 +13911,7 @@ data _null_; put ' %end; '; put ' other = [best.]; '; put ' '; - put ' data &tempds/view=&tempds; '; + put ' data &tempds; '; put ' attrib _all_ label=''''; '; put ' %do i=1 %to &numcols; '; put ' %if &&typelong&i=char or &fmt=Y %then %do; '; @@ -13974,8 +13973,7 @@ data _null_; put ' %end; '; put ' '; put ' proc sql; '; - put ' drop view &tempds; '; - put ' drop table &colinfo; '; + put ' drop table &colinfo, &tempds; '; put ' '; put ' %if &showmeta=YES %then %do; '; put ' data _null_; file &jref encoding=''utf-8'' mod; '; @@ -19004,7 +19002,7 @@ data _null_; put ' %put &sysmacroname: Switching to DATASTEP engine; '; put ' %goto datastep; '; put ' %end; '; - put ' data &tempds /view=&tempds;set &ds; '; + put ' data &tempds;set &ds; '; put ' %if &fmt=N %then format _numeric_ best32.;; '; put ' /* PRETTY is necessary to avoid line truncation in large files */ '; put ' proc json out=&jref pretty '; @@ -19055,7 +19053,7 @@ data _null_; put ' %end; '; put ' other = [best.]; '; put ' '; - put ' data &tempds/view=&tempds; '; + put ' data &tempds; '; put ' attrib _all_ label=''''; '; put ' %do i=1 %to &numcols; '; put ' %if &&typelong&i=char or &fmt=Y %then %do; '; @@ -19117,8 +19115,7 @@ data _null_; put ' %end; '; put ' '; put ' proc sql; '; - put ' drop view &tempds; '; - put ' drop table &colinfo; '; + put ' drop table &colinfo, &tempds; '; put ' '; put ' %if &showmeta=YES %then %do; '; put ' data _null_; file &jref encoding=''utf-8'' mod; '; diff --git a/base/mp_jsonout.sas b/base/mp_jsonout.sas index 251850e..dc750fc 100644 --- a/base/mp_jsonout.sas +++ b/base/mp_jsonout.sas @@ -131,7 +131,7 @@ %put &sysmacroname: Switching to DATASTEP engine; %goto datastep; %end; - data &tempds /view=&tempds;set &ds; + data &tempds;set &ds; %if &fmt=N %then format _numeric_ best32.;; /* PRETTY is necessary to avoid line truncation in large files */ proc json out=&jref pretty @@ -182,7 +182,7 @@ %end; other = [best.]; - data &tempds/view=&tempds; + data &tempds; attrib _all_ label=''; %do i=1 %to &numcols; %if &&typelong&i=char or &fmt=Y %then %do; @@ -244,8 +244,7 @@ %end; proc sql; - drop view &tempds; - drop table &colinfo; + drop table &colinfo, &tempds; %if &showmeta=YES %then %do; data _null_; file &jref encoding='utf-8' mod; diff --git a/meta/mm_createwebservice.sas b/meta/mm_createwebservice.sas index 371ebde..4b41b4f 100644 --- a/meta/mm_createwebservice.sas +++ b/meta/mm_createwebservice.sas @@ -164,7 +164,7 @@ data _null_; put ' %put &sysmacroname: Switching to DATASTEP engine; '; put ' %goto datastep; '; put ' %end; '; - put ' data &tempds /view=&tempds;set &ds; '; + put ' data &tempds;set &ds; '; put ' %if &fmt=N %then format _numeric_ best32.;; '; put ' /* PRETTY is necessary to avoid line truncation in large files */ '; put ' proc json out=&jref pretty '; @@ -215,7 +215,7 @@ data _null_; put ' %end; '; put ' other = [best.]; '; put ' '; - put ' data &tempds/view=&tempds; '; + put ' data &tempds; '; put ' attrib _all_ label=''''; '; put ' %do i=1 %to &numcols; '; put ' %if &&typelong&i=char or &fmt=Y %then %do; '; @@ -277,8 +277,7 @@ data _null_; put ' %end; '; put ' '; put ' proc sql; '; - put ' drop view &tempds; '; - put ' drop table &colinfo; '; + put ' drop table &colinfo, &tempds; '; put ' '; put ' %if &showmeta=YES %then %do; '; put ' data _null_; file &jref encoding=''utf-8'' mod; '; diff --git a/viya/mv_createwebservice.sas b/viya/mv_createwebservice.sas index e0768bd..16b8601 100644 --- a/viya/mv_createwebservice.sas +++ b/viya/mv_createwebservice.sas @@ -308,7 +308,7 @@ data _null_; put ' %put &sysmacroname: Switching to DATASTEP engine; '; put ' %goto datastep; '; put ' %end; '; - put ' data &tempds /view=&tempds;set &ds; '; + put ' data &tempds;set &ds; '; put ' %if &fmt=N %then format _numeric_ best32.;; '; put ' /* PRETTY is necessary to avoid line truncation in large files */ '; put ' proc json out=&jref pretty '; @@ -359,7 +359,7 @@ data _null_; put ' %end; '; put ' other = [best.]; '; put ' '; - put ' data &tempds/view=&tempds; '; + put ' data &tempds; '; put ' attrib _all_ label=''''; '; put ' %do i=1 %to &numcols; '; put ' %if &&typelong&i=char or &fmt=Y %then %do; '; @@ -421,8 +421,7 @@ data _null_; put ' %end; '; put ' '; put ' proc sql; '; - put ' drop view &tempds; '; - put ' drop table &colinfo; '; + put ' drop table &colinfo, &tempds; '; put ' '; put ' %if &showmeta=YES %then %do; '; put ' data _null_; file &jref encoding=''utf-8'' mod; ';