All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.mauricio.async.db.postgresql.column.PostgreSQLIntervalEncoderDecoder.scala Maven / Gradle / Ivy

/*
 * Copyright 2013 Maurício Linhares
 * Copyright 2013 Dylan Simon
 *
 * Maurício Linhares licenses this file to you 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 com.github.mauricio.async.db.postgresql.column

import com.github.mauricio.async.db.column.ColumnEncoderDecoder
import com.github.mauricio.async.db.exceptions.DateEncoderNotAvailableException
import com.github.mauricio.async.db.util.Log
import org.joda.time.{Period, ReadablePeriod, ReadableDuration}
import org.joda.time.format.{ISOPeriodFormat, PeriodFormatterBuilder}

object PostgreSQLIntervalEncoderDecoder extends ColumnEncoderDecoder {

  private val log = Log.getByName(this.getClass.getName)

  /* Postgres accepts all ISO8601 formats. */
  private val formatter = ISOPeriodFormat.standard

  override def encode(value: Any): String = {
    value match {
      case t: ReadablePeriod   => formatter.print(t)
      case t: ReadableDuration => t.toString // defaults to ISO8601
      case _ => throw new DateEncoderNotAvailableException(value)
    }
  }

  /* these should only be used for parsing: */
  private def postgresYMDBuilder(builder: PeriodFormatterBuilder) =
    builder.appendYears
      .appendSuffix(" year", " years")
      .appendSeparator(" ")
      .appendMonths
      .appendSuffix(" mon", " mons")
      .appendSeparator(" ")
      .appendDays
      .appendSuffix(" day", " days")
      .appendSeparator(" ")

  private val postgres_verboseParser =
    postgresYMDBuilder(
      new PeriodFormatterBuilder().appendLiteral("@ ")
    ).appendHours
      .appendSuffix(" hour", " hours")
      .appendSeparator(" ")
      .appendMinutes
      .appendSuffix(" min", " mins")
      .appendSeparator(" ")
      .appendSecondsWithOptionalMillis
      .appendSuffix(" sec", " secs")
      .toFormatter

  private def postgresHMSBuilder(builder: PeriodFormatterBuilder) =
    builder
      // .printZeroAlways // really all-or-nothing
      .rejectSignedValues(true) // XXX: sign should apply to all
      .appendHours
      .appendSuffix(":")
      .appendMinutes
      .appendSuffix(":")
      .appendSecondsWithOptionalMillis

  private val hmsParser =
    postgresHMSBuilder(new PeriodFormatterBuilder()).toFormatter

  private val postgresParser =
    postgresHMSBuilder(
      postgresYMDBuilder(new PeriodFormatterBuilder())
    ).toFormatter

  /* These sql_standard parsers don't handle negative signs correctly. */
  private def sqlDTBuilder(builder: PeriodFormatterBuilder) =
    postgresHMSBuilder(builder.appendDays.appendSeparator(" "))

  private val sqlDTParser =
    sqlDTBuilder(new PeriodFormatterBuilder()).toFormatter

  private val sqlParser =
    sqlDTBuilder(
      new PeriodFormatterBuilder().printZeroAlways
        .rejectSignedValues(true) // XXX: sign should apply to both
        .appendYears
        .appendSeparator("-")
        .appendMonths
        .rejectSignedValues(false)
        .printZeroNever
        .appendSeparator(" ")
    ).toFormatter

  /* This supports all positive intervals, and intervalstyle of postgres_verbose, and iso_8601 perfectly.
   * If intervalstyle is set to postgres or sql_standard, some negative intervals may be rejected.
   */
  def decode(value: String): Period = {
    if (value.isEmpty)
      /* huh? */
      Period.ZERO
    else {
      val format =
        (
          if (value(0).equals('P'))
            /* iso_8601 */
            formatter
          else if (value.startsWith("@ "))
            postgres_verboseParser
          else {
            /* try to guess based on what comes after the first number */
            val i = value.indexWhere(
              !_.isDigit,
              if ("-+".contains(value(0))) 1 else 0
            )
            if (i < 0 || ":.".contains(value(i)))
              /* simple HMS (to support group negation) */
              hmsParser
            else if (value(i).equals('-'))
              /* sql_standard: Y-M */
              sqlParser
            else if (
              value(i).equals(' ') && i + 1 < value.length && value(
                i + 1
              ).isDigit
            )
              /* sql_standard: D H:M:S */
              sqlDTParser
            else
              postgresParser
          }
        )
      if ((format eq hmsParser) && value(0).equals('-'))
        format.parsePeriod(value.substring(1)).negated
      else if (value.endsWith(" ago"))
        /* only really applies to postgres_verbose, but shouldn't hurt */
        format.parsePeriod(value.stripSuffix(" ago")).negated
      else
        format.parsePeriod(value)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy