Skip to content

Commit

Permalink
Fix #2: Support Scala 3 for Scala.js (#6)
Browse files Browse the repository at this point in the history
* Update build for split src, move and duplicate files

* Down to a few errors on Scala 3

* Compiles but one test fails

* Compiles and tests run on 3.0.0-M3 Scala.js

* Revert removal of scalac option and upgrade sbt-dotty

* Fix build to let JVM run tests for each Scala version
  • Loading branch information
ekrich authored Feb 1, 2021
1 parent 5825016 commit 677b7c6
Show file tree
Hide file tree
Showing 16 changed files with 421 additions and 13 deletions.
26 changes: 22 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ val scala213 = "2.13.4"
val scala300 = "3.0.0-M3"

val versionsBase = Seq(scala212, scala211, scala213)
val versionsAll = versionsBase :+ scala300
val versionsJS = versionsBase
val versionsJVM = versionsBase :+ scala300
val versionsJS = versionsJVM
val versionsNative = versionsBase

ThisBuild / scalaVersion := scala213
Expand Down Expand Up @@ -63,13 +63,29 @@ lazy val root = (project in file("."))
testSuiteNative
)

// For Scala 3 enums
def sourceDir(projectDir: File, scalaVersion: String): Seq[File] = {
def versionDir(versionDir: String): File =
projectDir / "shared" / "src" / "main" / versionDir

CrossVersion.partialVersion(scalaVersion) match {
case Some((3, _)) => Seq(versionDir("scala-3"))
case Some((2, _)) => Seq(versionDir("scala-2"))
case _ => Seq() // unknown version
}
}

lazy val sjavatime = crossProject(JSPlatform, NativePlatform)
.crossType(CrossType.Full)
.settings(commonSettings)
.settings(
Test / test := {},
mappings in (Compile, packageBin) ~= {
_.filter(!_._2.endsWith(".class"))
},
Compile / unmanagedSourceDirectories ++= {
val projectDir = baseDirectory.value.getParentFile()
sourceDir(projectDir, scalaVersion.value)
}
)
.jsSettings(
Expand All @@ -88,13 +104,15 @@ lazy val testSuite = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.settings(commonSettings: _*)
.settings(skipPublish: _*)
.settings(
//testOptions += Tests.Argument(TestFrameworks.JUnit, "-a", "-s", "-v"),
testOptions += Tests.Argument(TestFrameworks.JUnit, "-a", "-s", "-v"),
scalacOptions += "-target:jvm-1.8"
)
.jvmSettings(
name := "java.time testSuite on JVM",
crossScalaVersions := versionsJVM,
libraryDependencies +=
"com.novocode" % "junit-interface" % "0.11" % Test
("com.novocode" % "junit-interface" % "0.11" % Test)
.withDottyCompat(scalaVersion.value)
)
.jsSettings(
name := "java.time testSuite on JS",
Expand Down
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ addSbtPlugin("org.scala-native" % "sbt-scala-native" % scalaNativeVersion)
addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % crossVer)

// Dotty - Scala 3
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.1")
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.2")

// includes sbt-dynver sbt-pgp sbt-sonatype sbt-git
addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.5")
71 changes: 71 additions & 0 deletions sjavatime/shared/src/main/scala-3/java/time/DayOfWeek.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package java.time

import java.{lang => jl}
import java.time.temporal._

enum DayOfWeek(value: Int) extends jl.Enum[DayOfWeek] with TemporalAccessor
with TemporalAdjuster {

case MONDAY extends DayOfWeek(1)

case TUESDAY extends DayOfWeek(2)

case WEDNESDAY extends DayOfWeek(3)

case THURSDAY extends DayOfWeek(4)

case FRIDAY extends DayOfWeek(5)

case SATURDAY extends DayOfWeek(6)

case SUNDAY extends DayOfWeek(7)

def getValue(): Int = value

// Not implemented
// def getDisplayName(style: TextStyle, locale: ju.Locale): String

def isSupported(field: TemporalField): Boolean = field match {
case _: ChronoField => field == ChronoField.DAY_OF_WEEK
case null => false
case _ => field.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 ChronoField.DAY_OF_WEEK => ordinal + 1

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

case _ => field.getFrom(this)
}

def plus(days: Long): DayOfWeek = {
val offset = (days % 7 + 7) % 7
DayOfWeek.of((ordinal + offset.toInt) % 7 + 1)
}

def minus(days: Long): DayOfWeek = plus(-(days % 7))

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

def adjustInto(temporal: Temporal): Temporal =
temporal.`with`(ChronoField.DAY_OF_WEEK, ordinal + 1)
}

object DayOfWeek {

def of(dayOfWeek: Int): DayOfWeek = values.lift(dayOfWeek - 1).getOrElse {
throw new DateTimeException(s"Invalid value for weekday: $dayOfWeek")
}

def from(temporal: TemporalAccessor): DayOfWeek =
DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK))
}
102 changes: 102 additions & 0 deletions sjavatime/shared/src/main/scala-3/java/time/Month.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package java.time

import java.{lang => jl}
import java.time.temporal._

enum Month(value: Int, defaultLength: Int) extends jl.Enum[Month] with TemporalAccessor
with TemporalAdjuster {

case JANUARY extends Month(1, 31)

case FEBRUARY extends Month(2, 28)

case MARCH extends Month(3, 31)

case APRIL extends Month(4, 30)

case MAY extends Month(5, 31)

case JUNE extends Month(6, 30)

case JULY extends Month(7, 31)

case AUGUST extends Month(8, 31)

case SEPTEMBER extends Month(9, 30)

case OCTOBER extends Month(10, 31)

case NOVEMBER extends Month(11, 30)

case DECEMBER extends Month(12, 31)

import Month._

private lazy val defaultFirstDayOfYear =
values.take(value - 1).foldLeft(1)(_ + _.minLength())

def getValue(): Int = value

// Not implemented
// def getDisplayName(style: TextStyle, locale: Locale): Locale

def isSupported(field: TemporalField): Boolean = field match {
case _: ChronoField => field == ChronoField.MONTH_OF_YEAR
case null => false
case _ => field.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 ChronoField.MONTH_OF_YEAR => ordinal + 1

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

case _ => field.getFrom(this)
}

def plus(months: Long): Month = {
val offset = if (months < 0) months % 12 + 12 else months % 12
of((ordinal + offset.toInt) % 12 + 1)
}

def minus(months: Long): Month = {
val offset = if (months < 0) months % 12 else months % 12 - 12
of((ordinal - offset.toInt) % 12 + 1)
}

def length(leapYear: Boolean): Int = if (leapYear) maxLength() else minLength()

def minLength(): Int = defaultLength

def maxLength(): Int =
if (value == 2) defaultLength + 1 else defaultLength

def firstDayOfYear(leapYear: Boolean): Int =
if (leapYear && value > 2) defaultFirstDayOfYear + 1
else defaultFirstDayOfYear

def firstMonthOfQuarter(): Month = of((ordinal / 3) * 3 + 1)

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

def adjustInto(temporal: Temporal): Temporal =
temporal.`with`(ChronoField.MONTH_OF_YEAR, value)
}

object Month {

def of(month: Int): Month = values.lift(month - 1).getOrElse {
throw new DateTimeException(s"Invalid value for month: $month")
}

def from(temporal: TemporalAccessor): Month =
Month.of(temporal.get(ChronoField.MONTH_OF_YEAR))
}
21 changes: 21 additions & 0 deletions sjavatime/shared/src/main/scala-3/java/time/chrono/IsoEra.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package java.time.chrono

import java.{lang => jl}
import java.time.DateTimeException

enum IsoEra extends jl.Enum[IsoEra] with Era {

case BCE extends IsoEra

case CE extends IsoEra

def getValue(): Int = ordinal
}

object IsoEra {

// TODO"values can not be called with parens
def of(isoEra: Int): IsoEra = values.lift(isoEra).getOrElse {
throw new DateTimeException(s"Invalid value for isoEra: $isoEra")
}
}
133 changes: 133 additions & 0 deletions sjavatime/shared/src/main/scala-3/java/time/temporal/ChronoField.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package java.time.temporal

import java.{lang => jl}
import java.time.Year

import ChronoUnit._

