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

java.time.Instant.scala Maven / Gradle / Ivy

package java.time

import scala.scalajs.js

import java.time.Preconditions.requireDateTimeParse
import java.time.chrono.IsoChronology
import java.time.format.DateTimeParseException
import java.time.temporal._

import scala.util.control.NonFatal

/** Created by alonsodomin on 26/12/2015. */
final class Instant private (private val seconds: Long, private val nanos: Int)
    extends TemporalAccessor with Temporal with TemporalAdjuster
    with Comparable[Instant] with java.io.Serializable {

  import Preconditions._
  import Constants._
  import Instant._
  import ChronoField._
  import ChronoUnit._

  requireDateTime(seconds >= MinSecond && seconds <= MaxSecond,
      s"Invalid seconds: $seconds")
  requireDateTime(nanos >= 0 && nanos <= MaxNanosInSecond,
      s"Invalid nanos: $nanos")

  def isSupported(field: TemporalField): Boolean = field match {
    case _: ChronoField =>
      field == INSTANT_SECONDS || field == NANO_OF_SECOND || field == MICRO_OF_SECOND ||
        field == MILLI_OF_SECOND

    case null => false
    case _    => field.isSupportedBy(this)
  }

  def isSupported(unit: TemporalUnit): Boolean = unit match {
    case _: ChronoUnit => unit.isTimeBased || unit == DAYS
    case null          => false
    case _             => unit.isSupportedBy(this)
  }

  // Implemented by TemporalAccessor
  // def range(field: TemporalField): ValueRange

  // Implemented by TemporalAccessor
  // def get(field: TemporalField): Int

  def getLong(field: TemporalField): Long = field match {
    case INSTANT_SECONDS => seconds
    case NANO_OF_SECOND  => nanos
    case MICRO_OF_SECOND => nanos / NANOS_IN_MICRO
    case MILLI_OF_SECOND => nanos / NANOS_IN_MILLI

    case _: ChronoField =>
      throw new UnsupportedTemporalTypeException(s"Field not supported: $field")

    case _ => field.getFrom(this)
  }

  def getEpochSecond(): Long = seconds

  def getNano(): Int = nanos

  override def `with`(adjuster: TemporalAdjuster): Instant =
    adjuster.adjustInto(this).asInstanceOf[Instant]

  def `with`(field: TemporalField, value: Long): Instant = {
    val msg = s"Invalid value for field $field: $value"
    field match {
      case INSTANT_SECONDS =>
        requireDateTime(value >= MinSecond && value <= MaxSecond, msg)
        if (value == seconds) this
        else ofEpochSecond(value, nanos)

      case NANO_OF_SECOND =>
        requireDateTime(value >= 0 && value <= MaxNanosInSecond, msg)
        if (value == nanos) this
        else ofEpochSecond(seconds, value)

      case MICRO_OF_SECOND =>
        requireDateTime(value >= 0 && value <= MaxNanosInSecond / NANOS_IN_MICRO, msg)
        val newNanos = value * NANOS_IN_MICRO
        if (newNanos == nanos) this
        else ofEpochSecond(seconds, newNanos)

      case MILLI_OF_SECOND =>
        requireDateTime(value >= 0 && value <= MaxNanosInSecond / NANOS_IN_MILLI, msg)
        val newNanos = value * NANOS_IN_MILLI
        if (newNanos == nanos) this
        else ofEpochSecond(seconds, newNanos)

      case _: ChronoField =>
        throw new UnsupportedTemporalTypeException(s"Field not supported: $field")

      case _ => field.adjustInto(this, value)
    }
  }

  def truncatedTo(unit: TemporalUnit): Instant = {
    if (unit == NANOS) {
      this
    } else {
      val duration = unit.getDuration
      if (duration.getSeconds > SECONDS_IN_DAY)
        throw new UnsupportedTemporalTypeException("Unit too large")

      val unitNanos = duration.toNanos
      if ((NANOS_IN_DAY % unitNanos) != 0)
        throw new UnsupportedTemporalTypeException("Unit must be a multiple of a standard day")

      val extraNanos = (seconds % SECONDS_IN_DAY) * NANOS_IN_SECOND + nanos
      val extraNanosPerUnit = (extraNanos / unitNanos) * unitNanos
      plusNanos(extraNanosPerUnit - extraNanos)
    }
  }

  def plus(amount: Long, unit: TemporalUnit): Instant = unit match {
    case NANOS     => plusNanos(amount)
    case MICROS    => plusNanos(Math.multiplyExact(amount, NANOS_IN_MICRO))
    case MILLIS    => plusMillis(amount)
    case SECONDS   => plusSeconds(amount)
    case MINUTES   => plusSeconds(Math.multiplyExact(amount, SECONDS_IN_MINUTE))
    case HOURS     => plusSeconds(Math.multiplyExact(amount, SECONDS_IN_HOUR))
    case HALF_DAYS => plusSeconds(Math.multiplyExact(amount, SECONDS_IN_DAY) / 2)
    case DAYS      => plusSeconds(Math.multiplyExact(amount, SECONDS_IN_DAY))

    case _: ChronoUnit =>
      throw new UnsupportedTemporalTypeException(s"Unit not supported: $unit")

    case _ => unit.addTo(this, amount)
  }

  def plusSeconds(secs: Long): Instant = plus(secs, 0)

  def plusMillis(millis: Long): Instant =
    plusNanos(Math.multiplyExact(millis, NANOS_IN_MILLI))

  def plusNanos(nans: Long): Instant = plus(0, nans)

  private def plus(secs: Long, nans: Long): Instant = {
    if (secs == 0 && nans == 0) {
      this
    } else {
      val secondsFromNanos = Math.floorDiv(nans, NANOS_IN_SECOND)
      val remainingNanos = Math.floorMod(nans, NANOS_IN_SECOND)
      val additionalSecs = Math.addExact(secs, secondsFromNanos)
      ofEpochSecond(
          Math.addExact(seconds, additionalSecs),
          Math.addExact(nanos, remainingNanos)
      )
    }
  }

  override def minus(amount: TemporalAmount): Instant =
    amount.subtractFrom(this).asInstanceOf[Instant]

  override def minus(amount: Long, unit: TemporalUnit): Instant = {
    if (amount == Long.MinValue) plus(Long.MaxValue, unit).plus(1, unit)
    else plus(-amount, unit)
  }

  def minusSeconds(secs: Long): Instant = minus(secs, SECONDS)

  def minusMillis(millis: Long): Instant = minus(millis, MILLIS)

  def minusNanos(nans: Long): Instant = minus(nans, NANOS)

  // Not implemented
  // def query[R](query: TemporalQuery[R]): R

  def adjustInto(temporal: Temporal): Temporal =
    temporal.`with`(INSTANT_SECONDS, seconds).`with`(NANO_OF_SECOND, nanos)

  def until(end: Temporal, unit: TemporalUnit): Long = {
    val endInstant = from(end)

    def nanosUntil: Long = {
      val secsDiff: Long = Math.subtractExact(endInstant.seconds, seconds)
      val nanosBase: Long = Math.multiplyExact(secsDiff, NANOS_IN_SECOND)
      Math.addExact(nanosBase, endInstant.nanos - nanos)
    }

    def secondsUntil: Long = {
      val secsDiff: Long = Math.subtractExact(endInstant.seconds, seconds)
      val nanosDiff: Int = endInstant.nanos - nanos

      // correct "off by one" in the seconds difference
      if (secsDiff > 0 && nanosDiff < 0) {
        secsDiff - 1
      } else if (secsDiff < 0 && nanosDiff > 0) {
        secsDiff + 1
      } else {
        secsDiff
      }
    }

    unit match {
      case NANOS     => nanosUntil
      case MICROS    => nanosUntil / NANOS_IN_MICRO
      case MILLIS    => Math.subtractExact(endInstant.toEpochMilli(), toEpochMilli())
      case SECONDS   => secondsUntil
      case MINUTES   => secondsUntil / SECONDS_IN_MINUTE
      case HOURS     => secondsUntil / SECONDS_IN_HOUR
      case HALF_DAYS => secondsUntil / (SECONDS_IN_HOUR * 12)
      case DAYS      => secondsUntil / SECONDS_IN_DAY

      case _: ChronoUnit =>
        throw new UnsupportedTemporalTypeException(s"Unit not supported: $unit")

      case _ => unit.between(this, end)
    }
  }

  // Not implemented
  // def atOffset(offset: ZoneOffset): OffsetDateTime

  // Not implemented
  // def atZone(zone: ZoneId): ZonedDateTime

  def toEpochMilli(): Long = {
    val millis: Long = Math.multiplyExact(seconds, MILLIS_IN_SECOND.toLong)
    millis + nanos / NANOS_IN_MILLI
  }

  def compareTo(that: Instant): Int = {
    val cmp = seconds compareTo that.seconds
    if (cmp != 0) {
      cmp
    } else {
      nanos compareTo that.nanos
    }
  }

  def isAfter(that: Instant): Boolean = compareTo(that) > 0

  def isBefore(that: Instant): Boolean = compareTo(that) < 0

  override def equals(other: Any): Boolean = other match {
    case that: Instant => seconds == that.seconds && nanos == that.nanos
    case _             => false
  }

  override def hashCode(): Int = (seconds + 51 * nanos).hashCode

  override def toString: String = {
    def tenThousandPartsAndRemainder: (Long, Long) = {
      if (seconds < -secondsFromZeroToEpoch) {
        val quot = seconds / secondsInTenThousandYears
        val rem = seconds % secondsInTenThousandYears
        (quot, rem)
      } else {
        val quot = Math.floorDiv(seconds, secondsInTenThousandYears)
        val rem = Math.floorMod(seconds, secondsInTenThousandYears)
        (quot, rem)
      }
    }

    def dateTime(epochSecond: Long): (LocalDate, LocalTime) = {
      val epochDay = Math.floorDiv(epochSecond, SECONDS_IN_DAY)
      val secondsOfDay = Math.floorMod(epochSecond, SECONDS_IN_DAY).toInt
      (LocalDate.ofEpochDay(epochDay), LocalTime.ofSecondOfDay(secondsOfDay).withNano(nanos))
    }

    val (hi, lo) = tenThousandPartsAndRemainder
    val epochSecond = lo
    val (date, time) = dateTime(epochSecond)

    val years = hi * 10000 + date.getYear

    val yearSegment = {
      if (years > 9999) s"+$years"
      else if (years < 0 && years > -1000) "-%04d".format(Math.abs(years))
      else years.toString
    }

    val monthSegement = "%02d".format(date.getMonthValue)
    val daySegment = "%02d".format(date.getDayOfMonth)

    val timePart = {
      val timeStr = time.toString
      if (time.getSecond == 0 && time.getNano == 0) timeStr + ":00"
      else timeStr
    }

    val dateSegment = s"$yearSegment-$monthSegement-$daySegment"
    s"${dateSegment}T${timePart}Z"
  }

  // Not implemented
  // def format(format: DateTimeFormatter): String

}

object Instant {
  import Constants._
  import ChronoField._

  private final val iso = IsoChronology.INSTANCE

  final val EPOCH = new Instant(0, 0)

  private val MinSecond = -31557014167219200L
  private val MaxSecond = 31556889864403199L
  private val MaxNanosInSecond = 999999999

  private val MaxYear = 1000000000
  private val MinYear = -1000000000

  /*
   * 146097 days in 400 years
   * 86400 seconds in a day
   * 25 cycles of 400 years
   */
  private val secondsInTenThousandYears = 146097L * SECONDS_IN_DAY * 25L
  private val secondsFromZeroToEpoch = ((146097L * 5L) - (30L * 365L + 7L)) * SECONDS_IN_DAY

  final val MIN = ofEpochSecond(MinSecond)
  final val MAX = ofEpochSecond(MaxSecond, MaxNanosInSecond)

  def now(): Instant = {
    val date = new js.Date()
    ofEpochMilli(date.getTime.toLong)
  }

  // Not implemented
  // def now(zoneId: ZoneId): Instant
  // def now(clock: Clock): Instant

  def ofEpochSecond(epochSecond: Long): Instant =
    ofEpochSecond(epochSecond, 0)

  def ofEpochSecond(epochSecond: Long, nanos: Long): Instant = {
    val adjustedSeconds = Math.addExact(epochSecond,
        Math.floorDiv(nanos, NANOS_IN_SECOND))
    val adjustedNanos = Math.floorMod(nanos, NANOS_IN_SECOND).toInt
    new Instant(adjustedSeconds, adjustedNanos)
  }

  def ofEpochMilli(epochMilli: Long): Instant = {
    val seconds = Math.floorDiv(epochMilli, MILLIS_IN_SECOND)
    val nanos = Math.floorMod(epochMilli, MILLIS_IN_SECOND)
    new Instant(seconds, nanos.toInt * NANOS_IN_MILLI)
  }

  def from(temporal: TemporalAccessor): Instant = temporal match {
    case temporal: Instant => temporal
    case _                 =>
      ofEpochSecond(temporal.getLong(INSTANT_SECONDS), temporal.getLong(NANO_OF_SECOND))
  }

  private def parseSegment(segment: String, classifier: String): Int = {
    try {
      segment.toInt
    } catch {
      case _: NumberFormatException =>
        throw new DateTimeParseException(s"$segment is not a valid $classifier",
            segment, 0)
    }
  }

  private def toEpochDay(year: Int, month: Int, day: Int): Long = {
    val leapYear = iso.isLeapYear(year)

    val extremeLeapYear = 999999996
    val epochDaysToAccountForExtreme = (3 * DAYS_IN_YEAR) + DAYS_IN_LEAP_YEAR

    requireDateTimeParse(year <= MaxYear || year >= MinYear,
        s"$year out of bounds, year > 1000000000 || year < -1000000000",
        year.toString, 0)

    val monthDay = MonthDay.of(month, day)
    if (monthDay.getMonth == Month.FEBRUARY && leapYear) {
      requireDateTimeParse(monthDay.getDayOfMonth <= 29,
          "Day range out of bounds <= 29 for leap years", day.toString, 0)
    }

    if (year == MaxYear)
      LocalDate.of(extremeLeapYear, month, day).toEpochDay + epochDaysToAccountForExtreme
    else if (year == MinYear)
      LocalDate.of(-extremeLeapYear, month, day).toEpochDay - epochDaysToAccountForExtreme
    else
      LocalDate.of(year, month, day).toEpochDay
  }

