Someone needed a Javascript calendar which would allow them to select multiple dates or date ranges. I pointed them towards Multidatespickr (winner by default), but notice that output was a little unwieldy.

What it needed (I thought) was some nice ranges, so I pulled out some old code I used for indexing PDFs via PrinceXML, and co-erced it into coffee-script. Don’t panic, regular Javascript versions available!
The GenericRanger™
This function takes ugly input like this:
GenericRanger([1, 2, 3, 25, 87, 88, 89])
and outputs shit that the beautiful people can love:
“1–3, 25, 87–89”
## The Generic Ranger
GenericRanger = (genericRange) ->
indexing = last: 0, start: 0, output: ''
## We cannot trust fool users to pre-sort the dates
genericRange.sort (a,b) ->
return 0 if Number(a) is Number(b)
return `Number(a) > Number(b) ? 1 : 0`
genericRange.forEach (v) ->
$page = Number v
return if $page is indexing.last
## The range compaction code is hard to explain,
## mainly beacuse I didn't comment when I wrote it
## many moons ago, and probably didn't know how it
## worked then, either.
if indexing.last > 0 and $page is indexing.last + 1
indexing.start = if indexing.start then indexing.start else indexing.last
return indexing.last = $page
if indexing.start
if indexing.last - indexing.start > 0
## This is an endash. If you don't know what
## or why, you have no job making nice ranges
## in the first place.
indexing.output += String.fromCharCode(0x2013) + indexing.last
indexing.start = 0
else
## This should never be reached under normal
## operation, so if you see a "..," appear in
## your index, let me know. :)
indexing.start = 0
indexing.output += '.., ' + indexing.last
indexing.output += ', ' if indexing.output.length
indexing.output += $page
return indexing.last = $page
## If we were counting out a range, then it's over now.
indexing.output += String.fromCharCode(0x2013) + indexing.last if indexing.start
indexing.output
The DayNumber class
Unfortunately Javascript Date objects don’t really work too well with The GenericRanger™. So what I needed, was a little class to encapsulate a native Date object, and output a number (the number of days since 1970) when I squeezed it. By squeezing it, I mean that The GenericRanger™ calls Number(obj) on it.
Here’s the encapsulating class:
class DayNumber
constructor: (@date) ->
toString: -> Math.round(@date.getTime() - @date.getTimezoneOffset() * 6e4) / 864e5
That’s it.
Don’t believe me? Well, here’s some test code that brings it all together:
dayNumberTest = ->
# Create some regular Javascript Dates
dates = [ new Date(1995, 11, 21),
new Date(1995, 11, 22) ]
dates.push new Date('December 14, 1995 03:24:00')
dates.push new Date('1995-12-15T03:24:00')
dates.push new Date(1995, 11, 17)
dates.push new Date(819158400000)
dates.push new Date(819244800000)
# Convert those dates into DayNumbers
dayNumbers = (new DayNumber date for date in dates)
console.log("GenericRanger:", GenericRanger dayNumbers)
dayNumberTest()
And this is what comes out. Yes, unfortunately those strange numbers are “days since 01-01-1970” and are probably not what you were expecting, but how do I know what you are expecting? There are so many ways to write dates, so many databases to put them in…
GenericRanger: 9478, 9480–9482, 9485–9486
I’d recommend momentjs for converting the dates to whatever format you require, something like this should work fine:
rangerOutput="9478, 9480–9482, 9485–9486"
newMoment = (day) ->
moment.unix(day * 86400).format("ddd DD MMM")
rangeList = rangerOutput.split(", ").map (range) ->
[left, right] = range.split String.fromCharCode(0x2013)
right = left unless right?
from: newMoment(left),
to: newMoment right
console.log(
rangeList.map (o) ->
return o.from + `(o.to != o.from ? " to " + o.to : "")`
.join(", ")
)
Now your output is ready for the beautiful people again.
Thu 14 Dec, Sat 16 Dec to Mon 18 Dec, Thu 21 Dec to Fri 22 Dec
P.S. I lied about there being Javascript versions available