The DateRanger™

by Christopher Anderson — on  ,  , 

cover-image

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.

multidatespickr input

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

Comments