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

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

package java.time

import scala.scalajs.js

import java.time.chrono._
import java.time.format.DateTimeParseException
import java.time.temporal._

import scala.util.control.NonFatal

final class LocalDate private (year: Int, month: Month, dayOfMonth: Int)
    extends ChronoLocalDate with Serializable {
  import Preconditions.requireDateTime
  import ChronoField._
  import ChronoUnit._
  import LocalDate._

  requireDateTime(year >= Year.MIN_VALUE && year <= Year.MAX_VALUE,
      s"Invalid value for year: $year")

  private val _isLeapYear = iso.isLeapYear(year)

  requireDateTime(dayOfMonth > 0 && dayOfMonth <= month.maxLength,
      s"Invalid value for dayOfMonth: $dayOfMonth")
  requireDateTime(_isLeapYear || dayOfMonth <= month.minLength,
      s"Invalid value for dayOfMonth: $dayOfMonth")

  private lazy val dayOfYear =
    month.firstDayOfYear(_isLeapYear) + dayOfMonth - 1

  private lazy val epochDay = {
    val year1 = year - 1970
    val daysBeforeYear = daysBeforeYears(Math.floorMod(year1, 400))._2
    val offset =
      Math.floorDiv(year1, 400).toLong * daysInFourHundredYears
    offset + daysBeforeYear + dayOfYear - 1
  }

  private lazy val dayOfWeek =
    Math.floorMod(epochDay + 3, 7).toInt + 1

  private def prolepticMonth = year.toLong * 12 + getMonthValue - 1

  // Implemented by ChronoLocalDate
  // def isSupported(field: TemporalField): Boolean
  // def isSupported(unit: TemporalUnit): Boolean

  override def range(field: TemporalField): ValueRange = field match {
    case DAY_OF_MONTH => ValueRange.of(1, lengthOfMonth)
    case DAY_OF_YEAR  => ValueRange.of(1, lengthOfYear)

    case ALIGNED_WEEK_OF_MONTH =>
      ValueRange.of(1, if (lengthOfMonth > 28) 5 else 4)

    case YEAR_OF_ERA =>
      ValueRange.of(1, if (year > 0) 999999999 else 1000000000)

    case _ => super.range(field)
  }

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

  def getLong(field: TemporalField): Long = field match {
    case DAY_OF_WEEK                  => dayOfWeek
    case ALIGNED_DAY_OF_WEEK_IN_MONTH => (dayOfMonth - 1) % 7 + 1
    case ALIGNED_DAY_OF_WEEK_IN_YEAR  => (getLong(DAY_OF_YEAR) - 1) % 7 + 1
    case DAY_OF_MONTH                 => dayOfMonth
    case DAY_OF_YEAR                  => dayOfYear
    case EPOCH_DAY                    => epochDay
    case ALIGNED_WEEK_OF_MONTH        => (dayOfMonth - 1) / 7 + 1
    case ALIGNED_WEEK_OF_YEAR         => (dayOfYear - 1) / 7 + 1
    case MONTH_OF_YEAR                => getMonthValue
    case PROLEPTIC_MONTH              => prolepticMonth
    case YEAR_OF_ERA                  => if (year > 0) year else 1 - year
    case YEAR                         => year
    case ERA                          => if (year > 0) 1 else 0

    case _: ChronoField =>
      throw new UnsupportedTemporalTypeException(s"Unsupported field: $field")

    case _ => field.getFrom(this)
  }

  def getChronology(): IsoChronology = iso

  override def getEra(): Era = if (year > 0) IsoEra.CE else IsoEra.BCE

  def getYear(): Int = year

  def getMonthValue(): Int = month.getValue

  def getMonth(): Month = month

  def getDayOfMonth(): Int = dayOfMonth

  def getDayOfYear(): Int = dayOfYear

  def getDayOfWeek(): DayOfWeek = DayOfWeek.of(dayOfWeek)

  override def isLeapYear(): Boolean = _isLeapYear

  def lengthOfMonth(): Int = month.length(_isLeapYear)

  override def lengthOfYear(): Int = if (_isLeapYear) 366 else 365

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

  override def `with`(field: TemporalField, value: Long): LocalDate = {
    val msg = s"Invalid value for $field: $value"
    field match {
      case DAY_OF_WEEK | ALIGNED_DAY_OF_WEEK_IN_MONTH |
          ALIGNED_DAY_OF_WEEK_IN_YEAR =>
        requireDateTime(value > 0 && value <= 7, msg)
        plus(value.toInt - get(field), DAYS)

      case DAY_OF_MONTH =>
        requireDateTime(value > 0 && value <= 31, msg)
        withDayOfMonth(value.toInt)

      case DAY_OF_YEAR =>
        requireDateTime(value > 0 && value <= 366, msg)
        withDayOfYear(value.toInt)

      case EPOCH_DAY =>
        ofEpochDay(value)

      case ALIGNED_WEEK_OF_MONTH =>
        requireDateTime(value > 0 && value <= 5, msg)
        plus(value.toInt - get(field), WEEKS)

      case ALIGNED_WEEK_OF_YEAR =>
        requireDateTime(value > 0 && value <= 53, msg)
        plus(value.toInt - get(field), WEEKS)

      case MONTH_OF_YEAR =>
        requireDateTime(value > 0 && value <= 12, msg)
        withMonth(value.toInt)

      case PROLEPTIC_MONTH =>
        requireDateTime(value >= -11999999988L && value <= 11999999999L, msg)
        val year = Math.floorDiv(value, 12).toInt
        val month = Math.floorMod(value, 12).toInt + 1
        val day = dayOfMonth min Month.of(month).length(iso.isLeapYear(year))
        LocalDate.of(year, month, day)

      case YEAR_OF_ERA =>
        requireDateTime(value > 0 && value <= Year.MAX_VALUE + 1, msg)
        if (getEra == IsoEra.CE) withYear(value.toInt)
        else withYear(1 - value.toInt)

      case YEAR =>
        requireDateTime(value >= Year.MIN_VALUE && value <= Year.MAX_VALUE, msg)
        withYear(value.toInt)

      case ERA =>
        requireDateTime(value >= 0 && value <= 1, msg)
        val yearOfEra = get(YEAR_OF_ERA)
        if (getEra == IsoEra.BCE && value == 1) withYear(yearOfEra)
        else if (getEra == IsoEra.CE && value == 0) withYear(1 - yearOfEra)
        else this

      case _: ChronoField =>
        throw new UnsupportedTemporalTypeException(s"Unsupported field: $field")

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

  def withYear(year: Int): LocalDate = {
    if (dayOfMonth == 29 && month == Month.FEBRUARY && !iso.isLeapYear(year))
      LocalDate.of(year, 2, 28)
    else
      LocalDate.of(year, month, dayOfMonth)
  }

  def withMonth(month: Int): LocalDate = {
    val m = Month.of(month)
    LocalDate.of(year, m, dayOfMonth min m.length(isLeapYear))
  }

  def withDayOfMonth(dayOfMonth: Int): LocalDate =
    LocalDate.of(year, month, dayOfMonth)

  def withDayOfYear(dayOfYear: Int): LocalDate =
    LocalDate.ofYearDay(year, dayOfYear)

  override def plus(amount: TemporalAmount): LocalDate =
    amount.addTo(this).asInstanceOf[LocalDate]

  override def plus(amount: Long, unit: TemporalUnit): LocalDate = unit match {
    case DAYS   => plusDays(amount)
    case WEEKS  => plusWeeks(amount)
    case MONTHS => plusMonths(amount)
    case YEARS  => plusYears(amount)

    case DECADES =>
      val years = Math.multiplyExact(amount, 10)
      plusYears(years)

    case CENTURIES =>
      val years = Math.multiplyExact(amount, 100)
      plusYears(years)

    case MILLENNIA =>
      val years = Math.multiplyExact(amount, 1000)
      plusYears(years)

    case ERAS =>
      `with`(ERA, Math.addExact(get(ERA), amount))

    case _: ChronoUnit =>
      throw new UnsupportedTemporalTypeException(s"Unsupported unit: $unit")

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

  def plusYears(years: Long): LocalDate = {
    val year1 = year + years
    requireDateTime(year1 >= Year.MIN_VALUE && year1 <= Year.MAX_VALUE,
        s"Invalid value for year: $year1")
    val leap = iso.isLeapYear(year1)
    val day1 =
      if (month == Month.FEBRUARY && dayOfMonth == 29 && !leap) 28
      else dayOfMonth
    LocalDate.of(year1.toInt, month, day1)
  }

  def plusMonths(months: Long): LocalDate = {
    val month1 = getMonthValue + Math.floorMod(months, 12).toInt
    val year1 = year + Math.floorDiv(months, 12) +
        (if (month1 > 12) 1 else 0)
    requireDateTime(year1 >= Year.MIN_VALUE && year1 <= Year.MAX_VALUE,
        s"Invalid value for year: $year1")
    val month2 = Month.of(if (month1 > 12) month1 - 12 else month1)
    val day = dayOfMonth min month2.length(iso.isLeapYear(year1))
    LocalDate.of(year1.toInt, month2, day)
  }

  def plusWeeks(weeks: Long): LocalDate =
    plusDays(Math.multiplyExact(weeks, 7))

  def plusDays(days: Long): LocalDate = {
    val epochDay1 = Math.addExact(epochDay, days)
    LocalDate.ofEpochDay(epochDay1)
  }

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

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

  def minusYears(years: Long): LocalDate = plusYears(-years)

  def minusMonths(months: Long): LocalDate = plusMonths(-months)

  def minusWeeks(weeks: Long): LocalDate = plusWeeks(-weeks)

  def minusDays(days: Long): LocalDate =
    if (days != Long.MinValue) plusDays(-days)
    else plusDays(Long.MaxValue).plusDays(1)

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

  // Implemented by ChronoLocalDate
  // def adjustInto(temporal: Temporal): Temporal

  def until(end: Temporal, unit: TemporalUnit): Long = {
    import scala.math.Ordering.Implicits._

    val other = LocalDate.from(end)
    unit match {
      case DAYS  => other.toEpochDay - epochDay
      case WEEKS => (other.toEpochDay - epochDay) / 7

      case MONTHS =>
        val dmonths = other.prolepticMonth - prolepticMonth
        if (other.getDayOfMonth < dayOfMonth && dmonths > 0) dmonths - 1
        else if (other.getDayOfMonth > dayOfMonth && dmonths < 0) dmonths + 1
        else dmonths

      case YEARS =>
        val dyears = other.getYear - year
        if ((other.getMonthValue, other.getDayOfMonth) < (getMonthValue, dayOfMonth) &&
            dyears > 0)
          dyears - 1
        else if ((other.getMonthValue, other.getDayOfMonth) > (getMonthValue, dayOfMonth) &&
            dyears < 0)
          dyears + 1
        else
          dyears

      case DECADES   => until(end, YEARS) / 10
      case CENTURIES => until(end, YEARS) / 100
      case MILLENNIA => until(end, YEARS) / 1000

      case ERAS =>
        val year1 = other.getYear
        if (year <= 0 && year1 > 0) 1
        else if (year > 0 && year1 <= 0) -1
        else 0

      case _: ChronoUnit =>
        throw new UnsupportedTemporalTypeException(s"Unsupported unit: $unit")

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

  def until(end: ChronoLocalDate): Period = {
    val other = LocalDate.from(end)
    val dmonths = other.prolepticMonth - prolepticMonth
    val ddays = other.getDayOfMonth - dayOfMonth
    val corr = {
      if (dmonths > 0 && ddays < 0) -1
      else if (dmonths < 0 && ddays > 0) 1
      else 0
    }
    val months = dmonths + corr
    val days = {
      if (corr < 0) plus(months, MONTHS).until(other, DAYS).toInt
      else if (corr > 0) ddays - other.lengthOfMonth
      else ddays
    }
    val years = (months / 12).toInt
    val months1 = (months % 12).toInt
    Period.of(years, months1, days)
  }

  // Not implemented
  // def format(formatter: java.time.format.DateTimeFormatter): String

  // TODO
  // def atTime(time: LocalTime): LocalDateTime
  // def atTime(hour: Int, minute: Int): LocalDateTime
  // def atTime(hour: Int, minute: Int, second: Int): LocalDateTime
  // def atTime(hour: Int, minute: Int, second: Int, nanoOfSecond: Int):
  //     LocalDateTime

  // Not implemented
  // def atTime(time: OffsetTime): OffsetDateTime


  // TODO
  // def atStartOfDay(): LocalDateTime

  // Not implemented
  // def atStartOfDay(id: ZoneId): ZonedDateTime

  override def toEpochDay(): Long = epochDay

  // Implemented by ChronoLocalDate
  // def compare(other: ChronoLocalDate): Int
  // def isAfter(other: ChronoLocalDate): Boolean
  // def isBefore(other: ChronoLocalDate): Boolean
  // def isEqual(other: ChronoLocalDate): Boolean

  override def equals(other: Any): Boolean = other match {
    case other: LocalDate => isEqual(other)
    case _                => false
  }

  override def hashCode(): Int = epochDay.hashCode

  override def toString(): String = {
    if (year >= 0 && year < 10000)
      f"$year%04d-$getMonthValue%02d-$dayOfMonth%02d"
    else
      f"$year%+05d-$getMonthValue%02d-$dayOfMonth%02d"
  }
}

object LocalDate {
  import Preconditions._

  private final val iso = IsoChronology.INSTANCE

  private val daysBeforeYears = Stream.iterate(1970 -> 0) { case (year, day) =>
    if (iso.isLeapYear(year)) (year + 1) -> (day + 366)
    else (year + 1) -> (day + 365)
  }.take(400).toVector

  private final val daysInFourHundredYears = 146097

  final val MIN = new LocalDate(Year.MIN_VALUE, Month.JANUARY, 1)
  final val MAX = new LocalDate(Year.MAX_VALUE, Month.DECEMBER, 31)

  def now(): LocalDate = {
    val d = new js.Date()
    of(d.getFullYear, d.getMonth + 1, d.getDate)
  }

  // Not implemented
  // def now(zone: ZoneId): LocalDate
  // def now(clock: Clock): LocalDate

  def of(year: Int, month: Month, dayOfMonth: Int): LocalDate =
    new LocalDate(year, month, dayOfMonth)

  def of(year: Int, month: Int, dayOfMonth: Int): LocalDate =
    new LocalDate(year, Month.of(month), dayOfMonth)

  def ofYearDay(year: Int, dayOfYear: Int): LocalDate = {
    requireDateTime(dayOfYear > 0 && dayOfYear <= 366,
        s"Invalid value for dayOfYear: $dayOfYear")
    val leap = iso.isLeapYear(year)
    requireDateTime(dayOfYear <= 365 || leap,
        s"Invalid value for dayOfYear: $dayOfYear")
    val month = Month.values().takeWhile(_.firstDayOfYear(leap) <= dayOfYear).last
    val dayOfMonth = dayOfYear - month.firstDayOfYear(leap) + 1
    of(year, month, dayOfMonth)
  }

  def ofEpochDay(epochDay: Long): LocalDate = {
    val quot = Math.floorDiv(epochDay, daysInFourHundredYears)
    val rem = Math.floorMod(epochDay, daysInFourHundredYears).toInt
    val (year1, start) = daysBeforeYears.takeWhile(_._2 <= rem).last
    val year2 = year1 + quot * 400
    requireDateTime(year2 >= Year.MIN_VALUE && year2 <= Year.MAX_VALUE,
        s"Invalid value for year: $year2")
    val dayOfYear = rem - start + 1
    LocalDate.ofYearDay(year2.toInt, dayOfYear.toInt)
  }

  def from(temporal: TemporalAccessor): LocalDate = temporal match {
    case temporal: LocalDate =>
      temporal

    case _ =>
      ofEpochDay(temporal.getLong(ChronoField.EPOCH_DAY))
  }

  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)
    }
  }

  def parse(text: CharSequence): LocalDate = {
    try {
      val pattern = """(^[-+]?)(\d*)-(\d*)-(\d*)""".r
      val pattern(sign, yearSegment, monthSegment, daySegment) = text

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

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

      LocalDate.of(year.toInt, month.toInt, day.toInt)
    } catch {
      case err: DateTimeParseException =>
        throw err
      case NonFatal(err) =>
        throw new DateTimeParseException(s"Invalid date $text", text, 0)
    }
  }

  // def parse(text: CharSequence,
  //     formatter: java.time.format.DateTimeFormatter): LocalDate
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy