├── manual ├── images │ └── .empty ├── scalajs-support.md ├── datetime-formatting.md ├── scheduler.md ├── support.md ├── type-safe-dsl.md ├── introduction.md ├── development.md └── src │ └── main │ └── scala │ └── pl │ └── metastack │ └── metatime │ └── manual │ ├── Deploy.scala │ ├── Shared.scala │ ├── Examples.scala │ └── Manual.scala ├── project ├── build.properties ├── plugins.sbt └── Build.scala ├── .travis.yml ├── version.sbt ├── shared └── src │ ├── main │ └── scala │ │ └── pl │ │ └── metastack │ │ └── metatime │ │ ├── Timezone.scala │ │ ├── Range.scala │ │ ├── Timestamp.scala │ │ ├── Locale.scala │ │ ├── Implicits.scala │ │ ├── Pattern.scala │ │ ├── Constants.scala │ │ ├── Offset.scala │ │ ├── PatternTime.scala │ │ ├── Scheduler.scala │ │ ├── PatternDateTime.scala │ │ ├── DateTime.scala │ │ ├── Time.scala │ │ ├── Date.scala │ │ └── Component.scala │ └── test │ └── scala │ └── pl │ └── metastack │ └── metatime │ ├── PeriodTest.scala │ ├── TimestampTest.scala │ ├── LocaleTest.scala │ ├── PatternTest.scala │ ├── DateTest.scala │ ├── DateTimeTest.scala │ ├── TimeTest.scala │ └── SchedulerTest.scala ├── js └── src │ └── main │ └── scala │ └── pl │ └── metastack │ └── metatime │ ├── Platform.scala │ └── AsyncScheduler.scala ├── .gitignore ├── jvm └── src │ └── main │ └── scala │ └── pl │ └── metastack │ └── metatime │ ├── Platform.scala │ └── AsyncScheduler.scala └── README.md /manual/images/.empty: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | scala: 3 | - 2.11.7 4 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "0.1.0-SNAPSHOT" 2 | -------------------------------------------------------------------------------- /shared/src/main/scala/pl/metastack/metatime/Timezone.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | class Timezone { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /shared/src/test/scala/pl/metastack/metatime/PeriodTest.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | class PeriodTest { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /manual/scalajs-support.md: -------------------------------------------------------------------------------- 1 | # Scala.js support 2 | 3 | Library offers support for Scala.js which enables users to use all these features in Scala.js also -------------------------------------------------------------------------------- /shared/src/main/scala/pl/metastack/metatime/Range.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | case class Range(lowerBound: Long, upperBound: Long) 4 | -------------------------------------------------------------------------------- /js/src/main/scala/pl/metastack/metatime/Platform.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | object Platform { 4 | implicit lazy val DefaultScheduler: Scheduler = new AsyncScheduler 5 | } -------------------------------------------------------------------------------- /shared/src/main/scala/pl/metastack/metatime/Timestamp.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | case class Unix(value: Long) extends Timestamp 4 | 5 | trait Timestamp { 6 | def time: Time = ??? 7 | def date: Date = ??? 8 | def dateTime: DateTime = ??? 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | .cache/ 6 | .history/ 7 | .lib/ 8 | dist/* 9 | target/ 10 | lib_managed/ 11 | src_managed/ 12 | project/boot/ 13 | project/plugins/project/ 14 | 15 | # Scala-IDE specific 16 | .scala_dependencies 17 | .worksheet 18 | .idea 19 | -------------------------------------------------------------------------------- /shared/src/test/scala/pl/metastack/metatime/TimestampTest.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | import org.scalatest.FunSuite 4 | 5 | class TimestampTest extends FunSuite { 6 | ignore("construct") { 7 | assert(Unix(???).date == ???) 8 | assert(Unix(???).dateTime == ???) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /shared/src/test/scala/pl/metastack/metatime/LocaleTest.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | import org.scalatest.FunSuite 4 | 5 | class LocaleTest extends FunSuite { 6 | ignore("construct") { 7 | assert(Locale("en_GB") == Locale.English.GreatBritain) 8 | assert(Locale("pl_PL") == Locale.Polish) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn 2 | 3 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.6") 4 | 5 | addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.5.0") 6 | 7 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "0.5.0") 8 | 9 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") 10 | 11 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.0") 12 | -------------------------------------------------------------------------------- /jvm/src/main/scala/pl/metastack/metatime/Platform.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | import java.util.concurrent.Executors 4 | import scala.concurrent.ExecutionContext 5 | 6 | object Platform { 7 | implicit lazy val DefaultScheduler: Scheduler = new AsyncScheduler( 8 | Executors.newSingleThreadScheduledExecutor(), 9 | ExecutionContext.Implicits.global) 10 | } 11 | -------------------------------------------------------------------------------- /manual/datetime-formatting.md: -------------------------------------------------------------------------------- 1 | # DateTime Formatting 2 | 3 | MetaTime provides various ways to customize the format of the Time, Date and DateTime components. You may wish to use the default patterns or you can create your own, its as easy as describing component next to each other. 4 | 5 | For example below is usage of default format: 6 | [scala type="section" value="formatting" file="Examples"] 7 | -------------------------------------------------------------------------------- /manual/scheduler.md: -------------------------------------------------------------------------------- 1 | # Scheduler 2 | 3 | One of the most powerful feature offered by MetaTime is the scheduler, user may schedule different tasks / activities by defining the relative time in future from now or absolute Time/DateTime components. One simple example of scheduler usage looks like this: 4 | 5 | [scala type="section" value="scheduler" file="Examples"] 6 | Above example will schedule the task for 01 May 2016 midnight. 7 | 8 | -------------------------------------------------------------------------------- /manual/support.md: -------------------------------------------------------------------------------- 1 | # Support 2 | ## Bugs/feature requests 3 | Please use our [issue tracker](https://github.com/MetaStack-pl/MetaTime/issues) to file bugs and feature requests. 4 | 5 | ## Discussions 6 | For discussions and general questions, please join our Gitter channel instead: [![Join the chat at https://gitter.im/MetaStack-pl/MetaTime](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/MetaStack-pl/MetaTime?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 7 | -------------------------------------------------------------------------------- /manual/type-safe-dsl.md: -------------------------------------------------------------------------------- 1 | # Type-safe DSL 2 | 3 | MetaTime is a type-safe DSL which offers to prevent a lot of type errors and gives users opportunity to focus on how best library can be used in their projects. 4 | 5 | Library provides various ways to perform addition of DateTime components, for example: 6 | 7 | For example below is usage of addition of date and day: 8 | [scala type="section" value="addition" file="Examples"] 9 | 10 | For example below is usage of subtraction of date and day: 11 | [scala type="section" value="subtraction" file="Examples"] -------------------------------------------------------------------------------- /manual/introduction.md: -------------------------------------------------------------------------------- 1 | [package value="pl.metastack.metatime.manual"] 2 | # Introduction 3 | MetaTime is a type-safe library for working with times and dates in Scala and Scala.js. 4 | 5 | ## Installation 6 | Add the following dependencies to your build configuration: 7 | 8 | ```scala 9 | libraryDependencies += "pl.metastack" %% "metatime" % "%version%" // Scala 10 | libraryDependencies += "pl.metastack" %%% "metatime" % "%version%" // Scala.js 11 | ``` 12 | 13 | ## Key Features 14 | * Type-safe DSL 15 | * Scheduler 16 | * Date / Time formatting 17 | * Scala.js support 18 | * Duration 19 | * Timezones 20 | 21 | -------------------------------------------------------------------------------- /shared/src/main/scala/pl/metastack/metatime/Locale.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | object Locale { 4 | object English { 5 | case object America extends Locale 6 | case object GreatBritain extends Locale 7 | } 8 | 9 | object German { 10 | case object Germany extends Locale 11 | case object Switzerland extends Locale 12 | case object Austria extends Locale 13 | } 14 | 15 | case object Polish extends Locale 16 | 17 | def apply(isoCode: String): Locale = ??? 18 | } 19 | 20 | trait Locale { 21 | def isoCode: String = ??? 22 | def languageCode: String = ??? 23 | def countryCode: String = ??? 24 | } 25 | -------------------------------------------------------------------------------- /shared/src/main/scala/pl/metastack/metatime/Implicits.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | object Implicits { 4 | implicit class IntExtensions(value: Int) { 5 | def year: Year = Year(value) 6 | def years: Year = Year(value) 7 | def day: Day = Day(value) 8 | def days: Day = Day(value) 9 | def week: Day = Day(7 * value) 10 | def weeks: Day = Day(7 * value) 11 | def hour: Hour = Hour(value) 12 | def hours: Hour = Hour(value) 13 | def minute: Minute = Minute(value) 14 | def minutes: Minute = Minute(value) 15 | def second: Second = Second(value) 16 | def seconds: Second = Second(value) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /manual/development.md: -------------------------------------------------------------------------------- 1 | # Development 2 | [![Build Status](https://travis-ci.org/MetaStack-pl/MetaTime.svg)](https://travis-ci.org/MetaStack-pl/MetaTime) 3 | 4 | ## Tests 5 | The proper functioning of each operation is backed by [test cases](https://github.com/MetaStack-pl/MetaTime/tree/master/shared/src/test/scala/pl/metastack/metatime). These also provide complementary documentation. 6 | 7 | ## Manual 8 | Run the following command to generate the manual: 9 | 10 | ```bash 11 | $ sbt manual/runMain pl.metastack.metatime.manual.Manual 12 | ``` 13 | 14 | Deploy it with: 15 | 16 | ```bash 17 | $ sbt manual/runMain pl.metastack.metatime.manual.Deploy 18 | ``` 19 | -------------------------------------------------------------------------------- /manual/src/main/scala/pl/metastack/metatime/manual/Deploy.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime.manual 2 | 3 | import java.io.File 4 | 5 | import org.eclipse.jgit.api.Git 6 | import org.eclipse.jgit.storage.file.FileRepositoryBuilder 7 | 8 | import pl.metastack.metatime.BuildInfo 9 | 10 | object Deploy extends App with Shared { 11 | val repo = FileRepositoryBuilder.create(new File(projectPath, ".git")) 12 | val git = new Git(repo) 13 | git.add().addFilepattern(projectName).call() 14 | if (!git.status().call().isClean) { 15 | git.commit() 16 | .setAll(true) 17 | .setMessage(s"Update $projectName v${BuildInfo.version}").call() 18 | git.push().call() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /shared/src/main/scala/pl/metastack/metatime/Pattern.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | trait Pattern[+T,ItemType <: Component]{ 4 | 5 | def string(s: String): T = add(_ => s) 6 | def newLine: T = add(_ => "\n") 7 | def dash : T = string("-") 8 | def space: T = string(" ") 9 | def colon: T = string(":") 10 | def comma: T = string(",") 11 | def dot : T = string(".") 12 | 13 | def add(item: ItemType => String): T 14 | 15 | val months = Seq("January", "February", "March", 16 | "April", "May", "June", "July", "August", 17 | "September", "October", "November", "December") 18 | 19 | val weekDays = Seq("Thursday", "Friday", "Saturday", 20 | "Sunday", "Monday", "Tuesday", "Wednesday") 21 | } -------------------------------------------------------------------------------- /manual/src/main/scala/pl/metastack/metatime/manual/Shared.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime.manual 2 | 3 | import java.io.File 4 | 5 | import pl.metastack.metatime.BuildInfo 6 | 7 | trait Shared { 8 | val organisation = "MetaStack-pl" 9 | val repoName = s"$organisation.github.io" 10 | val projectName = "metatime" 11 | val projectPath = new File("..", repoName) 12 | val manualPath = new File(projectPath, projectName) 13 | val manualPathStr = manualPath.getPath 14 | val manualVersionPath = new File(manualPath, "v" + BuildInfo.version) 15 | val manualVersionPathStr = manualVersionPath.getPath 16 | val imagesPath = new File(manualVersionPath, "images") 17 | val isSnapshot = BuildInfo.version.endsWith("SNAPSHOT") 18 | } 19 | -------------------------------------------------------------------------------- /manual/src/main/scala/pl/metastack/metatime/manual/Examples.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime.manual 2 | 3 | import pl.metastack.metadocs.SectionSupport 4 | import pl.metastack.metatime._ 5 | 6 | import scala.concurrent.Promise 7 | 8 | object Examples extends SectionSupport { 9 | section("introduction") { 10 | DateTime(2015, 1, 1).format(PatternDateTime.DefaultDate) 11 | } 12 | 13 | section("formatting") { 14 | DateTime(2015, 1, 1).format(PatternDateTime.DefaultDate) 15 | } 16 | section("subtraction") { 17 | Date(2015, 1, 20) - Day(1) 18 | } 19 | 20 | section("addition") { 21 | Date(2015, 1, 20) + Day(1) 22 | } 23 | 24 | section("scheduler") { 25 | val scheduler: Scheduler = Platform.DefaultScheduler 26 | scheduler.at(DateTime(2016, 5, 1, 0, 0, 0, 0)) { 27 | //TASK 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MetaTime 2 | [![Build Status](https://travis-ci.org/MetaStack-pl/MetaTime.svg)](https://travis-ci.org/MetaStack-pl/MetaTime) 3 | [![Join the chat at https://gitter.im/MetaStack-pl/MetaTime](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/MetaStack-pl/MetaTime?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | MetaTime is a time formatting library for Scala and Scala.js. 6 | 7 | ## Links 8 | * [Single-page manual](http://metastack.pl/metatime/latest.html) 9 | * [Multi-page manual](http://metastack.pl/metatime/latest/index.html) 10 | * [Versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22pl.metastack%22%20AND%20a%3A%22metatime_2.11%22) 11 | 12 | ## License 13 | MetaTime is licensed under the terms of the Apache v2.0 license. 14 | 15 | ## Authors 16 | * Tim Nieradzik 17 | * Syed Waqar Abbas 18 | -------------------------------------------------------------------------------- /shared/src/main/scala/pl/metastack/metatime/Constants.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | object Constants { 4 | final val YearIndex = 1 5 | final val MonthIndex = 2 6 | final val DayIndex = 3 7 | final val HourIndex = 4 8 | final val MinuteIndex = 5 9 | final val SecondIndex = 6 10 | final val MillisecondIndex = 7 11 | 12 | final val MillisInSecond = 1000 13 | final val SecondsInMinute = 60 14 | final val MillisInMinute = SecondsInMinute * MillisInSecond 15 | final val MinutesInHour = 60 16 | final val SecondsInHour = MinutesInHour * SecondsInMinute 17 | final val MillisInHour = MinutesInHour * MillisInMinute 18 | 19 | final val HoursInDay = 24 20 | final val MinutesInDay = HoursInDay * MinutesInHour 21 | final val SecondsInDay = HoursInDay * SecondsInHour 22 | final val MillisInDay = HoursInDay * MillisInHour 23 | final val DefaultYear = 1970 24 | final val DaysInMonth = 30 25 | final val DaysInYear = 365 26 | 27 | final val In = "in " 28 | final val Ago = " ago" 29 | } 30 | 31 | -------------------------------------------------------------------------------- /jvm/src/main/scala/pl/metastack/metatime/AsyncScheduler.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | import java.util.concurrent.{ScheduledExecutorService, TimeUnit} 4 | import scala.concurrent.duration._ 5 | import scala.concurrent.ExecutionContext 6 | 7 | class AsyncScheduler(s: ScheduledExecutorService, 8 | ec: ExecutionContext) extends Scheduler { 9 | def schedule(interval: FiniteDuration, r: Runnable): Cancelable = 10 | schedule(interval.length, interval.unit, r) 11 | 12 | def schedule(interval: Long, unit: TimeUnit, r: Runnable): Cancelable = { 13 | require(interval > 0) 14 | val initialDelay = interval 15 | val task = s.scheduleAtFixedRate(r, initialDelay, interval, unit) 16 | Cancelable(task.cancel(true)) 17 | } 18 | 19 | def scheduleOnce(initialDelay: FiniteDuration, r: Runnable): Cancelable = 20 | scheduleOnce(initialDelay.length, initialDelay.unit, r) 21 | 22 | def scheduleOnce(initialDelay: Long, unit: TimeUnit, r: Runnable): Cancelable = { 23 | if (initialDelay <= 0) { 24 | ec.execute(r) 25 | Cancelable() 26 | } else { 27 | val task = s.schedule(r, initialDelay, unit) 28 | Cancelable(task.cancel(true)) 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /js/src/main/scala/pl/metastack/metatime/AsyncScheduler.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import scala.scalajs.js 6 | 7 | class AsyncScheduler extends Scheduler { 8 | type Timeout = js.Dynamic 9 | type Interval = js.Dynamic 10 | 11 | def setTimeout(delayMillis: Long, r: Runnable): Timeout = { 12 | val lambda: js.Function = () => r.run() 13 | js.Dynamic.global.setTimeout(lambda, delayMillis) 14 | } 15 | 16 | def setInterval(intervalMillis: Long, r: Runnable): Interval = { 17 | val lambda: js.Function = () => r.run() 18 | js.Dynamic.global.setInterval(lambda, intervalMillis) 19 | } 20 | 21 | def clearTimeout(task: Timeout): Unit = 22 | js.Dynamic.global.clearTimeout(task) 23 | 24 | def clearInterval(task: Interval): Unit = 25 | js.Dynamic.global.clearInterval(task) 26 | 27 | def schedule(interval: FiniteDuration, r: Runnable): Cancelable = { 28 | val task = setInterval(interval.toMillis, r) 29 | Cancelable(clearInterval(task)) 30 | } 31 | 32 | def scheduleOnce(initialDelay: FiniteDuration, r: Runnable): Cancelable = { 33 | val task = setTimeout(initialDelay.toMillis, r) 34 | Cancelable(clearTimeout(task)) 35 | } 36 | } -------------------------------------------------------------------------------- /shared/src/main/scala/pl/metastack/metatime/Offset.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | trait Offset[+T <: Component] { 4 | val component: T 5 | 6 | def format: String = 7 | if (component.unix().value <= 0) Constants.In + absoluteVal 8 | else absoluteVal + Constants.Ago 9 | 10 | def absoluteVal: String = 11 | component match { 12 | case date: Date => math.abs(date.year) + " year(s), " + math.abs(date.month) + 13 | " month(s) and " + math.abs(date.day) 14 | case year: Year => math.abs(year.year) + " year(s)" 15 | case month: Month => math.abs(month.month) + " month(s)" 16 | case day: Day => math.abs(day.day) + " day(s)" 17 | case time: Time => math.abs(time.h) + " hour(s), " + math.abs(time.m) + " minute(s), " + 18 | math.abs(time.s) + " second(s) and " + math.abs(time.ms) + " millisecond(s)" 19 | case hour: Hour => math.abs(hour.h) + " hour(s)" 20 | case minute: Minute => math.abs(minute.m) + " minute(s)" 21 | case second: Second => math.abs(second.s) + " second(s)" 22 | case milliseconds: Millisecond => math.abs(milliseconds.ms) + " millisecond(s)" 23 | } 24 | } 25 | 26 | object Offset { 27 | def apply[T <: Component](value: T): Offset[T] = 28 | new Offset[T] { override val component = value } 29 | } 30 | -------------------------------------------------------------------------------- /shared/src/test/scala/pl/metastack/metatime/PatternTest.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | import org.scalatest.FunSuite 4 | 5 | class PatternTest extends FunSuite { 6 | import Implicits._ 7 | 8 | test("FormatDateTime") { 9 | val dateTime = DateTime(2015, 1, 1) 10 | assert(dateTime.format(PatternDateTime.DefaultDate) == "Thursday, 1 January 2015") 11 | } 12 | 13 | test("FormatTime") { 14 | val time = Time(11, 1, 0, 0) 15 | assert(time.format(PatternTime.DefaultTime) == "11:01 AM") 16 | } 17 | 18 | ignore("Format from now") { 19 | val offset = (DateTime.now() + 1.day).fromNow 20 | assert(offset.format == s"1 day(s) from now") 21 | } 22 | 23 | test("Custom pattern") { 24 | val dateTime = DateTime(2016, 6, 1, 0, 0, 0, 0) 25 | val formatter = PatternDateTime.branch(Seq( 26 | (0.seconds to 1.minute) -> PatternDateTime.JustNow, 27 | (1.minute to 59.minute ) -> PatternDateTime.MinutesAgo, 28 | (59.minute to 1.day ) -> PatternDateTime.HoursAgo, 29 | (1.day to 1.week ) -> PatternDateTime.DaysAgo 30 | ), PatternDateTime.DefaultDateTime) 31 | 32 | val dt = DateTime(2015, 1, 1) 33 | 34 | val now = DateTime.now() 35 | assert(formatter.format((now - 5.minutes).asInstanceOf[DateTime]) == "5 minute(s) ago") 36 | assert(formatter.format((now - 60.minutes).asInstanceOf[DateTime]) == "1 hour(s) ago") 37 | 38 | val twoWeeks = (now - 2.weeks).asInstanceOf[DateTime] 39 | assert(formatter.format(twoWeeks) == PatternDateTime.DefaultDateTime.format(twoWeeks)) 40 | } 41 | } -------------------------------------------------------------------------------- /shared/src/test/scala/pl/metastack/metatime/DateTest.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | import org.scalatest.FunSuite 4 | 5 | class DateTest extends FunSuite { 6 | import Implicits._ 7 | 8 | test("construct") { 9 | val date = Date(2015, 12, 5) 10 | assert(date.year == 2015) 11 | assert(date.month == 12) 12 | assert(date.day == 5) 13 | } 14 | 15 | test("implicits") { 16 | assert(Year(2015) == 2015.year) 17 | } 18 | 19 | test("dayUnix") { 20 | assert(Day(1 + 1.0 / 24).unix().value == 90000000) 21 | } 22 | 23 | test("date") { 24 | assert(Year(2015).date == Date(2015, 1, 1)) 25 | assert(Day(1) == Day(1)) 26 | } 27 | 28 | test("add") { 29 | val dt = (Date(2015) + 1.day).asInstanceOf[Date] 30 | assert(Year(2015) + 1.day == Date(2015, 1, 1)) 31 | } 32 | //TODO forbid addition of absolute dates in future 33 | ignore("datePlus") { 34 | val date = (Date(2015, 1, 1) + Date(2015, 1, 1)).asInstanceOf[Date] 35 | assert(Date(2015, 1, 1) + Date(2015, 1, 1) == Date(2060, 1, 2)) 36 | } 37 | 38 | ignore("until") { 39 | assert((Year(2015) until Year(2016)) == ???) 40 | } 41 | 42 | ignore("now") { 43 | /** Current time */ 44 | assert(DateTime.now() == ???) 45 | 46 | /** Unix timestamp */ 47 | assert(DateTime.now().timestamp == ???) 48 | } 49 | 50 | test("fromNow") { 51 | assert(Date(2012, 1, 1).fromNow.format == "4 year(s) ago") 52 | assert(Date(2015, 8, 8).fromNow.format == "8 month(s) ago") 53 | assert(Date(2018, 10, 1).fromNow.format == "in 2 year(s)") 54 | } 55 | } -------------------------------------------------------------------------------- /shared/src/test/scala/pl/metastack/metatime/DateTimeTest.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | import org.scalatest.FunSuite 4 | import scala.concurrent.Promise 5 | import scala.concurrent.ExecutionContext.Implicits.global 6 | 7 | class DateTimeTest extends FunSuite { 8 | test("construct") { 9 | val dateTime = DateTime(2016, 1, 1, 10, 10, 50, 500) 10 | assert(dateTime.year == 2016) 11 | assert(dateTime.month == 1) 12 | assert(dateTime.day == 1) 13 | assert(dateTime.h == 10) 14 | assert(dateTime.m == 10) 15 | assert(dateTime.s == 50) 16 | assert(dateTime.ms == 500) 17 | } 18 | 19 | test("date") { 20 | assert(Year(2015).dateTime == DateTime(2015, 1, 0, 0, 0, 0)) 21 | } 22 | 23 | test("Add day") { 24 | assert( 25 | DateTime(2016, 1, 1, 1, 1, 1, 1) + Day(1) == 26 | DateTime(2016, 1, 2, 1, 1, 1, 1)) 27 | } 28 | 29 | test("Add second") { 30 | val dt = DateTime.now() 31 | val dtAfter = dt + Second(1) 32 | assert(dtAfter.unix().value == dt.unix().value + 1000) 33 | } 34 | 35 | test("now") { 36 | val scheduler: Scheduler = Platform.DefaultScheduler 37 | val dt = DateTime.now() 38 | val dtAfter = (dt + Second(5)).asInstanceOf[DateTime] 39 | val p = Promise[Boolean]() 40 | scheduler.at(dtAfter) { 41 | p.success((DateTime.now() - dt).unix().value < 10000) 42 | } 43 | p.future.map(assert(_)) 44 | } 45 | 46 | ignore("fromNow") { 47 | assert(DateTime(2018, 10, 1, 1, 1, 1, 1).fromNow.format == "in 2 year(s)") 48 | assert(DateTime(2013, 2, 1, 1, 1, 1, 1).fromNow.format == "3 year(s) ago") 49 | } 50 | } -------------------------------------------------------------------------------- /shared/src/test/scala/pl/metastack/metatime/TimeTest.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | import org.scalatest.FunSuite 4 | import scala.concurrent.Promise 5 | import scala.concurrent.ExecutionContext.Implicits.global 6 | 7 | class TimeTest extends FunSuite { 8 | import Implicits._ 9 | 10 | test("construct") { 11 | val time = Time(23, 10, 9, 555) 12 | assert(time.h == 23) 13 | assert(time.m == 10) 14 | assert(time.s == 9) 15 | assert(time.ms == 555) 16 | } 17 | 18 | test("implicits") { 19 | assert(Hour(23) == 23.hour) 20 | } 21 | 22 | ignore("hour") { 23 | assert(Hour(25).days == Day(1 + 1.0 / 24)) 24 | } 25 | 26 | test("operators") { 27 | assert(Time(23, 10) > Time(22, 0)) 28 | assert(Time(23, 10) > Hour(22)) 29 | assert(Hour(2) > Minute(80)) 30 | } 31 | 32 | test("plusTime") { 33 | assert(Time(Hour(8) + Hour(2)) == Time(hour = 10)) 34 | assert(Time(Minute(20) + Second(40)) != Time(hour = 10, minute = 20)) 35 | assert(Time(Hour(2) + Minute(60)) == Time(hour = 3)) 36 | } 37 | 38 | test("minusTime") { 39 | assert(Time(Minute(50) - Minute(10)) == Time(minute = 40)) 40 | assert(Time(Second(1) - Millisecond(100)) == Time(milliseconds = 900)) 41 | assert(Time(Hour(20) - Minute(100)) == Time(hour = 18, minute = 20)) 42 | } 43 | 44 | test("now") { 45 | val scheduler: Scheduler = Platform.DefaultScheduler 46 | val timeBefore = Time.now() 47 | val p = Promise[Boolean]() 48 | scheduler.at(Time(0, 0, 1, 0)) { 49 | p.success((Time.now() - timeBefore).unix().value >= 1000) 50 | } 51 | p.future.map(assert(_)) 52 | } 53 | 54 | ignore("locale") { 55 | implicit val locale = Locale.English.America 56 | //assert(Time(23).format == "11 pm") 57 | } 58 | 59 | test("fromNow") { 60 | assert(Minute(120).fromNow.format == "2 hour(s) ago") 61 | } 62 | 63 | test("millis") { 64 | assert(Minute(2).milliseconds() == 120000) 65 | assert(Time(1, 0, 0, 0).milliseconds() == 3600000) 66 | } 67 | } -------------------------------------------------------------------------------- /shared/src/main/scala/pl/metastack/metatime/PatternTime.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | case class PatternTime(parts: Seq[Time => String] = Nil) extends Pattern[PatternTime, Time] { 4 | 5 | def amPm: PatternTime = 6 | add(time => 7 | if (time.h > 12) "PM" 8 | else "AM") 9 | 10 | def hour(amPm: Boolean = false): PatternTime = add(_.h.toString) 11 | def minute: PatternTime = add(time => 12 | f"${time.m}%02d".toString) 13 | 14 | def minuteAgo: PatternTime = add(time => 15 | time.fromNow.component.dateTime.m.toString) 16 | 17 | def hourAgo: PatternTime = add(time => 18 | time.fromNow.component.dateTime.h.toString) 19 | 20 | def second: PatternTime = add(_.s.toString) 21 | 22 | def secondsFract(digits: Int): PatternTime = { 23 | add(_.ms.toString) 24 | } 25 | 26 | //def newLine: PatternTime = add(_ => "\n") 27 | 28 | def add(item: Time => String): PatternTime = copy(parts = parts ++ Seq(item)) 29 | 30 | def format(time: Time): String = 31 | parts.foldLeft("") { case (string, format) => string + format(time) } 32 | 33 | def concat(pattern: PatternTime): PatternTime = PatternTime(parts ++ pattern.parts) 34 | 35 | def ++(pattern: PatternTime): PatternTime = concat(pattern) 36 | } 37 | 38 | object PatternTime { 39 | 40 | val DefaultTime = PatternTime().hour(amPm = true).colon.minute.space.amPm 41 | 42 | val JustNow = PatternTime().string("just now") 43 | 44 | def ago(pattern: PatternTime): PatternTime = pattern.space.string("ago") 45 | 46 | val MinutesAgo: PatternTime = ago(PatternTime().minuteAgo.space.string("minute(s)")) 47 | val HoursAgo: PatternTime = ago(PatternTime().hourAgo.space.string("hour(s)")) 48 | val DaysAgo: PatternTime = ago(PatternTime().minute.space.string("day(s)")) 49 | 50 | def branch(ranges: Seq[(Range, PatternTime)], fallback: PatternTime): PatternTime = { 51 | PatternTime().add { time => 52 | val unixVal = time.fromNow.component.unix().value 53 | val x =ranges.find { case (r, p) => 54 | (r.lowerBound to r.upperBound).contains(unixVal) 55 | } 56 | x.fold(fallback.format(time)) { case (r, p) => p.format(time) } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /shared/src/test/scala/pl/metastack/metatime/SchedulerTest.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | import org.scalatest.AsyncFunSuite 4 | import scala.concurrent.Promise 5 | 6 | class SchedulerTest extends AsyncFunSuite { 7 | val scheduler: Scheduler = Platform.DefaultScheduler 8 | 9 | test("Schedule once at Time") { 10 | val timeBefore = Time.now() 11 | val p = Promise[Boolean]() 12 | scheduler.at(Time(0, 0, 1, 0)) { 13 | p.success((Time.now() - timeBefore).unix().value >= 1000) 14 | } 15 | p.future.map(assert(_)) 16 | } 17 | 18 | test("Schedule once at DateTime") { 19 | val dt = DateTime.now() 20 | val dtAfter = (dt + Second(1)).asInstanceOf[DateTime] 21 | assert(dtAfter.unix().value == dt.unix().value + 1000) 22 | val p = Promise[Boolean]() 23 | scheduler.at(dtAfter) { 24 | p.success((DateTime.now() - dt).unix().value >= 1000) 25 | } 26 | p.future.map(assert(_)) 27 | } 28 | 29 | test("Schedule once at Offset") { 30 | val timeBefore = Time.now() 31 | val offset = Offset(Second(1)) 32 | val p = Promise[Boolean]() 33 | scheduler.at(offset) { 34 | p.success((Time.now() - timeBefore).unix().value >= 1000) 35 | } 36 | p.future.map(assert(_)) 37 | } 38 | 39 | ignore("Schedule repeatedly at Time") { 40 | val timeBefore = Time.now() 41 | val p = Promise[Boolean]() 42 | scheduler.every(Time(0, 0, 1, 0)) { 43 | // TODO Check whether every() is called more than once in the same intervals 44 | p.success(Math.abs((Time.now() - timeBefore).unix().value) >= 1000) 45 | } 46 | p.future.map(assert(_)) 47 | } 48 | 49 | ignore("Schedule repeatedly at DateTime") { 50 | val dt = DateTime.now() 51 | val dtAfter = (dt + Second(1)).asInstanceOf[DateTime] 52 | val p = Promise[Boolean]() 53 | scheduler.every(dtAfter) { 54 | p.success((DateTime.now() - dt).unix().value >= 1000) 55 | } 56 | p.future.map(assert(_)) 57 | } 58 | 59 | ignore("Schedule repeatedly at Offset") { 60 | val timeBefore = Time.now() 61 | val offset = Offset(Second(1)) 62 | val p = Promise[Boolean]() 63 | scheduler.every(offset) { 64 | p.success(Math.abs((Time.now() - timeBefore).unix().value) >= 1000) 65 | } 66 | p.future.map(assert(_)) 67 | } 68 | } -------------------------------------------------------------------------------- /shared/src/main/scala/pl/metastack/metatime/Scheduler.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | import scala.concurrent.duration._ 4 | 5 | /** 6 | * Inspired from Monifu's scheduling code. 7 | */ 8 | trait Scheduler { 9 | def schedule(interval: FiniteDuration, r: Runnable): Cancelable 10 | def scheduleOnce(initialDelay: FiniteDuration, action: Runnable): Cancelable 11 | 12 | def schedule(interval: FiniteDuration)(action: => Unit): Cancelable = 13 | schedule(interval, new Runnable { 14 | def run(): Unit = action 15 | }) 16 | 17 | def scheduleOnce(initialDelay: FiniteDuration)(action: => Unit): Cancelable = 18 | scheduleOnce(initialDelay, new Runnable { 19 | def run(): Unit = action 20 | }) 21 | 22 | def currentTimeMillis(): Long = System.currentTimeMillis() 23 | 24 | def at(time: Time)(f: => Unit): Cancelable = 25 | at(time.fromNow)(f) 26 | def at(dateTime: DateTime)(f: => Unit): Cancelable = 27 | at(dateTime.fromNow)(f) 28 | def at(time: Offset[_])(f: => Unit): Cancelable = 29 | scheduleOnce(Math.abs(time.component.asInstanceOf[Component].milliseconds()).millis)(f) 30 | 31 | def in(time: Time)(f: => Unit): Cancelable = 32 | scheduleOnce(time.milliseconds.millis)(f) 33 | def in(date: Date)(f: => Unit): Cancelable = 34 | scheduleOnce(date.milliseconds.millis)(f) 35 | def in(dateTime: DateTime)(f: => Unit): Cancelable = 36 | scheduleOnce(dateTime.milliseconds.millis)(f) 37 | 38 | def every(time: Time)(f: => Unit): Cancelable = 39 | schedule(time.milliseconds.millis)(f) 40 | def every(dateTime: DateTime)(f: => Unit): Cancelable = 41 | schedule(dateTime.milliseconds.millis)(f) 42 | def every(time: Offset[Component])(f: => Unit): Cancelable = 43 | schedule(Math.abs(time.component.milliseconds()).millis)(f) 44 | } 45 | 46 | trait Cancelable { 47 | def cancel(): Boolean 48 | } 49 | 50 | object Cancelable { 51 | def apply(): Cancelable = apply({}) 52 | 53 | def apply(callback: => Unit): Cancelable = 54 | new Cancelable { 55 | var isCanceled = false 56 | 57 | def cancel(): Boolean = 58 | if (isCanceled) false 59 | else { 60 | isCanceled = true 61 | callback 62 | true 63 | } 64 | } 65 | } 66 | 67 | trait Task { 68 | def cancel(): Unit = ??? 69 | } 70 | -------------------------------------------------------------------------------- /project/Build.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import sbt.Keys._ 3 | import xerial.sbt.Sonatype.sonatypeSettings 4 | import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ 5 | import sbtbuildinfo.BuildInfoPlugin 6 | import sbtbuildinfo.BuildInfoPlugin.autoImport._ 7 | 8 | object Build extends sbt.Build { 9 | object Dependencies { 10 | val ScalaTest = "3.0.0-M14" 11 | val MetaDocs = "0.1.1" 12 | val MetaRx = "0.1.6" 13 | } 14 | 15 | val SharedSettings = Seq( 16 | name := "MetaTime", 17 | organization := "pl.metastack", 18 | scalaVersion := "2.11.7", 19 | scalacOptions := Seq( 20 | "-unchecked", 21 | "-deprecation", 22 | "-encoding", "utf8" 23 | ) 24 | ) 25 | 26 | lazy val root = project.in(file(".")) 27 | .aggregate(js, jvm) 28 | .settings(SharedSettings: _*) 29 | .settings(publishArtifact := false) 30 | 31 | lazy val metaTime = crossProject.in(file(".")) 32 | .settings(SharedSettings: _*) 33 | .settings(sonatypeSettings: _*) 34 | .settings( 35 | pomExtra := 36 | https://github.com/MetaStack-pl/MetaTime 37 | 38 | 39 | Apache-2.0 40 | https://www.apache.org/licenses/LICENSE-2.0.html 41 | 42 | 43 | 44 | git@github.com:MetaStack-pl/MetaTime.git 45 | 46 | 47 | 48 | tindzk 49 | Tim Nieradzik 50 | http://github.com/tindzk/ 51 | 52 | , 53 | 54 | autoAPIMappings := true, 55 | apiMappings += 56 | (scalaInstance.value.libraryJar -> url(s"http://www.scala-lang.org/api/${scalaVersion.value}/")) 57 | ) 58 | .jsSettings( 59 | libraryDependencies += 60 | "org.scalatest" %%% "scalatest" % Dependencies.ScalaTest % "test", 61 | libraryDependencies += 62 | "pl.metastack" %%% "metarx" % Dependencies.MetaRx % "test", 63 | 64 | /* Use io.js for faster compilation of test cases */ 65 | scalaJSStage in Global := FastOptStage 66 | ) 67 | .jvmSettings( 68 | libraryDependencies += 69 | "org.scalatest" %% "scalatest" % Dependencies.ScalaTest % "test", 70 | libraryDependencies += 71 | "pl.metastack" %% "metarx" % Dependencies.MetaRx % "test" 72 | ) 73 | 74 | lazy val js = metaTime.js 75 | lazy val jvm = metaTime.jvm 76 | 77 | lazy val manual = project.in(file("manual")) 78 | .dependsOn(jvm) 79 | .enablePlugins(BuildInfoPlugin) 80 | .settings(SharedSettings: _*) 81 | .settings( 82 | publishArtifact := false, 83 | libraryDependencies ++= Seq( 84 | "pl.metastack" %% "metadocs" % Dependencies.MetaDocs, 85 | "org.eclipse.jgit" % "org.eclipse.jgit" % "4.1.1.201511131810-r"), 86 | buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion), 87 | buildInfoPackage := "pl.metastack.metatime", 88 | name := "MetaTime manual") 89 | } 90 | -------------------------------------------------------------------------------- /shared/src/main/scala/pl/metastack/metatime/PatternDateTime.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | case class PatternDateTime(parts: Seq[DateTime => String] = Nil) extends Pattern[PatternDateTime, DateTime] { 4 | 5 | def year(short: Boolean = false): PatternDateTime = 6 | add(dt => 7 | if (!short) dt.year.toString 8 | else dt.year.toString.drop(2)) 9 | 10 | def month: PatternDateTime = add(_.month.toString) 11 | 12 | def monthName(short: Boolean = false): PatternDateTime = { 13 | add(dateTime => 14 | months(dateTime.month - 1)) 15 | } 16 | 17 | def day: PatternDateTime = add(_.day.toInt.toString) 18 | 19 | def amPm: PatternDateTime = 20 | add(dt => 21 | if (dt.h > 12) "PM" 22 | else "AM") 23 | 24 | def hour(amPm: Boolean = false): PatternDateTime = add(_.h.toString) 25 | def minute: PatternDateTime = add(dt => 26 | f"${dt.asInstanceOf[Time].m}%02d".toString) 27 | 28 | def minuteAgo: PatternDateTime = add(dt => 29 | dt.asInstanceOf[Time].fromNow.component.dateTime.m.toString) 30 | 31 | def hourAgo: PatternDateTime = add(dt => 32 | dt.asInstanceOf[Time].fromNow.component.dateTime.h.toString) 33 | 34 | def second: PatternDateTime = add(_.asInstanceOf[Time].s.toString) 35 | 36 | /** Fractional seconds padded to `digits` */ 37 | def secondsFract(digits: Int): PatternDateTime = { 38 | add(_.asInstanceOf[Time].ms.toString) 39 | } 40 | 41 | /** 42 | * Day of the week such as Monday, Tuesday, Wednesday etc. 43 | * @param short Short version of weekdays (Mon, Tue, Wed) 44 | */ 45 | def weekDay(short: Boolean = false): PatternDateTime = { 46 | add(dateTime => 47 | weekDays(dateTime.weekDay() - 1)) 48 | } 49 | 50 | def add(item: DateTime => String): PatternDateTime = copy(parts = parts ++ Seq(item)) 51 | 52 | def format(dateTime: DateTime): String = 53 | parts.foldLeft("") { case (string, format) => string + format(dateTime) } 54 | 55 | def concat(pattern: PatternDateTime): PatternDateTime = PatternDateTime(parts ++ pattern.parts) 56 | 57 | def ++(pattern: PatternDateTime): PatternDateTime = concat(pattern) 58 | } 59 | 60 | object PatternDateTime { 61 | val Iso8601 = PatternDateTime() 62 | .year().dash.month.dash.day.space 63 | .hour().colon.minute.colon.second.dot.secondsFract(3) 64 | .string("Z") 65 | 66 | // Tuesday, 1 March 2016 67 | val DefaultDate = PatternDateTime().weekDay().comma.space.day.space.monthName().space.year() 68 | 69 | // 6:57 PM 70 | val DefaultTime = PatternDateTime().hour(amPm = true).colon.minute.space.amPm 71 | 72 | val DefaultDateTime = DefaultDate ++ DefaultTime 73 | 74 | val JustNow = PatternDateTime().string("just now") 75 | 76 | def ago(pattern: PatternDateTime): PatternDateTime = pattern.space.string("ago") 77 | 78 | val MinutesAgo: PatternDateTime = ago(PatternDateTime().minuteAgo.space.string("minute(s)")) 79 | val HoursAgo: PatternDateTime = ago(PatternDateTime().hourAgo.space.string("hour(s)")) 80 | val DaysAgo: PatternDateTime = ago(PatternDateTime().minute.space.string("day(s)")) 81 | 82 | def branch(ranges: Seq[(Range, PatternDateTime)], fallback: PatternDateTime): PatternDateTime = { 83 | PatternDateTime().add { dt => 84 | val unixVal = dt.fromNow.component.unix().value 85 | val x =ranges.find { case (r, p) => 86 | (r.lowerBound to r.upperBound).contains(unixVal) 87 | } 88 | x.fold(fallback.format(dt)) { case (r, p) => p.format(dt) } 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /shared/src/main/scala/pl/metastack/metatime/DateTime.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | trait DateTime extends Time with Date { 4 | def format(pattern: PatternDateTime): String = pattern.format(this) 5 | 6 | def timestamp: Timestamp = ??? 7 | 8 | override def equals(that: Any): Boolean = 9 | that match { 10 | case other: DateTime => other.year == year && other.month == month && other.day == day && 11 | h == other.h && m == other.m && s == other.s && ms == other.ms 12 | case _ => false 13 | } 14 | 15 | override def toString: String = { 16 | year + "-" + month + "-" + day.toInt + " " + 17 | h + ":" + m + ":" + s + ":" + ms 18 | } 19 | 20 | override def unix(): Unix = Unix( 21 | Year(year).unix().value + 22 | Month(month).unix().value + 23 | Day(day).unix().value + 24 | Hour(h).unix().value + 25 | Minute(m).unix().value + 26 | Second(s).unix().value + 27 | Millisecond(ms).unix().value) 28 | } 29 | 30 | object DateTime { 31 | 32 | def calculateDateTime(milliDate: Long, milliTime: Long): DateTime = { 33 | val date = Date(milliDate) 34 | val time = Time(milliTime) 35 | DateTime(date.year, date.month, date.day, time.h, time.m, time.s, time.ms) 36 | } 37 | 38 | def now(): DateTime = calculateDateTime(Date.now().unix().value, Time.now.unix().value) 39 | def apply(millisecond: Long): DateTime = calculateDateTime(millisecond, millisecond) 40 | 41 | 42 | def apply(yr: Int, mo: Int, dy: Int, 43 | hour: Int, minute: Int, second: Int, millisecond: Int): DateTime = 44 | new DateTime { 45 | override val year: Int = yr 46 | override val month: Int = mo 47 | override val day: Double = dy 48 | override val h: Int = hour 49 | override val m: Int = minute 50 | override val s: Int = second 51 | override val ms: Int = millisecond 52 | } 53 | 54 | def apply(yr: Int = 0, mo: Int = 1, dy: Double = 0.0, 55 | hr: Int = 0, min: Int = 0, sec: Int = 0, 56 | milli: Int = 0): DateTime = new DateTime { 57 | override val year: Int = yr 58 | override val month: Int = mo 59 | override val day: Double = dy 60 | override val h: Int = hr 61 | override val m: Int = min 62 | override val s: Int = sec 63 | override val ms: Int = milli 64 | } 65 | 66 | def apply(): DateTime = new DateTime { 67 | override val year: Int = 0 68 | override val month: Int = 1 69 | override val day: Double = 0.0 70 | override val h: Int = 0 71 | override val m: Int = 0 72 | override val s: Int = 0 73 | override val ms: Int = 0 74 | } 75 | 76 | def apply(comp : Component) : DateTime = { 77 | comp match { 78 | case dT: DateTime => 79 | DateTime(dT.year, dT.month, dT.day, dT.h, dT.m, dT.s, dT.ms) 80 | 81 | case tempYear: Year => 82 | DateTime(tempYear.year, 1, 0, 0, 0, 0, 0) 83 | 84 | case tempMonth: Month => 85 | DateTime(0, tempMonth.month, 0, 0, 0, 0, 0) 86 | 87 | case tempDay: Day => 88 | DateTime(0, 1, tempDay.day, 0, 0, 0, 0) 89 | 90 | case tempHour: Hour => 91 | DateTime(0, 1, 0, tempHour.h, 0, 0, 0) 92 | 93 | case tempMinute: Minute => 94 | DateTime(0, 1, 0, 0, tempMinute.m, 0, 0) 95 | 96 | case tempSecond: Second => 97 | DateTime(0, 1, 0, 0, 0, tempSecond.s, 0) 98 | 99 | case tempMilliSecond: Millisecond => 100 | DateTime(0, 1, 0, 0, 0, 0, tempMilliSecond.ms) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /shared/src/main/scala/pl/metastack/metatime/Time.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | trait Hour extends Component { 4 | val h: Int 5 | 6 | override def unix(): Unix = Unix(h * Minute(60).unix().value) 7 | 8 | override def equals(that: Any): Boolean = 9 | that match { 10 | case other: Hour => other.h == h 11 | case _ => false 12 | } 13 | } 14 | 15 | object Hour { 16 | def apply(value: Int): Hour = new Hour { 17 | override val h = value 18 | } 19 | } 20 | 21 | trait Minute extends Component { 22 | val m: Int 23 | 24 | override def unix(): Unix = Unix(m * Second(60).unix().value) 25 | 26 | override def equals(that: Any): Boolean = 27 | that match { 28 | case other: Minute => other.m == m 29 | case _ => false 30 | } 31 | } 32 | 33 | object Minute { 34 | def apply(value: Int): Minute = new Minute { 35 | override val m = value 36 | } 37 | } 38 | 39 | trait Second extends Component { 40 | val s: Int 41 | 42 | override def unix(): Unix = Unix(s * Millisecond(1000).unix().value) 43 | 44 | override def equals(that: Any): Boolean = 45 | that match { 46 | case other: Second => other.s == s 47 | case _ => false 48 | } 49 | } 50 | 51 | object Second { 52 | def apply(value: Int): Second = new Second { 53 | override val s = value 54 | } 55 | } 56 | 57 | trait Millisecond extends Component { 58 | val ms: Int 59 | 60 | override def unix(): Unix = Unix(ms) 61 | 62 | override def equals(that: Any): Boolean = 63 | that match { 64 | case other: Millisecond => other.ms == ms 65 | case _ => false 66 | } 67 | } 68 | 69 | object Millisecond { 70 | def apply(value: Int): Millisecond = 71 | new Millisecond { 72 | override val ms = value 73 | } 74 | } 75 | 76 | trait Time extends Hour with Minute with Second with Millisecond { 77 | 78 | def format(pattern: PatternTime): String = pattern.format(this) 79 | 80 | override def equals(that: Any): Boolean = 81 | that match { 82 | case other: Time => other.h == h && other.m == m && other.s == s && other.ms == ms 83 | case _ => false 84 | } 85 | 86 | override def toString: String = h + ":" + m + ":" + s + ":" + ms 87 | 88 | override def unix(): Unix = Unix( 89 | Hour(h).unix().value + 90 | Minute(m).unix().value + 91 | Second(s).unix().value + 92 | Millisecond(ms).unix().value) 93 | } 94 | 95 | object Time { 96 | def apply(hour: Int = 0, 97 | minute: Int = 0, 98 | second: Int = 0, 99 | millisecond: Int = 0): Time = new Time { 100 | override val h: Int = hour 101 | override val m: Int = minute 102 | override val s: Int = second 103 | override val ms: Int = millisecond 104 | } 105 | 106 | def apply(comp : Component) : Time = { 107 | comp match { 108 | case t: Time => 109 | Time(hour = t.h, 110 | minute = t.m, 111 | second = t.s, 112 | millisecond = t.ms) 113 | case hr: Hour => Time(hour = hr.h) 114 | case m: Minute => Time(minute = m.m) 115 | case s: Second => Time(second = s.s) 116 | case ms: Millisecond => Time(millisecond = ms.ms) 117 | } 118 | } 119 | 120 | def apply(milliseconds: Long): Time = { 121 | val hour = ((milliseconds / 122 | (Hour(1).unix().value)).toInt) % 24 123 | val minute = ((milliseconds % 124 | (Hour(1).unix().value)) / 125 | (Minute(1).unix().value)).toInt 126 | val second = (((milliseconds % 127 | (Hour(1).unix().value)) % 128 | (Minute(1).unix().value)) / 129 | Second(1).unix().value).toInt 130 | val millisecond = ((((milliseconds % 131 | (Hour(1).unix().value)) % 132 | (Minute(1).unix().value)) % 133 | Second(1).unix().value) / 134 | Millisecond(1).unix().value).toInt 135 | Time(hour, minute, second, millisecond) 136 | } 137 | 138 | /** Current time */ 139 | def now(): Time = { 140 | val currTimeInMilliSec: Long = System.currentTimeMillis() 141 | new Time { 142 | override val h: Int = (((currTimeInMilliSec / 1000) / 3600) % 24).toInt 143 | override val m: Int = (((currTimeInMilliSec / 1000) / 60) % 60).toInt 144 | override val s: Int = ((currTimeInMilliSec / 1000) % 60).toInt 145 | override val ms: Int = (currTimeInMilliSec % 1000).toInt 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /manual/src/main/scala/pl/metastack/metatime/manual/Manual.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime.manual 2 | 3 | import java.io.File 4 | import java.nio.file.{Files, Paths} 5 | 6 | import org.eclipse.jgit.api.Git 7 | import org.joda.time.DateTime 8 | 9 | import pl.metastack.metadocs.document._ 10 | import pl.metastack.metadocs.input._ 11 | import pl.metastack.metadocs.input.metadocs._ 12 | import pl.metastack.metadocs.output.html.Components 13 | import pl.metastack.metadocs.output.html.document.{Book, SinglePage} 14 | 15 | import pl.metastack.metatime.BuildInfo 16 | 17 | object Manual extends App with Shared { 18 | if (!projectPath.exists()) 19 | Git.cloneRepository() 20 | .setURI(s"git@github.com:$organisation/$repoName.git") 21 | .setDirectory(projectPath) 22 | .call() 23 | 24 | manualPath.mkdir() 25 | manualVersionPath.mkdirs() 26 | manualVersionPath.listFiles().foreach(_.delete()) 27 | 28 | imagesPath.mkdirs() 29 | imagesPath.listFiles().foreach(_.delete()) 30 | 31 | val meta = Meta( 32 | date = DateTime.now(), 33 | title = "MetaTime User Manual v" + BuildInfo.version, 34 | author = "Tim Nieradzik", 35 | affiliation = "MetaStack Sp. z o.o.", 36 | `abstract` = "Time formatting library for Scala and Scala.js", 37 | language = "en-GB", 38 | url = "", 39 | editSourceURL = Some("https://github.com/MetaStack-pl/MetaTime/edit/master/")) 40 | 41 | val instructionSet = DefaultInstructionSet 42 | .inherit(BookInstructionSet) 43 | .inherit(CodeInstructionSet) 44 | .inherit(DraftInstructionSet) 45 | .withAliases( 46 | "b" -> Bold, 47 | "i" -> Italic, 48 | "li" -> ListItem) 49 | 50 | val rawTrees = Seq( 51 | "introduction", "type-safe-dsl", "scheduler", 52 | "datetime-formatting", "scalajs-support", "development", "support" 53 | ).map(chapter => s"manual/$chapter.md") 54 | .map(file => 55 | Markdown.loadFileWithExtensions(file, 56 | instructionSet, 57 | constants = Map("version" -> BuildInfo.version), 58 | generateId = caption => Some(caption.collect { 59 | case c if c.isLetterOrDigit => c 60 | case c if c.isSpaceChar => '-' 61 | }.toLowerCase) 62 | ).get) 63 | 64 | val docTree = Document.mergeTrees(rawTrees) 65 | 66 | // Explicitly print out all chapters/sections which is useful when 67 | // restructuring the document 68 | println("Document tree:") 69 | println(Extractors.references(docTree)) 70 | println() 71 | 72 | Document.printTodos(docTree) 73 | 74 | val pipeline = 75 | Document.pipeline 76 | .andThen(CodeProcessor.embedListings("manual") _) 77 | .andThen(CodeProcessor.embedOutput _) 78 | val docTreeWithCode = pipeline(docTree) 79 | 80 | val skeleton = Components.pageSkeleton( 81 | cssPaths = Seq( 82 | "css/kult.css", 83 | "css/default.min.css" 84 | ), 85 | jsPaths = Seq( 86 | "//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js", 87 | "js/main.js", 88 | "js/highlight.pack.js" 89 | ), 90 | script = Some("hljs.initHighlightingOnLoad();"), 91 | favicon = Some("favicon.ico") 92 | )(_, _, _) 93 | 94 | // TODO Copy scaladocs as well 95 | 96 | SinglePage.write(docTreeWithCode, 97 | skeleton, 98 | s"$manualPathStr/v${BuildInfo.version}.html", 99 | meta = Some(meta), 100 | toc = true, 101 | tocDepth = 2) // Don't include subsections 102 | 103 | Book.write(docTreeWithCode, 104 | skeleton, 105 | s"$manualPathStr/v${BuildInfo.version}", 106 | meta = Some(meta), 107 | tocDepth = 2) 108 | 109 | val links = 110 | Set("css", "js", "favicon.ico").flatMap { folder => 111 | Seq( 112 | s"$manualPathStr/$folder" -> s"../$folder", 113 | s"$manualVersionPathStr/$folder" -> s"../../$folder") 114 | }.toMap ++ 115 | Map(s"$manualPathStr/images" -> s"v${BuildInfo.version}/images") ++ ( 116 | if (isSnapshot) Map.empty 117 | else Map( 118 | s"$manualPathStr/latest" -> s"v${BuildInfo.version}", 119 | s"$manualPathStr/latest.html" -> s"v${BuildInfo.version}.html")) 120 | 121 | links.map { case (from, to) => 122 | Paths.get(from) -> Paths.get(to) 123 | }.foreach { case (from, to) => 124 | if (Files.exists(from)) Files.delete(from) 125 | Files.createSymbolicLink(from, to) 126 | } 127 | 128 | /* Replace images */ 129 | new File("manual", "images") 130 | .listFiles() 131 | .filter(_.getName.endsWith(".png")) 132 | .foreach { image => 133 | Files.copy(image.toPath, new File(imagesPath, image.getName).toPath) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /shared/src/main/scala/pl/metastack/metatime/Date.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | trait Year extends Component { 4 | val year: Int 5 | 6 | override def unix(): Unix = Unix(daysOf(this).toLong * 24 * 3600 * 1000) 7 | 8 | override def equals(that: Any): Boolean = 9 | that match { 10 | case other: Year => other.year == year 11 | case _ => false 12 | } 13 | } 14 | 15 | object Year { 16 | def apply(value: Int): Year = new Year { 17 | override val year = value 18 | } 19 | } 20 | 21 | trait Month extends Component { 22 | val month: Int 23 | 24 | override def unix(): Unix = Unix(daysOf(this).toLong * 24 * 3600 * 1000) 25 | 26 | override def equals(that: Any): Boolean = 27 | that match { 28 | case other: Month => other.month == month 29 | case _ => false 30 | } 31 | } 32 | 33 | object Month { 34 | def apply(value: Int): Month = new Month { 35 | override val month = value 36 | } 37 | } 38 | 39 | trait Day extends Component { 40 | val day: Double 41 | 42 | override def unix(): Unix = Unix((day * 24 * 3600 * 1000).toLong) 43 | 44 | override def equals(that: Any): Boolean = 45 | that match { 46 | case other: Day => other.day == day 47 | case _ => false 48 | } 49 | 50 | def truncate: Day = Day(Math.round(day)) 51 | } 52 | 53 | object Day { 54 | def apply(value: Double): Day = new Day { 55 | override val day = value 56 | } 57 | } 58 | 59 | trait Date extends Year with Month with Day { 60 | 61 | override def equals(that: Any): Boolean = 62 | that match { 63 | case other: Date => other.year == year && other.month == month && other.day == day 64 | case _ => false 65 | } 66 | 67 | override def toString: String = year + "-" + month + "-" + day.toInt 68 | 69 | 70 | override def unix(): Unix = Unix( 71 | Year(year).unix().value + 72 | Month(month).unix().value + 73 | Day(day).unix().value) 74 | } 75 | 76 | object Date { 77 | def apply(y: Int): Date = new Date { 78 | override val year: Int = y 79 | override val month: Int = 1 80 | override val day: Double = 1.0 81 | } 82 | def apply(year: Int, month: Int): Date = ??? 83 | def parse(format: String, timezone: Timezone): Date = ??? 84 | 85 | val YearsSequence = Seq(365, 365, 366, 365) 86 | val DaysInMonths = Seq(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) 87 | val LeapDaysInMonths = Seq(31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) 88 | 89 | def isLeapYear(year: Int): Boolean = (year % 4) == 0 90 | def accumulate(f: Int => Seq[Int], y: Int): Seq[Int] = f(y) 91 | def accuPrevElements(seq: Seq[Int]) = seq.scanLeft(0)(_ + _).tail 92 | 93 | def accuYears(totalDays: Int) : Seq[Int] = { 94 | val yearLength = Stream.continually(YearsSequence).flatten.take((totalDays / 365) + 1).toList 95 | accuPrevElements(yearLength) 96 | } 97 | 98 | def yearsPassed(totalDays: Int) : Int = { 99 | accumulate(accuYears, totalDays).zipWithIndex.indexWhere( { 100 | case (accDays, year) => totalDays <= accDays 101 | }) + 1970 102 | } 103 | 104 | def daysPassed(totalDays: Int) : Int = { 105 | val accumulatedDaysList = accumulate(accuYears, totalDays).filter(x => x <= totalDays) 106 | accumulatedDaysList.length match { 107 | case 0 => totalDays 108 | case n => totalDays - accumulatedDaysList(n - 1) 109 | } 110 | } 111 | 112 | def findMonth(days: Int, year: Int): Int = { 113 | accumulate(accuDays, year).filter(x => x <= days).length + 1 114 | } 115 | 116 | def accuDays(year: Int): Seq[Int] = { 117 | if(isLeapYear(year)) 118 | accuPrevElements(LeapDaysInMonths) 119 | else 120 | accuPrevElements(DaysInMonths) 121 | } 122 | 123 | def findDay(days: Int, year: Int): Int = { 124 | val accumulatedDays = accumulate(accuDays, year).filter(x => x <= days) 125 | accumulatedDays.length match { 126 | case 0 => days 127 | case n => days - accumulatedDays(n - 1) 128 | } 129 | } 130 | 131 | def milliToDays(milliSeconds: Long, flagNow: Boolean): Int = { 132 | if((milliSeconds % (60 * 60 * 24 * 1000) > 0) && flagNow){ 133 | (milliSeconds / (60 * 60 * 24 * 1000)).toInt + 1 134 | } 135 | else { 136 | (milliSeconds / (60 * 60 * 24 * 1000)).toInt 137 | } 138 | } 139 | 140 | def calculateDate(totalDays: Int): Date = 141 | { 142 | if(totalDays == 0) { 143 | Date(1970, 0, 0) 144 | } 145 | else { 146 | val year = yearsPassed(math.abs(totalDays)) 147 | val daysOfThisYear = daysPassed(math.abs(totalDays)) 148 | val month = findMonth(daysOfThisYear, year) 149 | val day = findDay(daysOfThisYear, year) 150 | if (totalDays <= 0) Date(-year, -month, -day) 151 | else Date(year, month, day) 152 | } 153 | } 154 | 155 | def now(): Date = { 156 | val currTimeInMilliSec: Long = System.currentTimeMillis() 157 | calculateDate(milliToDays(currTimeInMilliSec, true)) 158 | } 159 | 160 | def apply(milliseconds: Long): Date = { 161 | calculateDate(milliToDays(milliseconds, false)) 162 | } 163 | 164 | def apply(comp : Component) : Date = { 165 | comp match { 166 | case date: Date => 167 | Date(y = date.year, 168 | m = date.month, 169 | d = date.day) 170 | case d: Day => Date(d = d.day) 171 | case month: Month => Date(m = month.month) 172 | case y: Year => Date(y = y.year) 173 | } 174 | } 175 | 176 | def apply(y: Int = 0, 177 | m: Int = 1, 178 | d: Double = 0.0): Date = new Date { 179 | override val year: Int = y 180 | override val month: Int = m 181 | override val day: Double = d 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /shared/src/main/scala/pl/metastack/metatime/Component.scala: -------------------------------------------------------------------------------- 1 | package pl.metastack.metatime 2 | 3 | import pl.metastack.metatime.Constants._ 4 | 5 | // TODO Rename Component to a better name 6 | trait Component extends Ordered[Component] { 7 | def until(component: Component): Component = ??? 8 | 9 | def days: Day = Day(unix().value / 0.0) 10 | 11 | def format(implicit locale: Locale): String = ??? 12 | 13 | def date: Date = Date(this) 14 | 15 | def dateTime: DateTime = DateTime(this) 16 | 17 | def isLeapYear(year: Int) = year % 4 == 0 18 | 19 | def to(until: Component): Range = { 20 | Range(unix().value.toInt, until.unix().value.toInt) 21 | } 22 | 23 | def unix(): Unix 24 | 25 | override def compare(that: Component): Int = { 26 | unix().value.compare(that.unix().value) 27 | } 28 | 29 | def weekDay(): Int = { 30 | val thisComp = getConstructionType(this) 31 | thisComp match { 32 | case _: DateTime => 33 | val dateTime = DateTime(this) 34 | calcWeekDay(Date(dateTime.year, dateTime.month, dateTime.day)) 35 | case _: Date => 36 | calcWeekDay(Date(this)) 37 | } 38 | } 39 | 40 | def calcWeekDay(date: Date): Int = date.daysOf(date) % 7 41 | 42 | def milliseconds(): Long = { 43 | val thisComp = getConstructionType(this) 44 | thisComp match { 45 | case _: DateTime => 46 | val dateTime = DateTime(this) 47 | Date(dateTime.year, dateTime.month, dateTime.day).milliseconds() + 48 | Time(dateTime.h, dateTime.m, dateTime.s, dateTime.ms).milliseconds() 49 | case _: Time => 50 | val time = Time(this) 51 | time.h * MillisInHour + time.m * MillisInMinute + time.s * MillisInSecond + time.ms 52 | case _: Date => 53 | val date = Date(this) 54 | date.year * DaysInYear * MillisInDay + date.month * DaysInMonth * MillisInDay + (date.day * MillisInDay).toLong 55 | } 56 | } 57 | 58 | def yearsFromDefault(years: Int) = years - DefaultYear 59 | 60 | def fromNow: Offset[Component] = Offset(calcOffset()) 61 | 62 | def calcTimeDifference: Time = { 63 | val timeNow = Time.now() 64 | val otherTime = Time(unix().value) 65 | if ((otherTime.h == 0) && (otherTime.m == 0) && (otherTime.s == 0)) 66 | Time(timeNow.h, timeNow.m, timeNow.s, otherTime.ms) 67 | else if (otherTime.h == 0 && otherTime.m == 0) 68 | Time(timeNow.h, timeNow.m, otherTime.s, otherTime.ms) 69 | else if (otherTime.h == 0) 70 | Time(timeNow.h, otherTime.m, otherTime.s, otherTime.ms) 71 | else 72 | Time(Time.now().unix().value - unix().value) 73 | } 74 | 75 | def calcDateDifference(): Date = { 76 | val otherDate = Date(unix().value) 77 | val nowDate = Date.now() 78 | 79 | if (yearsFromDefault(otherDate.year) == 0 && otherDate.month == 0) 80 | Date(nowDate.year, nowDate.month, otherDate.day) 81 | else if (yearsFromDefault(otherDate.year) == 0) 82 | Date(nowDate.year, otherDate.month, otherDate.day) 83 | else 84 | Date(nowDate.unix().value - otherDate.unix().value) 85 | } 86 | 87 | def calcDateTimeDifference(): DateTime = { 88 | val smDate = DateTime(unix().value) 89 | val dt = DateTime.now() 90 | 91 | if((yearsFromDefault(smDate.year) == 0) && (smDate.month == 0) && (smDate.day == 0) 92 | && (smDate.h == 0) && (smDate.m == 0) && (smDate.s == 0)) { 93 | DateTime(dt.year, dt.month, dt.day, dt.h, dt.m, dt.s, smDate.ms) 94 | } 95 | else if((yearsFromDefault(smDate.year) == 0) && (smDate.month == 0) 96 | && (smDate.day == 0) && (smDate.h == 0) && (smDate.m == 0)) { 97 | DateTime(dt.year, dt.month, dt.day, dt.h, dt.m, smDate.s, smDate.ms) 98 | } 99 | else if((yearsFromDefault(smDate.year) == 0) && (smDate.month == 0) 100 | && (smDate.day == 0) && (smDate.h == 0)) { 101 | DateTime(dt.year, dt.month, dt.day, dt.h, smDate.m, smDate.s, smDate.ms) 102 | } 103 | else if((yearsFromDefault(smDate.year) == 0) && (smDate.month == 0) 104 | && (smDate.day == 0)) { 105 | DateTime(dt.year, dt.month, dt.day, smDate.h, smDate.m, smDate.s, smDate.ms) 106 | } 107 | else if((yearsFromDefault(smDate.year) == 0) && (smDate.month == 0)) { 108 | DateTime(dt.year, dt.month, smDate.day, smDate.h, smDate.m, smDate.s, smDate.ms) 109 | } 110 | else if(yearsFromDefault(smDate.year) == 0) { 111 | DateTime(dt.year, smDate.month, smDate.day, smDate.h, smDate.m, smDate.s, smDate.ms) 112 | } 113 | else if(yearsFromDefault(smDate.year) == 0) { 114 | DateTime(dt.year, smDate.month, smDate.day, smDate.h, smDate.m, smDate.s, smDate.ms) 115 | } 116 | else { 117 | DateTime(dt.unix().value - smDate.unix().value) 118 | } 119 | } 120 | 121 | def firstValidUnit(dt: DateTime, unit: Int): Boolean = { 122 | val func = Seq[DateTime => Boolean]( 123 | _.year - DefaultYear == 0, 124 | _.month <= 1, 125 | _.day == 0, 126 | _.h == 0, 127 | _.m == 0, 128 | _.s == 0 129 | ) 130 | func.slice(0, unit).forall(_(dt)) 131 | } 132 | 133 | def mostSignificantTime(timeDiff: Time): Component = { 134 | val timeNow = Time.now() 135 | if(timeDiff.h == timeNow.h && timeDiff.m == timeNow.m && timeDiff.s == timeNow.s) 136 | Millisecond(timeDiff.ms) 137 | else if(timeDiff.h == timeNow.h && timeDiff.m == timeNow.m) 138 | Second(timeDiff.s) 139 | else if(timeDiff.h == timeNow.h || timeDiff.h == 0) 140 | Minute(timeDiff.m) 141 | else { 142 | Hour(timeNow.h - timeDiff.h) 143 | } 144 | } 145 | 146 | def absoluteDateTime(dt: DateTime): DateTime = { 147 | if(dt.unix().value <= 0) 148 | DateTime(math.abs(dt.year), math.abs(dt.month), 149 | math.abs(dt.day), math.abs(dt.h), math.abs(dt.m), 150 | math.abs(dt.s), math.abs(dt.ms)) 151 | else 152 | dt 153 | } 154 | 155 | def mostSignificantDateTime(dt: DateTime): Component = { 156 | val dtDiff = absoluteDateTime(dt) 157 | if(firstValidUnit(dtDiff, SecondIndex)) 158 | Millisecond(dt.ms) 159 | else if(firstValidUnit(dtDiff, MinuteIndex)) 160 | Second(dt.s) 161 | else if(firstValidUnit(dtDiff, HourIndex)) 162 | Minute(dt.m) 163 | else if(firstValidUnit(dtDiff, DayIndex)) 164 | Hour(dt.h) 165 | else if(firstValidUnit(dtDiff, MonthIndex)) 166 | Day(dt.day) 167 | else if(firstValidUnit(dtDiff, YearIndex)) { 168 | if(dt.month < 0) 169 | Month(dt.month + 1) 170 | else 171 | Month(dt.month - 1) 172 | } 173 | else if(dt.year < 0) 174 | Year(dt.year + DefaultYear) 175 | else 176 | Year(dt.year - DefaultYear) 177 | } 178 | 179 | def mostSignificantDate(dateDiff: Date): Component = { 180 | if((yearsFromDefault(dateDiff.year) == 0) && (dateDiff.month == 1)) 181 | Day(dateDiff.day) 182 | else if(yearsFromDefault(dateDiff.year) == 0) { 183 | if(dateDiff.month < 0) Month(dateDiff.month + 1) 184 | else Month(dateDiff.month - 1) 185 | } 186 | else { 187 | if(dateDiff.year < 0) 188 | Year(dateDiff.year + DefaultYear) 189 | else 190 | Year(dateDiff.year - DefaultYear) 191 | } 192 | } 193 | 194 | def calcOffset(): Component = { 195 | if(math.abs(unix().value) < MillisInDay) { 196 | mostSignificantTime(calcTimeDifference) 197 | } 198 | else getConstructionType(this) match { 199 | case _: DateTime => 200 | mostSignificantDateTime(calcDateTimeDifference()) 201 | case _: Date => 202 | mostSignificantDate(calcDateDifference()) 203 | } 204 | } 205 | 206 | def getConstructionType(other: Component) : Component = { 207 | other match { 208 | case _: DateTime => DateTime() 209 | case _: Time | _: Hour | _: Minute | _: Second | _: Millisecond => Time() 210 | case _: Date | _: Year | _: Month | _: Day => Date() 211 | } 212 | } 213 | 214 | def operationResultType(other: Component) : Component = { 215 | val thisComp = getConstructionType(this) 216 | val thatComp = getConstructionType(other) 217 | thisComp match { 218 | case _: DateTime => DateTime() 219 | case _: Time => thatComp match { 220 | case _: DateTime => DateTime() 221 | case _: Time => Time() 222 | case _: Date => DateTime() 223 | } 224 | case _: Date => thatComp match { 225 | case _: DateTime => DateTime() 226 | case _: Date => Date() 227 | case _: Time => DateTime() 228 | } 229 | } 230 | } 231 | 232 | def +(other: Component): Component = { 233 | operationResultType(other) match { 234 | case _: DateTime => 235 | DateTime(millisecond = unix().value + other.unix().value) 236 | case _: Time => 237 | Time(milliseconds = unix().value + other.unix().value) 238 | case _: Date => 239 | Date(milliseconds = unix().value + other.unix().value) 240 | } 241 | } 242 | 243 | def -(other: Component): Component = { 244 | operationResultType(other) match { 245 | case _: DateTime => 246 | DateTime(millisecond = unix().value - other.unix().value) 247 | case _: Time => 248 | Time(milliseconds = unix().value - other.unix().value) 249 | case _: Date => 250 | Date(milliseconds = unix().value - other.unix().value) 251 | } 252 | } 253 | 254 | val DaysInMonths = Seq(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) 255 | val YearsSequence = Seq(365, 365, 366, 365) 256 | 257 | def daysOfMonth(month: Int) : Int = { 258 | val accumulatedDays = DaysInMonths.zipWithIndex.foldLeft(Seq.empty[Int]) { 259 | case (Nil, (cur, _)) => Seq(cur) 260 | case (acc, (cur, i)) => acc ++ Seq(acc.last + cur) 261 | } 262 | if(month <= 1) 0 263 | else if ((month >= 2) && this.isLeapYear(Date.now().year)) 264 | accumulatedDays(month - 2) + 1 265 | else 266 | accumulatedDays(month - 2) 267 | } 268 | 269 | def daysOfYear(y: Int) : Int = { 270 | if(y < DefaultYear) 271 | Stream.continually(YearsSequence).flatten.take(y).toList.sum 272 | else 273 | Stream.continually(YearsSequence).flatten.take(yearsFromDefault(y)).toList.sum 274 | } 275 | 276 | def daysOf(component: Component) : Int = { 277 | component match { 278 | case d: Date => 279 | d.day.toInt + daysOfMonth(d.month) + daysOfYear(d.year) 280 | case m: Month => daysOfMonth(m.month) 281 | case y: Year => daysOfYear(y.year) 282 | } 283 | } 284 | } --------------------------------------------------------------------------------