how to keep datatype when substracting day from date/time column in SAS - date

My question is really simple, hope someone deigns to answer!.
Being very to new SAS, the date and its formats really is confusing me.
I have timestamp column from which I need to substract 2 days while keeping its datatype
The value of the column is "2022-04-20-19.37.57.714699"
What I need is "2022-04-18-19.37.57.714699"
When I try this I get number datatype:
PROC SQL;
CREATE TABLE my_table AS
SELECT
cust_id,query_date, (query_date)-2 as calc_date
FROM other_table
;quit;
I try format,datetime function, but ended up with "Statement is not valid or it is used out of proper order"
Thanks

Assuming that the QUERY_DATE variable is numeric and has datetime values in it (the number of seconds since 1960) then you can use the INTNX() function with the DTDAY interval to adjust the value by two days. To keep the same time of day use SAME for the alignment parameter.
intnx('dtday',query_date,-2,'same')
Alternatively you could just subtract 48 hours worth of seconds from the value.
query_date -2*'24:00:00't
If you want the values to display in a human readable way then attach any of the many datetime formats, such as DATETIME to the new variable.
CREATE TABLE my_table AS
SELECT cust_id,query_date
, intnx('dtday',query_date,2,'same') as calc_date format=datetime20.
FROM other_table
;
If the variable is just a string then you cannot subtract from strings. You will have to convert the strings into numbers to perform arithmetic. You probably have too many decimal places for SAS datetime informats/formats to replicate (and perhaps to be uniquely stored in a floating point value) so just convert the date part and then append back the rest to keep the same time of day. Since dates are stored as number of days you can just subtract the 2 days using normal subtraction.
put(input(query_date,yymmdd10.)-2,yymmdd10.)||substr(query_date,11)

This should solve your problem. The first issue to tackle is converting the date string into a sas datetime. The input() function with the anydtdtm. informat accomplishes that with a small caveat as seen in the output.
data test;
date_txt = '2022-04-20-19.37.57.714699';
query_date = input(date_txt, anydtdtm.); * convert string into sas datetime;
calc_date = intnx('dtday', query_date, -2, 's'); * backup 2 days preserving the time;
format query_date calc_date e8601dt26.6;
run;
date_txt
query_date
calc_date
2022-04-20-19.37.57.714699
2022-04-20T19:37:57.000000
2022-04-18T19:37:57.000000
The default width of the informat is 19 characters which excludes the fractional seconds, but the informat correctly converted the string into a datetime.
To attempt to capture the full width of the date string, I modified the informat to anydtdtm26.. However, that change resulted in an error and missing values for query_date and calc_date. Although the anydtdtm informat is robust with converting a wide variety of date and time formats, I suspected that the problem lies with the periods used to delimit the hours and minutes.
To correct that problem I used prxchange() function to replace the periods after hours and minutes with colons which are standard time componenent delimiters. That change allows the anydtdtm informat to properly convert the fractional seconds.
data test2;
date_txt = '2022-04-20-19.37.57.714699';
date_mod = prxchange('s/\./:/', 2, date_txt); * replace first 2 periods w/ colons;
query_date = input(date_mod, anydtdtm26.); * convert string into sas datetime;
calc_date = intnx('dtday', query_date, -2, 's'); * backup 2 days preserving the time;
format query_date calc_date e8601dt26.6;
run;
date_txt
date_mod
query_date
calc_date
2022-04-20-19.37.57.714699
2022-04-20-19:37:57.714699
2022-04-20T19:37:57.714699
2022-04-18T19:37:57.714699
Although I used a data step to illustrate the solution the functions can also be used in a SQL statement.

Related

Convert character date to SAS date

My data is currently structured in the following way (dummy data below), where the date is formated as such example: 3/7/20 (M/D/YY) but I need the data in the following month-year form: 032020 (i.e. I need format mmyyn6.). I have tried a number of different things to get in this format, but nothing has worked.
Current data structure:
DATA HAVE;
INPUT GROUP $ DATE $ COUNT_CUMU;
DATALINES;
A 3/7/20 2
A 3/8/20 8
A 3/9/20 16
RUN;
These solutions don't work, and give me extraneous numbers.
DATA WANT1;
SET HAVE;
MONTH_YEAR = INPUT(DATE,mmyyn6.);
FORMAT DATE MMDDYY8.;
RUN;
PROC SQL;
CREATE TABLE WANT2 AS
SELECT *,
INPUT(DATE, ANYDTDTM.) AS MONTH_YEAR FORMAT=mmyyn6.
FROM HAVE;
QUIT;
This solution works, but is not the format I need it in.
PROC SQL;
CREATE TABLE WANT3 AS
SELECT *,
INPUT(DATE, ANYDTDTM.) AS MONTH_YEAR FORMAT=DTMONYY7.
FROM HAVE;
QUIT;
Thank you for any advise or code you can share.
It is easy to do what you asked for.
Use the MMDDYY informat to convert the strings into date values. Note that the INPUT() function does not care if you use a width on the informat that is larger than the length of the string being read, so use the maximum width the informat supports.
You can use the MMDDYYN format to display dates without any separator character.
You can use MMYYN format to display only the month and year. But in that case you might always want to change the date values to the first of the month.
And it works for the example data you provided.
DATA HAVE;
INPUT GROUP $ DATE $ COUNT_CUMU;
DATALINES;
A 3/7/20 2
A 3/8/20 8
A 3/9/20 16
;
data want;
set have;
real_date = input(date,mmddyy10.);
format real_date yymmdd10. ;
month_year = intnx('month',real_date,0);
year_month = month_year;
format month_year mmyyn6. year_month yymmn6. ;
run;
Results:
COUNT_ month_ year_
Obs GROUP DATE CUMU real_date year month
1 A 3/7/20 2 2020-03-07 032020 202003
2 A 3/8/20 8 2020-03-08 032020 202003
3 A 3/9/20 16 2020-03-09 032020 202003
If it does not work for you then you need to show examples of the input strings that do not work. Or explain how having a date value that is displayed using the MMDDYYN format does not work for you.
PS You should avoid using only two digits to record or display years. Look up Y2K problem. You should also avoid either MDY or DMY ordering of date digits to avoid confusing 50% of your audience. If you want to use only digits then use YMD order (YYMMDD or YYMMDDN format).

Convert Character Date variable to SAS Date

I have the following Variable called Date in an excel file which I'm reading into SAS:
Date
May2005
June2005
July2005
..
July2015
Both the format and the informat are characters ($8)
I wanted to convert these into a SAS Date variable.
How can I accomplish this task?
I thought about using substr to first create a month and year variable,
then use proc format to convert all the months to numeric (e.g 'jan' = 1).
The use the mdy date function to create a new date. But I wonder if there is a shorter way to accomplish this task?
You can use the ANYDTDTE. informat if you prepend a day to your month+year string.
data want ;
set have ;
actual_date = input('01'||date,anydtdte.);
format actual_date date9.;
run;
Note that the FORMAT or INFORMAT attached to the character variable is meaningless, but having a variable of only length 8 will not allow room to store longer month names. Perhaps the length got set to only 8 because your particular example set of data did not include any longer month names.
If you are running such an old version of SAS that the ANYDTDTE. informat does not exist or does not work with fully spelled out months then you will need to work a little harder. You could transform the string into DATE9 format.
actual_date = input
('01'||substr(date,1,3)||substr(date,length(date)-3)
,DATE9.);
As #Tom hints towards, you have to use an informat that SAS can interpret as a numeric value when reading in character dates. I'm not sure if there is one that reads MONTHYYYYw., (naturally, ANYDTDTE works but I prefer to avoid it). In this case, I would use MONYYw., combined with substr to get the length 3 Month abbreviation and the 2 digit year:
data have;
input Date $13.;
datalines;
January2005
Feburary2005
March2005
April2005
May2005
June2005
July2005
August2005
September2005
October2005
November2005
December2005
;
run;
data want;
set have;
Date2 = input(SUBSTR(Date,1,3)||SUBSTR(Date,length(date)-1,2),MONYY13.);
Format Date2 DATE8.;
run;
proc print data = want; run;

Converting dates and timestamps when inserting data into Teradata

I am block-inserting data from Stata (a statistics package) into a Teradata database. I am having trouble converting dates and timestamps from Stata's native format to Teradata's.
Stata stores dates as days since 01/01/1960, so that 01jan1960 is 0 and 02jan1960 is 1. Timestamps are stored as milliseconds since 01jan1960 00:00:00.000, so that 1000 is 01jan1960 00:00:01. Here are some examples:
timestamp Stata's tstamp date Stata's date
2015-04-13 03:07:08 1744513628000 2015-04-13 20191
2015-04-14 19:55:43 1744660543000 2015-04-14 20192
2015-04-08 11:41:39 1744112499000 2015-04-08 20186
2015-04-15 06:53:34 1744700014000 2015-04-15 20193
I tried 2 approaches. The first involves converting the dates/timestamps to strings in Stata before inserting and then doing something like this once the data is inserted:
ALTER TABLE mytable ALTER date_variable DATETIME
However, I cannot figure out how to do the second part from the documentation I have and after searching the various fora.
The second approach is leaving the dates and timestamps as integers, and then doing some of conversion once the integers are inserted. Perhaps I can also pre-convert dates in Stata to TD's internal format with:
gen td_date = ((year(stata_dt)-1900)*10000 + month(stata_dt)*100 + day(stata_dt))
However, I am not sure what the formula for timestamps would be. I am also not sure how to do the second part (making the integers into dates/timestamps).
You can't change the datatype of a column in Teradata from string to date/timestamp.
But when you insert a string into a date/timestamp column there will be an automatic typecast. So simply convert to a string with 'yyyy-mm-dd' or 'yyyy-mm-dd hh:mi:ss' format.
You could also do the conversion during load on Teradata using calculations, but IMHO the 1st solution is preferable:
-- add the number of days to the start date
DATE '1960-01-01' + stata_dt
-- I use a similar approach for Unix Timestamps starting 1970 :-)
-- split into days and seconds
CAST(DATE '1960-01-01' + (stata_ts / 86400000) AS TIMESTAMP(0))
+ ((stata_ts MOD 86400000 / 1000) * INTERVAL '00:00:01' HOUR TO SECOND)

How do I convert date and seconds fields to a timestamp field in PostgreSQL?

I have a date field, which is just text, and a field showing seconds since midnight.
CREATE TABLE t ("s" text, "d" text, "ts" timestamp)
;
INSERT INTO t ("s", "d")
VALUES
(24855, 20130604),
(24937.7, 20130604)
;
How can I convert these fields into a timestamp, so I can run time-based queries?
See a demo with SQL Fiddle:
http://sqlfiddle.com/#!15/67018/2
Since your data includes fractional seconds, you need to adjust for those:
UPDATE t
SET ts = to_timestamp(d||s, 'YYYYMMDDSSSS.MS');
Assuming milliseconds as the maximum precision. Else employ US for microseconds.
Consider the fine print in the manual:
In a conversion from string to timestamp, millisecond (MS) or
microsecond (US) values are used as the seconds digits after the
decimal point. For example to_timestamp('12:3', 'SS:MS') is not 3
milliseconds, but 300, because the conversion counts it as 12 + 0.3
seconds. This means for the format SS:MS, the input values 12:3,
12:30, and 12:300 specify the same number of milliseconds. To get
three milliseconds, one must use 12:003, which the conversion counts
as 12 + 0.003 = 12.003 seconds.
Also note that to_timestamp() returns timestamp with time zone, which is coerced to timestamp in the context of the update, which turns out all right. For other contexts, you may want to cast explicitly or use this alternative, yielding timestamp:
SELECT d::date + interval '1s' * s::numeric AS ts FROM t;
The plain cast to date (d::date) works because your dates are in standard ISO 8601 format - or so I assume.
Concatenate the two fields together and then use to_timestamp()
UPDATE t SET ts = to_timestamp(d||s, 'YYYYMMDDSSSS');
For a discussion of the formatting string used in to_timestamp, see Table 9-21: Template Patterns for Date/Time Formatting in the PostgreSQL documentation.

Microsoft Hex dates

I have the following from a Microsoft SQL Server database for date/time value:
0x00009CEF00A25634
I found this post:
Help me translate long value, expressed in hex, back in to a date/time
Which seemed to be on the right track but by using the code I didn't get the right dates, are my hex dates in a different format? How would I convert them to a normal date, I am using PHP/PostgreSQL.
select CAST (0x00009CEF00A25634 as datetime) gives 2009-12-30 09:51:03.000
This is two integers. One for the date part 0x00009CEF (decimal 40175) and one for the time part 00A25634 (decimal 10638900). The date part is a signed integer giving number of days since 1 Jan 1900. The time part is an integer representing number of ticks.
There are 300 ticks in a second.
It can be seen that the following also returns the same result
SELECT DATEADD(MILLISECOND,10638900*10/3.0, DATEADD(DAY,40175, '19000101'))
You will need to figure out how to apply this to postgres.
Edit: an answer here apparently does this. I haven't tested it myself.
This works for me while migrating from SQL to MySQL :
SELECT (CAST('1900-01-01 00:00:00' + INTERVAL CAST(CONV(substr(HEX( 0x0000A249004576D0 ),1,8), 16, 10) AS SIGNED) DAY + INTERVAL CAST(CONV(substr(HEX( 0x0000A249004576D0 ),9,8), 16, 10) AS SIGNED)* 10000/3 MICROSECOND AS DATETIME)) AS newdate