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

kyo.Duration.scala Maven / Gradle / Ivy

There is a newer version: 0.14.1
Show newest version
package kyo

import java.time.Duration as JavaDuration
import java.time.temporal.ChronoUnit.*
import kyo.Duration.Units
import kyo.Duration.Units.*
import scala.concurrent.duration.Duration as ScalaDuration

/** Represents a duration of time. */
type Duration = Duration.Value

/** Companion object for Duration type. */
object Duration:

    opaque type Value = Long

    given CanEqual[Duration, Duration] = CanEqual.derived

    /** Exception thrown for invalid duration parsing. */
    case class InvalidDuration(message: String) extends Exception(message)

    /** Parses a string representation of a duration.
      *
      * @param s
      *   The string to parse
      * @return
      *   A Result containing either the parsed Duration or an InvalidDuration error
      */
    def parse(s: String): Result[InvalidDuration, Duration] =
        val pattern = """(\d+)\s*([a-zA-Z]+)""".r
        s.trim.toLowerCase match
            case "infinity" | "inf" => Result.success(Infinity)
            case pattern(value, unit) =>
                for
                    longValue <-
                        Result.catching[NumberFormatException](value.toLong)
                            .mapFail(_ => InvalidDuration(s"Invalid number: $value"))
                    unitEnum <-
                        Units.values.find(_.names.exists(_.startsWith(unit)))
                            .map(Result.success)
                            .getOrElse(Result.fail(InvalidDuration(s"Invalid unit: $unit")))
                yield fromUnits(longValue, unitEnum)
            case _ => Result.fail(InvalidDuration(s"Invalid duration format: $s"))
        end match
    end parse

    /** Represents zero duration. */
    val Zero: Duration = 0L

    /** Represents infinite duration. */
    val Infinity: Duration = Long.MaxValue

    /** Creates a Duration from nanoseconds.
      *
      * @param value
      *   The number of nanoseconds
      * @return
      *   A Duration instance
      */
    inline def fromNanos(value: Long): Duration = value

    /** Creates a Duration from a value and unit.
      *
      * @param value
      *   The numeric value
      * @param unit
      *   The unit of time
      * @return
      *   A Duration instance
      */
    def fromUnits(value: Long, unit: Units): Duration =
        if value <= 0 then Duration.Zero else Duration.*(value)(unit.factor).min(Infinity)

    /** Converts a Java Duration to a Duration.
      *
      * @param value
      *   The Java Duration to convert
      * @return
      *   A Duration instance
      */
    def fromJava(value: JavaDuration): Duration =
        (value.toNanos: @annotation.switch) match
            case 0                       => Zero
            case n if n >= Long.MaxValue => Infinity
            case n                       => n.nanos

    /** Converts a Scala Duration to a Duration.
      *
      * @param value
      *   The Scala Duration to convert
      * @return
      *   A Duration instance
      */
    def fromScala(value: ScalaDuration): Duration =
        if value.isFinite then value.toNanos.nanos.max(Zero) else Infinity

    /** Enumeration of time units with their conversion factors and names. */
    enum Units(val factor: Double, val names: List[String]):
        case Nanos   extends Units(NANOS.getDuration.toNanos.toDouble, List("ns", "nanos", "nanosecond", "nanoseconds"))
        case Micros  extends Units(MICROS.getDuration.toNanos.toDouble, List("µs", "micros", "microsecond", "microseconds"))
        case Millis  extends Units(MILLIS.getDuration.toNanos.toDouble, List("ms", "millis", "millisecond", "milliseconds"))
        case Seconds extends Units(SECONDS.getDuration.toNanos.toDouble, List("s", "seconds", "second"))
        case Minutes extends Units(MINUTES.getDuration.toNanos.toDouble, List("m", "minutes", "minute"))
        case Hours   extends Units(HOURS.getDuration.toNanos.toDouble, List("h", "hours", "hour"))
        case Days    extends Units(DAYS.getDuration.toNanos.toDouble, List("d", "days", "day"))
        case Weeks   extends Units(WEEKS.getDuration.toNanos.toDouble, List("w", "weeks", "week"))
        case Months  extends Units(MONTHS.getDuration.toNanos.toDouble, List("m", "months", "month"))
        case Years   extends Units(YEARS.getDuration.toNanos.toDouble, List("y", "years", "year"))
    end Units

    extension (self: Duration)

        private inline def toLong: Long = self

        inline infix def >=(that: Duration): Boolean = self.toLong >= that.toLong
        inline infix def <=(that: Duration): Boolean = self.toLong <= that.toLong
        inline infix def >(that: Duration): Boolean  = self.toLong > that.toLong
        inline infix def <(that: Duration): Boolean  = self.toLong < that.toLong
        inline infix def ==(that: Duration): Boolean = self.toLong == that.toLong
        inline infix def !=(that: Duration): Boolean = self.toLong != that.toLong

        inline infix def +(that: Duration): Duration =
            val sum: Long = self.toLong + that.toLong
            if sum >= 0 then sum else Duration.Infinity

        inline infix def *(factor: Double): Duration =
            if factor <= 0 || self.toLong <= 0L then Duration.Zero
            else if factor <= Long.MaxValue / self.toLong.toDouble then Math.round(self.toLong.toDouble * factor)
            else Duration.Infinity

        inline def max(that: Duration): Duration = Math.max(self.toLong, that.toLong)
        inline def min(that: Duration): Duration = Math.min(self.toLong, that.toLong)

        inline def to(unit: Units): Long =
            Math.max(Math.round(self.toLong / unit.factor), Duration.Zero)

        inline def toNanos: Long   = self.toLong
        inline def toMicros: Long  = self.to(Micros)
        inline def toMillis: Long  = self.to(Millis)
        inline def toSeconds: Long = self.to(Seconds)
        inline def toMinutes: Long = self.to(Minutes)
        inline def toHours: Long   = self.to(Hours)
        inline def toDays: Long    = self.to(Days)
        inline def toWeeks: Long   = self.to(Weeks)
        inline def toMonths: Long  = self.to(Months)
        inline def toYears: Long   = self.to(Years)

        /** Converts the Duration to a Scala Duration.
          *
          * @return
          *   A Scala Duration instance
          */
        def toScala: ScalaDuration =
            (self: @annotation.switch) match
                case Duration.Zero     => ScalaDuration.Zero
                case Duration.Infinity => ScalaDuration.Inf
                case n                 => ScalaDuration.fromNanos(n.toNanos)

        /** Converts the Duration to a Java Duration.
          *
          * @return
          *   A Java Duration instance
          */
        def toJava: JavaDuration =
            (self: @annotation.switch) match
                case Duration.Zero => JavaDuration.ZERO
                case n             => JavaDuration.of(n.toNanos, NANOS)

        /** Converts the Duration to a human-readable string.
          *
          * @return
          *   A string representation of the Duration
          */
        inline def show: String = s"Duration($self ns)"

        /** Checks if the Duration is finite.
          *
          * @return
          *   true if the Duration is finite, false otherwise
          */
        // TODO Is this Robust enough?
        private[kyo] inline def isFinite: Boolean = self < Duration.Infinity
    end extension

end Duration

extension (value: Long)
    /** Creates a Duration of nanoseconds. */
    inline def nanos: Duration = Duration.fromNanos(value)

    /** Creates a Duration of microseconds. */
    inline def micros: Duration = value.asUnit(Micros)

    /** Creates a Duration of milliseconds. */
    inline def millis: Duration = value.asUnit(Millis)

    /** Creates a Duration of seconds. */
    inline def seconds: Duration = value.asUnit(Seconds)

    /** Creates a Duration of minutes. */
    inline def minutes: Duration = value.asUnit(Minutes)

    /** Creates a Duration of hours. */
    inline def hours: Duration = value.asUnit(Hours)

    /** Creates a Duration of days. */
    inline def days: Duration = value.asUnit(Days)

    /** Creates a Duration of weeks. */
    inline def weeks: Duration = value.asUnit(Weeks)

    /** Creates a Duration of months. */
    inline def months: Duration = value.asUnit(Months)

    /** Creates a Duration of years. */
    inline def years: Duration = value.asUnit(Years)

    inline def nano: Duration   = compiletime.error("please use `.nanos`")
    inline def micro: Duration  = compiletime.error("please use `.micros`")
    inline def milli: Duration  = compiletime.error("please use `.millis`")
    inline def second: Duration = compiletime.error("please use `.seconds`")
    inline def minute: Duration = compiletime.error("please use `.minutes`")
    inline def hour: Duration   = compiletime.error("please use `.hours`")
    inline def day: Duration    = compiletime.error("please use `.days`")
    inline def week: Duration   = compiletime.error("please use `.weeks`")
    inline def month: Duration  = compiletime.error("please use `.months`")
    inline def year: Duration   = compiletime.error("please use `.years`")

    /** Creates a Duration from a specific unit.
      *
      * @param unit
      *   The unit of time
      * @return
      *   A Duration instance
      */
    inline def asUnit(unit: Units): Duration =
        Duration.fromUnits(value, unit)
end extension

/** Extension methods for the value 1 to create singular Durations. */
extension (value: 1)
    inline def nano: Duration   = Duration.fromNanos(value)
    inline def micro: Duration  = value.asUnit(Micros)
    inline def milli: Duration  = value.asUnit(Millis)
    inline def second: Duration = value.asUnit(Seconds)
    inline def minute: Duration = value.asUnit(Minutes)
    inline def hour: Duration   = value.asUnit(Hours)
    inline def day: Duration    = value.asUnit(Days)
    inline def week: Duration   = value.asUnit(Weeks)
    inline def month: Duration  = value.asUnit(Months)
    inline def year: Duration   = value.asUnit(Years)
end extension




© 2015 - 2024 Weber Informatics LLC | Privacy Policy