enum ChronoField(_range: ValueRange, baseUnit: ChronoUnit,
rangeUnit: ChronoUnit, flags: Int)
extends jl.Enum[ChronoField] with TemporalField {

// private final val isTimeBasedFlag = 1
// private final val isDateBasedFlag = 2

case NANO_OF_SECOND extends ChronoField(
ValueRange.of(0, 999999999), NANOS, SECONDS, 1)

case NANO_OF_DAY extends ChronoField(
ValueRange.of(0, 86399999999999L), NANOS, DAYS, 1)

case MICRO_OF_SECOND extends ChronoField(
ValueRange.of(0, 999999), MICROS, SECONDS, 1)

case MICRO_OF_DAY extends ChronoField(
ValueRange.of(0, 86399999999L), MICROS, DAYS, 1)

case MILLI_OF_SECOND extends ChronoField(
ValueRange.of(0, 999), MILLIS, SECONDS, 1)

case MILLI_OF_DAY extends ChronoField(
ValueRange.of(0, 86399999), MILLIS, DAYS, 1)

case SECOND_OF_MINUTE extends ChronoField(
ValueRange.of(0, 59), SECONDS, MINUTES, 1)

case SECOND_OF_DAY extends ChronoField(
ValueRange.of(0, 86399), SECONDS, DAYS, 1)

case MINUTE_OF_HOUR extends ChronoField(
ValueRange.of(0, 59), MINUTES, HOURS, 1)

case MINUTE_OF_DAY extends ChronoField(
ValueRange.of(0, 1439), MINUTES, DAYS, 1)

case HOUR_OF_AMPM extends ChronoField(
ValueRange.of(0, 11), HOURS, HALF_DAYS, 1)

case CLOCK_HOUR_OF_AMPM extends ChronoField(
ValueRange.of(1, 12), HOURS, HALF_DAYS, 1)

case HOUR_OF_DAY extends ChronoField(
ValueRange.of(0, 23), HOURS, DAYS, 1)

case CLOCK_HOUR_OF_DAY extends ChronoField(
ValueRange.of(1, 24), HOURS, DAYS, 1)

case AMPM_OF_DAY extends ChronoField(
ValueRange.of(0, 1), HALF_DAYS, DAYS, 1)

case DAY_OF_WEEK extends ChronoField(
ValueRange.of(1, 7), DAYS, WEEKS, 2)

case ALIGNED_DAY_OF_WEEK_IN_MONTH extends ChronoField(
ValueRange.of(1, 7), DAYS, WEEKS, 2)

case ALIGNED_DAY_OF_WEEK_IN_YEAR extends ChronoField(
ValueRange.of(1, 7), DAYS, WEEKS, 2)

case DAY_OF_MONTH extends ChronoField(
ValueRange.of(1, 28, 31), DAYS, MONTHS, 2)

case DAY_OF_YEAR extends ChronoField(
ValueRange.of(1, 365, 366), DAYS, YEARS, 2)

case EPOCH_DAY extends ChronoField(
ValueRange.of(-365249999634L, 365249999634L), DAYS, FOREVER, 2)

case ALIGNED_WEEK_OF_MONTH extends ChronoField(
ValueRange.of(1, 4, 5), WEEKS, MONTHS, 2)

case ALIGNED_WEEK_OF_YEAR extends ChronoField(
ValueRange.of(1, 53), WEEKS, YEARS, 2)

case MONTH_OF_YEAR extends ChronoField(
ValueRange.of(1, 12), MONTHS, YEARS, 2)

case PROLEPTIC_MONTH extends ChronoField(
ValueRange.of(-11999999988L, 11999999999L), MONTHS, FOREVER, 2)

case YEAR_OF_ERA extends ChronoField(
ValueRange.of(1, 999999999, 1000000000), YEARS, ERAS, 2)

case YEAR extends ChronoField(
ValueRange.of(Year.MIN_VALUE, Year.MAX_VALUE), YEARS, FOREVER, 2)

case ERA extends ChronoField(ValueRange.of(0, 1), ERAS, FOREVER, 2)

case INSTANT_SECONDS extends ChronoField(
ValueRange.of(Long.MinValue, Long.MaxValue), SECONDS, FOREVER, 0)

case OFFSET_SECONDS extends ChronoField(
ValueRange.of(-64800, 64800), SECONDS, FOREVER, 0)

// Not implemented:
// def String getDisplayName(locale: java.util.Locale)

def getBaseUnit(): TemporalUnit = baseUnit

def getRangeUnit(): TemporalUnit = rangeUnit

def range(): ValueRange = _range

def isDateBased(): Boolean = (flags & 2) != 0

def isTimeBased(): Boolean = (flags & 1) != 0

def checkValidValue(value: Long): Long =
_range.checkValidValue(value, this)

def checkValidIntValue(value: Long): Int =
_range.checkValidIntValue(value, this)

def isSupportedBy(temporal: TemporalAccessor): Boolean =
temporal.isSupported(this)

def rangeRefinedBy(temporal: TemporalAccessor): ValueRange =
temporal.range(this)

def getFrom(temporal: TemporalAccessor): Long = temporal.getLong(this)

def adjustInto[R <: Temporal](temporal: R, newValue: Long): R =
temporal.`with`(this, newValue).asInstanceOf[R]
}
Loading

0 comments on commit 677b7c6

Please sign in to comment.