ColdFusion Date Logic - coldfusion

I am embarrassed to admit that I am not the greatest when it comes to dates and date logic ColdFusion.
<!---checks frequency for form schedule and sets datepart. RecordType_Frequency is a column in database daily, weekly, monthly etc.--->
<CFSWITCH expression="#RecordType_Frequency#">
<CFCASE value="Daily">
<CFSET datepart = "d">
</CFCASE>
<CFCASE value="Weekly">
<CFSET datepart = "ww">
</CFCASE>
<CFCASE value="Monthly">
<CFSET datepart = "m">
</CFCASE>
<CFCASE value="Quarterly">
<CFSET datepart = "q">
</CFCASE>
<CFCASE value="Yearly">
<CFSET datepart = "yyyy">
</CFCASE>
</CFSWITCH>
<!---setting dates based on database values for when the form should schedule--->
<!---enddate Uses the RecordType_Frequency_StartDate column from the database which is a date in the past. Coefficient is a stored db value for the frequency 1,2 etc. for could scheduled every 1 year, 2 year --->
<cfset enddate = datediff(datepart,RecordType_Frequency_StartDate,todaydate) + Coefficient>
<!---start date is set to current RecordType_Frequency_StartDate which is a column value from the database--->
<cfset startdate = RecordType_Frequency_StartDate>
<!---sets the next start date for when the for should schedule based on historic db start date--->
<cfset new_date = dateformat(DateADD(datepart,Coefficient,startdate),'MM-DD-YYYY')>
<cfloop from="1" to="#enddate#" index="i">
<cfset new_date = dateformat(DateADD(datepart,Coefficient,startdate),'MM-DD-YYYY')>
<cfset startdate = new_date>
<cfset diff = datediff(datepart,RecordType_Frequency_StartDate,startdate)>
<cfif (startdate GT todaydate)>
<cfset next_date= startdate>
<cfoutput>
<!---I need this output to equal the next date value that would fall based on the schedule, future date. I am seeing multiple dates and need to figure out how to capture would weould truly be the next scheduled date--->
Next Date = #diff# - #dateformat(next_date)#<br />
</cfoutput>
</cfif>
</cfloop>
In summary, I have forms that are on a schedule. The start/set up date is the only date that I have to use. I need to to grab or populate the next scheduled date for the form using what information I have. Obviously the next creation date needs to fall in the future as this date will be used in conjunction with a scheduled event.
I have posted the code with comments and need help grabbing the next logical date closest to the current date that should fall in sequence.

http://cfquickdocs.com/#DateAdd
If all you need is the next possible date, use the dateadd() function.
For example, if you want the next weekday use: dateadd("w", 1, now())

Assuming I understand your problem, then a UDF that I wrote a while back might solve your problem:
http://www.bryantwebconsulting.com/blog/index.cfm/2011/2/24/EnglinshFriendly-Interval-Calculation
Among several other options for the "interval" argument, it should also accept the ones you use in your switch block.

Related

Using Ben Nadel POI to read excel

I'm using Ben Nadel's Reading Excel Files With ColdFusion And POI codes at https://www.bennadel.com/blog/472-reading-excel-files-with-coldfusion-and-poi.htm to read my excel file.
With his example codes I can read the excel file when rows in my excel are made uniform. Ben mentioned about this in his comment. But my excel however does not always has uniform rows. Some has birth date, some does not have gender, etc.
Ben Nadel's codes produce error once it hits the empty cell. It says, the
objCell variable does not exist. The following code crashes:
<cfset objCell = objRow.GetCell(JavaCast( "int", intCell )) />
Does anyone has an example and do not mind sharing the codes that can also read empty cells when looping?
I'm pasting Ben's codes here:
<!---
Create the Excel file system object. This object is
responsible for reading in the given Excel file.
--->
<cfset objExcelFileSystem = CreateObject(
"java",
"org.apache.poi.poifs.filesystem.POIFSFileSystem"
).Init(
CreateObject(
"java",
"java.io.FileInputStream"
).Init(
ExpandPath( "./jenna_jameson.xls" )
)
) />
<!---
Get the workbook from the Excel file system object that
we just created. Remember, the workbook contains the
Excel sheets that have our data.
--->
<cfset objWorkBook = CreateObject(
"java",
"org.apache.poi.hssf.usermodel.HSSFWorkbook"
).Init(
objExcelFileSystem
) />
<!---
For this demo, we are only interested in reading in the
data from the first sheet. Remember, since Java is zero-
based, not one-based like ColdFusion, the first Excel
sheet is at index ZERO (not ONE).
--->
<cfset objSheet = objWorkBook.GetSheetAt(
JavaCast( "int", 0 )
) />
<!---
We are going to build a ColdFusion query that houses the
Excel data, but we don't know anything about the data
just yet. So, just create the place holder for the query
and then we will add to it when we have more information.
--->
<cfset qCell = "" />
<!---
Get the Excel sheet's row iterator. This appears to be some
sort of implementation of the Java class java.util.TreeMap,
but I don't know much about that. What I do know, is that
this will allow us to loop over the rows in the Excel file
until there are no more to loop over. The interface for it
looks like the standard iterator interface.
--->
<cfset objRowIterator = objSheet.rowIterator() />
<!---
User the row iterator to loop over all the physical rows in
the Excel sheet. This condition checks to see if we have a
row to read in. At this point, the iterator is NOT pointing
at a valid Excel data row.
--->
<cfloop condition="objRowIterator.HasNext()">
<!---
We have determined that we have a valid row to read.
Now, move the iterator to point to this valid row.
--->
<cfset objRow = objRowIterator.Next() />
<!---
Get the number of physical cells in this row. While I
think that this can possibly change from row to row,
for the purposes of this demo, I am going to assume
that all rows are uniform and that this row is a model
of how the rest of the data will be displayed.
--->
<cfset intCellCount = objRow.GetPhysicalNumberOfCells() />
<!---
Check to see if the query variable we have it actually
a query. If we have not done anything to it yet, then
it should still just be a string value (Yahoo for
dynamic typing!!!). If that is the case, then let's use
this first data row to set up the query object.
--->
<cfif NOT IsQuery( qCell )>
<!---
Create an empty query. Doing it this way creates a
query with neither column nor row values.
--->
<cfset qCell = QueryNew( "" ) />
<!---
Now that we have an empty query, we are going to
loop over the cells COUNT for this data row and for
each cell, we are going to create a query column
of type VARCHAR. I understand that cells are going
to have different data types, but I am chosing to
store everything as a string to make it easier.
--->
<cfloop index="intCell" from="0" to="#(intCellCount - 1)#"
step="1">
<!---
Add the column. Notice that the name of the
column is the text "column" plus the column
index. I am starting my column indexes at ONE
rather than ZERO to get it back into a more
ColdFusion standard notation.
--->
<cfset QueryAddColumn(qCell,"column#(intCell + 1)#",
"CF_SQL_VARCHAR",ArrayNew( 1 )) />
</cfloop>
</cfif>
<!---
ASSERT: Whether we are on our first Excel data row or
our Nth data row, at this point, we have a ColdFusion
query object that has the proper columns defined.
--->
<!---
Add a row to the query so that we can store this row's
data values.
--->
<cfset QueryAddRow( qCell ) />
<!--- Loop over the cells in this row to find values. --->
<cfloop index="intCell" from="0" to="#(intCellCount - 1)#"
step="1">
<!---
When getting the value of a cell, it is important
to know what type of cell value we are dealing
with. If you try to grab the wrong value type,
an error might be thrown. For that reason, we must
check to see what type of cell we are working with.
These are the cell types and they are constants
of the cell object itself:
0 - CELL_TYPE_NUMERIC
1 - CELL_TYPE_STRING
2 - CELL_TYPE_FORMULA
3 - CELL_TYPE_BLANK
4 - CELL_TYPE_BOOLEAN
5 - CELL_TYPE_ERROR
--->
<!--- Get the cell from the row object. --->
----- **When it hit an empty cell CF throws error** ---
<cfset objCell = objRow.GetCell(JavaCast( "int", intCell)) />
<!--- Get the type of data in this cell. --->
<cfset objCellType = objCell.GetCellType() />
<!---
Get teh value of the cell based on the data type.
The thing to worry about here is cell forumlas and
cell dates. Formulas can be strange and dates are
stored as numeric types. For this demo, I am not
going to worry about that at all. I will just grab
dates as floats and formulas I will try to grab as
numeric values.
--->
<cfif (objCellType EQ objCell.CELL_TYPE_NUMERIC)>
<!---
Get numeric cell data. This could be a
standard number, could also be a date value.
I am going to leave it up to the calling
program to decide.
--->
<cfset objCellValue = objCell.GetNumericCellValue() />
<cfelseif (objCellType EQ objCell.CELL_TYPE_STRING)>
<cfset objCellValue = objCell.GetStringCellValue() />
<cfelseif (objCellType EQ objCell.CELL_TYPE_FORMULA)>
<!---
Since most forumlas deal with numbers, I am
going to try to grab the value as a number. If
that throws an error, I will just grab it as a
string value.
--->
<cftry>
<cfset objCellValue = objCell.GetNumericCellValue() />
<cfcatch>
<!---
The numeric grab failed. Try to get the
value as a string. If this fails, just
force the empty string.
--->
<cftry>
<cfset objCellValue = objCell.GetStringCellValue() />
<cfcatch>
<!--- Force empty string. --->
<cfset objCellValue = "" />
</cfcatch>
</cftry>
</cfcatch>
</cftry>
<cfelseif (objCellType EQ objCell.CELL_TYPE_BLANK)>
<cfset objCellValue = "" />
<cfelseif (objCellType EQ objCell.CELL_TYPE_BOOLEAN)>
<cfset objCellValue = objCell.GetBooleanCellValue() />
<cfelse>
<!--- If all else fails, get empty string. --->
<cfset objCellValue = "" />
</cfif>
<!---
ASSERT: At this point, we either got the cell value
out of the Excel data cell or we have thrown an
error or didn't get a matching type and just
have the empty string by default. No matter what,
the object objCellValue is defined and has some
sort of SIMPLE ColdFusion value in it.
--->
<!---
Now that we have a value, store it as a string in
the ColdFusion query object. Remember again that my
query names are ONE based for ColdFusion standards.
That is why I am adding 1 to the cell index.
--->
<cfset qCell[ "column#(intCell + 1)#" ][ qCell.RecordCount ] =
JavaCast( "string", objCellValue ) />
</cfloop>
</cfloop>
<!---
At this point, the excel data should be in a ColdFusion
query object. However, if the query did not contain any
record, then the row iterator was never launched which
mean we never actually defined a query. As one final check
just make sure we are dealing with a query.
--->
<cfif NOT IsQuery( qCell )>
<!--- Just define an empty query. --->
<cfset qCell = QueryNew( "" ) />
</cfif>
Perform below two steps to read empty cells also in ColdFusion-8:
Add a variable outside loops to represent HSSFCell. Example:
<cfset jCell = createObject("java", "org.apache.poi.hssf.usermodel.HSSFCell")>
Use function objRow.GetLastCellNum() instead of objRow.GetPhysicalNumberOfCells(). Example:
<cfset intCellCount = objRow.GetLastCellNum() />
Add a <cfif> condition to set value of variable objCellType based on existence of variable objCell. Example:
<cfset objCell = objRow.GetCell(JavaCast( "int", intCell)) />
<cfif structKeyExists(variables, "objCell")>
<cfset objCellType = objCell.GetCellType() />
<cfelse>
<cfset objCellType = jCell.CELL_TYPE_BLANK />
</cfif>
Replace variable name objCell in all references to objCell.CELL_TYPE_[type_name] with variable name created in step 1. Example:
<cfif (objCellType EQ jCell.CELL_TYPE_NUMERIC)>

group cfdirectory by dateLastModified (date not time)

I am running ColdFusion 9 and Oracle database. I am trying to group all files from a directory by the dateLastModified using:
<cfdirectory action="LIST"
directory="#path_to_files#"
name="res"
sort="datelastmodified desc,name ASC"
filter="#filters#">
For example:
Date of File 1: 2016-10-03 11:49:00 am
Date of File 2: 2016-10-03 07:49:00 am
Date of File 3: 2016-08-03 07:49:00 am
File 1 & 2 should be group 1 and File 3 should be group 2
If I use a query of query, how can I compare the date based on days not time? This is what I have so far but it does not seem to work. I get no records.
<cfquery name="getfiles" dbtype="query">
SELECT name, datelastmodified
FROM res
WHERE CAST( [datelastmodified] as date) = CAST(<cfqueryparam cfsqltype="CF_SQL_DATE" value="#d#"/> as date)
</cfquery>
Does anyone know a different method to group files based on days not time?
method to group files based on days not time
To group by Date (only), add the "date only" column to your SELECT list. Then ORDER BY the new column:
<cfquery name="qSortedFiles" dbtype="query">
SELECT CAST(DateLastModified AS Date) AS DateOnly
, Name
, DateLastModified
FROM getFiles
ORDER BY DateOnly
</cfquery>
<cfoutput query="qSortedFiles" group="DateOnly">
#DateOnly#<br>
<cfoutput>
- #DateLastModified#<br>
</cfoutput>
</cfoutput>
I get no records.
FWIW, the reason is that despite using CAST ... as Date, apparently CF still preserves the "time" portion internally. So the WHERE clause is still comparing apples and oranges:
<!--- comparing Date and Time to Date (only)
WHERE 2016-08-03 07:49:00 am = 2016-08-03 00:00:00 am
Instead of using an equals comparison, use this approach:
WHERE DateLastModified >= {TheStartDateAtMidnight}
AND DateLastModified < {TheNextDayAtMidnight}
This type of comparison is more flexible as it works with both date and time columns - and dates (only).
WHERE CAST(DateLastModified AS Date) >= <cfqueryparam value="#someDate#" cfsqltype="cf_sql_date">
AND CAST(DateLastModified AS Date) < <cfqueryparam value="#dateAdd('d', 1, someDate)#" cfsqltype="cf_sql_date">
Step 1 - Use cfdirectory to get a query object.
Step 2 - Add a column using queryaddcolumn.
Step 3 - Loop through the query. Use querySetCell and dateformat on your new column
Step 4 - use cfoutput, group by your new column, to do what you need to do. Your cfdirectory tag already has the data sorted.
So I figured it out. Thanks to #Dan and #Leigh for their suggestion. I used both as guides to get what I wanted.
I used cfdirectory to get a query object.
I created a new query using QueryNew, QueryAddRow and QuerySetCell.
On the columns of the new query contained the formatted date (mm/dd/yyyy). Make sure that you declare the column as varchar not date when setting the column names QueryNew.
I used cfloop and group option and cfoutput to display the records.
<cfset path_to_files = "d:\inetpub\wwwroot\mailrideli\webrpt\">
<cfset filters = "*.pdf|*.txt|*.xls">
<cfdirectory action="LIST" directory="#path_to_files#" name="res" sort="datelastmodified desc,name ASC" filter="#filters#">
<cfset q = QueryNew("Name,dateformated,datelastmodified, size","Varchar, Varchar, date, varchar")>
<cfset count = 1>
<cfset newRow = QueryAddRow(q,res.recordCount)>
<cfloop query="res">
<cfset d = DateFormat(res.dateLastModified,"mm/dd/yyyy")>
<cfset temp = QuerySetCell(q, "Name", "#res.name#", count)>
<cfset temp = QuerySetCell(q, "dateformated", "#d#", count)>
<cfset temp = QuerySetCell(q, "datelastmodified", "#res.datelastmodified#", count)>
<cfset temp = QuerySetCell(q, "size", "#res.size#", count)>
<cfset count += 1>
</cfloop>
<cfoutput>
<cfloop query="q" group="dateformated">
#q.dateformated#<br />
<table>
<tr>
<th>File Name</th>
<th>Size (bytes)</th>
<th>Last Modified</th>
</tr>
<cfloop>
<tr>
<td>#q.name#</td>
<td>#q.size#</td>
<td>#dateformat(q.dateLastModified, 'mm/dd/yyyy')# #timeformat(q.dateLastModified, 'hh:mm:ssTT')#</td>
</tr>
</cfloop>
</table>
</cfloop>
</cfoutput>
I hope it helps anyone out there.

How to compare a string from a query results?

I want to get the total number of records that a column has for a string.
For example, seller column = 'ONA'. I want to know how many records are equal to "ONA" and how many are not.
This is what I have:
<cfset stringONA= "ONA">
<CFSET onaseller= 0>
<CFSET notseller=0>
<cfloop query="getunion_again">
<cfif #getunion_again.seller# eq stringONA>
<cfset onaseller = onaseller +1 >
<P>TEST</P>
<cfelse>
<cfset notseller = notseller +1>
</cfif>
</cfloop>
<cfdump var=#onaseller #>
<cfdump var=#notseller #>
I'm not getting any errors, just not getting any count.
Wouldn't it just be easier to do a query-of-query to get the number of records that match your criteria without looping?
<cfquery name="qryCount" dbtype="query">
SELECT COUNT(*) AS positive_count
FROM getunion_again
WHERE seller = <cfqueryparam cfsqltype="cf_sql_varchar" value="#stringONA#">
</cfquery>
<cfset onaseller = qryCount.positive_count>
<cfset notseller = getunion_again.recordcount - onaseller>
One can use listValueCountNoCase() to count the matches in a list, and one can use valueList() to extract a query column as a list. And the number of mismatches is the number of rows less the number of matches.
So:
values = valueList(getunion_again.seller);
onaseller = listValueCountNoCase(values, stringONA);
notseller = getunion_again.recordCount - onaseller;

ColdFusion having multiple cfqueries in cffunction

I am trying to write a function for a survey where it pulls questions from a database. The catch is that there are both active and unactive questions. I need older questions to show up when someone views the results from an old survey.
Here is the code I am trying within a CFC:
<cffunction name="getAllQuestions" access="public" returntype="query">
<cfargument name="survey" default=0>
<cfif len(#survey#) gt 0>
<cfquery name="getsdate" datasource="blah.database">
select * from past_survey
where survey_id = #survey#
</cfquery>
<cfreturn getsdate>
</cfif>
<cfquery name="getquestions" datasource="blah.database">
select * from pool_questions
<cfif len(#survey#) eq 0>
where active_flag='Y'
<cfelse>
where <cfqueryparam value="#dateformat
(getsdate.survey_date, "yyyy/mm/dd")#"> BETWEEN start_date AND
end_date
</cfif>
order by qtn_nb
</cfquery>
<cfreturn getquestions>
</cffunction>
#survey# is the survey id which is generated by the form. What I am trying to do is that if survey has a value to run query getsdate. Then the second query would run no matter if survey has a value or not. If there is not value it should pull all active questions. If there is a value then it should check if the survey date is between the start date and end date for past questions.
Any advice on how to make this work would be greatly appreciated. Thank you in advance!
<cffunction name="getAllQuestions" access="public" returntype="struct">
<cfargument name="survey" required="true" default="0" type="numeric">
<cfset var qryReturn = ""> <!---Always var scope your variables to prevent them from leaking to other functions --->
<cfset var structReturn = structNew()>
<cfset structReturn.pastSurvey = "">
<cfset structReturn.surveyQuestions = "">
<cfif survey GT 0>
<cfquery name="qryReturn" datasource="blah.database">
SELECT *
FROM past_survey
<!--- Always cfqueryparam to prevent SQL injection attacks & also always reference the scope to prevent confusion --->
WHERE survey_id = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.survey#">
</cfquery>
<cfset structReturn.pastSurvey = qryReturn>
<cfelse>
<cfquery name="qryReturn" datasource="blah.database">
SELECT *
FROM pool_questions
<cfif arguments.survey EQ 0>
WHERE active_flag = 'Y'
<cfelse>
WHERE <cfqueryparam value="#dateformat
(getsdate.survey_date, "yyyy/mm/dd")#"> BETWEEN start_date AND
end_date
</cfif>
ORDER BY qtn_nb
</cfquery>
<cfset structReturn.surveyQuestions = qryReturn>
</cfif>
<cfreturn structReturn>
</cffunction>
You probably should be doing this in two separate functions, but I will attempt to answer your question.
My code will return a struct of queries (you can change to an array if you prefer) that returns a past survey and the survey questions
Note: In your example code, you have a few bad practices.
You are checking the length of the survey value rather than checking the value itself.
If you want to ensure that survey always has a value regardless of if it is passed or not, set requried=true and give it a default value.
Use cfqueryparam to prevent sql injection attacks
Any variables created in the function need to be var scoped to prevent them from leaking to other cffunctions in the same cfcomponent. I always do this at the top. Yes, even the name you give a cfquery needs to be var scoped.
Since you are doing a return after your first query, if the survey value is greater than 0 it will never get to the second query where it has the date check.
I see the following problems you need to address.
First, your survey argument has a default value of 0 and you are doing conditional logic on the length of it. The length of "0" is 1, so that condition will always return true.
Next, you state that you want the 2nd query to run whether the first one runs or not, but you refer to a value from the 1st query in the 2nd one. That means if the 1st query does not run, the 2nd one will crash due to an undefined variable.
Next, dateformat returns a string. Applying it the way you do in the 2nd query is at best unnecessary, and at worse, and indication that you are storing the start and end dates in pool_questions as strings. If you are attempting to strip out the time portion of the datefield in the first query, ColdFusion has a cast() function for that.
Also, scope your variables. ie - instead of <cfif len(survey), do this <cfif len(arguments.survey).
Also, var your local variables. In this case, it's the names of your two queries.
That should get you started.

Can CFSWITCH prevent SQL injection when used for a sort column?

I use the following technique to ensure that any sort column params coming from the client go through a ListFindNoCase() function:
<cfif ListFindNoCase("date,score", params.order) EQ 0>
<cfset params.order = "date">
</cfif>
This way, any sort column request gets vetted against the list values before being sent to the server. I then added the following code to my function:
<cfswitch expression="#params.order#">
<cfcase value="date">
<cfset params.order = "date DESC">
</cfcase>
<cfcase value="score">
<cfset params.order = "score ASC">
</cfcase>
<cfdefaultcase>
<cfset params.order = "date DESC">
</cfdefaultcase>
</cfswitch>
Since default case will always set order to "date DESC" if the expression does not match the first two cases, doesn't that render ListCaseNoFind() redundant?
I wanted to make sure that this is true before I removed the ListFindNoCase() function!
Sure, that is safe. You're hardcoding the order by, so there is no chance that extraneous SQL can be injected.