diff --git a/build.sbt b/build.sbt index 17655f5..204c387 100644 --- a/build.sbt +++ b/build.sbt @@ -13,6 +13,10 @@ ThisBuild / organization := "org.vastblue" ThisBuild / organizationName := "vastblue.org" ThisBuild / organizationHomepage := Some(url("https://vastblue.org/")) +//cancelable in Global := true + +parallelExecution := false + ThisBuild / scmInfo := Some( ScmInfo( url("https://github.com/philwalk/pallet"), diff --git a/project/build.properties b/project/build.properties index 3040987..2743082 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.4 +sbt.version=1.9.6 diff --git a/src/main/scala-2.13/vastblue/Info.scala b/src/main/scala-2.13/vastblue/Info.scala new file mode 100644 index 0000000..00f9cbc --- /dev/null +++ b/src/main/scala-2.13/vastblue/Info.scala @@ -0,0 +1,7 @@ +package vastblue + +object Info { + lazy val scalaRuntimeVersion: String = { + scala.util.Properties.versionNumberString + } +} diff --git a/src/main/scala-2.13/vastblue/pathextend.scala b/src/main/scala-2.13/vastblue/pathextend.scala index 8a79a2a..ab622f8 100644 --- a/src/main/scala-2.13/vastblue/pathextend.scala +++ b/src/main/scala-2.13/vastblue/pathextend.scala @@ -7,8 +7,6 @@ import java.io.{FileOutputStream, OutputStreamWriter} import java.security.{DigestInputStream, MessageDigest} import scala.jdk.CollectionConverters._ import vastblue.Platform._ -import vastblue.time.FileTime -import vastblue.time.FileTime._ import vastblue.DriveRoot._ // TODO: factor out code common to scala3 and scala2.13 versions @@ -97,30 +95,9 @@ object pathextend { def pathFields = p.iterator.asScala.toList def reversePath: String = pathFields.reverse.mkString("/") def lastModified: Long = p.toFile.lastModified - def lastModifiedTime = whenModified(p.toFile) - def lastModSeconds: Double = { - secondsBetween(lastModifiedTime, now).toDouble - } - def lastModMinutes: Double = lastModSeconds / 60.0 - def lastModHours: Double = lastModMinutes / 60.0 - def lastModDays: Double = round(lastModHours / 24.0) - def weekDay: java.time.DayOfWeek = { - p.lastModifiedTime.getDayOfWeek - } def round(number: Double, scale: Int = 6): Double = { BigDecimal(number).setScale(scale, BigDecimal.RoundingMode.HALF_UP).toDouble } - def age: String = { // readable description of lastModified - if (lastModMinutes <= 60.0) { - "%1.2f minutes".format(lastModMinutes) - } else if (lastModHours <= 24.0) { - "%1.2f hours".format(lastModHours) - } else if (lastModDays <= 365.25) { - "%1.2f days".format(lastModDays) - } else { - "%1.2f years".format(lastModDays / 365.25) - } - } def files: Seq[JFile] = p.toFile match { case f if f.isDirectory => f.files @@ -155,7 +132,6 @@ object pathextend { def contentAnyEncoding: String = p.toFile.contentAnyEncoding def bytes: Array[Byte] = JFiles.readAllBytes(p) def byteArray: Array[Byte] = bytes - def ageInDays: Double = FileTime.ageInDays(p.toFile) def trimmedLines: Seq[String] = linesCharset(DefaultCharset).map { _.trim } def trimmedSql: Seq[String] = @@ -568,7 +544,7 @@ object pathextend { } else { val posix = if (str.drop(1).startsWith(":")) { val driveRoot = DriveRoot(str.take(2)) - str.drop(2).string match { + str.drop(2) match { case "/" => driveRoot.posix case pathstr if pathstr.startsWith("/") => diff --git a/src/main/scala-2.13/vastblue/time/TimeExtensions.scala b/src/main/scala-2.13/vastblue/time/TimeExtensions.scala deleted file mode 100644 index 97fc97a..0000000 --- a/src/main/scala-2.13/vastblue/time/TimeExtensions.scala +++ /dev/null @@ -1,114 +0,0 @@ -package vastblue.time - -import java.time._ -//import java.time.format._ -import java.time.temporal.TemporalAdjusters -import scala.runtime.RichInt - -//import io.github.chronoscala.Imports.* -//import io.github.chronoscala.* -import vastblue.time.FileTime._ -import java.time.DayOfWeek -//import java.time.DayOfWeek.* -//import scala.language.implicitConversions - -trait TimeExtensions { - // implicit def ld2zdt(ld:LocalDate):ZonedDateTime = { ld.atStartOfDay.withZoneSameLocal(zoneid) } - implicit def date2option(date: LocalDateTime): Option[LocalDateTime] = Some(date) - - implicit def ldt2zdt(ldt: LocalDateTime): ZonedDateTime = { - ldt.atZone(UTC) - } - implicit def str2richStr(s: String): RichString = new RichString(s) - implicit def ta2zdt(ta: java.time.temporal.TemporalAccessor): ZonedDateTime = { - try { - ta match { - case ld: LocalDateTime => - ld.atZone(zoneid) - } - } catch { - case _: java.time.DateTimeException => - sys.error(s"cannot convert to LocalDateTime from: ${ta.getClass.getName}") - } - } - implicit def dateTimeOrdering: Ordering[LocalDateTime] = Ordering.fromLessThan(_ isBefore _) - - // implicit def sqlDate2LocalDateTime(sd:java.sql.Date):LocalDateTime = sd.toLocalDate.atStartOfDay() - // implicit def sqlDate2LocalDate(sd:java.sql.Date):LocalDate = sd.toLocalDate - implicit def int2richInt(i: Int): RichInt = new RichInt(i) - implicit def int2Period(i: Int): java.time.Period = java.time.Period.ofWeeks(i) - - import java.time.Duration - // implicit class aInterval(val i:Interval) { def toDuration = i.duration } - /* - implicit class aDayOfWeek(val d:java.time.DayOfWeek){ - def >=(other:java.time.DayOfWeek) = { d.compareTo(other) >= 0 } - def > (other:java.time.DayOfWeek) = { d.compareTo(other) > 0 } - def <=(other:java.time.DayOfWeek) = { d.compareTo(other) <= 0 } - def < (other:java.time.DayOfWeek) = { d.compareTo(other) < 0 } - } - implicit class aDuration(val pd:java.time.Duration) { - def getStandardSeconds:Long = pd.seconds - def getStandardMinutes: Long = getStandardSeconds / 60 - def getStandardHours: Long = getStandardMinutes / 60 - def getStandardDays: Long = getStandardHours / 24 - } - */ - def between(d1: LocalDateTime, d2: LocalDateTime) = Duration.between(d1, d2) - - implicit class aDateTime(val d: LocalDateTime) extends Ordered[aDateTime] { - override def compare(that: aDateTime): Int = { - val (a, b) = (getMillis(), that.getMillis()) - if (a < b) -1 - else if (a > b) +1 - else 0 - } - def ymd: String = d.format(dateTimeFormatPattern(dateonlyFmt)) - - def ymdhms: String = d.format(dateTimeFormatPattern(datetimeFmt7)) - - def startsWith(str: String): Boolean = d.toString(ymdhms).startsWith(str) - - def toString(fmt: String): String = { - d.format(dateTimeFormatPattern(fmt)) - } - def getMillis(): Long = { - d.atZone(zoneid).toInstant().toEpochMilli() - } - def >(other: LocalDateTime): Boolean = { - d.compareTo(other) > 0 - } - def >=(other: LocalDateTime): Boolean = { - d.compareTo(other) >= 0 - } - def to(other: LocalDateTime): Duration = { - between(d, other) - } -// def to(other:LocalDateTime):Duration = { -// Duration.between(d,other) -// } - def +(p: java.time.Period) = d.plus(p) - def -(p: java.time.Period) = d.minus(p) - - def minute = d.getMinute - def second = d.getSecond - def hour = d.getHour - def day = d.getDayOfMonth - def month = d.getMonth - def year = d.getYear - - def setHour(h: Int): LocalDateTime = d.plusHours((d.getHour + h).toLong) - def setMinute(m: Int): LocalDateTime = d.plusMinutes((d.getMinute + m).toLong) - - def compare(that: LocalDateTime): Int = d.getMillis() compare that.getMillis() - def dayOfYear = d.getDayOfYear - def getDayOfYear = d.getDayOfYear - def dayOfMonth = d.getDayOfMonth - def getDayOfMonth = d.getDayOfMonth - def dayOfWeek: DayOfWeek = d.getDayOfWeek // .getValue - def getDayOfWeek: DayOfWeek = d.getDayOfWeek // .getValue - def withDayOfWeek(dow: java.time.DayOfWeek): LocalDateTime = - d.`with`(TemporalAdjusters.next(dow)) - def lastDayOfMonth: LocalDateTime = d.`with`(LastDayAdjuster) - } -} diff --git a/src/main/scala-3/vastblue/DriveRoot.scala b/src/main/scala-3/vastblue/DriveRoot.scala index 475a9a0..89913cb 100644 --- a/src/main/scala-3/vastblue/DriveRoot.scala +++ b/src/main/scala-3/vastblue/DriveRoot.scala @@ -4,13 +4,11 @@ import java.nio.file.{Path, Paths} import DriveRoot._ import vastblue.Platform.cygdrive -opaque type DriveRoot = String - // DriveRoot Strings must match "" or "[A-Z]:" // The `toPath` method resolves path to root of disk, // rather than the one returned by `Paths.get("C:")`. object DriveRoot { - type DriveRoot = String + opaque type DriveRoot = String // empty string or uppercase "[A-Z]:" def apply(s: String): DriveRoot = { diff --git a/src/main/scala-3/vastblue/Info.scala b/src/main/scala-3/vastblue/Info.scala new file mode 100644 index 0000000..0b86462 --- /dev/null +++ b/src/main/scala-3/vastblue/Info.scala @@ -0,0 +1,12 @@ +package vastblue + +object Info { + import java.io.FileInputStream + import java.util.jar.JarInputStream + + lazy val scalaRuntimeVersion: String = { + val scala3LibJar = classOf[CanEqual[_, _]].getProtectionDomain.getCodeSource.getLocation.toURI.getPath + val manifest = new JarInputStream(new FileInputStream(scala3LibJar)).getManifest + manifest.getMainAttributes.getValue("Implementation-Version") + } +} diff --git a/src/main/scala-3/vastblue/pathextend.scala b/src/main/scala-3/vastblue/pathextend.scala index 07f4115..c70b302 100644 --- a/src/main/scala-3/vastblue/pathextend.scala +++ b/src/main/scala-3/vastblue/pathextend.scala @@ -7,8 +7,6 @@ import java.io.{FileOutputStream, OutputStreamWriter} import java.security.{DigestInputStream, MessageDigest} import scala.jdk.CollectionConverters.* import vastblue.Platform.* -import vastblue.time.FileTime -import vastblue.time.FileTime.* import vastblue.DriveRoot.* // TODO: factor out code common to scala3 and scala2.13 versions @@ -97,30 +95,9 @@ object pathextend { def pathFields = p.iterator.asScala.toList def reversePath: String = pathFields.reverse.mkString("/") def lastModified: Long = p.toFile.lastModified - def lastModifiedTime = whenModified(p.toFile) - def lastModSeconds: Double = { - secondsBetween(lastModifiedTime, now).toDouble - } - def lastModMinutes: Double = lastModSeconds / 60.0 - def lastModHours: Double = lastModMinutes / 60.0 - def lastModDays: Double = round(lastModHours / 24.0) - def weekDay: java.time.DayOfWeek = { - p.lastModifiedTime.getDayOfWeek - } def round(number: Double, scale: Int = 6): Double = { BigDecimal(number).setScale(scale, BigDecimal.RoundingMode.HALF_UP).toDouble } - def age: String = { // readable description of lastModified - if (lastModMinutes <= 60.0) { - "%1.2f minutes".format(lastModMinutes) - } else if (lastModHours <= 24.0) { - "%1.2f hours".format(lastModHours) - } else if (lastModDays <= 365.25) { - "%1.2f days".format(lastModDays) - } else { - "%1.2f years".format(lastModDays / 365.25) - } - } def files: Seq[JFile] = p.toFile match { case f if f.isDirectory => f.files @@ -155,7 +132,6 @@ object pathextend { def contentAnyEncoding: String = p.toFile.contentAnyEncoding def bytes: Array[Byte] = JFiles.readAllBytes(p) def byteArray: Array[Byte] = bytes - def ageInDays: Double = FileTime.ageInDays(p.toFile) def trimmedLines: Seq[String] = linesCharset(DefaultCharset).map { _.trim } def trimmedSql: Seq[String] = @@ -568,7 +544,7 @@ object pathextend { } else { val posix = if (str.drop(1).startsWith(":")) { val driveRoot = DriveRoot(str.take(2)) - str.drop(2).string match { + str.drop(2) match { case "/" => driveRoot.posix case pathstr if pathstr.startsWith("/") => diff --git a/src/main/scala-3/vastblue/time/TimeExtensions.scala b/src/main/scala-3/vastblue/time/TimeExtensions.scala deleted file mode 100644 index 873d147..0000000 --- a/src/main/scala-3/vastblue/time/TimeExtensions.scala +++ /dev/null @@ -1,141 +0,0 @@ -package vastblue.time - -import java.time.* -//import java.time.format.* -import java.time.temporal.TemporalAdjusters -import scala.runtime.RichInt - -//import io.github.chronoscala.Imports.* -//import io.github.chronoscala.* -import vastblue.time.FileTime.* -import java.time.DayOfWeek -//import java.time.DayOfWeek.* -//import scala.language.implicitConversions - -trait TimeExtensions { - // implicit def ld2zdt(ld:LocalDate):ZonedDateTime = { ld.atStartOfDay.withZoneSameLocal(zoneid) } - implicit def date2option(date: LocalDateTime): Option[LocalDateTime] = Some(date) - - implicit def ldt2zdt(ldt: LocalDateTime): ZonedDateTime = { - ldt.atZone(UTC) - } - implicit def str2richStr(s: String): RichString = new RichString(s) - implicit def ta2zdt(ta: java.time.temporal.TemporalAccessor): ZonedDateTime = { - try { - ta match { - case ld: LocalDateTime => - ld.atZone(zoneid) - } - } catch { - case _: java.time.DateTimeException => - sys.error(s"cannot convert to LocalDateTime from: ${ta.getClass.getName}") - } - } - implicit def dateTimeOrdering: Ordering[LocalDateTime] = Ordering.fromLessThan(_ isBefore _) - - // implicit def sqlDate2LocalDateTime(sd:java.sql.Date):LocalDateTime = sd.toLocalDate.atStartOfDay() - // implicit def sqlDate2LocalDate(sd:java.sql.Date):LocalDate = sd.toLocalDate - implicit def int2richInt(i: Int): RichInt = new RichInt(i) - implicit def int2Period(i: Int): java.time.Period = java.time.Period.ofWeeks(i) - - import java.time.Duration - // extension(i: Interval) { def toDuration = i.duration } - /* - implicit class aDayOfWeek(private[Time] val d:java.time.DayOfWeek){ - def >=(other:java.time.DayOfWeek) = { d.compareTo(other) >= 0 } - def > (other:java.time.DayOfWeek) = { d.compareTo(other) > 0 } - def <=(other:java.time.DayOfWeek) = { d.compareTo(other) <= 0 } - def < (other:java.time.DayOfWeek) = { d.compareTo(other) < 0 } - } - implicit class aDuration(private[Time] val pd:java.time.Duration) { - def getStandardSeconds:Long = pd.seconds - def getStandardMinutes: Long = getStandardSeconds / 60 - def getStandardHours: Long = getStandardMinutes / 60 - def getStandardDays: Long = getStandardHours / 24 - } - */ - def between(d1: LocalDateTime, d2: LocalDateTime) = Duration.between(d1, d2) - - extension (zdt: ZonedDateTime) { - def toDateTime: LocalDate = zdt.toLocalDate - } - extension (format: String) { - def toDateTime = parseDateStr(format) - } - extension (ldt: java.time.LocalDateTime) { - def atStartOfDay(): LocalDateTime = - ldt.withHour(0).withMinute(0).withSecond(0).withNano(0) // atStartOfDay(zoneid) - def atStartOfDay(zone: ZoneId): ZonedDateTime = ldt.atStartOfDay().atZone(zone) - // def zonedDateTime = { atStartOfDay.withZoneSameLocal(zoneid) } - } - extension (d: java.time.DayOfWeek) { - def >=(other: java.time.DayOfWeek) = { d.compareTo(other) >= 0 } - def >(other: java.time.DayOfWeek) = { d.compareTo(other) > 0 } - def <=(other: java.time.DayOfWeek) = { d.compareTo(other) <= 0 } - def <(other: java.time.DayOfWeek) = { d.compareTo(other) < 0 } - } - extension (pd: java.time.Duration) { - def getStandardSeconds: Long = pd.getSeconds.toLong - def getStandardMinutes: Long = getStandardSeconds / 60 - def getStandardHours: Long = getStandardMinutes / 60 - def getStandardDays: Long = getStandardHours / 24 - } - extension (d: LocalDateTime) { - def ymd: String = d.format(dateTimeFormatPattern(dateonlyFmt)) - - def ymdhms: String = d.format(dateTimeFormatPattern(datetimeFmt7)) - - def startsWith(str: String): Boolean = d.toString(ymdhms).startsWith(str) - - def fmt(fmt: String): String = { - d.format(dateTimeFormatPattern(fmt)) - } - def toString(fmt: String): String = { - d.format(dateTimeFormatPattern(fmt)) - } - def getMillis(): Long = { - d.atZone(zoneid).toInstant().toEpochMilli() - } - def >(other: LocalDateTime): Boolean = { - d.compareTo(other) > 0 - } - def >=(other: LocalDateTime): Boolean = { - d.compareTo(other) >= 0 - } - def <(other: LocalDateTime): Boolean = { - d.compareTo(other) < 0 - } - def <=(other: LocalDateTime): Boolean = { - d.compareTo(other) <= 0 - } - def to(other: LocalDateTime): Duration = { - Duration.between(d, other) - } -// def to(other:LocalDateTime):Duration = { -// Duration.between(d,other) -// } - def +(p: java.time.Period) = d.plus(p) - def -(p: java.time.Period) = d.minus(p) - - def minute = d.getMinute - def second = d.getSecond - def hour = d.getHour - def day = d.getDayOfMonth - def month = d.getMonth - def year = d.getYear - - def setHour(h: Int): LocalDateTime = d.plusHours((d.getHour + h).toLong) - def setMinute(m: Int): LocalDateTime = d.plusMinutes((d.getMinute + m).toLong) - - def compare(that: LocalDateTime): Int = d.getMillis() compare that.getMillis() - def dayOfYear = d.getDayOfYear - def getDayOfYear = d.getDayOfYear - def dayOfMonth = d.getDayOfMonth - def getDayOfMonth = d.getDayOfMonth - def dayOfWeek: DayOfWeek = d.getDayOfWeek // .getValue - def getDayOfWeek: DayOfWeek = d.getDayOfWeek // .getValue - def withDayOfWeek(dow: java.time.DayOfWeek): LocalDateTime = - d.`with`(TemporalAdjusters.next(dow)) - def lastDayOfMonth: LocalDateTime = d.`with`(LastDayAdjuster) - } -} diff --git a/src/main/scala/vastblue/Platform.scala b/src/main/scala/vastblue/Platform.scala index 811c3a5..51631aa 100644 --- a/src/main/scala/vastblue/Platform.scala +++ b/src/main/scala/vastblue/Platform.scala @@ -44,6 +44,7 @@ import vastblue.DriveRoot._ */ object Platform { def main(args: Array[String]): Unit = { + printf("runtime scala version: [%s]\n", vastblue.Info.scalaRuntimeVersion) printf("SYSTEMDRIVE: %s\n", envOrElse("SYSTEMDRIVE")) for (arg <- args) { val list = findAllInPath(arg) @@ -235,7 +236,16 @@ object Platform { } def execBinary(args: String*): Seq[String] = { - Process(Array(args: _*)).lazyLines_! + args.take(1) match { + case Nil => + sys.error(s"missing program name") + Nil + case progname => + if (progname.isEmpty) { + hook += 1 + } + Process(Array(args: _*)).lazyLines_! + } } def shellExec(cmd: String): Seq[String] = { execBinary(bashPath.norm, "-c", cmd) @@ -259,7 +269,7 @@ object Platform { def exeFilterList = Set( // intellij provides anemic Path; filter problematic versions of various Windows executables. - "C:/Users/philwalk/AppData/Local/Programs/MiKTeX/miktex/bin/x64/pdftotext.exe", + "~/AppData/Local/Programs/MiKTeX/miktex/bin/x64/pdftotext.exe", "C:/ProgramData/anaconda3/Library/usr/bin/cygpath.exe", "C:/Windows/System32/bash.exe", "C:/Windows/System32/find.exe", @@ -317,10 +327,35 @@ object Platform { found.reverse.distinct } - lazy val whereExe = Seq("where.exe", "where").lazyLines_!.take(1).toList.mkString("").replace('\\', '/') + lazy val WINDIR = Option(System.getenv("SYSTEMROOT")).getOrElse("").replace('\\', '/') + lazy val whereExe = { + WINDIR match { + case "" => + Seq("where.exe", "where").lazyLines_!.take(1).toList.mkString("").replace('\\', '/') + case path => + s"$path/System32/where.exe" + } + } - // cat is needed to read /proc/ files - lazy val catExe = Seq("where.exe", "cat").lazyLines_!.take(1).toList.mkString("").replace('\\', '/') + // the following is to assist finding a usable posix environment + // when cygpath.exe is not found in the PATH. + lazy val winshellBinDirs: Seq[String] = Seq( + "c:/msys64/usr/bin", + "c:/cygwin64/bin", + "c:/rtools42/usr/bin", + "c:/Program Files/Git/bin", + "c:/Program Files/Git/usr/bin", + ).filter { _.path.isDirectory } + + def discovered(progname: String): String = { + val found = winshellBinDirs + .find { (dir: String) => + val cygpath: Path = JPaths.get(dir, progname) + cygpath.isFile + } + .map { (dir: String) => s"$dir/$progname" } + found.getOrElse("") + } // get path to binaryName via 'which.exe' or 'where' def where(binaryName: String): String = { @@ -328,12 +363,29 @@ object Platform { // prefer binary with .exe extension, ceteris paribus val binName = setSuffix(binaryName) // getStdout hides stderr: INFO: Could not find files for the given pattern(s) - getStdout(whereExe, binName).take(1).mkString.replace('\\', '/') + val fname: String = getStdout(whereExe, binName).take(1).toList match { + case Nil => + discovered(binName) + case str :: tail => + str + } + fname.replace('\\', '/') } else { exec("which", binaryName) } } + // in Windows, cat is needed to read /proc/ files + lazy val catExe: String = { + val result = where("cat") + result match { + case "" => + "cat" + case prog => + prog.replace('\\', '/') + } + } + lazy val unamefull = uname("-a") lazy val unameshort = unamefull.toLowerCase.replaceAll("[^a-z0-9].*", "") lazy val isCygwin = unameshort.toLowerCase.startsWith("cygwin") @@ -348,13 +400,14 @@ object Platform { def listPossibleRootDirs(startDir: String): Seq[JFile] = { JPaths.get(startDir).toAbsolutePath.toFile match { case dir if dir.isDirectory => - // NOTE: /opt/gitbash is excluded by this approach: def defaultRootNames = Seq( "cygwin64", "msys64", "git-sdk-64", + "Git", "gitbash", "MinGW", + "rtools42", ) dir.listFiles.toList.filter { f => f.isDirectory && defaultRootNames.exists { name => @@ -381,8 +434,9 @@ object Platform { } } + lazy val programFiles = Option(System.getenv("PROGRAMFILES")).getOrElse("") def possibleWinshellRootDirs = { - listPossibleRootDirs("/") ++ listPossibleRootDirs("/opt") + listPossibleRootDirs("/") ++ listPossibleRootDirs("/opt") ++ listPossibleRootDirs(programFiles) } lazy val envPath: Seq[String] = Option(System.getenv("PATH")) match { diff --git a/src/main/scala/vastblue/time/FileTime.scala b/src/main/scala/vastblue/time/FileTime.scala deleted file mode 100644 index aac76e5..0000000 --- a/src/main/scala/vastblue/time/FileTime.scala +++ /dev/null @@ -1,511 +0,0 @@ -package vastblue.time - -import vastblue.pathextend._ - -import java.util.concurrent.TimeUnit -import java.time.ZoneId -import java.time.format.{DateTimeFormatter, DateTimeFormatterBuilder} -import java.time.format._ -import java.time.LocalDateTime -import java.time.temporal.{TemporalAdjuster, TemporalAdjusters} -import scala.util.matching.Regex - -object FileTime extends vastblue.time.TimeExtensions { - def zoneid = ZoneId.systemDefault - def zoneOffset = zoneid.getRules().getStandardOffset(now.toInstant()) - def zoneOffsetHours = zoneOffset.getHour - - lazy val NullDate: LocalDateTime = - LocalDateTime.parse("0000-01-01T00:00:00") // .ofInstant(Instant.ofEpochMilli(0)) - - // Patterns permit but don't require time fields - // Used to parse both date and time from column 1. - // Permits but does not require column to be double-quoted. - lazy val YMDColumnPattern: Regex = """[^#\d]?(2\d{3})[-/](\d{1,2})[-/](\d{1,2})(.*)""".r - lazy val MDYColumnPattern: Regex = """[^#\d]?(\d{1,2})[-/](\d{1,2})[-/](2\d{3})(.*)""".r - - lazy val standardTimestampFormat = datetimeFmt6 - lazy val datetimeFmt8 = "yyyy-MM-dd HH:mm:ss-ss:S" - lazy val datetimeFmt7 = "yyyy-MM-dd HH:mm:ss.S" - lazy val datetimeFmt6 = "yyyy-MM-dd HH:mm:ss" // date-time-format - lazy val datetimeFmt5 = "yyyy-MM-dd HH:mm" // 12-hour format - lazy val datetimeFmt5b = "yyyy-MM-dd kk:mm" // 24-hour format - lazy val dateonlyFmt = "yyyy-MM-dd" // date-only-format - - lazy val datetimeFormatter8: DateTimeFormatter = dateTimeFormatPattern(datetimeFmt8) - lazy val datetimeFormatter7: DateTimeFormatter = dateTimeFormatPattern(datetimeFmt7) - lazy val datetimeFormatter6: DateTimeFormatter = dateTimeFormatPattern(datetimeFmt6) - lazy val datetimeFormatter5: DateTimeFormatter = dateTimeFormatPattern(datetimeFmt5) - lazy val datetimeFormatter5b: DateTimeFormatter = dateTimeFormatPattern(datetimeFmt5b) - lazy val dateonlyFormatter: DateTimeFormatter = dateTimeFormatPattern(dateonlyFmt) - - lazy val EasternTime: ZoneId = java.time.ZoneId.of("America/New_York") - lazy val MountainTime: ZoneId = java.time.ZoneId.of("America/Denver") - lazy val UTC: ZoneId = java.time.ZoneId.of("UTC") - - def LastDayAdjuster: TemporalAdjuster = TemporalAdjusters.lastDayOfMonth() - - // ============================== - - def dateTimeFormatPattern( - fmt: String, - zone: ZoneId = ZoneId.systemDefault() - ): DateTimeFormatter = { - val dtf1 = DateTimeFormatter.ofPattern(fmt).withZone(zone) - val dtf = if (fmt.length <= "yyyy-mm-dd".length) { - import java.time.temporal.ChronoField - new DateTimeFormatterBuilder() - .append(dtf1) - .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) - .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) - .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) - .toFormatter() - } else { - dtf1 - } - dtf - } - - /** - * Get a diff between two dates - * @param date1 - * the oldest date - * @param date2 - * the newest date - * @param timeUnit - * the unit in which you want the diff - * @return - * the diff value, in the provided unit - */ - def diffDays(date1: LocalDateTime, date2: LocalDateTime): Long = { - diff(date1, date2, TimeUnit.DAYS) - } - def diffHours(date1: LocalDateTime, date2: LocalDateTime): Long = { - diff(date1, date2, TimeUnit.HOURS) - } - def diffSeconds(date1: LocalDateTime, date2: LocalDateTime): Long = { - diff(date1, date2, TimeUnit.SECONDS) - } - def diff(date1: LocalDateTime, date2: LocalDateTime, timeUnit: TimeUnit): Long = { - val diffInMillies = date2.getMillis() - date1.getMillis() - timeUnit.convert(diffInMillies, TimeUnit.MILLISECONDS) - } - - // type LocalDateTime = LocalDateTime - // signed number of days between specified dates. - // if date1 > date2, a negative number of days is returned. - def daysBetween(idate1: LocalDateTime, idate2: LocalDateTime): Long = { - assert(idate1 != null, "idate1 is null") - assert(idate2 != null, "idate2 is null") - val elapsedDays: Long = if (idate1.getMillis() < idate2.getMillis()) { - diffDays(idate1, idate2) - } else { - -diffDays(idate2, idate1) - } - elapsedDays - } - // private var hook = 0 - def secondsBetween(idate1: LocalDateTime, idate2: LocalDateTime): Long = { - val seconds = diffSeconds(idate1, idate2) // .getStandardDays - val elapsedSeconds: Long = if (idate1.getMillis() <= idate2.getMillis()) { - seconds - } else { - -seconds // negative number - } - elapsedSeconds - } - def secondsSince(date1: LocalDateTime): Long = secondsBetween(date1, now) - - def endOfMonth(d: LocalDateTime): LocalDateTime = { - val month: java.time.YearMonth = { java.time.YearMonth.from(d) } - month.atEndOfMonth.atStartOfDay - } - - def minutesBetween(date1: LocalDateTime, date2: LocalDateTime): Double = { - secondsBetween(date1, date2).toDouble / 60.0 - } - def minutesSince(date1: LocalDateTime): Double = minutesBetween(date1, now) - - def hoursBetween(date1: LocalDateTime, date2: LocalDateTime): Double = { - minutesBetween(date1, date2) / 60.0 - } - def hoursSince(date1: LocalDateTime): Double = hoursBetween(date1, now) - - def whenModified(f: java.io.File): LocalDateTime = { - val lastmod = if (f.exists) f.lastModified else -1 - epoch2DateTime(lastmod, MountainTime) - } - - def epoch2DateTime(epoch: Long, timezone: java.time.ZoneId = UTC): LocalDateTime = { - val instant = java.time.Instant.ofEpochMilli(epoch) - java.time.LocalDateTime.ofInstant(instant, timezone) - } - - def nowZoned(zone: ZoneId = MountainTime): LocalDateTime = LocalDateTime.now(zone) - lazy val now: LocalDateTime = nowZoned(MountainTime) - def nowUTC = LocalDateTime.now() - - // def fixDateFormat = vastblue.time.Time.fixDateFormat _ - // def ageInMinutes = vastblue.time.Time.ageInMinutes _ - def ageInMinutes(f: java.io.File): Double = { - if (f.exists) { - val diff = (now.getMillis() - f.lastModified) / (60 * 1000).toDouble - diff - } else { - 1e6 // missing files are VERY stale - } - } - def ageInDays(f: java.io.File): Double = { - ageInMinutes(f) / (24 * 60) - } - def ageInDays(fname: String): Double = { - ageInDays(new java.io.File(fname)) - } - - def parse(str: String, format: String): LocalDateTime = { -// if( timeDebug ) System.err.print("parse(str=[%s], format=[%s]\n".format(str,format)) - if (format.length <= "yyyy-mm-dd".length) { - LocalDateTime.parse(str, dateTimeFormatPattern(format)) - } else { - LocalDateTime.parse(str, dateTimeFormatPattern(format)) - } - } - - /** The new parser does not depend on MDate */ - def parseDateNew(_datestr: String, format: String = ""): LocalDateTime = { - val datestr = _datestr - .replaceAll("/", "-") - . // normalize field separator - replaceAll("\"", "") - . // remove quotes - replaceAll(""" (\d):""", " 0$1:") - . // make sure all time fields are 2 digits (zero filled) - replaceAll("\\s+", " ") - .trim // compress random spaces to a single space, then trim - - val pattern = ( - format != "", - datestr.contains(":"), - datestr.matches(""".* (AM|PM)\b.*"""), - datestr.contains(".") - ) match { - case (true, _, _, _) => format // user-specified format - case (_, false, _, _) => "yyyy-MM-dd" - case (_, true, false, false) => "yyyy-MM-dd HH:mm:ss" - case (_, true, true, false) => "yyyy-MM-dd hh:mm:ss aa" - case (_, true, false, true) => "yyyy-MM-dd HH:mm:ss.SSS" - case (_, true, true, true) => "yyyy-MM-dd hh:mm:ss aa.SSS" - } - try { - parse(datestr, pattern) - } catch { - case e: IllegalArgumentException => - e.getMessage.contains("Illegal instant due to time zone offset") match { - case true => - throw e - case false => - parse(datestr, pattern) - } - } - } - - def parseDateTime(str: String): LocalDateTime = parseDateStr(str) - lazy val ThreeIntegerFields1 = """(\d{2,4})\D(\d{1,2})\D(\d{1,2})""".r - lazy val ThreeIntegerFields3 = """(\d{1,2})\D(\d{1,2})\D(\d{2,4})""".r - lazy val ThreeIntegerFields2 = """(\d{2,2})\D(\d{1,2})\D(\d{1,2})""".r - def parseDateStr(_inputdatestr: String): LocalDateTime = { // , offset: Int=0):LocalDateTime = { - val _datestr = _inputdatestr.trim.replaceAll("\"", "") - if (_datestr.isEmpty) { - BadDate - } else { - _datestr match { - case ThreeIntegerFields1(_y, _m, _d) => - if (_y.length > 2) { - val (y, m, d) = (_y.toInt, _m.toInt, _d.toInt) - new RichString("%4d-%02d-%02d".format(y, m, d)).toDateTime - } else { - val nums = List(_y, _m, _d).map { _.toInt } - val possibleDays = nums.zipWithIndex.filter { case (n, i) => n <= 31 } - val possibleMonths = nums.zipWithIndex.filter { case (n, i) => n <= 12 } - val (y, m, d) = possibleMonths match { - case (n, 0) :: list => - (nums(2), nums(0), nums(1)) // m/d/y - case (n, 1) :: list => - (nums(2), nums(1), nums(0)) // d/m/y - case _ => - possibleDays match { - case (n, 0) :: list => (nums(2), nums(1), nums(0)) // d/m/y - case _ => - (nums(2), nums(0), nums(1)) // m/d/y - } - } - val year = if (y >= 1000) y else y + 2000 - new RichString("%4d-%02d-%02d".format(year, m, d)).toDateTime - } - case ThreeIntegerFields3(_m, _d, _y) => - val (y, m, d) = (_y.toInt, _m.toInt, _d.toInt) - val year = if (y >= 1000) y else y + 2000 - new RichString("%4d-%02d-%02d".format(year, m, d)).toDateTime - case ThreeIntegerFields2(_x, _y, _z) => - val nums = List(_x, _y, _z).map { _.toInt } - // val possibleDays = nums.zipWithIndex.filter { case (n,i) => n <= 31 } - // val possibleMonths = nums.zipWithIndex.filter { case (n,i) => n <= 12 } - val List(y, m, d) = nums - val year = if (y >= 1000) y else y + 2000 - new RichString("%4d-%02d-%02d".format(year, m, d)).toDateTime - case _ => - // next, treat yyyyMMdd (8 digits, no field separators) - if (_datestr.matches("""2\d{7}""")) { - new RichString(_datestr.replaceAll("(....)(..)(..)", "$1-$2-$3")).toDateTime - - } else if (_datestr.matches("""\d{2}\D\d{2}\D\d{2}""")) { - // MM-dd-yy - val fixed = _datestr.split("\\D").toList match { - case m :: d :: y :: Nil => - "%04d-%02d-%02d 00:00:00".format(2000 + y.toInt, m.toInt, d.toInt) - case _ => - _datestr // no fix - } - // printf("%s\n",datetimeFormatter.getClass) - // datetimeFormatter6.parse(fixed) - LocalDateTime.parse(fixed, datetimeFormatter6) - } else if (_datestr.matches("""2\d{3}\D\d{2}\D\d{2}\.\d{4}""")) { - // yyyy-MM-dd.HHMM - - val fixed = - _datestr.replaceAll("""(....)\D(..)\D(..)\.(\d\d)(\d\d)""", "$1-$2-$3 $4:$5:00") - LocalDateTime.parse(fixed, datetimeFormatter6) - } else { - val datestr = _datestr.replaceAll("/", "-") - parseDateString(datestr) - /* - try { - } catch { - case e: Exception => - if (vastblue.MDate.debug) System.err.printf("e[%s]\n",e.getMessage) - val mdate = vastblue.MDate.parseDate(datestr) // .replaceAll("\\D+","")) - // val timestamp = new LocalDateTime(mdate.getEpoch) - val standardFormat = mdate.toString(standardTimestampFormat) - val timestamp = standardFormat.toDateTime - val hour = timestamp.getHour // hourOfDay.get - val extraHours = if (datestr.contains(" PM") && hour < 12) { - 12 - } else { - 0 - } - val hours = (offset + extraHours).toLong - timestamp.plusHours(hours) - } - */ - } - } - } - } - - def standardTime(datestr: String): String = { // }, offset: Int=0): String = { - // parseDateStr(datestr, offset).toString(standardTimestampFormat) - parseDateStr(datestr).toString(standardTimestampFormat) - } - def parseDate(datestr: String): LocalDateTime = { // }, offset: Int=0): LocalDateTime = { - parseDateStr(datestr) // , offset) - } - - def getDaysElapsed(idate1: LocalDateTime, idate2: LocalDateTime): Long = { - if (idate2.getMillis() < idate1.getMillis()) { - // - (idate2 to idate1).getStandardDays - -diffDays(idate2, idate1) - } else { - // (idate1 to idate2).getStandardDays - diffDays(idate1, idate2) - } - } - def getDaysElapsed(datestr1: String, datestr2: String): Long = { - getDaysElapsed(parseDateStr(datestr1), parseDateStr(datestr2)) - } - def selectZonedFormat(_datestr: String): java.time.format.DateTimeFormatter = { - val datestr = _datestr.replaceAll("/", "-") - val numfields = datestr.split("\\D+") - numfields.length <= 3 match { - case true => - dateonlyFormatter - case false => - datetimeFormatter6 - } - } - def ti(s: String): Int = { - s match { - case n if n.matches("0\\d+") => - n.replaceAll("0+(.)", "$1").toInt - case n => - n.toInt - } - } - def numerifyNames(datestr: String) = { - val noweekdayName = datestr.replaceAll( - "(Sun[day]*|Mon[day]*|Tue[sday]*|Wed[nesday]*|Thu[rsday]*|Fri[day]*|Sat[urday]*),? *", - "" - ) - noweekdayName match { - case str if str.matches("(?i).*[JFMASOND][aerpuco][nbrylgptvc][a-z]*.*") => - var ff = str.split("[,\\s]+") - if (ff(0).matches("\\d+")) { - // swap 1st and 2nd fields (e.g., convert "01 Jan" to "Jan 01") - val tmp = ff(0) - ff(0) = ff(1) - ff(1) = tmp - } - val month = monthAbbrev2Number(ff.head.take(3)) - ff = ff.drop(1) - val (day, year, timestr, tz) = ff.toList match { - case d :: y :: Nil => - (d.toInt, y.toInt, "", "") - case d :: y :: ts :: tz :: Nil if ts.contains(":") => - (d.toInt, y.toInt, " " + ts, " " + tz) - case d :: ts :: y :: tail if ts.contains(":") => - (d.toInt, y.toInt, " " + ts, "") - case d :: y :: ts :: tail => - (d.toInt, y.toInt, " " + ts, "") - case other => - sys.error(s"bad date [$other]") - } - "%4d-%02d-%02d%s%s".format(year, month, day, timestr, tz) - case str => - str - } - } - def monthAbbrev2Number(name: String): Int = { - name.toLowerCase.substring(0, 3) match { - case "jan" => 1 - case "feb" => 2 - case "mar" => 3 - case "apr" => 4 - case "may" => 5 - case "jun" => 6 - case "jul" => 7 - case "aug" => 8 - case "sep" => 9 - case "oct" => 10 - case "nov" => 11 - case "dec" => 12 - } - } - - lazy val mmddyyyyPattern: Regex = """(\d{1,2})\D(\d{1,2})\D(\d{4})""".r - lazy val mmddyyyyTimePattern: Regex = """(\d{1,2})\D(\d{1,2})\D(\d{4})(\D\d\d:\d\d(:\d\d)?)""".r - lazy val mmddyyyyTimePattern2: Regex = """(\d{1,2})\D(\d{1,2})\D(\d{4})\D(\d\d):(\d\d)""".r - lazy val mmddyyyyTimePattern3: Regex = """(\d{1,2})\D(\d{1,2})\D(\d{4})\D(\d\d):(\d\d):(\d\d)""".r - lazy val mmddyyyyTimePattern3tz: Regex = - """(\d{1,2})\D(\d{1,2})\D(\d{4})\D(\d\d):(\d\d):(\d\d)\D(-?[0-9]{4})""".r - lazy val yyyymmddPattern: Regex = """(\d{4})\D(\d{1,2})\D(\d{1,2})""".r - lazy val yyyymmddPatternWithTime: Regex = """(\d{4})\D(\d{1,2})\D(\d{1,2})(\D.+)""".r - lazy val yyyymmddPatternWithTime2: Regex = """(\d{4})\D(\d{1,2})\D(\d{1,2}) +(\d{2}):(\d{2})""".r - lazy val yyyymmddPatternWithTime3: Regex = - """(\d{4})\D(\d{1,2})\D(\d{1,2})\D(\d{2}):(\d{2}):(\d{2})""".r - lazy val validYearPattern = """(1|2)\d{3}""" // only consider years between 1000 and 2999 - def parseDateString(_datestr: String): LocalDateTime = { - // scalafmt: { optIn.breakChainOnFirstMethodDot = true } - var datestr = _datestr - .replaceAll("/", "-") - .replaceAll("#", "") - .replaceAll("-[0-9]+:[0-9]+$", "") - .replaceAll("([0-9])T([0-9])", "$1 $2") - .trim - datestr = datestr match { - case mmddyyyyPattern(m, d, y) => - "%s-%02d-%02d".format(y, ti(m), ti(d)) - case mmddyyyyTimePattern(m, d, y, t) => - "%s-%02d-%02d%s".format(y, ti(m), ti(d), t) - case mmddyyyyTimePattern2(m, d, y, h, min) if y.matches(validYearPattern) => - "%s-%02d-%02d %02d:%02d".format(y, ti(m), h, min) - case mmddyyyyTimePattern3(m, d, y, h, min, s) if y.matches(validYearPattern) => - "%s-%02d-%02d %02d:%02d:02d".format(y, ti(m), h, min, s) - case mmddyyyyTimePattern3tz(m, d, y, h, min, s, tz) if y.matches(validYearPattern) => - "%s-%02d-%02d %02d:%02d:02d %s".format(y, ti(m), h, min, s) - case yyyymmddPattern(y, m, d) if y.matches(validYearPattern) => - "%s-%02d-%02d".format(y, ti(m), ti(d)) - case yyyymmddPatternWithTime(y, m, d, t) if y.matches(validYearPattern) => - "%s-%02d-%02d%s".format(y, ti(m), ti(d), t) - case yyyymmddPatternWithTime2(y, m, d, hr, min) if y.matches(validYearPattern) => - if (hr.toInt > 12) { - "%s-%02d-%02d %s:%s".format(y, ti(m), ti(d), hr, min) - } else { - "%s-%02d-%02d %s:%s".format(y, ti(m), ti(d), hr, min) - } - case yyyymmddPatternWithTime3(y, m, d, hr, min, sec) if y.matches(validYearPattern) => - if (hr.toInt > 12) { - "%s-%02d-%02d %s:%s:%s".format(y, ti(m), ti(d), hr, min, sec) - } else { - "%s-%02d-%02d %s:%s:%s".format(y, ti(m), ti(d), hr, min, sec) - } - case other => - val withNums = numerifyNames(other) - withNums - } - - val numfields = datestr.split("\\D+").map { _.trim }.filter { _.nonEmpty }.map { _.toInt } - numfields.length match { - case 1 => - val dstr = if (datestr.startsWith("2")) { - // e.g., 20220330 - datestr.replaceAll("(\\d{4})(\\d{2})(\\d{2})", "$1-$2-$3") - } else if (datestr.drop(4).startsWith("2")) { - // e.g., 03302022 - datestr.replaceAll("(\\d{2})(\\d{2})(\\d{4})", "$3-$1-$2") - } else { - sys.error(s"bad date format [$datestr]") - } - val fmtr = datetimeFormatter6 - LocalDateTime.parse(s"${dstr} 00:00:00", fmtr) - case 3 => - val fmtr = datetimeFormatter6 - LocalDateTime.parse(s"${datestr} 00:00:00", fmtr) - case 5 => - if (numfields(3) <= 12) { - LocalDateTime.parse(datestr, datetimeFormatter5) - } else { - LocalDateTime.parse(datestr, datetimeFormatter5b) - } - case 6 => - LocalDateTime.parse(datestr, datetimeFormatter6) - case 7 => - LocalDateTime.parse(datestr, datetimeFormatter7) - case _ => - // System.err.printf("%d datetime fields: [%s] [%s]\n".format(numfields.size,numfields.mkString("|"),datestr)) - LocalDateTime.parse(datestr, datetimeFormatter6) - } - } - class RichString(val s: String) extends AnyVal { - import java.time._ - def str = s // .replaceAll(" ","T") - def toDateTime: LocalDateTime = parseDateString( - str - ) // LocalDateTime.parse(str,selectZonedFormat(str)) - def toInstant: Instant = Instant.parse(str) - // def toLocalDate = LocalDate.parse(str) -// def toDateTime:LocalDateTime = toLocalDate.atStartOfDay - def toDateTimeOption: Option[LocalDateTime] = toOption(toDateTime) -// def toLocalDateOption = toOption(toLocalDate) - def toDateTime(format: String): String = dateTimeFormat(format) - def toLocalDate(format: String): String = localDateTimeFormat(format) - def toDateTimeOption(format: String): Option[String] = toOption(toDateTime(format)) - def toLocalDateOption(format: String): Option[String] = toOption(toLocalDate(format)) - - private def toOption[A](f: => A): Option[A] = try { - Some(f) - } catch { - case _: IllegalArgumentException => None - } - - def dateTimeFormat(format: String): String = - s.format(DateTimeFormatter.ofPattern(format)) // .parseDateTime(s) - def localDateTimeFormat(format: String): String = - s.format(DateTimeFormatter.ofPattern(format)) // .parseLocalDate(s) - } - - lazy val BadDate: LocalDateTime = parseDateStr("1900-01-01") - lazy val EmptyDate: LocalDateTime = parseDateStr("1800-01-01") - def date2string(d: LocalDateTime, fmt: String = "yyyy-MM-dd"): String = d match { - case EmptyDate => "" - case other => other.toString(fmt) - } -} diff --git a/src/test/scala/TestUniPath.scala b/src/test/scala/TestUniPath.scala index 41a7e94..26dfd03 100644 --- a/src/test/scala/TestUniPath.scala +++ b/src/test/scala/TestUniPath.scala @@ -5,6 +5,8 @@ import vastblue.pathextend._ import vastblue.Platform._ // isWinshell class TestUniPath { + val verbose = Option(System.getenv("VERBOSE_TESTS")).nonEmpty + def testArgs = Seq.empty[String] @Test def test1(): Unit = { val wherebash = where("bash") diff --git a/src/test/scala/vastblue/ParseDateSpec.scala b/src/test/scala/vastblue/ParseDateSpec.scala deleted file mode 100644 index 2e9a26f..0000000 --- a/src/test/scala/vastblue/ParseDateSpec.scala +++ /dev/null @@ -1,76 +0,0 @@ -package vastblue - -import org.scalatest.BeforeAndAfter -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import vastblue.time.FileTime._ - -class ParseDateSpec extends AnyFunSpec with Matchers with BeforeAndAfter { -//import vastblue.MDate - @volatile lazy val datePairs: List[(String, String)] = List( - ("01/16/15", "2015/01/16"), - ("01/25/15", "2015/01/25"), - ("01/26/15", "2015/01/26"), - ("01/27/15", "2015/01/27"), - ("01/29/15", "2015/01/29"), - ("03/02/15", "2015/03/02"), - ("03/05/15", "2015/03/05"), - ("03/20/15", "2015/03/20"), - ("04/01/15", "2015/04/01"), - ("04/09/15", "2015/04/09"), - ("06/05/15", "2015/06/05"), - ("06/11/15", "2015/06/11"), - ("06/26/15", "2015/06/26"), - ("07/14/15", "2015/07/14"), - ("07/16/15", "2015/07/16"), - ("07/19/15", "2015/07/19"), - ("08/04/15", "2015/08/04"), - ("08/06/15", "2015/08/06"), - ("08/18/15", "2015/08/18"), - ("08/23/15", "2015/08/23"), - ("08/31/15", "2015/08/31"), - ("09/08/15", "2015/09/08"), - ("10/21/15", "2015/10/21"), - ("10/25/15", "2015/10/25"), - ("11/14/15", "2015/11/14"), - ("11/15/15", "2015/11/15"), - ("11/16/15", "2015/11/16"), - ("11/17/15", "2015/11/17"), - ("11/22/15", "2015/11/22"), - ("11/23/15", "2015/11/23"), - ("11/29/15", "2015/11/29"), - ("12/08/15", "2015/12/08"), - ("12/11/15", "2015/12/11"), - ("12/12/15", "2015/12/12"), - ("12/13/15", "2015/12/13"), - ("12/14/15", "2015/12/14"), - ("12/25/15", "2015/12/25") - ) - @volatile lazy val dateTimePairs: List[(String, String)] = List( - ("Sat Oct 16 13:04:02 2021 -0600", "2021/10/16 13:04:02") - ) - describe("MDate") { - describe("parse()") { - it("should correctly parse dateTime Strings") { - var lnum = 0 - dateTimePairs.foreach { case (str, expected) => - val md = parseDateStr(str) - val value: String = md.toString("yyyy/MM/dd HH:mm:ss") -// printf("expected[%s], value[%s]\n",expected,value) - assert(value == expected, s"line ${lnum}:\n [$value]\n [$expected]") - lnum += 1 - } - } - it("should correctly parse date Strings") { - var lnum = 0 - datePairs.foreach { case (str, expected) => - val md = parseDateStr(str) - val value: String = md.toString("yyyy/MM/dd") -// printf("expected[%s], value[%s]\n",expected,value) - assert(value == expected, s"line ${lnum}:\n [$value]\n [$expected]") - lnum += 1 - } - } - } - } -} diff --git a/src/test/scala/vastblue/TimeSpec.scala b/src/test/scala/vastblue/TimeSpec.scala deleted file mode 100644 index 060015f..0000000 --- a/src/test/scala/vastblue/TimeSpec.scala +++ /dev/null @@ -1,35 +0,0 @@ -package vastblue - -import vastblue.time.FileTime._ -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers - -class TimeSpec extends AnyFunSpec with Matchers { // with BeforeAndAfter { - - describe("time functions and lazy vals should initialize without throwing exceptions") { - describe("NullDate") { - it("should correctly initialize") { - printf("NullDate:%s\n", NullDate) - assert(true, "NullDate") - } - } - describe("nowUTC show return differing timestamps after elapsed of time") { - it("should correctly initialize") { - val now1 = nowUTC - printf("======= now1[%s]\n", now1) - Thread.sleep(2000) - val now2 = nowUTC - printf("======= now2[%s]\n", now2) - val elapsedSeconds = secondsBetween(now1, now2) - printf("between %s and %s: %s Seconds elapsed\n", now1, now2, elapsedSeconds) - val elapsedMinutes = minutesBetween(now1, now2) - printf("between %s and %s: %s Minutes elapsed\n", now1, now2, elapsedMinutes) - val elapsedHours = hoursBetween(now1, now2) - printf("between %s and %s: %s Hours elapsed\n", now1, now2, elapsedHours) - val elapsedDays = daysBetween(now1, now2) - printf("between %s and %s: %s Days elapsed\n", now1, now2, elapsedDays) - assert(true, "passed") - } - } - } -} diff --git a/src/test/scala/vastblue/file/EzPathTest.scala b/src/test/scala/vastblue/file/EzPathTest.scala index 6754efc..75bbdc0 100644 --- a/src/test/scala/vastblue/file/EzPathTest.scala +++ b/src/test/scala/vastblue/file/EzPathTest.scala @@ -8,14 +8,14 @@ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers class EzPathTest extends AnyFunSpec with Matchers with BeforeAndAfter { + val upathstr = "/opt/ue" + val wpathstr = upathstr.replace('/', '\\') + val posixAbsstr = s"$platformPrefix$upathstr" // current working directory prefix + val windowsAbsstr = posixAbsstr.replace('/', '\\') // Windows version + val localAbsstr = if (isWindows) windowsAbsstr else posixAbsstr + describe("EzPath constructors") { it("should correctly create and display EzPath objects") { - val upathstr = "/opt/ue" - val wpathstr = upathstr.replace('/', '\\') - val posixAbsstr = s"$platformPrefix$upathstr" // current working directory prefix - val windowsAbsstr = posixAbsstr.replace('/', '\\') // Windows version - val localAbsstr = if (isWindows) windowsAbsstr else posixAbsstr - printf("notWindows: %s\n", notWindows) printf("isWindows: %s\n", isWindows) @@ -25,7 +25,10 @@ class EzPathTest extends AnyFunSpec with Matchers with BeforeAndAfter { printf("posixAbsstr [%s]\n", posixAbsstr) printf("windowsAbsstr [%s]\n", windowsAbsstr) printf("\n") + assert(upathstr.contains("/")) + } + it("PathUnx should display with constructed slash type") { // test whether input strings have forward or back slash // accept defaults (also should match Paths.get) @@ -39,16 +42,6 @@ class EzPathTest extends AnyFunSpec with Matchers with BeforeAndAfter { assert(unxa.initstring == upathstr) assert(unxa.abs == posixAbsstr) - val wina = PathWin(upathstr) // should match java.nio.file.Paths.get - printf("wina.pstr [%s], ", wina.initstring) - printf("wina.norm [%s], ", wina.norm) - printf("wina.sl [%s], ", wina.sl) - printf("wina.abs [%s]\n", wina.abs) - // printf("\n") - assert(wina.sl == Slash.Win) - assert(wina.initstring == upathstr) - assert(wina.abs == localAbsstr.replace('/', Slash.win)) - val unxb = PathUnx(upathstr) // should match java.nio.file.Paths.get printf("unxb.pstr [%s], ", unxb.initstring) printf("unxb.norm [%s], ", unxb.norm) @@ -58,6 +51,19 @@ class EzPathTest extends AnyFunSpec with Matchers with BeforeAndAfter { assert(unxb.sl == Slash.Unx) assert(unxb.initstring == upathstr) assert(unxb.abs == posixAbsstr) + printf("\n") + } + + it("PathWin should display with constructed slash type") { + val wina = PathWin(upathstr) // should match java.nio.file.Paths.get + printf("wina.pstr [%s], ", wina.initstring) + printf("wina.norm [%s], ", wina.norm) + printf("wina.sl [%s], ", wina.sl) + printf("wina.abs [%s]\n", wina.abs) + // printf("\n") + assert(wina.sl == Slash.Win) + assert(wina.initstring == upathstr) + assert(wina.abs == localAbsstr.replace('/', Slash.win)) val winb = PathWin(upathstr) // should match java.nio.file.Paths.get printf("winb.pstr [%s], ", winb.initstring) @@ -74,11 +80,14 @@ class EzPathTest extends AnyFunSpec with Matchers with BeforeAndAfter { printf("winw.norm [%s], ", winw.norm) printf("winw.sl [%s], ", winw.sl) printf("winw.abs [%s]\n", winw.abs) - printf("\n") + // printf("\n") assert(winw.sl == Slash.Win) assert(winw.initstring == windowsAbsstr) assert(winw.abs == windowsAbsstr) + printf("\n") + } + it("ExPath should display with os-appropriate slash type") { val ezpc = EzPath(wpathstr) // should match java.nio.file.Paths.get printf("ezpc.pstr [%s], ", ezpc.initstring) printf("ezpc.norm [%s], ", ezpc.norm) @@ -99,7 +108,7 @@ class EzPathTest extends AnyFunSpec with Matchers with BeforeAndAfter { printf("ezpd.norm [%s], ", ezpd.norm) printf("ezpd.sl [%s], ", ezpd.sl) printf("ezpd.abs [%s]\n", ezpd.abs) - printf("\n") + // printf("\n") assert(ezpc.sl == defaultSlash) if (isWindows) { assert(ezpd.initstring == wpathstr) @@ -129,7 +138,7 @@ class EzPathTest extends AnyFunSpec with Matchers with BeforeAndAfter { printf("ezpu.norm [%s], ", ezpu.norm) printf("ezpu.sl [%s], ", ezpu.sl) printf("ezpu.abs [%s]\n", ezpu.abs) - printf("\n") + // printf("\n") assert(ezpu.sl == Slash.Unx) if (isWindows) { assert(ezpu.initstring == wpathstr) diff --git a/src/test/scala/vastblue/file/FilenameTest.scala b/src/test/scala/vastblue/file/FilenameTest.scala index f050fe4..9e549c6 100644 --- a/src/test/scala/vastblue/file/FilenameTest.scala +++ b/src/test/scala/vastblue/file/FilenameTest.scala @@ -7,6 +7,7 @@ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers class FilenameTest extends AnyFunSpec with Matchers with BeforeAndAfter { + val verbose = Option(System.getenv("VERBOSE_TESTS")).nonEmpty describe("File.exists") { it("should correctly see whether a mapped dir (like W:/alltickers) exists or not") { val testdir = "w:/alltickers" diff --git a/src/test/scala/vastblue/file/PathSpec.scala b/src/test/scala/vastblue/file/PathSpec.scala index dade9a9..6f6aacc 100644 --- a/src/test/scala/vastblue/file/PathSpec.scala +++ b/src/test/scala/vastblue/file/PathSpec.scala @@ -1,15 +1,15 @@ package vastblue.file import org.scalatest._ -import vastblue.pathextend._ -import vastblue.file.Paths.{canExist, normPath} import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers +import vastblue.pathextend._ +import vastblue.file.Paths.{canExist, normPath} import vastblue.Platform.{driveRoot, cwd, cygdrive} class PathSpec extends AnyFunSpec with Matchers with BeforeAndAfter { - var hook: Int = 0 - var verbose: Boolean = false + val verbose = Option(System.getenv("VERBOSE_TESTS")).nonEmpty + var hook: Int = 0 val cygroot: String = cygdrive match { case str if str.endsWith("/") => str diff --git a/src/test/scala/vastblue/file/PathnameTest.scala b/src/test/scala/vastblue/file/PathnameTest.scala index 6603923..5d9fe89 100644 --- a/src/test/scala/vastblue/file/PathnameTest.scala +++ b/src/test/scala/vastblue/file/PathnameTest.scala @@ -7,6 +7,7 @@ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers class PathnameTest extends AnyFunSpec with Matchers with BeforeAndAfter { + val verbose = Option(System.getenv("VERBOSE_TESTS")).nonEmpty lazy val TMP = { val gdir = Paths.get("/g") gdir.isDirectory && gdir.paths.nonEmpty match { diff --git a/src/test/scala/vastblue/file/RootRelativeTest.scala b/src/test/scala/vastblue/file/RootRelativeTest.scala index 7d7d294..f61e029 100644 --- a/src/test/scala/vastblue/file/RootRelativeTest.scala +++ b/src/test/scala/vastblue/file/RootRelativeTest.scala @@ -8,6 +8,7 @@ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers class RootRelativeTest extends AnyFunSpec with Matchers with BeforeAndAfter { + val verbose = Option(System.getenv("VERBOSE_TESTS")).nonEmpty describe("Root-relative paths") { it("should correctly resolve pathRelative paths in Windows") { // NOTE: current working directory is set before running test (e.g., in IDE)