adrianmc adrianmc - 9 months ago 36
Javascript Question

Getting every Friday until a certain date, but in a functional style?

For example, I want to get the dates of every Friday from now until 30 days from now.

Currently, I can make use of the underscore library and moment.js to do this. But the result is super verbose and annoyingly procedural/imperative. Observe:

var initDate = moment().day("Friday");
var endDate = moment().add(30, 'days');

var result = [];

while (_.last(result).isBefore(endDate)) {
var x = _.last(result);

alert(result); // answer here

// create a new moment from given moment and add 7 days
function nextWeek(initMoment) {
var x = moment(initMoment);
return x.add(7,'days');

Here is the corresponding fiddle:

I was wondering if there's a way to use Haskell-like features such as list comprehensions or infinite lists (lazy evaluation) to make this a lot more concise. Maybe something like:

var initDate = moment();
var endDate = moment().add(30,days);

var everyFriday = genLazyList(initDate, nextFridayFrom);
var result = _.filter(everyFriday, function(input){ return input.isBefore(endDate); });

Note that everyFriday is an infinite list generated by genLazyList and is not evaluated until the _.filter() function is called upon it. And nextFridayFrom() is a function that genLazyList uses to make the lazy list.


Note I am using stream.js and moment.js in my answer. So you'll have to do the following includes in your HTML (note that my syntax is in jade):


And here is my code:

var initDate = moment().day("Friday");
var endDate = moment().add(30,'days');

function allFridays() {
    return new Stream(initDate, function(){
            return allFridays().map(function(date){return moment(date).add(7,'days')});

allFridays().takeWhile(function(h){return h.isBefore(endDate)}).print();

Or in my favourite flavour, coffeescript:

initDate = moment().day('Friday')
endDate = moment().add(30, 'days')

allFridays = ->
  new Stream(initDate, ->
    allFridays().map (date) ->
      moment(date).add 7, 'days'

allFridays().takeWhile((h) -> h.isBefore endDate).print()

Isn't that just beautiful?

A quick explanation:

  1. I make an infinite stream of moment (date) objects which represent all fridays from now until forever
  2. I take (i.e. retrieve) the elements of this stream up until the first moment which does not represent a time before my endDate.

And that's it!

Note that the takeWhile() function is not yet implemented in the official stream.js release, but I simply copy and pasted it in from its lib folder. See its git repo here.