
Working with dates and times
Working with dates is a very common task in our software. This section describes how we can perform date calculations in our CoffeeScript applications and provides some useful utility methods that you can use in your own applications.
Performing date calculations
Performing date calculations is not as intuitive as one would like in JavaScript. For example, subtracting two dates returns the number of milliseconds between the two dates. JavaScript does not provide methods to add hours, days, months, and so on for a date. In the next section, we will create methods to address each of these complications.
Let's define methods to calculate the difference between two dates and add a timespan to a date as follows:
MILLISECONDS_PER_SECOND = 1000 MILLISECONDS_PER_MINUTE = MILLISECONDS_PER_SECOND * 60 MILLISECONDS_PER_HOUR = MILLISECONDS_PER_MINUTE * 60 MILLISECONDS_PER_DAY = MILLISECONDS_PER_HOUR * 24 MILLISECONDS_PER_WEEK = MILLISECONDS_PER_DAY * 7 MILLISECONDS_PER_YEAR = MILLISECONDS_PER_WEEK * 52 dateDifference = (startDate, endDate, units = 'days') -> elapsed = endDate - startDate switch units when 'days' return elapsed / MILLISECONDS_PER_DAY when 'weeks' return elapsed / MILLISECONDS_PER_WEEK when 'months' return elapsed / MILLISECONDS_PER_YEAR * 12 when 'years' return elapsed / MILLISECONDS_PER_YEAR when 'hours' return elapsed / MILLISECONDS_PER_HOUR when 'minutes' return elapsed / MILLISECONDS_PER_MINUTE when 'seconds' return elapsed / MILLISECONDS_PER_SECOND return elapsed dateAdd = (date, amount, units = 'days') -> workingDate = new Date(date.valueOf()) switch units when 'days' workingDate.setDate date.getDate() + amount when 'weeks' workingDate.setDate date.getDate() + amount * 7 when 'months' workingDate.setMonth date.getMonth() + amount when 'years' workingDate.setFullYear date.getFullYear() + amount when 'hours' workingDate.setHours date.getHours() + amount when 'minutes' workingDate.setMinutes date.getMinutes() + amount when 'seconds' workingDate.setSeconds date.getSeconds() + amount return workingDate module.exports = dateAdd: dateAdd dateDifference: dateDifference
First, define some useful constants to help convert milliseconds to days or years.
Next, we define a method to calculate the difference between two dates. The dateDifference()
method takes a startDate
and endDate
parameter as well as an optional units
parameter (which defaults to days
) that represents the units to be returned.
The dateDifference()
method essentially subtracts the two dates and converts the resultant milliseconds to the desired units.
We then define the dateAdd()
method. This method accepts date
, amount
, and an optional units
parameter representing the units of the amount being added (which defaults to days
).
To add time to a date, you must use a little trick to set the proper date unit to its value and the amount to be added. For example, to add 5 days to the current date, you would use the following code:
currentDate = new Date() currentDate.setDays currentDate.getDays() + 5 console.log currentDate
You can subtract amounts from the given date by using negative values. For example, 7 days ago would be as follows:
currentDate = new Date() console.log dateAdd currentDate, -7
An example of using these date math
functions is shown as follows:
dm = require './date_math' zeroPad = (value, length = 2) -> return "00000000000000#{value}".split('')[-length..].join('') formatDate = (date) -> year = date.getFullYear() month = date.getMonth() + 1 day = date.getDate() hour = date.getHours() minute = date.getMinutes() return "#{year}-#{zeroPad month}-#{zeroPad day} #{zeroPad hour}:#{zeroPad minute}" currentDate = new Date() newCentury = new Date(2000, 0) console.log 'Current date: ', formatDate currentDate console.log 'Days since Jan. 1, 2000: ', dm.dateDifference newCentury, currentDate console.log 'Years since Jan. 1, 2000: ', dm.dateDifference newCentury, currentDate, 'years' console.log '3 days from now: ', formatDate dm.dateAdd currentDate, 3 console.log '3 days ago: ', formatDate dm.dateAdd currentDate, -3 console.log '3 months from now: ', formatDate dm.dateAdd currentDate, 3, 'months' console.log '3 years from now: ', formatDate dm.dateAdd currentDate, 3, 'years' console.log '3 hours from now: ', formatDate dm.dateAdd currentDate, 3, 'hours'
Its output will be:
Current date: 2013-10-21 18:54 Days since Jan. 1, 2000: 5042.746363993056 Years since Jan. 1, 2000: 13.806287101965928 3 days from now: 2013-10-24 18:54 3 days ago: 2013-10-18 18:54 3 months from now: 2014-01-21 18:54 3 years from now: 2016-10-21 18:54 3 hours from now: 2013-10-21 21:54
Measuring elapsed time
Using what we covered about working with dates and times, we can easily take it one step further and create a performance measurement tool that can clock the start and end times to execute a function and display execution statistics once complete.
In this section, we define helper methods to format and display results, and a timer()
method that performs the timing function:
dm = require './date_math' padRight = (value, zeroPadding) -> "00000000000000#{value}".split('')[-zeroPadding..].join('') padLeft = (value, zeroPadding) -> "#{value}00000000000000"[0...zeroPadding] formatNumber = (value, decimalPlaces = 0, zeroPadding = 0) -> valueParts = (value + '').split '.' resultParts = [] resultParts.push padRight valueParts[0], zeroPadding if decimalPlaces resultParts.push padLeft valueParts[1], decimalPlaces return resultParts.join '.' formatTime = (value) -> hours = 0 minutes = 0 seconds = value / 1000 if seconds > 60 minutes = Math.floor seconds / 60 seconds -= minutes * 60 if minutes > 60 hours = Math.floor minutes / 60 minutes -= hours * 60 return "#{formatNumber hours, 0, 2}:" + \ "#{formatNumber minutes, 0, 2}:" + \ "#{formatNumber seconds, 4, 2}" displayResults = (results) -> totalTime = 0 minimumTime = Number.POSITIVE_INFINITY maximumTime = 0 for result in results minimumTime = result if result < minimumTime maximumTime = result if result > maximumTime totalTime += result console.log "Statistics" console.log "Times run: #{results.length}" console.log "Total: #{formatTime totalTime}" console.log "Minimum: #{formatTime minimumTime}" console.log "Maximum: #{formatTime maximumTime}" console.log "Average: #{formatTime totalTime / results.length}" timer = (func, numberOfTimesToExecute = 1) -> timerResults = [] console.log 'Running...' for lap in [1..numberOfTimesToExecute] start = new Date() func() end = new Date() timerResults.push \ dm.dateDifference(start, end, 'milliseconds') displayResults timerResults module.exports = timer: timer
This little performance utility module begins by requiring our date_utils
library as we will be using the dateDifference()
method to calculate how long a method takes to execute.
Then, we have some formatting helper methods. The formatNumber()
method will format a number for display and includes optional parameters for the number of decimal places and zero padding. For example, formatNumber(5.4, 3, 2)
will produce 05.400
.
The formatTime()
method will take a value in milliseconds and display it as hours, minutes, and seconds. For example, formatTime(5680)
will be displayed as 00:00:05.680
.
After our formatting helper methods, we have a method that displays the performance measurement results, but let's look at the timer()
method first.
The timer()
method is really the heart of our module. It is responsible for executing the function being measured and gathering timing statistics with each run. The method takes a function (func
) as a parameter and a numberOfTimesToExecute
optional parameter representing the number of times to execute the function, which defaults to 1.
The timer()
method then declares a timerResults
array to store our execution times.
We then loop between 1 and numberOfTimesToExecute
. With each iteration, we perform the following tasks:
- Store the current date and time in a variable called
start
- Execute the function that is being measured
- Store the current date and time after the execution of a variable called
end
- Push the results into our
timerResults
array
Once the function execution has been measured, the timer()
method calls displayResults()
to pass the timerResults
array. The displayResults()
method displays the number of executions, the total time, minimum time, maximum time, and the average time.
It is worth noting that the act of measuring performance negatively impacts the performance, however minimally. When working to improve performance in your code, it is better to compare results between tests with the understanding that each test executes with roughly the same overhead.
Let's try running our timer as follows:
tu = require './timer_utils' test = () -> for i in [1..1000000] d = new Date() tu.timer test, 5
Our little timer demo declares a test
method that simply iterates from one to a million, and with each iteration, the current date/time is stored in the d
variable.
We then pass our test()
method to timer()
and have it execute five times.
The output for the preceding code is:
Running... Statistics Times run: 5 Total: 00:00:02.2810 Minimum: 00:00:00.4220 Maximum: 00:00:00.4920 Average: 00:00:00.4562