/* The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is comprised of the ADF directory The Initial Developer of the Original Code is PaperThin, Inc. Copyright (c) 2009-2025. All Rights Reserved. By downloading, modifying, distributing, using and/or accessing any files in this directory, you agree to the terms and conditions of the applicable end user license agreement. */ /* *************************************************************** Author: PaperThin, Inc. Name: date_2_0.cfc Summary: Date Utils functions for the ADF Library Version: 2.0 History: 2015-08-31 - GAC - Created 2016-09-29 - GAC - Added functions to calculated the first and last days of the quarter 2020-12-16 - GAC - Added the countDown() and countDownText() functions 2025-05-15 - GAC - Added the buildTimespanTimeStruct() buildTickCountTimeStruct() functions */ component displayname="date_2_0" extends="date_1_2" hint="Date Utils functions for the ADF Library" { property name="version" value="2_0_2"; property name="type" value="singleton"; property name="wikiTitle" value="Date_2_0"; /* Author: PaperThin, Inc. Name: $firstDayOfQuarter Summary: Returns date for first day of the quarter for the provided month/year Returns: Date Arguments: Numeric - inMonth Numeric - inYear History: 2016-09-29 - GAC - Created 2017-01-27 - GAC - Updated inYear parameter issue */ public date function firstDayOfQuarter(required numeric inMonth, required numeric inYear) { var dateObj = createDate(arguments.inYear, arguments.inMonth, 1); return DateAdd("q", Quarter(dateObj)-1, '01-01-' & year(dateObj)); } /* Author: PaperThin, Inc. Name: $firstDayOfQuarterByDate Summary: Returns date for first day of the quarter from the provided date Returns: Date Arguments: Date - inDate History: 2016-09-29 - GAC - Created */ public date function firstDayOfQuarterByDate(date inDate=Now()) { return firstDayOfQuarter(inMonth=Month(arguments.inDate),inYear=Year(arguments.inDate)); } /* Author: PaperThin, Inc. Name: $lastDayOfQuarter Summary: Returns date for last day of the quarter for the provided month/year Returns: Date Arguments: Numeric - inMonth Numeric - inYear History: 2016-09-29 - GAC - Created */ public date function lastDayOfQuarter(required numeric inMonth, required numeric inYear) { var qtrStartDate = firstDayOfQuarter(inMonth=arguments.inMonth,inYear=arguments.inYear); return DateAdd("d",-1,DateAdd("m",3,qtrStartDate)); } /* Author: PaperThin, Inc. Name: $lastDayOfQuarterByDate Summary: Returns date for last day of the quarter from the provided date Returns: Date Arguments: Date - inDate History: 2016-09-29 - GAC - Created */ public date function lastDayOfQuarterByDate(date inDate=Now()) { return lastDayOfQuarter(inMonth=Month(arguments.inDate),inYear=Year(arguments.inDate)); } /* Author: PaperThin, Inc. Name: $epochTimeToLocalDate Summary: Returns datetime for provided Epoch Time (number of seconds since Jan 01 1970) Returns: Date Arguments: epoch - date in seconds History: 2016-09-29 - GAC - Created */ public date function epochTimeToLocalDate(epoch) { return DateAdd("s",arguments.epoch,DateConvert("utc2Local", "January 1 1970 00:00")); } /* countDown(dateTime) * Counts down to a date. * * @param dateTime Date to count down to. (Required) * @return Returns a structure. * @author Nathan Dintenfass (nathan@changemedia.com) * @version 1, October 11, 2002 2020/12/16 - Added Updated to break into 2 functions Updated this function to only return the data structure */ public struct function countDown(required date dateTime) { var retData = {}; var dateToUse = parseDateTime(arguments.dateTime); //grab now(), so we don't have to do it over and over var rightNow = now(); //a string used to return var returnString = ""; //var to hold the sum to determine if it has passed var sum = ""; //a var to tack on the beginning and end of string return var prefix = ""; var suffix = ""; //get the absolute difference in years, months, days, hours, minutes and seconds retData.years = dateDiff("yyyy",rightNow,dateToUse); retData.months = dateDiff("m",rightNow,dateToUse); retData.days = dateDiff("d",rightNow,dateToUse); retData.hours = dateDiff("h",rightNow,dateToUse); retData.minutes = dateDiff("n",rightNow,dateToUse); retData.seconds = dateDiff("s",rightNow,dateToUse) - (60*retData.minutes); //now go back through them in reverse order and delete off the appropriate units retData.minutes = retData.minutes - (60*retData.hours); retData.hours = retData.hours - (24*retData.days); retData.days = retData.days - dateDiff("d",dateAdd("m",-1*retData.months,dateToUse),dateToUse); retData.months = retData.months - (12*retData.years); return retData; } /* countDownText(dateTime,showTime) * Counts down to a date/time. * * @param dateTime Date to count down to. (Required) * @param showTime Display Hours,Minutes,Seconds after the Years,Months,Days * @return Returns a string * @author Nathan Dintenfass (nathan@changemedia.com) * @version 1, October 11, 2002 2020/12/16 - Added Updated to break into 2 functions Updated date string formatting */ public string function countDownText(required date dateTime, boolean showTime=true) { //a struct to hold the data for the countdown var countdownData = countDown(dateTime=arguments.dateTime); //a string used to return var returnString = ""; //var to hold the sum to determine if it has passed var sum = ""; //a var to tack on the beginning and end of string return var prefix = ""; var suffix = ""; //first, gather the sum, to know if it's future or past sum = countDownData.years + countdownData.months + countdownData.days + countdownData.hours + countdownData.minutes + countdownData.seconds; //if the sum is 0, it's right now! if( NOT sum ) return "Right Now!"; //if the sum is negative, it's in the past, so multiply -1 for display purposes if( sum LT 0 ) { countdownData.seconds = countdownData.seconds * -1; countdownData.minutes = countdownData.minutes * -1; countdownData.hours = countdownData.hours * -1; countdownData.days = countdownData.days * -1; countdownData.months = countdownData.months * -1; countdownData.years = countdownData.years * -1; //prefix = "passed "; suffix = " ago"; } //add the prefix returnString = returnString & prefix; // build the return string if( countDownData.years GT 0 ) { returnString = returnString & countdownData.years; if ( countDownData.years EQ 1 ) returnString = returnString & " year"; else returnString = returnString & " years"; } if( countdownData.months GTE 0 ) { if( LEN(TRIM(returnString)) ) returnString = returnString & ", "; returnString = returnString & countdownData.months; if ( countDownData.months EQ 1 ) returnString = returnString & " month"; else returnString = returnString & " months"; } if( countdownData.days GTE 0 ) { if( LEN(TRIM(returnString)) ) returnString = returnString & ", "; returnString = returnString & countdownData.days; if ( countDownData.days EQ 1 ) returnString = returnString & " day"; else returnString = returnString & " days"; } if( countdownData.hours GTE 0 AND arguments.showTime ) { if( LEN(TRIM(returnString)) ) returnString = returnString & ", "; returnString = returnString & countdownData.hours; if ( countDownData.hours EQ 1 ) returnString = returnString & " hour"; else returnString = returnString & " hours"; } if( countdownData.minutes GTE 0 AND arguments.showTime ) { if( LEN(TRIM(returnString)) ) returnString = returnString & ", "; returnString = returnString & countdownData.minutes; if ( countDownData.minutes EQ 1 ) returnString = returnString & " minute"; else returnString = returnString & " minutes"; } if( countdownData.seconds GTE 0 AND arguments.showTime ) { if( LEN(TRIM(returnString)) ) returnString = returnString & ", "; returnString = returnString & countdownData.seconds; if ( countDownData.seconds EQ 1 ) returnString = returnString & " second"; else returnString = returnString & " seconds"; } //add the suffix returnString = returnString & suffix; //return the string return returnString; } /* calcExecutionTime(startMS,endMS,mask,trimZeros); mask - string Use dd for days, hh for hours, mm for minutes, ss for seconds, lll for milliseconds padded with leading zeros Use d for days, h for hours, m for minutes, s for seconds, l for milliseconds Omit a mask unit to not display it and add its value to the next smallest unit. History: 2025-05-14 - GAC - Added the trimZeros argument to trim sets of leading zeros to help with readability. - Updated to convert the 'n' key in the updated timestruct to a 'm' since, since this function does not calculate months */ public string function calcExecutionTime(required numeric startMS, required numeric endMS, string mask='dd:hh:mm:ss.lll', boolean trimZeros=true) { var retStr = '00:00:00:00.000'; var execMS = ABS(arguments.endMS - arguments.startMS); // var execMS = ( arguments.startMS GT arguments.endMS ) ? (arguments.startMS - arguments.endMS) : (arguments.endMS - arguments.startMS); var timeData = createTimeStruct(timespan=execMS); var signStr = ( arguments.startMS GT arguments.endMS ) ? '-' : ''; var maskStr = arguments.mask; var v = 0; var uList = 'd:24,h:60,m:60,s:60,l:1000'; var mList = 'dd,hh,mm,ss,lll'; //var mList = 'dd,d,hh,h,mm,m,ss,s,lll,ll,l'; var zArr = []; var zItm = 0; var uListRnds = ListLen(uList)-1; for ( var r=1; r <= uListRnds; r++) { var unitGrp = ListGetAt(uList,r,','); var nextPos = r+1; var nextGrp = ListGetAt(uList,nextPos,','); var unit = ListFirst(unitGrp,':'); var multi = ListLast(unitGrp,':'); var next = ListFirst(nextGrp,':'); if ( FindNoCase(unit,maskStr) EQ 0 AND timeData[unit] GT 0 ) { if ( next EQ 'l' ) next = 'ms'; var combVal = timeData[unit] * VAL(multi); timeData[next] = timeData[next] + combVal; } } if ( NOT StructIsEmpty(timeData) ) { for ( var i=1; i <= ListLen(mList,','); i++) { var mItem = ListGetAt(mList,i,','); var key = LEFT(mItem,1); if ( key EQ 'l' ) key = 'ms'; else if ( key EQ 'm' ) key = 'n'; if ( FindNoCase(mItem,maskStr) ) { if ( LEN(mItem) > 1 ) // AND key NEQ "d" { if ( key EQ 'ms' ) v = NumberFormat(timeData[key],'000'); else v = NumberFormat(timeData[key],'00'); } else v = NumberFormat(timeData[key],'0'); maskStr = replace(maskStr, mItem, v, "ONE"); } } retStr = maskStr; } // Trim the leading 00 digits if ( arguments.trimZeros ) { zArr = ListToArray(retStr,':'); for ( var j=1; j <= ArrayLen(zArr); j++ ) { zItm = VAL(zArr[j]); if ( zItm EQ 0 ) retStr = ListDeleteAt(retStr,1,':'); else break; } } // if needed, add the negative sign and trim retStr = TRIM(( TRIM(signStr) NEQ '' ) ? signStr & retStr : retStr); return retStr; } /* calcExecutionTimeFromDates(startDT,endDT,mask,trimZeros); mask - string Use dd for days, hh for hours, mm for minutes, ss for seconds, lll for milliseconds padded with leading zeros Use d for days, h for hours, m for minutes, s for seconds, l for milliseconds Omit a mask unit to not display it and add its value to the next smallest unit. History: 2025-05-13 - GAC - Updated to use the logic from the calcExecutionTime() as the main body of the function to eliminate duplicated code in 2 similar functions */ public string function calcExecutionTimeFromDates(required date startDT, required date endDT, string mask='dd:hh:mm:ss.lll', boolean trimZeros=true) { var retStr = '00:00:00:00.000'; var sDateTime = ParseDateTime(arguments.startDT); var eDateTime = ParseDateTime(arguments.endDT); var startMS = sDateTime.getTime(); var endMS = eDateTime.getTime(); retStr = calcExecutionTime(startMS=startMS, endMS=endMS, mask=arguments.mask, trimZeros=arguments.trimZeros); return retStr; } /* createTimeStruct(timespan,mask) https://cflib.org/udf/CreateTimeStruct * Converts a given number of days, hours, minutes, OR seconds to a struct of days, hours, minutes, AND seconds. * * @param timespan The timespan to convert. * @param mask The the base time unit to use for the provided timespan value. * @return Returns a structure. * @author Dave Pomerance (dpomerance@mos.org) * @version 1, January 7, 2002 2023/01/19 - Added and updated to add milliseconds 2025/05/14 - Updated to fix the CF INT limitation with a time struct more that 20 days which created an INT value larger than 2,147,483,647 */ public struct function createTimeStruct(required numeric timespan, string mask="ms") { return buildTickCountTimeStruct(tickCount=timespan, keys="d,h,n,s,ms", baseUnit=arguments.mask); //baseUnit - not implemented yet } /* public struct function createTimeStruct(required numeric timespan, string mask="ms") { var retStruct = {}; // if timespan isn't an integer, convert mask towards s until timespan is an integer or mask is ms while ( INT(arguments.timespan) neq arguments.timespan AND arguments.mask neq "ms" ) { if (arguments.mask eq "d") { arguments.timespan = arguments.timespan * 24; arguments.mask = "h"; } else if (arguments.mask eq "h") { arguments.timespan = arguments.timespan * 60; arguments.mask = "m"; } else if (arguments.mask eq "m") { arguments.timespan = arguments.timespan * 60; arguments.mask = "s"; } else if (arguments.mask eq "s") { arguments.timespan = arguments.timespan * 60; arguments.mask = "ms"; } } // only 5 allowed values for mask - if not one of those, return blank struct if ( ListFind("d,h,m,s,ms", arguments.mask) ) { // compute seconds if (arguments.mask eq "ms") { retStruct.ms = (arguments.timespan mod 1000) + (arguments.timespan - Int(arguments.timespan)); arguments.timespan = int(arguments.timespan/1000); arguments.mask = "s"; } else retStruct.ms = 0; if (arguments.mask eq "s") { retStruct.s = (arguments.timespan mod 60) + (arguments.timespan - Int(arguments.timespan)); arguments.timespan = int(arguments.timespan/60); arguments.mask = "m"; } else retStruct.s = 0; // compute minutes if (mask eq "m") { retStruct.m = arguments.timespan mod 60; arguments.timespan = int(arguments.timespan/60); arguments.mask = "h"; } else retStruct.m = 0; // compute hours, days if (mask eq "h") { retStruct.h = arguments.timespan mod 24; retStruct.d = int(arguments.timespan/24); arguments.mask = "d"; } else { retStruct.h = 0; retStruct.d = arguments.timespan; } } return retStruct; } */ /* buildTimespanTimeStruct(timespan,keys) - Break down a ColdFusion timespan into its time parts. @param timespan - The timespan value (numeric). @param keys - time parts to add the return struct: y, m, d, h, n, s, ms @return - A structure containing the timespan parts History: 2025-05-14 - Added */ function buildTimespanTimeStruct(required numeric timespan, string keys="y,m,d,h,n,s,ms") { var result = {}; var tmp = {}; // Define milliseconds in various time units as BigInteger tmp.millisecondsPerSecond = createObject("java", "java.math.BigInteger").init("1000"); tmp.secondsPerMinute = createObject("java", "java.math.BigInteger").init("60"); tmp.minutesPerHour = createObject("java", "java.math.BigInteger").init("60"); tmp.hoursPerDay = createObject("java", "java.math.BigInteger").init("24"); // Assumptions for months and years (these are averages and can lead to inaccuracies for specific date differences) tmp.daysPerMonth = createObject("java", "java.math.BigInteger").init("30"); tmp.daysPerYear = createObject("java", "java.math.BigInteger").init("365"); // Calculate derived millisecond constants tmp.millisecondsPerMinute = tmp.millisecondsPerSecond.multiply(tmp.secondsPerMinute); tmp.millisecondsPerHour = tmp.millisecondsPerMinute.multiply(tmp.minutesPerHour); tmp.millisecondsPerDay = tmp.millisecondsPerHour.multiply(tmp.hoursPerDay); tmp.millisecondsPerMonth = tmp.millisecondsPerDay.multiply(tmp.daysPerMonth); tmp.millisecondsPerYear = tmp.millisecondsPerDay.multiply(tmp.daysPerYear); // Convert the input timespan (fractional days) to BigDecimal tmp.timespanBD = createObject("java", "java.math.BigDecimal").init(ARGUMENTS.timespan); // Convert millisecondsPerDay (which is a BigInteger) to BigDecimal for multiplication tmp.millisecondsPerDayAsBigDecimal = createObject("java", "java.math.BigDecimal").init(tmp.millisecondsPerDay); // Multiply to get total milliseconds as a BigDecimal, this tmpains precision tmp.exactTotalMillisecondsBigDecimal = tmp.timespanBD.multiply(tmp.millisecondsPerDayAsBigDecimal); // Round to the nearest whole millisecond tmp.roundingModeHalfUp = ""; try { tmp.roundingModeHalfUp = createObject("java", "java.math.RoundingMode").HALF_UP; } catch (any e) { tmp.roundingModeHalfUp = 4; // Integer constant for RoundingMode.HALF_UP } tmp.totalMilliseconds = tmp.exactTotalMillisecondsBigDecimal.setScale(0, tmp.roundingModeHalfUp).toBigInteger(); tmp.remainingMilliseconds = tmp.totalMilliseconds; // Extract years if ( listFindNoCase(arguments.keys,"y") ) { result.y = 0; // Years if (tmp.millisecondsPerYear.compareTo(createObject("java", "java.math.BigInteger").ZERO) > 0) { tmp.years = tmp.remainingMilliseconds.divide(tmp.millisecondsPerYear); tmp.remainingMilliseconds = tmp.remainingMilliseconds.remainder(tmp.millisecondsPerYear); result.y = tmp.years.longValue(); } } // Extract months if ( listFindNoCase(arguments.keys,"m") ) { result.m = 0; // Months if (tmp.millisecondsPerMonth.compareTo(createObject("java", "java.math.BigInteger").ZERO) > 0) { tmp.months = tmp.remainingMilliseconds.divide(tmp.millisecondsPerMonth); tmp.remainingMilliseconds = tmp.remainingMilliseconds.remainder(tmp.millisecondsPerMonth); result.m = tmp.months.longValue(); } } // Extract days if ( listFindNoCase(arguments.keys,"d") ) { result.d = 0; // Days if (tmp.millisecondsPerDay.compareTo(createObject("java", "java.math.BigInteger").ZERO) > 0) { tmp.days = tmp.remainingMilliseconds.divide(tmp.millisecondsPerDay); tmp.remainingMilliseconds = tmp.remainingMilliseconds.remainder(tmp.millisecondsPerDay); result.d = tmp.days.longValue(); } } // Extract hours if ( listFindNoCase(arguments.keys,"h") ) { result.h = 0; // Hours if (tmp.millisecondsPerHour.compareTo(createObject("java", "java.math.BigInteger").ZERO) > 0) { tmp.hours = tmp.remainingMilliseconds.divide(tmp.millisecondsPerHour); tmp.remainingMilliseconds = tmp.remainingMilliseconds.remainder(tmp.millisecondsPerHour); result.h = tmp.hours.longValue(); } } // Extract minutes if ( listFindNoCase(arguments.keys,"n") ) { result.n = 0; // Minutes (n is common in date functions for minutes) if (tmp.millisecondsPerMinute.compareTo(createObject("java", "java.math.BigInteger").ZERO) > 0) { tmp.minutes = tmp.remainingMilliseconds.divide(tmp.millisecondsPerMinute); tmp.remainingMilliseconds = tmp.remainingMilliseconds.remainder(tmp.millisecondsPerMinute); result.n = tmp.minutes.longValue(); } } // Extract seconds if ( listFindNoCase(arguments.keys,"s") ) { result.s = 0; // Seconds if (tmp.millisecondsPerSecond.compareTo(createObject("java", "java.math.BigInteger").ZERO) > 0) { tmp.seconds = tmp.remainingMilliseconds.divide(tmp.millisecondsPerSecond); tmp.remainingMilliseconds = tmp.remainingMilliseconds.remainder(tmp.millisecondsPerSecond); result.s = tmp.seconds.longValue(); } } if ( listFindNoCase(arguments.keys,"ms") OR listFindNoCase(arguments.keys,"l") ) result.ms = tmp.remainingMilliseconds.intValue(); return result; } /* buildTickCountTimeStruct(tickCount) - Wrapper function to decompose a duration given in milliseconds (e.g., from getTickCount()) into its constituent time parts by converting it to a fractional day timespan first. @param tickCount - The duration in milliseconds. @param keys - time parts to add the return struct: y, m, d, h, n, s, ms @return - A structure containing the timespan parts. History: 2025-05-14 - Added */ function buildTickCountTimeStruct(required numeric tickCount, string keys="y,m,d,h,n,s,ms") { var result = {}; // Number of milliseconds in a standard day var millisecondsInADay = 86400000; // (1000 ms/sec * 60 sec/min * 60 min/hr * 24 hr/day) // Convert the millisecond duration to a fractional day value (Ensure floating point division) var timespanAsFractionalDay = arguments.tickCount / millisecondsInADay; // Call the buildTimespanTimeStruct function result = buildTimespanTimeStruct(timespan=timespanAsFractionalDay,keys=arguments.keys); return result; } }