Groovy Dates are Ranges too

I have a Grails application that I use to keep track of training courses that I teach. For each course, I enter the start and end dates, among other information. It’s a pretty straightforward application that I’ve described here in several previous posts.

I’ve gotten to the point now where I want to do more than just store the data. I added a Google Maps display to show the locations of my training classes, which was pretty fun. I even got to put geodesic polylines on the map to connect my home to the various cities where I taught, and clicking on the markers lets me see the name of each course, the city and state information, and the dates I was there.

Now, however, I want to figure out how many days I’ve been teaching, both on a month-to-month basis as well as yearly. I’ve only been on my own for a bit under three years, and I’m still trying to figure out the right balance between teaching days and development days, while keeping the travel at least somewhat reasonable. Of course, I have to meet my own revenue projections as well. 🙂

Computing the number of days taught is what lead me to another exploration of Groovy capabilities. Dates and times are always a mess in Java (one might easily call them a disaster). I was dearly hoping that managing them in Groovy would be substantially easier, because, after all, everything in Groovy is substantially easier.

Here’s the problem: For each year, I want to populate an array indexed by month, where each element of the array contains the total number of days taught that month. What I have available is the start and end dates (as instances of the Date class) for each month. How do I process all my courses to date to populate that days array for each year?

I’m sure there are many different ways to solve this problem (and I welcome alternative suggestions), but here’s how I’ve gone about it so far. In my Grails application, I created a class called CourseStatistics and put it in my src/groovy folder. After all, it’s neither a domain class nor a controller class. That class looks like this:


class CourseStatistics {
  List courseList
  def teachingDays = [:]

  def computeTeachingDays() { ... }
}

The idea is that I instantiate the class, call the setCourseList() method with my complete list of courses (Course.list() makes that simple), then invoke computeTeachingDays() to populate the hash. My current implementation assumes that the hash will use the years as keys, and the values will be 12-element arrays representing days taught in each month that year.

Here’s the big complication. Some classes start in one month and end in another. For example, earlier this year I taught a class on the Spring Framework that started on April 30 and ended on May 2. So I can’t just count the days (even assuming that was easy with Dates) and add them to a particular month. I need to be able to iterate through the dates for each course, identifying which month it belongs to, and increment the proper month value.

I tried all sorts of odd constructs — while loops and such. Usually with a collection I just invoke the each() method and use the individual elements that way, but I don’t have that here. All I have for each course is a start date and an end date.

Once again, Groovy came to the rescue. Groovy dates are Ranges, in the Groovy sense of the word. I presume this is because they implement the plus(int) operator, where the argument is the number of days, but I haven’t confirmed that yet. The result is that it is perfectly fine to write a loop like this:

for (date in startDate..endDate) { ... }

and the date variable will take on the value of each date in the range. How cool is that?

The rest is just the standard, awkward date/time manipulation stuff that the Date and Calendar classes require in Java, though I was able to simplify the Groovy code a bit:


def computeTeachingDays() {
  Calendar cal = Calendar.instance
  courseList.each { c ->
    for (date in c.startDate..c.endDate) {
      cal.time = date
      int month = cal.get(Calendar.MONTH)
      int year = cal.get(Calendar.YEAR)
      if (teachingDays[year]) {
        teachingDays[year][month] += 1
      } else {
        teachingDays[year] = new int[12]
        teachingDays[year][month] = 1
      }
    }
  }
}

In Groovy, I just call Calendar.instance to invoke the getInstance() method, of course. Then it turns out that the time property on the calendar can be set to an individual date, and since this is Groovy, I can just assign it rather than call the associated setter method. I’m also using the year as the key in the hash map. Then, since I know that the value is going to be a 12-element array of ints, I figured why not just dimension it with the proper (static) type each time I add a new year?

Once this method is finished, I have a complete hash map with integer arrays holding the number of teaching days for each year. I had to go through some odd machinations to display it in my view (and it’s still awkward, since so far I’ve been forced to display it with months as the column headers rather than years), but that’s a story for another post.

The good news is that this entire computation is much, much simpler than the corresponding code would be in Java, and it’s easier to read and understand to boot. Once again, Groovy rocks.

(Someday I’ll comment on the current debate going on between people trying to decide whether to go with Groovy or JRuby, but it’s pretty obvious which side I happen to favor. Again, that’s a post for later.)

2 responses to “Groovy Dates are Ranges too”

  1. Groovy Dates are Ranges too

    […]Computing the number of days taught is what lead me to another exploration of Groovy capabilities. Dates and times are always a mess in Java (one might easily call them a disaster). I was dearly hoping that managing them in Groovy would be substan…

  2. […] Groovy I didn’t touch anything fancy in the Date/Calendar API; no need for it. But after reading a lengthy blogpost on the topic I just needed to have a look at […]

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from Stuff I've learned recently...

Subscribe now to keep reading and get access to the full archive.

Continue reading