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

org.sisioh.baseunits.scala.time.Duration.scala Maven / Gradle / Ivy

There is a newer version: 0.1.22
Show newest version
/*
 * Copyright 2011 Sisioh Project and the Others.
 * lastModified : 2011/04/22
 *
 * This file is part of Tricreo.
 *
 * 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 org.sisioh.baseunits.scala.time

import java.util.{ Calendar, TimeZone }

import org.sisioh.baseunits.scala.intervals.{ Limit, LimitValue }
import org.sisioh.baseunits.scala.util.Ratio

/**
 * 時間量(時間の長さ・期間の長さなど)を表すクラス。
 *
 * 負の時間量は表現しない。
 *
 * @author j5ik2o
 * @param quantity 時間の長さ
 * @param unit 時間の単位
 */
class Duration private[time] (val quantity: Long,
                              val unit: TimeUnit)
    extends Ordered[Duration] with Serializable {

  require(quantity >= 0, "Quantity: " + quantity + " must be zero or positive")

  /**
   * 指定した日付に、このオブジェクトが表現する長さの時間を加えた、未来の日付を取得する。
   *
   * このオブジェクトが表現する時間の長さの単位が 日 未満である場合は、元の日付をそのまま返す。
   *
   * @param day 元となる日付
   * @return このオブジェクトが表現する長さの時間が経過した未来の日付
   */
  def addedTo(day: CalendarDate): CalendarDate =
    addedTo(day, TimeZones.Default)

  /**
   * 指定した日付に、このオブジェクトが表現する長さの時間を加えた、未来の日付を取得する。
   *
   * このオブジェクトが表現する時間の長さの単位が 日 未満である場合は、元の日付をそのまま返す。
   *
   * @param day 元となる日付
   * @param timeZone タイムゾーン
   * @return このオブジェクトが表現する長さの時間が経過した未来の日付
   */
  def addedTo(day: CalendarDate, timeZone: TimeZone): CalendarDate = {
    //		only valid for days and larger units
    if (unit.compareTo(TimeUnit.Day) < 0) {
      day
    } else {
      val calendar = day.asJavaCalendarOnMidnight(timeZone)
      if (unit == TimeUnit.Day) {
        calendar.add(Calendar.DATE, quantity.asInstanceOf[Int])
      } else {
        addAmountToCalendar(inBaseUnits, calendar)
      }
      CalendarDate.from(calendar)
    }
  }

  /**
   * 指定した年月に、このオブジェクトが表現する長さの時間を加えた、未来の年月を取得する。
   *
   * このオブジェクトが表現する時間の長さの単位が 月 未満である場合は、元の年月をそのまま返す。
   *
   * @param month 元となる年月
   * @return このオブジェクトが表現する長さの時間が経過した未来の年月
   */
  def addedTo(month: CalendarYearMonth) = {
    //		only valid for days and larger units
    if (unit.compareTo(TimeUnit.Month) < 0) {
      month
    } else {
      val calendar = month.asJavaCalendarOnMidnight
      if (unit == TimeUnit.Month) {
        calendar.add(Calendar.MONTH, quantity.asInstanceOf[Int])
      } else {
        addAmountToCalendar(inBaseUnits, calendar)
      }
      CalendarYearMonth.from(calendar)
    }
  }

  /**
   * 指定した日時に、このオブジェクトが表現する長さの時間を加えた、未来の日時を取得する。
   *
   * @param point 元となる日時
   * @return このオブジェクトが表現する長さの時間が経過した未来の日時
   * @see addAmountToTimePoint(long, TimePoint)
   */
  def addedTo(point: TimePoint, timeZone: TimeZone = TimeZones.Default): TimePoint =
    addAmountToTimePoint(inBaseUnits, point, timeZone)

  /**
   * このオブジェクトの`quantity`フィールド(量)を返す。
   *
   * CAUTION: このメソッドは、このオブジェクトがカプセル化する要素を外部に暴露する。取り扱いには充分注意のこと。
   *
   * @return 量
   */
  val breachEncapsulationOfQuantity = quantity

  /**
   * このオブジェクトの`unit`フィールド(単位)を返す。
   *
   * CAUTION: このメソッドは、このオブジェクトがカプセル化する要素を外部に暴露する。取り扱いには充分注意のこと。
   *
   * @return 単位
   */
  val breachEncapsulationOfUnit = unit

  /**
   * 時間量同士の比較を行う。
   *
   * 基本単位(baseUnit)換算で比較し、時間量の少ない方を「小さい」と判断する。
   * 同じ基本単位に変換できない場合は[[java.lang.ClassCastException]]をスローする。
   *
   * 例えば「1ヶ月間」と「30日間」は、同じ基本単位に変換できないため、比較不能である。
   *
   * @param other 比較対照
   * @return `java.util.Comparable` compareTo(Object)に準じる
   * @throws ClassCastException 引数`other`の単位を、このオブジェクトの単位に変換できない場合
   */
  override def compare(other: Duration): Int = {
    if (other.unit.isConvertibleTo(unit) == false && quantity != 0 && other.quantity != 0) {
      throw new ClassCastException(other.toString() + " is not convertible to: " + toString())
    }
    val difference = inBaseUnits - other.inBaseUnits
    if (difference > 0) 1
    else if (difference < 0) -1
    else 0
  }

  /**
   * この時間量を、指定した時間量 `other` で割った商(割合)を取得する。
   *
   * @param divisor 割る数
   * @return 割合
   * @throws IllegalArgumentException 引数divisorの単位を、このオブジェクトの単位に変換できない場合
   * @throws ArithmeticException 引数`divisor`が0だった場合
   */
  def dividedBy(divisor: Duration): Ratio = {
    checkConvertible(divisor)
    Ratio(inBaseUnits, divisor.inBaseUnits)
  }

  override def equals(obj: Any): Boolean = obj match {
    case that: Duration => isConvertibleTo(that) && inBaseUnits == that.inBaseUnits
    case _              => false
  }

  override def hashCode: Int =
    31 * ((inBaseUnits ^ (inBaseUnits >>> 32)).asInstanceOf[Int] + unit.valueBaseType.hashCode)

  /**
   * このオブジェクトが表現する時間量と、引数 `other` に与えた時間量の差を返す。
   *
   * @param other 期間
   * @return 時間量の差
   * @throws IllegalArgumentException 引数otherの単位を、このオブジェクトの単位に変換できず、かつ、
   *                                  どちらのquantityも0ではない場合もしくは、引数otherの長さが、このオブジェクトよりも長い場合
   */
  def minus(other: Duration): Duration = {
    checkConvertible(other)
    checkGreaterThanOrEqualTo(other)
    val newQuantity = inBaseUnits - other.inBaseUnits
    new Duration(newQuantity, if (other.quantity == 0) unit.baseUnit else other.unit.baseUnit)
  }

  /**
   * この期間を1単位で表せる最大の時間単位を求める。
   *
   * 例えば、1〜23時間, 25〜47時間は hours だが、24時間, 48時間は days である。
   *
   * @return 時間単位
   */
  lazy val normalizedUnit: TimeUnit = {
    val units = unit.descendingUnits
    val baseAmount = inBaseUnits
    units.find(e => (baseAmount % e.getFactor) == 0).get
  }

  /**
   * このオブジェクトが表現する時間量と、引数 `other` に与えた時間量の和を返す。
   *
   * @param other 期間
   * @return 時間量の和
   * @throws IllegalArgumentException 引数otherの単位を、このオブジェクトの単位に変換できず、かつ、どちらのquantityも0ではない場合
   */
  def plus(other: Duration): Duration = {
    checkConvertible(other)
    val newQuantity = inBaseUnits + other.inBaseUnits
    new Duration(newQuantity, if (other.quantity == 0) unit.baseUnit else other.unit.baseUnit)
  }

  /**
   * 終了日時とこのオブジェクトが表現する時間量より、期間を生成する。
   *
   * @param end 終了日時(上側限界値)
   * @return 期間
   */
  def preceding(end: LimitValue[TimePoint]): TimeInterval =
    TimeInterval.preceding(end.asInstanceOf[Limit[TimePoint]], this)

  /**
   * 指定した日付を開始日とする、このオブジェクトが表現する長さを持つ期間を生成する。
   *
   * 生成する期間の開始日と終了日は期間に含む(閉じている)開区間を生成する。
   *
   * この時間量の単位が "日" 未満である場合は、開始日と終了日は同日となる。
   *
   * @param start 開始日時(下側限界値). `Limitless[CalendarDate]`の場合は、限界がないことを表す
   * @return 期間
   */
  def startingFromCalendarDate(start: LimitValue[CalendarDate]): CalendarInterval =
    CalendarInterval.startingFrom(start, this)

  /**
   * 指定した日時を開始日時とする、このオブジェクトが表現する長さを持つ期間を生成する。
   *
   * 生成する期間の開始日時は区間に含み(閉じている)、終了日時は区間に含まない(開いている)半開期間を生成する。
   *
   * @param start 開始日時(下側限界値). `Limitless[CalendarDate]`の場合は、限界がないことを表す
   * @return 期間
   */
  def startingFromTimePoint(start: LimitValue[TimePoint], timeZone: TimeZone = TimeZones.Default): TimeInterval =
    TimeInterval.startingFrom(start.asInstanceOf[Limit[TimePoint]], this, timeZone)

  /**
   * 指定した日付に、このオブジェクトが表現する長さの時間を引いた、過去の日付を取得する。
   *
   * このオブジェクトが表現する時間の長さの単位が 日 未満である場合は、元の日付をそのまま返す。
   *
   * @param day 元となる日付
   * @return このオブジェクトが表現する長さのを引いた、過去の日付
   */
  def subtractedFrom(day: CalendarDate): CalendarDate = {
    //		only valid for days and larger units
    if (unit.compareTo(TimeUnit.Day) < 0) {
      day
    } else {
      val calendar = day.asJavaCalendarOnMidnight
      if (unit.equals(TimeUnit.Day)) {
        calendar.add(Calendar.DATE, -1 * quantity.asInstanceOf[Int])
      } else {
        subtractAmountFromCalendar(inBaseUnits, calendar)
      }
      CalendarDate.from(calendar)
    }
  }

  /**
   * 指定した日時に、このオブジェクトが表現する長さの時間を引いた、過去の日時を取得する。
   *
   * @param point 元となる日時
   * @return このオブジェクトが表現する長さのを引いた、過去の日時
   * @see #addAmountToTimePoint(long, TimePoint)
   */
  def subtractedFrom(point: TimePoint, timeZone: TimeZone = TimeZones.Default): TimePoint =
    addAmountToTimePoint(-1 * inBaseUnits, point, timeZone)

  /**
   * この時間量の文字列表現を返す。
   *
   * @return 時間量の文字列表現
   */
  lazy val toNormalizedString: String = toNormalizedString(unit.descendingUnits)

  /**
   * この時間量の文字列表現を返す。
   *
   * @return 時間量の文字列表現
   * @see #toNormalizedString()
   */
  override def toString: String = toNormalizedString(unit.descendingUnitsForDisplay)

  private[time] def addAmountToCalendar(amount: Long, calendar: Calendar): Unit = {
    if (unit.isConvertibleToMilliseconds) {
      calendar.setTimeInMillis(calendar.getTimeInMillis + amount)
    } else {
      checkAmountValid(amount)
      calendar.add(unit.javaCalendarConstantForBaseType, amount.asInstanceOf[Int])
    }
  }

  private[time] def addAmountToTimePoint(amount: Long, point: TimePoint, timeZone: TimeZone): TimePoint = {
    if (unit.isConvertibleToMilliseconds) {
      TimePoint.from(amount + point.millisecondsFromEpoc)
    } else {
      val calendar = point.asJavaCalendar(timeZone)
      addAmountToCalendar(amount, calendar)
      TimePoint.from(calendar)
    }
  }

  lazy val inBaseUnits =
    quantity * unit.getFactor

  private[time] def subtractAmountFromCalendar(amount: Long, calendar: Calendar): Unit =
    addAmountToCalendar(-1 * amount, calendar)

  private def checkAmountValid(amount: Long) {
    require(amount >= Int.MinValue && amount <= Int.MaxValue, amount + " is not valid")
  }

  private def checkConvertible(other: Duration) {
    require((other.unit.isConvertibleTo(unit) == false && quantity != 0 && other.quantity != 0) == false,
      other.toString() + " is not convertible to: " + toString())
  }

  private def checkGreaterThanOrEqualTo(other: Duration) {
    require((compareTo(other) < 0) == false, this + " is before " + other)
  }

  private def isConvertibleTo(other: Duration) =
    unit.isConvertibleTo(other.unit)

  private def toNormalizedString(units: Seq[TimeUnit]): String = {
    val buffer = new StringBuffer
    var remainder = inBaseUnits
    var first = true
    units.foreach { aUnit =>
      val portion = remainder / aUnit.getFactor
      if (portion > 0) {
        if (first == false) {
          buffer.append(", ")
        } else {
          first = false;
        }
        buffer.append(aUnit.toString(portion))
      }
      remainder %= aUnit.getFactor
    }
    buffer.toString
  }
}

/**
 * `Duration`コンパニオンオブジェクト。
 *
 * @author j5ik2o
 */
object Duration {

  /** 長さ `0` の期間 */
  val None = milliseconds(0)

  /**
   * 長さが `howMany` 日の時間量を取得する。
   *
   * @param howMany 時間の長さ(日)
   * @return 時間量
   */
  def days(howMany: Int): Duration =
    Duration(howMany, TimeUnit.Day)

  /**
   * 長さが `days`日 + `hours`時間 + `minute`分 + `seconds`秒
   * + `milliseconds`ミリ秒 の時間量を取得する。
   *
   * @param days 時間の長さ(日)
   * @param hours 時間の長さ(時間)
   * @param minutes 時間の長さ(分)
   * @param seconds 時間の長さ(秒)
   * @param milliseconds  時間の長さ(ミリ秒)
   * @return 時間量
   */
  def daysHoursMinutesSecondsMilliseconds(days: Int, hours: Int, minutes: Int, seconds: Int,
                                          milliseconds: Long): Duration = {
    var result = Duration.days(days)
    if (hours != 0) {
      result = result.plus(Duration.hours(hours))
    }
    if (minutes != 0) {
      result = result.plus(Duration.minutes(minutes))
    }
    if (seconds != 0) {
      result = result.plus(Duration.seconds(seconds))
    }
    if (milliseconds != 0) {
      result = result.plus(Duration.milliseconds(milliseconds))
    }
    result
  }

  /**
   * 長さが `howMany` 時間の時間量を取得する。
   *
   * @param howMany 時間の長さ(時間)
   * @return 時間量
   */
  def hours(howMany: Int): Duration =
    Duration(howMany, TimeUnit.Hour)

  /**
   * 長さが `howMany` ミリ秒の時間量を取得する。
   *
   * @param howMany 時間の長さ(ミリ秒)
   * @return 時間量
   */
  def milliseconds(howMany: Long): Duration =
    Duration(howMany, TimeUnit.Millisecond)

  /**
   * 長さが `howMany` 分の時間量を取得する。
   *
   * @param howMany 時間の長さ(分)
   * @return 時間量
   */
  def minutes(howMany: Int): Duration =
    Duration(howMany, TimeUnit.Minute)

  /**
   * 長さが `howMany` ヶ月の時間量を取得する。
   *
   * @param howMany 時間の長さ(月)
   * @return 時間量
   */
  def months(howMany: Int): Duration =
    Duration(howMany, TimeUnit.Month)

  /**
   * 長さが `howMany` 四半期の時間量を取得する。
   *
   * @param howMany 時間の長さ(四半期)
   * @return 時間量
   */
  def quarters(howMany: Int): Duration =
    Duration(howMany, TimeUnit.Quarter)

  /**
   * 長さが `howMany` ミリの時間量を取得する。
   *
   * @param howMany 時間の長さ(ミリ)
   * @return 時間量
   */
  def seconds(howMany: Int): Duration =
    Duration(howMany, TimeUnit.Second)

  /**
   * 長さが `howMany` 週間の時間量を取得する。
   *
   * @param howMany 時間の長さ(週)
   * @return 時間量
   */
  def weeks(howMany: Int): Duration =
    Duration(howMany, TimeUnit.Week)

  /**
   * 長さが `howMany` 年の時間量を取得する。
   *
   * @param howMany 時間の長さ(年)
   * @return 時間量
   */
  def years(howMany: Int): Duration =
    Duration(howMany, TimeUnit.Year)

  private[time] def apply(howMany: Long, unit: TimeUnit): Duration =
    new Duration(howMany, unit)

  private[time] def unapply(duration: Duration) =
    Some(duration.quantity, duration.unit)

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy