Why should we teach Roman numerals?

In my local paper this morning, I read about how a North Carolina state commission plans to recommend changes to our teaching standards for mathematics. One of the topics that they want to bring back: Roman numerals. Why? According to my exhaustive 30 seconds of Internet research, the only practical applications of Roman numerals are: I) understanding Super Bowl numbering, and II) reading the time on old-fashion clocks.

But I don't need convincing. I believe that there are other advantages of teaching Roman numerals. The main lesson is this: the world has not always revolved around "base 10" numbering, and actually it still doesn't today. Having the ability to express numbers in other forms helps us to understand history, passage of time, technology, and even philosophy*.

In the popular media, binary (base 2) is famous for being "the language of computers". That may be so, but binary is not usually the language of computer programmers. When I was a kid, I spent many hours programming graphics on my TI 99/4A computer. I became proficient in translating decimal to hexadecimal (base 16) to binary -- all to express how the pixels would be drawn on the screen and in what color. Due to lack of practice and today's availability of handy tools and higher-level programming languages, I have since lost the ability to calculate all of these in my head. I also lost the ability to solve any Rubik's Cube that I pick up -- there go all of my party tricks.

But the SAS programming language retains many fun math tricks, including the ability to express numbers in many different ways, instantly. Here's an example of one number expressed six (or 6 or VI or 0110) different ways.

data _null_;
  x = 1956;
  put  / 'Decimal: '     x=best12.;
  put  / 'Roman: '       x=roman10.;
  put  / 'Word: '        x=words50.;
  put  / 'Binary: '      x=binary20.;
  put  / 'Octal: '       x=octal10.;
  put  / 'Hexadecimal: ' x=hex6.;
run;

The output:

Decimal: x=1956
Roman: x=MCMLVI
Word: x=one thousand nine hundred fifty-six
Binary: x=00000000011110100100
Octal: x=0000003644
Hexadecimal: x=0007A4

You might never need some of these number systems or SAS formats in your job, but knowing them makes you a more interesting person. If nothing else, it's a skill that you can trot out during cocktail parties. (I guess I attend different sorts of parties now.)

* For example, the number 'zero' has not always been with us. Introducing it into our numbering system allows us to think about 'nothing' in ways that earlier societies could not.

Post a Comment

Copy data and column names from SAS Enterprise Guide

While I've often written about how to get your SAS data to Microsoft Excel in some automated way, I haven't really addressed what's probably the most frequently used method: copy and paste. SAS Enterprise Guide 7.1 added a nifty little feature that makes copy-and-paste even more useful.

The new "Copy with headers" feature creates a tab-delimited version of your selected data cells, complete with a heading row that includes the column names. This is very convenient for copying into a new Microsoft Excel spreadsheet or other table structure (like a Google Docs spreadsheet). Note: this is different than the "copy data attributes" tip that I published a while back. That tip captures the column properties, but not the actual data values.

To get started, simply select the data that you want within the SAS Enterprise Guide data grid. The data cells must be contiguous, but you don't need to select all columns or all cells within the data set. With the selection active, right-click and select Copy with headers.

copy with headers
This action places the data values in tab-delimited form onto the Windows clipboard. The first line of the data will be the SAS variable names from your selected data. If you paste this into a text editor that shows a view of "special characters", you can see the tabs along with the end-of-line delimiters.

tab delimited content
When you paste the same content into Microsoft Excel, the Excel application knows how to automatically distribute these values into distinct columns. That's just what spreadsheet programs do.

paste into Excel
Microsoft Excel isn't the only app that can handle tab-delimited data in this way. You can paste the content into a Google Doc spreadsheet too.

paste into Google Docs
Or, you could simply paste into a text file as-is, and then save that file to read into a SAS program at a later time, bringing it full circle.

Post a Comment

Using Lua within your SAS programs

With apologies to this candy advertisement from the 1980s:

"Hey, you got your Lua in my SAS program."
"You got your SAS code in my Lua program!"

Announcer: "PROC LUA: Two great programming languages that program great together!"

What is Lua? It's an embeddable scripting language that is often used as a way to add user extensions to robust software applications. Lua has been embedded into SAS for some time already, as it's the basis for new ODS destinations like EXCEL and POWERPOINT. But SAS users haven't had a way to access it.

With SAS 9.4 Maintenance 3 (released July 2015), you can now run Lua code in the new LUA procedure. And from within that Lua code, you can exchange data with SAS and call SAS functions and submit SAS statements. (Running SAS within Lua within SAS -- it's just like Inception.)

Paul Tomas, the developer for PROC LUA, presented a demo of the feature and its usefulness in a recent SAS Tech Talk:


 
Paul also wrote a paper for SAS Global Forum 2015: Driving SAS with Lua.

Like many innovations that find their way into customer-facing features, this new item was added to help SAS R&D complete work for a SAS product (specifically, the new version of SAS Forecast Server). But the general technique was so useful that we decided to add it into Base SAS as a way for you to integrate Lua logic.

PROC LUA can be an alternative to the SAS macro language for injecting logical control into your SAS programs. For example, here's a sample program that generates a SAS data set only if the data set doesn't already exist.

proc lua ;
submit; 
 
-- example of logic control within LUA
if not sas.exists("work.sample") then
    print "Creating new WORK.SAMPLE"
	sas.submit [[
	  data work.sample;
	    set sashelp.class;
	  run;
	 ]]
   else print "WORK.SAMPLE already exists"
 end
 
endsubmit;
run;

First run:

NOTE: Lua initialized.
Creating new WORK.SAMPLE
    data work.sample;
      set sashelp.class;
    run;

And subsequent runs:

NOTE: Resuming Lua state from previous PROC LUA invocation.
WORK.SAMPLE already exists
NOTE: PROCEDURE LUA used (Total process time):
      real time           0.00 seconds
      cpu time            0.00 seconds

Unlike other embedded languages (like PROC GROOVY), Lua runs in the same process as SAS -- and not in a separate virtual machine process like a Java VM. This makes it easy to exchange information such as data and macro variables between your SAS and Lua programming structures.

If you have SAS 9.4M3 and have time to play with PROC LUA, let us know what interesting applications you come up with!

Post a Comment

SAS Enterprise Guide now updates itself

I returned to work from a 2+ week vacation this morning. When I fired up SAS Enterprise Guide (as I do each work day and occasionally on weekends), I was greeted with this message:

An update to SAS Enterprise Guide is available!
As a SAS insider, I knew this was coming. It's a new feature that was added in SAS Enterprise Guide 7.11. I intended to write a blog about this before now -- but then I went on vacation instead.

I'm a trusting fellow, but I still clicked on the link in the message to learn more information about the update. All of the improvements seemed good to me, so I clicked Close and Install.

My SAS Enterprise Guide session closed and a moment later I saw the patch being applied:

Applying patch
When complete, I was greeted with this good news:

Your software is now up to date!
And when I clicked Finish and the application restarted, I checked the Help->About SAS Enterprise Guide window to see that the update was in place.

about window with HF number
I think that this "automatic update" is a tremendous feature whose time has come (if it's not overdue). However, not everyone will want to update their software on SAS' schedule. You can defer the update with Remind me later, or select Skip this version in order to not be reminded again (until the next update). You can always check for updates from the Help menu.

UPDATE: Since posting this article last week, I've heard from several concerned citizens who have said "Whoah! I hope that admins can disable this! We can't have our users updating their own applications!" Yes, the "Check for Updates" notification and menu item can be disabled by using the methods in this soon-to-be-published SAS note. And the feature does not allow end users to update their PC applications if they don't already have privileges to do so.

Post a Comment

Copy an entire process flow in SAS Enterprise Guide

I've seen some crazy process flows in SAS Enterprise Guide. Crazy-big, and crazy-complex, used by real customers to accomplish real work. But while these process flows represent a ton of work, this is usually a calculated investment to automate processes that would be difficult to capture in another way.

For years, SAS Enterprise Guide users have asked for a way to reuse their process flows in new projects. You have always been able to copy-and-paste individual items (tasks, queries, programs) from one project to another, or from one flow to another in the same project. But when you tried to copy a collection of items from a flow, everything fell apart when you pasted into the new destination. The links/relationships among the objects were not retained, and that sabotaged your goal of saving time and effort.

In SAS Enterprise Guide 7.1, this is finally improved. You can now copy an entire flow, or multiple selected items from a flow, and paste that content intact into another project or process flow.

To copy an entire flow, right-click on the flow name within the Project Tree, then select Copy. In your destination project, right-click on an empty spot within the Project Tree and you'll see that Paste is enabled. When you paste, you'll see the entire flow transfer over, including the links and layout. It's like magic.

copypaste
To copy just a portion of the process flow, click-drag the cursor to "rubberband" a selection of connected items. (You can also use Ctrl+click to select the items that you want to include.) With the items selected, right-click on one of the selected items and choose Copy. You can then Paste the content into a new or existing process flow.

copyselect
You can paste a process flow into a new or an existing SAS Enterprise Guide project. Try it for yourself -- see how much time it can save for you!

Note: this feature was added in SAS Enterprise Guide 7.1. The first update (7.11) improved it further (such as capturing project prompts that are referenced in your flow).

Post a Comment

Ask the Expert: Creating custom tasks for SAS Enterprise Guide

If you have not yet discovered the new Ask the Expert series on the SAS Training site, you are missing out on a treasure. Visit the site right now and review all of the available topics, from "Newbie" to Analytics to Visualization to good ol' SAS programming. Go on; I'll wait.

Ask the Expert - they wear glasses, apparently
Welcome back! Amazing, right? You can get lost for hours learning new stuff or just reviewing what you thought you already knew. Some topics are available as live sessions that you can "attend" as they happen, but many of them are available on-demand, for free, no strings attached.

You might have noticed that I have a humble contribution in the "expert" collection: Developing Custom Tasks for SAS Enterprise Guide. During the 37-minute video, I lead you through the uses of SAS custom tasks and the basic steps for creating your own task. You'll learn what custom tasks can do and what they cannot do. You'll learn about the tools and APIs that support the creation of tasks. And you'll see the "inside" of a completed custom task project. (An aside: I owe a big "thank you" to the team that post-processed the recording of my expert talk -- I know that I wasn't that smooth and concise when I recorded it!)

It's a short introduction and watching it won't make you an expert in the topic, but it will help you to decide whether to learn more. You can learn more from my book on this topic, or you can arrange to attend an offering of the two-day course that we offer occasionally. Or you can learn it all on your own, as many have. There are plenty of examples and references to work from. If you're wondering what skills you should have before taking the class, watch my "about this course" video here.

In the "ask the expert" video, I referenced a collection of API libraries that make it easier to set up your custom task projects. These API libraries are available for each version of SAS Enterprise Guide, and they allow you to create tasks that are compatible with multiple versions of the SAS applications, even if you do not have those particular versions installed. (You still need at least one version of SAS Enterprise Guide or SAS Add-In for Microsoft Office in order to test and run your custom task.)

With the permission of the SAS R&D developers, I have made those libraries available here:

>> Download custom task API libraries (ZIP file 333KB)

The README.txt file in the ZIP file explains how to use the libraries.

If you have questions as you start your custom task adventures or if you just want to brag about your successes, post back here or on the SAS Enterprise Guide community. I'd love to hear from you!

Read More »

Post a Comment

Using LIBNAME XLSX to read and write Excel files

When you weren't watching, SAS did it again. We smuggled Yet Another Excel Engine into a SAS release.

SAS 9.4 Maintenance 2 added the XLSX engine, which allows you to read and write Microsoft Excel files as if they were data sets in a library. The big advantage of using this engine is that it accesses the XLSX file directly, and doesn't use the Microsoft data APIs as a go-between. (LIBNAME EXCEL and LIBNAME PCFILES rely on those Microsoft components.) That means that you can use this engine on Windows or Unix systems without having to worry about bitness (32-bit versus 64-bit) or setting up a separate PC Files Server process.

The XLSX engine does require a license for SAS/ACCESS to PC Files. Are you a SAS University Edition user? The SAS/ACCESS product is part of that package, so this technique works there. It's an easy way to get well-formed Excel data into your SAS process.

/* because Excel field names often have spaces */
options validvarname=any;
 
libname xl XLSX '/folders/myfolders/sas_tech_talks_15.xlsx';
 
/* discover member (DATA) names */
proc datasets lib=xl; quit;
 
libname xl CLEAR;

Example output:

xl_contents
Once the library is assigned, I can read the contents of a spreadsheet into a new SAS data set:

/* because Excel field names often have spaces */
options validvarname=any;
 
libname xl XLSX '/folders/myfolders/sas_tech_talks_15.xlsx';
 
/* read in one of the tables */
data confirmed;
  set xl.confirmed;
run;
 
libname xl CLEAR;

And here's the result in my SAS University Edition:

xl_confirmed
Sometimes you need just one value from a spreadsheet. That's a common use case for dynamic data exchange (DDE), which isn't as feasible as it once was. With the XLSX engine, you can use FIRSTOBS= and OBS= options to control how much data you retain:

/* read in just one value */
data _null_;
  set xl.confirmed (firstobs=6 obs=6 keep='Job title'n);
  call symput('VALUE','Job Title'n);
run;
%put &value;

Output:

 76         %put &value;
 Testing Manager,  Quality-driven User Experience Testing

You can also use the XLSX engine to create and update XLSX files.

libname xlout XLSX '/folders/myfolders/samples.xlsx';
 
data xlout.cars;
  set sashelp.cars;
run;
 
data xlout.classfit;
  set sashelp.classfit;
run;
 
data xlout.baseball;
  set sashelp.baseball;
run;
 
data xlout.air;
  set sashelp.air;
run;
 
libname xlout clear;

Here is my output in Microsoft Excel with all of these data sets now as sheets:

xl_xlsxout
Remember, you can also create Microsoft Excel files with Base SAS by using ODS EXCEL -- experimental in 9.4 Maintenance 2 but production in Maintenance 3 (coming soon).

The XLSX libname is different from the EXCEL and PCFILES engines in other ways. For example, the XLSX engine does not support Excel named ranges (which can surface a portion of a spreadsheet as a discrete table). Also, you won't see the familiar "$" decoration around the spreadsheet names when they are surfaced in the library within SAS. If you need that sort of flexibility, you can use PROC IMPORT to provide more control over exactly what Excel content is brought into SAS and how.

One other IMPORTANT caution: The XLSX engine is a sequential access engine in that it processes data one record after the other. The engine starts at the beginning of the file and continues in sequence to the end of the file. Some techniques to MODIFY the data in-place will not work. Also, some SAS data viewers cannot render the data from the XLSX engine. SAS VIEWTABLE and SAS Enterprise Guide and even SAS Studio can't open these tables directly in the data grid view. VIEWTABLE gives you a nice message, but SAS Enterprise Guide simply "hangs" in the attempt. For that reason, I recommend using DATA step to copy the Excel content that you want to another SAS library, then CLEAR the XLSX library to avoid accidentally opening a table in a viewer that won't support it. (This is currently a bug in SAS Enterprise Guide that should be fixed in a future release.)

I have found LIBNAME XLSX to be a quick, convenient method to bring in Excel data on any SAS platform. If you have SAS 9.4 Maintenance 2 or later, try it out! Let me know how it works for you by sharing a comment here.

Post a Comment

Filter your SAS Enterprise Guide data with a WHERE clause

Are you a VIEWTABLE fan from the SAS Windowing Environment (a.k.a. Display Manager, DMS, PC SAS)? If so, the latest version of SAS Enterprise Guide has a new feature that you'll love.

With the latest update to SAS Enterprise Guide 7.1 (7.11), you can now subset your data in the data grid by typing a WHERE clause filter. The new Where button appears at the top of the data grid.

Simply click the button, and type a filter expression into the text field. Hit the Enter key to apply the filter:

where_ex1
Just as you might expect from SAS, you can build more complex expressions too. For example, you can transform the variable that you're comparing to lower case so that case-insensitive filters are easier to build:

where_ex2
And if you have continuous variables such as a date variable, you can subset by using BETWEEN to specify the boundaries you want to see:

where_ex3
Or transform that continuous range to a category (such as a YEAR) with a SAS function, on the fly:

where_ex4

SAS Enterprise Guide has always allowed you to subset data easily, but only by adding a step in the process flow (either a Query Builder task or Filter and Sort task). Now, you can accomplish this "in place", saving time and storage as you explore. Let us know what you think of the new feature -- either here in the blog comments or within the SAS Enterprise Guide community.

Post a Comment

Using FILENAME ZIP to unzip and read data files in SAS

I've written about how to use the FILENAME ZIP method to read and update ZIP files in your SAS programs. The ZIP method was added in SAS 9.4, and its advantage is that you can accomplish more in SAS without having to launch external utilities such as WinZip, gunzip, or 7-Zip.

Several readers replied with questions about how you can use the content of these ZIP files within your SAS program. The basic scenario is: "I've got some data files in my ZIP archive. I want to use SAS to unzip these and then use them as data within my SAS process. Can I do this?"

Yes, you can -- but it does require an extra step. Even though FILENAME ZIP can show you the contents and structure of your ZIP file, most SAS procedures cannot access the content directly while it's in the archive. So, the additional step is to copy the file to another location, effectively extracting it from the ZIP file.

As an example, I created a ZIP file with two files and a subfolder:

data.zip
  |__ sas_tech_talks_15.xlsx
  |__ sas/
      |__ instanttitles.sas7bdat

This SAS program helps me to discover how FILENAME ZIP sees the file:

filename inzip ZIP "c:\projects\data.zip";
 
/* Read the "members" (files) from the ZIP file */
data contents(keep=memname isFolder);
 length memname $200 isFolder 8;
 fid=dopen("inzip");
 if fid=0 then
  stop;
 memcount=dnum(fid);
 do i=1 to memcount;
  memname=dread(fid,i);
  /* check for trailing / in folder name */
  isFolder = (first(reverse(trim(memname)))='/');
  output;
 end;
 rc=dclose(fid);
run;
 
/* create a report of the ZIP contents */
title "Files in the ZIP file";
proc print data=contents noobs N;
run;

Output:

        Files in the ZIP file                                         
 memname                       isFolder
 sas/                             1  
 sas/instanttitles.sas7bdat       0  
 sas_tech_talks_15.xlsx           0  
                N = 3

With this information, I can now "copy" the XLSX file out of the ZIP file and then import it into a SAS data set. Notice how I can use the "member" syntax (fileref with the file I want in parentheses) to address a specific file in the ZIP archive. I want to copy just from the actual files, and not the folder-level entries.

/* identify a temp folder in the WORK directory */
filename xl "%sysfunc(getoption(work))/sas_tech_talks_15.xlsx" ;
 
/* hat tip: "data _null_" on SAS-L */
data _null_;
   /* using member syntax here */
   infile inzip(sas_tech_talks_15.xlsx) 
       lrecl=256 recfm=F length=length eof=eof unbuf;
   file   xl lrecl=256 recfm=N;
   input;
   put _infile_ $varying256. length;
   return;
 eof:
   stop;
run;
 
proc import datafile=xl dbms=xlsx out=confirmed replace;
  sheet=confirmed;
run;

Sample output from my SAS log:

NOTE: The infile INZIP(sas_tech_talks_15.xlsx) is:
      Filename=c:\projects\data.zip,
      Member Name=sas_tech_talks_15.xlsx

NOTE: UNBUFFERED is the default with RECFM=N.
NOTE: The file XL is:
      Filename=C:\SAS Temporary Files\_TD396_\Prc2\sas_tech_talks_15.xlsx,
      RECFM=N,LRECL=256,File Size (bytes)=0,
      Last Modified=11May2015:11:38:59,
      Create Time=11May2015:11:20:23

NOTE: A total of 55 records were read from the infile library INZIP.
NOTE: 55 records were read from the infile INZIP(sas_tech_talks_15.xlsx).
NOTE: DATA statement used (Total process time):
      real time           0.00 seconds
      cpu time            0.00 seconds

To use the SAS data set in the file, I need to copy it into a location shared by a SAS library. In this example, I will again use the WORK location. Because my SAS data set is in a logical subfolder (named "sas") within the archive, I need to include that path as part of the member syntax on the INFILE statement.

/* Copy a zipped data set into the WORK library */
filename ds "%sysfunc(getoption(work))/instanttitles.sas7bdat" ;
 
data _null_;
   /* reference the member name WITH folder path */
   infile inzip(sas/instanttitles.sas7bdat) 
	  lrecl=256 recfm=F length=length eof=eof unbuf;
   file   ds lrecl=256 recfm=N;
   input;
   put _infile_ $varying256. length;
   return;
 eof:
   stop;
run;
 
proc contents data=work.instanttitles;
run;

Partial output in my example:

                             Files in the ZIP file                          
                             The CONTENTS Procedure

 Data Set Name        WORK.INSTANTTITLES            Observations          1475
 Member Type          DATA                          Variables             6   
 Engine               V9                            Indexes               0   
 Created              01/29/2015 15:09:54           Observation Length    248 
 Last Modified        01/29/2015 15:09:54           Deleted Observations  0   
 Protection                                         Compressed            NO  
 Data Set Type                                      Sorted                NO  
 Label                                                                        
 Data Representation  WINDOWS_64                                              
 Encoding             wlatin1  Western (Windows)                              

Of course, all of this can be automated even further by writing SAS code that automatically iterates through the ZIP file member names and copies/imports each of the members as needed.

Post a Comment

How to convert a Unix datetime to a SAS datetime

timeI watched with wonder as each of my daughters learned how to "tell time." Early in their primary school careers, they brought home worksheets that featured clock faces with big-hand/little-hand configurations that they had to decipher, and exercises that asked them to draw the hands as they should appear given a specific elapsed time. Now my daughters have digital watches, and have even adopted my habit of wearing them with the face on the underside of the wrist and setting the display to 24-hour time -- which serves to confound their friends.

But, to my disappointment, their school has failed to teach my daughters how to read a SAS datetime value, or even the more ubiquitous Unix (POSIX) datetime value. As a parent of the 21st century, I feel it is my duty to fill this gap in their education.

First, let's review how these datetime values are represented:

  • A SAS datetime value is the number of seconds that have elapsed since midnight of January 1, 1960 (01JAN1960:00:00:00).
  • A Unix (or POSIX) datetime value is the number of seconds* that have elapsed since midnight of January 1, 1970 (01JAN1970:00:00:00).

Conversion from Unix to SAS representation is simple math:

/* Number of seconds between 01JAN1960 and 01JAN1970: 315619200 */
sasDT = unixDT + 315619200;

Having trouble remembering that 9-digit constant? Then you can get SAS to infer that part for you and use the dhms() function:

/* DHMS function calculates datetime when you provide values for */
/*    date, hour, minute, and seconds                            */
/* In this case, "seconds" is a very high value!                 */
sasDT = dhms('01jan1970'd, 0, 0, unixDT);

Raw Unix times are often expressed as UTC, and you might prefer to show local times in your SAS reporting. That's a simple calculation with the (undocumented) gmtoff() function:

/* Convert from UTC to local time using GMTOFF */
sasDT = dhms('01jan1970'd,0,0, unixDT + gmtoff());

Update: Bruno told me that TZONEOFF is an official SAS function in SAS 9.4! See the comments for more details how you can use it instead of GMTOFF.

I have one more variation that I use every day. I have a project that reports against a database that stores transaction times in milliseconds instead of seconds. (This uses the database's intrinsic int8 or BIGINT type.) SAS datetime values can represent fractions of seconds, so this conversion simply requires that I divide by 1000. I can use a different SAS datetime format to see the precision (if that's what I want, though it's usually not).

/* for more precision */
format sasDT datetime22.3; 
/* mlliseconds from transaction database */
unixDTms = 1429193343362;
sasDT_ms = dhms('01jan1970'd,0,0, unixDTms/1000);
/* result: 16APR2015:14:09:03.362 */

Here's a complete program that you can experiment with:

data t;
  format sasDT_const 
    sasDT_dhms 
    sasDT_local datetime20.;
 
  /* for more precision */
  format sasDT_ms datetime22.3;
 
  /* Unix test datetime of 16APR2015:14:09:03 UTC */
  unixDT =   1429193343;
 
  /* mlliseconds from transaction database */
  unixDTms = 1429193343362;
 
  /* Number of seconds between 01JAN1960 and 01JAN1970: 315619200 */
  sasDT_const = unixDT + 315619200;
 
  /* DHMS function calculates datetime given values for */
  /*    date, hour, minute, and seconds                 */
  /* In this case, "seconds" is a very high value!      */
  sasDT_dhms = dhms('01jan1970'd,0,0,unixDT);
 
  /* converting a value from milliseconds */
  sasDT_ms = dhms('01jan1970'd,0,0,unixDTms/1000);
 
  /* Convert from UTC to local time using GMTOFF */
  /* use TZONEOFF function in 9.4! */
  sasDT_local = dhms('01jan1970'd,0,0,unixDT + gmtoff()); 
run;

Sample output from this program (which I ran from my EDT timezone):
time_out
* I intentionally avoided any discussion of leap seconds that go along with UTC, but you can learn more about that here.

See also

Read a Microsoft datetime value into a SAS datetime value

Post a Comment