  def parse(text: CharSequence): Instant = {
    try {
      val pattern = """(^[-+]?)(\d*)-(\d*)-(\d*)T(\d*):(\d*):(\d*).?(\d*)Z""".r
      val pattern(sign, yearSegment, monthSegment, daySegment,
          hourSegment, minutesSegment, secondsSegment, nanosecondsSegment) = text

      val year = parseSegment(sign + yearSegment, "year")
      val month = parseSegment(monthSegment, "month")
      val day = parseSegment(daySegment, "day")
      val nanoPower = 9

      requireDateTimeParse(!((sign != "+") && (year > 9999)),
          s"year > 9999 must be preceded by [+]", text, 0)

      val days = toEpochDay(year, month, day)
      val dayOffset = days

      val hourOffset = parseSegment(hourSegment, "hour")
      val minuteOffset = parseSegment(minutesSegment, "minutes")
      val secondsOffset = parseSegment(secondsSegment, "seconds")

      requireDateTimeParse(hourOffset <= HOURS_IN_DAY,
          s"hours are > $HOURS_IN_DAY", text, 0)

      requireDateTimeParse(minuteOffset <= MINUTES_IN_HOUR,
          s"minutes are > $MINUTES_IN_HOUR", text, 0)

      requireDateTimeParse(secondsOffset <= SECONDS_IN_MINUTE,
          s"seconds are > $SECONDS_IN_MINUTE", text, 0)

      val nanos = if (nanosecondsSegment != "") {
        val scale = Math.pow(10, nanoPower - nanosecondsSegment.length).toInt
        parseSegment(nanosecondsSegment, "nanoseconds") * scale
      } else {
        0
      }

      val epochSecondsOffset = {
        dayOffset * SECONDS_IN_DAY +
        hourOffset * SECONDS_IN_HOUR +
        minuteOffset * SECONDS_IN_MINUTE +
        secondsOffset
      }

      new Instant(epochSecondsOffset, nanos)
    } catch {
      case err: DateTimeParseException =>
        throw err
      case NonFatal(err) =>
        throw new DateTimeParseException(s"Invalid date $text", text, 0)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy