Wednesday, February 23, 2011

Scalendar v 1.0

Update

It is now hosted on both scalendar's github page, and on scala-tools. It was approved a couple of days ago, and hopefully, other people might find it useful.

As I worked on my wife's ipad app, I kept refining my Scala Calendar wrapper (which I coined Scalendar thanks to brad). It began to evolve into a really nice api, in which you work with immutable objects that wrap the Java Calendar api. While it completely interoperates with Java time, it works well enough as a stand-alone library.

The main functionality like date traversal and property getters haven't changed from the last post. If you're interested, read more about the changes after the jump.

Construction

My biggest issue with the version I was working with, was the ability to make time. As I began to use it throughout the app, I began to realize rather quickly that it was indeed the biggest issue.

// Now to convert from strings, you need to define
// a SimpleDateFormat, or just use the Pattern object

import com.philipcali.utils.calendar._

// If you don't plan on converting strings
// to time, then simply import
// import com.philipcali.utils.Scalendar

// A global implicit will do
implicit val pattern = Pattern("M-d-yyyy")

val monthsAlive = ("11-19-1985" to Scalendar.now).delta.months

// Creation from now
val now = Scalendar.now

// "Setters"
// It completely integrates with java.util.Calendar
import java.util.Calendar._

val endOfWorld = now.year(2012).month(DECEMBER).day(21)

val countdown = endOfWorld to now

"t %d days" format(countdown.delta.days) // t -667 days

"There's only %d months left!" format(countdown.reverse.delta.months)

// Optional, the "make time" syntax:
val made = Scalendar(year = 2012, month = DECEMBER, day = 21)
val madeMore = Scalendar(year = 2012, 
                         month = DECEMBER, day = 21, hour = 23)

made < endOfWorld // true, made 0's out anything below the day
madeMore < endOfWorld // false, because hour was set

// Of course time can be built with unix timestamp
val fromLong = Scalendar(Scalendar.now.time)

// Below is an eample of interaction with a DB and
// formatting the results via a duration traversal
val duration = Scalendar.now to Scalendar.now + (3 months)

// Fictional DBO querying
val events = Event.find (
  Event.userid === "philip.cali",
  Event.created > duration.start.time,
  Event.created < duration.end.time  
)

// Format events for each month
duration.traverse(1 month) { monthD => 
  // It's entirely possible to get more granular
  // but that's overkill for this example
  println("Events in %s" format(monthD.month.name))
  events.filter(e => monthD contains(e.created)) foreach(println)
}

I got rid of the hasNext method, as that typically requires state, and the Scalendar class is completely immutable. I've made use of Scala 2.8 package objects, and completely realized how valuable it was when I wanted to save imports by placing my implicit conversions in there. Now the clients get a rather robust date api just by importing Scalendar.

The code will be on my github very soon. I have to rip the api out of the project I'm currently using it in and create a separate project. One gave birth to the other.

2 comments:

  1. I would be interested in the benefit you see in your approach compared to ScalaJ Time (https://github.com/scalaj/scalaj-time ) which wraps the more sane JodaTime libraries and Scala Date&Time (https://github.com/soc88/scala-datetime ) which is a Scala-only port of JSR-310?

    Thanks!

    ReplyDelete
  2. Hi steve,

    First off... Thanks for the comment!

    The scala-datetime you mentioned is way more expansive and far more complex than what Scalendar is. I looked at scalaj-time, which is a beautiful wrap around JodaTime, but it requires JodaTime. Scalendar is more of a Scala way to interact with java.util time, and provides a convenient way to traverse a timespan (Duration) by a means of time, i.e.:

    //single import
    import com.github.philcali.scalendar._

    val year = Scalendar.now to Scalendar.now + 1.year
    for(month <- year by 1.month; week <- month by 1.week) // etc

    I think Scalendar takes a more simplistic approach in manipulating time. It was made when I needed some particular things on this personal project I was working, solving the aforementioned traversal problem as well as some neat date arithmetic, and interoperating with java.util.Date (this was my requirement). I thought I'd share it, in case anyone might stumble on this problem.

    ReplyDelete