Import of tz-lift-misc@14a648a3676f
authorTomas Zeman <tzeman@volny.cz>
Fri, 10 Feb 2012 09:53:04 +0100
changeset 5 993582ca8d2e
parent 4 bb4478e1cff7
child 6 98d9c92a726f
Import of tz-lift-misc@14a648a3676f
src/main/scala/net/tz/lift/boot/ProtoBoot.scala
src/main/scala/net/tz/lift/snippet/Panel.scala
src/main/scala/net/tz/lift/snippet/SnippetHelpers.scala
src/main/scala/net/tz/lift/snippet/Table.scala
src/main/scala/net/tz/lift/util/DB.scala
src/main/scala/net/tz/lift/util/DateExtension.scala
src/main/scala/net/tz/lift/util/Helpers.scala
src/main/scala/net/tz/lift/util/YmdDateTimeConverter.scala
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/net/tz/lift/boot/ProtoBoot.scala	Fri Feb 10 09:53:04 2012 +0100
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2011 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.tz.lift.boot
+
+import net.liftweb.common._
+import net.liftweb.http._
+import net.liftweb.mapper._
+import net.liftweb.sitemap._
+import net.liftweb.sitemap.Loc._
+import net.liftweb.util._
+import net.liftweb.widgets.menu.MenuWidget
+import net.tz.lift.snippet.ActionLinks
+import net.tz.lift.util.YmdDateTimeConverter
+
+/**
+ * Prototype boot class to be either extended or copied.
+ */
+class ProtoBoot extends Logger {
+
+  def boot = {
+    /* DB stuff */
+    /*
+    S.addAround(DB.buildLoanWrapper())
+    */
+
+    if (Props.mode == Props.RunModes.Development)
+      DB.addLogFunc { (dbLog, l) => dbLog.statementEntries.foreach { e =>
+        debug("Query: " + e.statement)
+    }}
+
+    /* Date format */
+    LiftRules.dateTimeConverter.default.set { () => YmdDateTimeConverter }
+
+    /* Handle end slash and drop it (except for home page) */
+    LiftRules.statelessRewrite.append {
+      case RewriteRequest(ParsePath(xs,_,_,true),_,_) if (xs.size > 1) &&
+        (xs.lastOption == Some("index")) =>
+          RewriteResponse(xs dropRight 1)
+    }
+
+    /* Snippet dispatch */
+    LiftRules.snippetDispatch.append {
+      case "Menubar" => new AnyRef with DispatchSnippet {
+        def dispatch: DispatchIt = {
+          case _ => { xhtml => MenuWidget() }
+        }
+      }
+      case "action-links" => ActionLinks
+    }
+
+    /* Sitemap */
+    SiteMap.enforceUniqueLinks = false
+
+    /*
+    LiftRules.setSiteMap(SiteMap(
+      Menu.i("Home") / "index" >> Hidden
+    ))
+    */
+
+    /* Menu widget */
+    MenuWidget.init()
+
+    /* Http conf */
+    LiftRules.logServiceRequestTiming = false
+    LiftRules.early.append(_.setCharacterEncoding("UTF-8"))
+  }
+
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/net/tz/lift/snippet/Panel.scala	Fri Feb 10 09:53:04 2012 +0100
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.tz.lift.snippet
+
+import net.liftweb.mapper.MappedField
+import scala.xml.{NodeSeq, Text}
+
+object AttrRow {
+  def apply(name: => NodeSeq, value: => NodeSeq) =  new AttrRow(name, value,
+    "attr-name", "attr-value")
+  def apply(f: MappedField[_, _]) = new AttrRow(Text(f.displayName), f.asHtml,
+    "attr-name", "attr-value")
+  def formRow(name: => NodeSeq, input: => NodeSeq) = new AttrRow(name, input,
+    "form-name", "form-value")
+  def submitRow(button: => NodeSeq) = new SpanRow(button)
+}
+
+class AttrRow(name: => NodeSeq, value: => NodeSeq, nameCss: String,
+  valueCss: String) extends Function0[NodeSeq] {
+  def tdN = <td class={nameCss}>{name}</td>
+  def tdV = <td class={valueCss}>{value}</td>
+  def apply(): NodeSeq = <tr>{tdN :: tdV :: Nil}</tr>
+}
+
+class SpanRow(value: => NodeSeq) extends AttrRow(NodeSeq.Empty, value, "", "")
+{
+  override def apply(): NodeSeq = <tr><td colspan="2">{value}</td></tr>
+}
+
+object Panel {
+  def apply(attrs: Iterable[AttrRow]) = new Panel(attrs)
+  def fromFields(fields: Iterable[MappedField[_,_]]) =
+    new Panel(fields.map(AttrRow(_)))
+}
+
+class Panel(attrs: => Iterable[AttrRow]) extends Function1[NodeSeq, NodeSeq]
+{
+  def apply(in: NodeSeq): NodeSeq = <table>{attrs.map(_())}</table>
+  def &(other: Function1[NodeSeq, NodeSeq]) = new Function1[NodeSeq, NodeSeq] {
+    def apply(in: NodeSeq): NodeSeq = List(Panel.this, other) flatMap (_(in))
+  }
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/net/tz/lift/snippet/SnippetHelpers.scala	Fri Feb 10 09:53:04 2012 +0100
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2011 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.tz.lift.snippet
+
+import net.liftweb.http._
+import scala.xml.{NodeSeq, Text}
+
+trait SnippetHelpers {
+  def mkPath(prefix: String, l: String*) =
+    (prefix :: l.toList) mkString ("/", "/", "")
+}
+
+class A(href: => String) extends Function1[NodeSeq, NodeSeq] {
+  def apply(in: NodeSeq): NodeSeq = <a href={href}>{in}</a>
+}
+
+object A {
+  def apply(href: String, in: NodeSeq): NodeSeq = (new A(href))(in)
+  def apply(href: String): A = new A(href)
+  def apply(href: String, cnt: String): NodeSeq = (new A(href))(Text(cnt))
+}
+
+object ActionLinks extends DispatchSnippet {
+  def dispatch: DispatchIt = {
+    case "are" => { xhtml => if (links.is.isEmpty) NodeSeq.Empty else xhtml }
+    case _ => render _
+  }
+
+  import scala.collection.mutable.{BufferLike, ListBuffer}
+
+  private object links extends RequestVar[ListBuffer[Either[Function0[NodeSeq], Function1[NodeSeq, NodeSeq]]]](new ListBuffer())
+
+  def render(in: NodeSeq): NodeSeq = links.is.flatMap { _.fold( _(), _(in) ) ++
+    Text("  ") }
+
+  def append(l: NodeSeq) = links.is.append(Left( { () => l } ))
+  def append(l: () => NodeSeq) = links.is.append(Left(l))
+  def append(l: NodeSeq => NodeSeq) = links.is.append(Right(l))
+}
+
+object SimpleForm {
+  def apply(l: Iterable[AttrRow], submitVal: String, onSubmit: () => Any):
+    (NodeSeq => NodeSeq) = {
+    val curS = S.currentSnippet
+    def doit() = {
+      onSubmit()
+      curS foreach { S.mapSnippet(_, loop) }
+    }
+
+    def loop: (NodeSeq => NodeSeq) = {
+      Panel(l ++ List(AttrRow.submitRow(SHtml.submit(submitVal, doit))))
+    }
+
+    loop
+  }
+
+  def apply(l: Iterable[AttrRow], submitVal: String): (NodeSeq => NodeSeq) =
+    apply(l, submitVal, () => ())
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/net/tz/lift/snippet/Table.scala	Fri Feb 10 09:53:04 2012 +0100
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2011 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.tz.lift.snippet
+
+import net.liftweb.common._
+import net.liftweb.util.Helpers._
+import scala.xml.{Elem, NodeSeq, Text}
+
+object Column {
+  def apply[T](name: String, f: T => NodeSeq): Column[T] =
+    new Column[T](Text(name), f, { _ => Empty})
+  def apply[T](name: String, f: T => NodeSeq, tdCss: String): Column[T] =
+    new Column[T](Text(name), f, { _ => Full(tdCss)})
+}
+
+class Column[T](name: => NodeSeq, f: T => NodeSeq, tdCss: T => Box[String])
+  extends Function1[T, NodeSeq] {
+
+  def th: Elem = <th>{name}</th>
+  def td(in: T): Elem = {
+    val r = <td>{apply(in)}</td>
+    tdCss(in) map { css => r % ("class" -> css) } openOr r
+  }
+  def apply(in: T): NodeSeq = f(in)
+}
+
+object Table {
+  def apply[T](cols: Iterable[Column[T]], rows: Iterable[T]) = new Table(Empty,
+    cols, rows, Empty)
+  def apply[T](css: String, cols: Iterable[Column[T]], rows: Iterable[T],
+    foot: NodeSeq) = new Table(Full(css), cols, rows, Full(foot))
+}
+
+class Table[T](css: Box[String], cols: Iterable[Column[T]],
+  rows: => Iterable[T], foot: Box[NodeSeq]) extends
+  Function1[NodeSeq, NodeSeq] {
+
+  def oddEven(i: Int) = (i % 2) match {
+    case 1 => "even"
+    case 0 => "odd"
+  }
+
+  def thead: Elem = <thead><tr>{ cols.map(c => c.th) }</tr></thead>
+  def tbody: Elem = <tbody>{
+    rows.zipWithIndex.map( r => tr(r._1, r._2))
+  }</tbody>
+  def tr(in: T, idx: Int): Elem = <tr class={oddEven(idx)}>{
+    cols.map(_.td(in))
+  }</tr>
+
+  def tfoot: Elem = <tfoot>{
+    foot map { f => <tr><td colspan={cols.size.toString}>{f}</td></tr>
+    } openOr NodeSeq.Empty
+  }</tfoot>
+
+  def apply(ns: NodeSeq): NodeSeq = {
+    val t = <table>{
+      List(thead, tfoot, tbody)
+    }</table>
+    css map { cl => t % ("class" -> cl) } openOr t
+  }
+
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/net/tz/lift/util/DB.scala	Fri Feb 10 09:53:04 2012 +0100
@@ -0,0 +1,166 @@
+/*
+ * Fix for ProtoDBVendor/StandardDBVendor connection leak.
+ *
+ * Original code is:
+ *
+ * Copyright 2006-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * All fixes are under the same license.
+ */
+package net.tz.lift.util
+
+import java.sql.{Connection, DriverManager}
+import net.liftweb.common._
+import net.liftweb.db.{ConnectionIdentifier, ConnectionManager}
+import net.liftweb.util._
+import net.liftweb.util.Helpers._
+
+/**
+ * The standard DB vendor.
+ * @param driverName the name of the database driver
+ * @param dbUrl the URL for the JDBC data connection
+ * @param dbUser the optional username
+ * @param dbPassword the optional db password
+ */
+class StandardDBVendor(driverName: String,
+                       dbUrl: String,
+                       dbUser: Box[String],
+                       dbPassword: Box[String]) extends ProtoDBVendor {
+  protected def createOne: Box[Connection] = try {
+    Class.forName(driverName)
+
+    val dm = (dbUser, dbPassword) match {
+      case (Full(user), Full(pwd)) =>
+        DriverManager.getConnection(dbUrl, user, pwd)
+
+      case _ => DriverManager.getConnection(dbUrl)
+    }
+
+    Full(dm)
+  } catch {
+    case e: Exception => e.printStackTrace; Empty
+  }
+}
+
+trait ProtoDBVendor extends ConnectionManager {
+  private val logger = Logger(classOf[ProtoDBVendor])
+  private var pool: List[Connection] = Nil
+  private var poolSize = 0
+  private var tempMaxSize = maxPoolSize
+
+  /**
+   * Override and set to false if the maximum pool size can temporarilly be expanded to avoid pool starvation
+   */
+  protected def allowTemporaryPoolExpansion = true
+
+  /**
+   *  Override this method if you want something other than
+   * 4 connections in the pool
+   */
+  protected def maxPoolSize = 4
+
+  /**
+   * The absolute maximum that this pool can extend to
+   * The default is 20.  Override this method to change.
+   */
+  protected def doNotExpandBeyond = 20
+
+  /**
+   * The logic for whether we can expand the pool beyond the current size.  By
+   * default, the logic tests allowTemporaryPoolExpansion &amp;&amp; poolSize &lt;= doNotExpandBeyond
+   */
+  protected def canExpand_? : Boolean = allowTemporaryPoolExpansion && poolSize <= doNotExpandBeyond
+
+  /**
+   *   How is a connection created?
+   */
+  protected def createOne: Box[Connection]
+
+  /**
+   * Test the connection.  By default, setAutoCommit(false),
+   * but you can do a real query on your RDBMS to see if the connection is alive
+   */
+  protected def testConnection(conn: Connection) {
+    conn.setAutoCommit(false)
+  }
+
+  def newConnection(name: ConnectionIdentifier): Box[Connection] =
+    synchronized {
+      pool match {
+        case Nil if poolSize < tempMaxSize =>
+          val ret = createOne
+          ret foreach { c =>
+            c.setAutoCommit(false)
+            poolSize = poolSize + 1
+            logger.debug("Created new pool entry. name=%s, poolSize=%d".format(name, poolSize))
+          }
+          ret
+
+        case Nil =>
+          val curSize = poolSize
+          logger.trace("No connection left in pool, waiting...")
+          wait(50L)
+          // if we've waited 50 ms and the pool is still empty, temporarily expand it
+          if (pool.isEmpty && poolSize == curSize && canExpand_?) {
+            tempMaxSize += 1
+            logger.debug("Temporarily expanding pool. name=%s, tempMaxSize=%d".format(name, tempMaxSize))
+          }
+          newConnection(name)
+
+        case x :: xs =>
+          logger.trace("Found connection in pool, name=%s".format(name))
+          pool = xs
+          try {
+            this.testConnection(x)
+            Full(x)
+          } catch {
+            case e => try {
+              logger.debug("Test connection failed, removing connection from pool, name=%s".format(name))
+              poolSize = poolSize - 1
+              tryo(x.close)
+              newConnection(name)
+            } catch {
+              case e => newConnection(name)
+            }
+          }
+      }
+    }
+
+  def releaseConnection(conn: Connection): Unit = synchronized {
+    if (tempMaxSize > maxPoolSize) {
+      tryo {conn.close()}
+      tempMaxSize -= 1
+      poolSize -= 1
+    } else {
+      pool = conn :: pool
+    }
+    logger.debug("Released connection. poolSize=%d".format(poolSize))
+    notifyAll
+  }
+
+  def closeAllConnections_!(): Unit = synchronized {
+    logger.info("Closing all connections, poolSize=%d".format(poolSize))
+    if (poolSize == 0) ()
+    else {
+      pool.foreach {c => tryo(c.close); poolSize -= 1}
+      pool = Nil
+      
+      if (poolSize > 0) wait(250)
+
+      closeAllConnections_!()
+    }
+  }
+}
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/net/tz/lift/util/DateExtension.scala	Fri Feb 10 09:53:04 2012 +0100
@@ -0,0 +1,44 @@
+/*
+ * Refactored from net.liftweb.util.TimeHelpers.scala, which is
+ *
+ * Copyright 2006-2011 WorldWide Conferencing, LLC and
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.tz.lift.util
+
+import java.util.{Calendar, Date}
+
+/**
+ * Fixed version of DateExtension (from
+ * net.liftweb.util.TimeSpan.DateExtension).
+ */
+class DateExtension(d: Date) {
+  /** @returns a Date object starting at 00:00 from date */
+  def noTime = {
+    val calendar = Calendar.getInstance
+    calendar.setTime(d)
+    calendar.set(Calendar.HOUR_OF_DAY, 0)
+    calendar.set(Calendar.MINUTE, 0)
+    calendar.set(Calendar.SECOND, 0)
+    calendar.set(Calendar.MILLISECOND, 0)
+    calendar.getTime
+  }
+}
+
+object DateExtension {
+  implicit def date2dateExtension(d: Date): DateExtension =
+    new DateExtension(d)
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/net/tz/lift/util/Helpers.scala	Fri Feb 10 09:53:04 2012 +0100
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2011 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.tz.lift.util
+
+import java.text.SimpleDateFormat
+import java.util.Date
+import net.liftweb.http.LiftRules
+import net.liftweb.util.Helpers.tryo
+import org.joda.time.{DateMidnight, DateTime, Period, ReadableInstant}
+import org.joda.time.format.{DateTimeFormat, PeriodFormatterBuilder}
+import scala.xml.{NodeSeq, Text}
+
+object AsIsoDateTime {
+  val df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")
+  def unapply(in: String): Option[Date] = tryo { df.parse(in) }
+  def apply(d: Date): String = df.format(d)
+}
+
+object AsDate {
+  import DateExtension._
+  def unapply(in: String): Option[Date] = LiftRules.dateTimeConverter().
+    parseDate(in).map(_.noTime)
+  def apply(d: Date) = LiftRules.dateTimeConverter().formatDate(d)
+}
+
+object AsDateTime {
+  def unapply(in: String): Option[Date] = LiftRules.dateTimeConverter().
+    parseDateTime(in)
+  def apply(d: Date) = LiftRules.dateTimeConverter().formatDateTime(d)
+}
+
+object AsDateMidnight {
+  lazy val fmt = DateTimeFormat.forPattern("yyyy-MM-dd")
+  def unapply(in: String): Option[DateMidnight] = apply(in)
+  def apply(in: String): Option[DateMidnight] = tryo {
+    fmt.parseDateTime(in).toDateMidnight
+  }
+  def apply(d: ReadableInstant) = fmt.print(d)
+
+  implicit def dt2d(d: DateTime): Date = d.toDate
+  implicit def dm2d(d: DateMidnight): Date = d.toDate
+  implicit def d2dm(d: Date): DateMidnight = new DateMidnight(d)
+  implicit def dm2dt(d: DateMidnight): DateTime = d.toDateTime
+}
+
+object AsTimePeriod {
+  lazy val fmt = (new PeriodFormatterBuilder).printZeroAlways.
+    minimumPrintedDigits(2).appendHours.
+    appendSeparator(":").appendMinutes.toFormatter
+  lazy val fmtDt = DateTimeFormat.forPattern("HH:mm")
+
+  /** Parses HH:mm time into period. */
+  def apply(in: String): Option[Period] = tryo { fmt.parsePeriod(in) }
+  def apply(p: Period) = fmt.print(p)
+  def apply(dt: DateTime) = fmtDt.print(dt)
+}
+
+object Bytes {
+  implicit def dbl2ns(d: Double): NodeSeq =
+    Text(String.format(fmt, d.asInstanceOf[AnyRef]))
+  def fmt = "%.2f"
+  def kb(v: Long): NodeSeq = v.toDouble / 1024
+  def mb(v: Long): NodeSeq = v.toDouble / (1024*1024)
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/net/tz/lift/util/YmdDateTimeConverter.scala	Fri Feb 10 09:53:04 2012 +0100
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 Tomas Zeman <tzeman@volny.cz>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.tz.lift.util
+
+import java.text.SimpleDateFormat
+import java.util.Date
+import net.liftweb.common._
+import net.liftweb.util.DateTimeConverter
+import net.liftweb.util.TimeHelpers._
+
+object YmdDateTimeConverter extends DateTimeConverter {
+  val df = new SimpleDateFormat("yyyy-MM-dd")
+  val dtf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
+
+  def formatDateTime(d: Date) = dtf.format(d)
+  def formatDate(d: Date) = df.format(d)
+  /**  Uses Helpers.hourFormat which includes seconds but not time zone */
+  def formatTime(d: Date) = hourFormat.format(d)
+
+  def parseDateTime(s: String) = tryo { dtf.parse(s) }
+  def parseDate(s: String) = tryo { df.parse(s) }
+  /** Tries Helpers.hourFormat and Helpers.timeFormat */
+  def parseTime(s: String) =
+    tryo{hourFormat.parse(s)} or tryo{timeFormatter.parse(s)}
+}
+
+// vim: set ts=2 sw=2 et: