diff --git a/src/main/scala/java/time/Instant.scala b/src/main/scala/java/time/Instant.scala new file mode 100644 index 0000000..e95a5f0 --- /dev/null +++ b/src/main/scala/java/time/Instant.scala @@ -0,0 +1,192 @@ +package java.time + +import java.time.Preconditions._ +import java.time.temporal._ +import scala.scalajs.js + +final class Instant(epochSecond:Long, nanoInSecond:Int) extends Temporal with TemporalAdjuster with Comparable[Instant]{ + + import ChronoUnit._ + import ChronoField._ + import Constants._ + import Instant._ + + requireDateTime(epochSecond >= MIN_EPOCH_SECOND && epochSecond <= MAX_EPOCH_SECOND, s"epochSecond out of range [$MIN_EPOCH_SECOND, $MAX_EPOCH_SECOND]") + requireDateTime(nanoInSecond >= 0 && nanoInSecond <= 999999999, s"nanoInSecond out of range [0, 999999999]") + + private def nanos(i:Instant):Long = i.getEpochSecond * NANOS_IN_SECOND + i.getNano + + private val totalNanos: Long = nanos(this) + + + def isBefore(o:Instant):Boolean = compareTo(o) < 0 + def isAfter(o:Instant):Boolean = compareTo(o) > 0 + + override def adjustInto(temporal: Temporal): Temporal = temporal.`with`(INSTANT_SECONDS, epochSecond).`with`(NANO_OF_SECOND, nanoInSecond) + + override def compareTo(o: Instant): Int = totalNanos.compareTo(nanos(o)) + + override def isSupported(unit: TemporalUnit): Boolean = unit match { + case _:ChronoUnit => unit == NANOS || unit == MICROS || unit == MILLIS || unit == SECONDS || unit == MINUTES || unit ==HOURS || unit == HALF_DAYS || unit == DAYS + case null => false + case _ => unit.isSupportedBy(this) + } + + def plus(amount: Long, unit: TemporalUnit): Instant = unit match { + case _:ChronoUnit => unit match { + case NANOS => plusNanos(amount) + case MICROS => plusNanos(amount * NANOS_IN_MICRO) + case MILLIS => plusNanos(amount * NANOS_IN_MILLI) + case SECONDS => plusSeconds(amount) + case MINUTES => plusSeconds(amount * SECONDS_IN_MINUTE) + case HOURS => plusSeconds(amount * SECONDS_IN_HOUR) + case HALF_DAYS => plusSeconds(amount * SECONDS_IN_HOUR * 12) + case DAYS => plusSeconds(amount * SECONDS_IN_DAY) + case _ => throw new UnsupportedTemporalTypeException(s"Unit not supported: $unit") + } + case _ => unit.addTo(this, amount) + } + + def plusNanos(nanosToAdd:Long):Instant = { + Instant.ofEpochSecond(epochSecond + nanosToAdd/NANOS_IN_SECOND, (nanoInSecond + nanosToAdd) % NANOS_IN_SECOND) + } + + def plusMillis(millisToAdd:Long):Instant = plusNanos(millisToAdd * NANOS_IN_MILLI) + + def plusSeconds(secondsToAdd:Long):Instant = Instant.ofEpochSecond(epochSecond + secondsToAdd, nanoInSecond) + + override def until(end: Temporal, unit: TemporalUnit): Long = unit match { + case _:ChronoUnit => + val endInstant = from(end) + unit match { + case NANOS => diffNanos(endInstant) + case MICROS => diffNanos(endInstant) / NANOS_IN_MICRO + case MILLIS => diffNanos(endInstant) / NANOS_IN_MILLI + case SECONDS => diffSeconds(endInstant) + case MINUTES => diffSeconds(endInstant) / SECONDS_IN_MINUTE + case HOURS => diffSeconds(endInstant) / SECONDS_IN_HOUR + case HALF_DAYS => diffSeconds(endInstant) / (SECONDS_IN_HOUR * 12) + case DAYS => diffSeconds(endInstant) / SECONDS_IN_DAY + case _ => throw new UnsupportedTemporalTypeException(s"Unit not supported: $unit") + } + case _ => unit.between(this, end) + } + + private def diffNanos(end: Instant): Long = end.totalNanos - totalNanos + private def diffSeconds(end: Instant): Long = end.getEpochSecond - epochSecond + + + def `with`(field: TemporalField, newValue: Long): Instant = field match { + case _:ChronoField => field match { + case NANO_OF_SECOND => new Instant(epochSecond, newValue.toInt) + case MICRO_OF_SECOND => new Instant(epochSecond, (newValue * NANOS_IN_MICRO).toInt) + case MILLI_OF_SECOND => new Instant(epochSecond, (newValue * NANOS_IN_MILLI).toInt) + case INSTANT_SECONDS => new Instant(newValue, nanoInSecond) + case _ => throw new UnsupportedTemporalTypeException(s"Field not supported: $field") + } + case _ => field.adjustInto(this, newValue) + } + override def isSupported(field: TemporalField): Boolean = field match { + case _:ChronoField => field == NANO_OF_SECOND || field == MICRO_OF_SECOND || field == MILLI_OF_SECOND || field == INSTANT_SECONDS + case null => false + case _ => field.isSupportedBy(this) + } + override def getLong(field: TemporalField): Long = field match { + case NANO_OF_SECOND => nanoInSecond + case MICRO_OF_SECOND => nanoInSecond / NANOS_IN_MICRO + case MILLI_OF_SECOND => nanoInSecond / NANOS_IN_MILLI + case INSTANT_SECONDS => epochSecond + case _ => throw new UnsupportedTemporalTypeException(s"Field not supported: $field") + } + + override def range(field: TemporalField): ValueRange = field match { + case NANO_OF_SECOND => ValueRange.of(0, NANOS_IN_SECOND-1) + case MICRO_OF_SECOND => ValueRange.of(0, MICROS_IN_SECOND-1) + case MILLI_OF_SECOND => ValueRange.of(0, MILLIS_IN_SECOND-1) + case INSTANT_SECONDS => ValueRange.of(Long.MinValue,Long.MaxValue) + case _ => + throw new UnsupportedTemporalTypeException(s"Field not supported: $field") + } + + def minusNanos(nanosToSubstract:Long):Instant = plusNanos(-nanosToSubstract) + + def minusMillis(millisToSubstract:Long):Instant = plusMillis(-millisToSubstract) + + def minusSeconds(secondsToSubstract:Long):Instant = plusSeconds(-secondsToSubstract) + + override def get(field: TemporalField): Int = field match { + case INSTANT_SECONDS => throw new DateTimeException(s"$INSTANT_SECONDS is too large to fit in an Int") + case _ => getLong(field).toInt + } + + def minus(amountToSubtract: Long, unit: TemporalUnit): Instant = + if (amountToSubtract == Long.MinValue) plus(Long.MaxValue, unit).plus(1, unit) + else plus(-amountToSubtract, unit) + + def getNano():Int = nanoInSecond + + def getEpochSecond():Long = epochSecond + + def toEpochMilli():Long = epochSecond * MICROS_IN_SECOND + (nanoInSecond / NANOS_IN_MILLI) + + override def toString: String = s"Instant(seconds:$epochSecond, nanoInSecond:$nanoInSecond" + + override def equals(obj: scala.Any): Boolean = obj match { + case o:Instant => epochSecond == o.getEpochSecond && nanoInSecond == o.getNano + case _ => false + } + override def hashCode(): Int = totalNanos.hashCode() + + def truncatedTo(unit:TemporalUnit):Instant = { + if (unit == NANOS) this + else if (unit.isTimeBased || unit == DAYS) { + val duration = unit.getDuration + val seconds = duration.getSeconds + if (seconds > 0) Instant.ofEpochSecond(epochSecond - (epochSecond % seconds)) + else Instant.ofEpochSecond(epochSecond, nanoInSecond - (nanoInSecond % duration.getNano)) + } else throw new UnsupportedTemporalTypeException(s"Unit not supported: $unit") + } +} +object Instant { + import ChronoUnit._ + import ChronoField._ + import Constants._ + + private val MIN_EPOCH_SECOND: Long = -31557014167219200L + private val MAX_EPOCH_SECOND: Long = 31556889864403199L + + val MIN:Instant = new Instant(MIN_EPOCH_SECOND,0) + val MAX:Instant = new Instant(MAX_EPOCH_SECOND,999999999) + + val EPOCH:Instant = new Instant(0,0) + + + def now():Instant = { + val d = new js.Date() + val millis: Double = d.getTime + new Instant((millis / 1000).toLong, (millis % 1000 * 1000).toInt) + } + + def ofEpochSecond(epochSecond:Long):Instant = new Instant(epochSecond,0) + + def ofEpochSecond(epochSecond:Long, nanoAdjustment:Long):Instant = { + val mod = nanoAdjustment % NANOS_IN_SECOND + if (mod <0) new Instant(epochSecond - (nanoAdjustment/NANOS_IN_SECOND) - 1, (NANOS_IN_SECOND + mod).toInt) + else new Instant(epochSecond + (nanoAdjustment/NANOS_IN_SECOND), mod.toInt) + } + + private def ofNano(nanos:Long):Instant = { + ofEpochSecond(nanos / NANOS_IN_SECOND, nanos % NANOS_IN_SECOND) + } + + def ofEpochMilli(epochMilli:Long):Instant = + new Instant(epochMilli / MILLIS_IN_SECOND, ((epochMilli % MILLIS_IN_SECOND) * NANOS_IN_MILLI).toInt) + + def from(temporal:TemporalAccessor): Instant = { + Instant.ofEpochSecond(temporal.getLong(INSTANT_SECONDS), temporal.getLong(NANO_OF_SECOND)) + } + + //Not implemented + //def parse(text:CharSequence):Instant + +} \ No newline at end of file diff --git a/testSuite/shared/src/test/scala/org/scalajs/testsuite/javalib/time/InstantTest.scala b/testSuite/shared/src/test/scala/org/scalajs/testsuite/javalib/time/InstantTest.scala new file mode 100644 index 0000000..1622511 --- /dev/null +++ b/testSuite/shared/src/test/scala/org/scalajs/testsuite/javalib/time/InstantTest.scala @@ -0,0 +1,195 @@ +package org.scalajs.testsuite.javalib.time + +import java.time.temporal.{ChronoField, ChronoUnit, UnsupportedTemporalTypeException} +import java.time.{DateTimeException, Instant} + +import org.junit.Assert._ +import org.junit.Test +import org.scalajs.testsuite.utils.AssertThrows + +class InstantTest extends TemporalTest[Instant]{ + + import AssertThrows._ + import DateTimeTestUtil._ + import Instant._ + import ChronoField._ + import ChronoUnit._ + + override def isSupported(unit: ChronoUnit): Boolean = unit == NANOS || unit == MICROS || unit == MILLIS || + unit == SECONDS || unit == MINUTES || unit ==HOURS || unit == HALF_DAYS || unit == DAYS + override def isSupported(field: ChronoField): Boolean = field == NANO_OF_SECOND || + field == MICRO_OF_SECOND || field == MILLI_OF_SECOND || field == INSTANT_SECONDS + + override val samples: Seq[Instant] = Seq(MIN, MAX, EPOCH) + + @Test def test_getLong():Unit = { + assertEquals(0L, MIN.getLong(NANO_OF_SECOND)) + assertEquals(0L, MIN.getLong(MICRO_OF_SECOND)) + assertEquals(0L, MIN.getLong(MILLI_OF_SECOND)) + assertEquals(-31557014167219200L, MIN.getLong(INSTANT_SECONDS)) + + assertEquals(999999999L, MAX.getLong(NANO_OF_SECOND)) + assertEquals(999999L, MAX.getLong(MICRO_OF_SECOND)) + assertEquals(999L, MAX.getLong(MILLI_OF_SECOND)) + assertEquals(31556889864403199L, MAX.getLong(INSTANT_SECONDS)) + + assertEquals(0L, EPOCH.getLong(NANO_OF_SECOND)) + assertEquals(0L, EPOCH.getLong(MICRO_OF_SECOND)) + assertEquals(0L, EPOCH.getLong(MILLI_OF_SECOND)) + assertEquals(0L, EPOCH.getLong(INSTANT_SECONDS)) + + } + @Test def test_now():Unit = { + assertNotNull(now()) + } + + @Test def test_ofEpochSecond():Unit = { + testDateTime(ofEpochSecond(0))(EPOCH) + testDateTime(ofEpochSecond(0, 0))(EPOCH) + testDateTime(ofEpochSecond(31556889864403199L, 999999999))(MAX) + testDateTime(ofEpochSecond(-31557014167219200L, 0))(MIN) + testDateTime(ofEpochSecond(123))(ofEpochSecond(123,0)) + testDateTime(ofEpochSecond(3, 1))(ofEpochSecond(4, -999999999)) + testDateTime(ofEpochSecond(3, 1))(ofEpochSecond(2, 1000000001)) + + expectThrows(classOf[DateTimeException], ofEpochSecond(31556889864403199L + 1)) + expectThrows(classOf[DateTimeException], ofEpochSecond(31556889864403199L, 999999999 + 1)) + expectThrows(classOf[DateTimeException], ofEpochSecond(-31557014167219200L - 1)) + expectThrows(classOf[DateTimeException], ofEpochSecond(-31557014167219200L - 1, -1)) + } + + @Test def test_getNano():Unit = { + assertEquals(0, EPOCH.getNano) + assertEquals(0, MIN.getNano) + assertEquals(999999999, MAX.getNano) + assertEquals(1, ofEpochSecond(4, -999999999).getNano) + assertEquals(1, ofEpochSecond(2, 1000000001).getNano) + assertEquals(234567890, ofEpochSecond(1, 234567890).getNano) + } + + @Test def test_with():Unit = { + val supportedFields = Set(NANO_OF_SECOND, MICRO_OF_SECOND, MILLI_OF_SECOND, INSTANT_SECONDS) + for (t <- samples) { + for (n <- Seq(0, 999, 999999, 999999999)) + testDateTime(t.`with`(NANO_OF_SECOND, n))(ofEpochSecond(t.getEpochSecond(), n)) + for (n <- Seq(0, 999, 999999)) + testDateTime(t.`with`(MICRO_OF_SECOND, n))(ofEpochSecond(t.getEpochSecond(), n * 1000)) + for (n <- Seq(0, 500, 999)) + testDateTime(t.`with`(MILLI_OF_SECOND, n))(ofEpochSecond(t.getEpochSecond(), n * 1000000)) + for (n <- Seq(-1000000000L, -86400L, -3600L, -60L, -1L, 0L, 1L, 60L, 3600L, 86400L, 1000000000L)) + testDateTime(t.`with`(INSTANT_SECONDS, n))(ofEpochSecond(n, t.getNano())) + + for (f <- ChronoField.values()) { + if (!supportedFields(f)) expectThrows(classOf[UnsupportedTemporalTypeException], t.`with`(f, 1)) + } + + expectThrows(classOf[DateTimeException], t.`with`(NANO_OF_SECOND, -1)) + expectThrows(classOf[DateTimeException], t.`with`(NANO_OF_SECOND, 999999999+1)) + expectThrows(classOf[DateTimeException], t.`with`(MICRO_OF_SECOND, -1)) + expectThrows(classOf[DateTimeException], t.`with`(MICRO_OF_SECOND, 999999+1)) + expectThrows(classOf[DateTimeException], t.`with`(MILLI_OF_SECOND, -1)) + expectThrows(classOf[DateTimeException], t.`with`(MILLI_OF_SECOND, 999+1)) + expectThrows(classOf[DateTimeException], t.`with`(INSTANT_SECONDS, MIN.getEpochSecond-1)) + expectThrows(classOf[DateTimeException], t.`with`(INSTANT_SECONDS, MAX.getEpochSecond+1)) + } + } + + @Test def test_truncatedTo():Unit = { + testDateTime(MIN.truncatedTo(NANOS))(MIN) + testDateTime(MIN.truncatedTo(MICROS))(MIN) + testDateTime(MIN.truncatedTo(MILLIS))(MIN) + testDateTime(MIN.truncatedTo(SECONDS))(MIN) + testDateTime(MIN.truncatedTo(MINUTES))(MIN) + testDateTime(MIN.truncatedTo(HOURS))(MIN) + testDateTime(MIN.truncatedTo(HALF_DAYS))(MIN) + testDateTime(MIN.truncatedTo(DAYS))(MIN) + + testDateTime(MAX.truncatedTo(NANOS))(MAX) + testDateTime(MAX.truncatedTo(MICROS))(MAX.minusNanos(999)) + testDateTime(MAX.truncatedTo(MILLIS))(MAX.minusNanos(999999)) + testDateTime(MAX.truncatedTo(SECONDS))(MAX.minusNanos(999999999)) + testDateTime(MAX.truncatedTo(MINUTES))(MAX.minusNanos(999999999).minusSeconds(59)) + testDateTime(MAX.truncatedTo(HOURS))(MAX.minusNanos(999999999).minusSeconds(59).minus(59, MINUTES)) + testDateTime(MAX.truncatedTo(HALF_DAYS))(MAX.minusNanos(999999999).minusSeconds(59).minus(59, MINUTES).minus(11, HOURS)) + testDateTime(MAX.truncatedTo(DAYS))(MAX.minusNanos(999999999).minusSeconds(59).minus(59, MINUTES).minus(23, HOURS)) + } + + + @Test def test_ofEpochMilli():Unit = { + testDateTime(ofEpochMilli(0))(EPOCH) + testDateTime(ofEpochMilli(1234))(ofEpochSecond(1,234000000)) + } + + @Test def test_from():Unit = { + for (t <- samples) + testDateTime(from(t))(t) + } + + @Test def test_compareTto():Unit = { + assertEquals(0, MIN.compareTo(MIN)) + assertEquals(0, MAX.compareTo(MAX)) + assertTrue(MAX.compareTo(MIN) > 0) + assertTrue(MIN.compareTo(MAX) < 0) + } + + @Test def test_isAfter(): Unit = { + assertFalse(MIN.isAfter(MIN)) + assertFalse(MIN.isAfter(MAX)) + assertTrue(MAX.isAfter(MIN)) + assertFalse(MAX.isAfter(MAX)) + } + + @Test def test_isBefore(): Unit = { + assertFalse(MIN.isBefore(MIN)) + assertTrue(MIN.isBefore(MAX)) + assertFalse(MAX.isBefore(MIN)) + assertFalse(MAX.isBefore(MAX)) + } + + @Test def test_adjustInto():Unit = { + for { + t1 <- samples + t2 <- samples + } { + testDateTime(t1.adjustInto(t2))(t1) + } + } + + + @Test def test_until(): Unit = { + assertEquals(63113904031622399L, MIN.until(MAX, SECONDS) ) + assertEquals(1051898400527039L, MIN.until(MAX, MINUTES)) + assertEquals(17531640008783L, MIN.until(MAX, HOURS)) + assertEquals(1460970000731L, MIN.until(MAX, HALF_DAYS)) + assertEquals(730485000365L, MIN.until(MAX, DAYS)) + + for (u <- timeBasedUnits) { + assertEquals(-MIN.until(MAX, u), MAX.until(MIN, u)) + assertEquals(0L, MIN.until(MIN, u)) + assertEquals(0L, MAX.until(MAX, u)) + } + for (u <- dateBasedUnits) { + if (u != DAYS) + expectThrows(classOf[UnsupportedTemporalTypeException], MIN.until(MIN, u)) + } + } + + @Test def test_plus():Unit = { + val values = Seq(Long.MinValue, -1000000000L, -86400L, -3600L, -60L, -1L, 0L, + 1L, 60L, 3600L, 86400L, 1000000000L, Long.MaxValue) + + for { + t <- samples + n <- values + } { + testDateTime(t.plus(n, NANOS))(t.plusNanos(n)) + testDateTime(t.plus(n, MICROS))(t.plusNanos(n * 1000)) + testDateTime(t.plus(n, MILLIS))(t.plusNanos(n * 1000000)) + testDateTime(t.plus(n, SECONDS))(t.plusSeconds(n)) + testDateTime(t.plus(n, MINUTES))(t.plusSeconds(n * 60)) + testDateTime(t.plus(n, HOURS))(t.plusSeconds(n * 60 * 60)) + testDateTime(t.plus(n, HALF_DAYS))(t.plusSeconds(n * 60 * 60 * 12)) + testDateTime(t.plus(n, DAYS))(t.plusSeconds(n * 60 * 60 * 24)) + } + } +}