├── .editorconfig ├── .gitignore ├── .pre-commit-config.yaml ├── .travis.yml ├── README.md ├── build.gradle.kts ├── duration ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── eu │ │ └── markov │ │ └── kotlin │ │ └── lucidtime │ │ └── duration │ │ ├── Days.kt │ │ ├── Duration.kt │ │ ├── Hours.kt │ │ ├── Microseconds.kt │ │ ├── Milliseconds.kt │ │ ├── Minutes.kt │ │ ├── Nanoseconds.kt │ │ ├── Seconds.kt │ │ └── Weeks.kt │ └── test │ └── kotlin │ └── eu │ └── markov │ └── kotlin │ └── lucidtime │ └── duration │ ├── DaysTest.kt │ ├── DurationTest.kt │ ├── HoursTest.kt │ ├── MicrosecondsTest.kt │ ├── MillisecondsTest.kt │ ├── MinutesTest.kt │ ├── NanosecondsTest.kt │ ├── SecondsTest.kt │ └── WeeksTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file has very limited formatting options, however because of its simplicity it is supported 2 | # by almost any IDE, text editor or auto-formatter you know of: https://editorconfig.org/ 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.py,*.java,*.kt,*.kts] 10 | indent_style = spaces 11 | indent_size = 4 12 | continuation_indent_size = 8 13 | max_line_length = 100 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.iml 3 | /captures 4 | /local.properties 5 | 6 | out/ 7 | build/ 8 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | - repo: git@github.com:macisamuele/language-formatters-pre-commit-hooks 2 | rev: v1.1.3 3 | hooks: 4 | - id: pretty-format-java 5 | args: [--autofix] 6 | - id: pretty-format-kotlin 7 | args: [--autofix] 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | script: ./gradlew test 7 | 8 | after_success: 9 | 10 | notifications: 11 | email: false 12 | 13 | sudo: false 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Lucid Time for Kotlin 2 | ===================== 3 | 4 | [![build status](https://travis-ci.org/markov/lucid-time.svg?branch=master)](https://travis-ci.org/markov/lucid-time) 5 | 6 | The goal of this project is to make working with time durations in Kotlin simpler. 7 | 8 | Usage 9 | ----- 10 | 11 | Add it to your gradle dependencies. 12 | 13 | ```kotlin 14 | dependencies { 15 | implementation("eu.markov.kotlin.lucidtime:duration:2.0.0") 16 | } 17 | ``` 18 | 19 | The main purpose is to make declaring time literals in code very concise and very close to the natural English language. 20 | 21 | ```kotlin 22 | val soon = Random.nextDouble(52.0).weeks 23 | val threeMin = 3.minutes 24 | val anHourAndAHalf: Duration = 1.5.hours 25 | val aLittleLonger: Duration = anHourAndAHalf + threeMin 26 | val aLittleLongerMillis: Long = notQuiteAsLong.toMillis 27 | val aWeekAndAHalfInMinutes: Long = 1.5.weeks.toMinutes 28 | ``` 29 | 30 | At the same time this library aims to keep guarantees when you compare durations from across the different types it exposes. 31 | 32 | ```kotlin 33 | assertTrue(59.minutes < 1.hour) 34 | assertTrue(60.minutes == 1.hour) 35 | assertTrue(61.minutes > 1.hour) 36 | ``` 37 | 38 | All insntances are immutable and the implementation of`equals()` and `hashCode()` makes them safe to use as keys in maps. 39 | 40 | ```kotlin 41 | val map = mapOf(1.hour to "an hour") 42 | assertEquals("an hour", map[60.minutes]) 43 | ``` 44 | 45 | The main goal when writing this code was to replace APIs using Java's [`TimeUnit`][java_time_unit] like: 46 | ```kotlin 47 | fun delayBy(amount: Long, unit: TimeUnit) {...} 48 | 49 | delayBy(150, TimeUnit.SECONDS) 50 | ``` 51 | 52 | And allow for simpler APIs like: 53 | ```kotlin 54 | fun delayBy(amount: Duration) {...} 55 | 56 | delayBy(150.seconds) 57 | delayBy(2.5.minutes) 58 | delayBy(2.minutes + 30.seconds) 59 | ``` 60 | 61 | As well as declaring constants like: 62 | ```kotlin 63 | val OLD_30_SECONDS_IN_MILLIS: Long = TimeUnit.SECONDS.toMillis(30) 64 | val NEW_30_SECONDS_IN_MILLIS: Long = 30.seconds.toMillis 65 | ``` 66 | 67 | License 68 | ======= 69 | 70 | Copyright 2018 Gesh Markov 71 | 72 | Licensed under the Apache License, Version 2.0 (the "License"); 73 | you may not use this file except in compliance with the License. 74 | You may obtain a copy of the License at 75 | 76 | http://www.apache.org/licenses/LICENSE-2.0 77 | 78 | Unless required by applicable law or agreed to in writing, software 79 | distributed under the License is distributed on an "AS IS" BASIS, 80 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 81 | See the License for the specific language governing permissions and 82 | limitations under the License. 83 | 84 | 85 | [java_time_unit]: https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/TimeUnit.html 86 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "1.3.10" apply false 3 | } 4 | 5 | subprojects { 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | group = "eu.markov.kotlin.lucidtime" 11 | version = "2.0.0" 12 | } 13 | -------------------------------------------------------------------------------- /duration/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.net.URI 2 | 3 | plugins { 4 | kotlin("jvm") 5 | `maven-publish` 6 | id("signing") 7 | id("org.jetbrains.dokka") version "0.9.17" 8 | } 9 | 10 | dependencies { 11 | implementation(kotlin("stdlib-jdk7")) 12 | 13 | testImplementation(kotlin("test")) 14 | testImplementation("junit:junit:4.12") 15 | testImplementation("org.hamcrest:hamcrest-all:1.3") 16 | } 17 | 18 | val sourcesJar by tasks.registering(Jar::class) { 19 | classifier = "sources" 20 | from(sourceSets["main"].allSource) 21 | } 22 | 23 | val dokka by tasks.getting(org.jetbrains.dokka.gradle.DokkaTask::class) { 24 | outputDirectory = "$buildDir/javadoc" 25 | } 26 | 27 | val javadocJar by tasks.registering(Jar::class) { 28 | classifier = "javadoc" 29 | from(dokka) 30 | } 31 | 32 | publishing { 33 | publications { 34 | register("mavenJava", MavenPublication::class) { 35 | from(components["java"]) 36 | artifact(sourcesJar.get()) 37 | artifact(javadocJar.get()) 38 | pom { 39 | name.set("Lucid Time Duration for Kotlin.") 40 | description.set("A simple time duration declaration and type system for kotlin.") 41 | url.set("https://github.com/markov/lucid-time") 42 | scm { 43 | url.set("https://github.com/markov/lucid-time") 44 | connection.set("scm:git:git://github.com/markov/lucid-time.git") 45 | developerConnection.set("scm:git:ssh://git@github.com/markov/lucid-time.git") 46 | } 47 | licenses { 48 | license { 49 | name.set("The Apache License, Version 2.0") 50 | url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") 51 | distribution.set("repo") 52 | } 53 | } 54 | developers { 55 | developer { 56 | id.set("markov") 57 | name.set("Gesh Markov") 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | if (project.hasProperty("sonatypeUsername") && 65 | project.hasProperty("sonatypePassword") 66 | ) { 67 | val sonatypeUsername: String by project 68 | val sonatypePassword: String by project 69 | 70 | val isSnapshot = version.toString().endsWith("-SNAPSHOT") 71 | val sonatypeRepoUrl = when { 72 | isSnapshot -> "https://oss.sonatype.org/content/repositories/snapshots/" 73 | else -> "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 74 | } 75 | 76 | repositories { 77 | maven { 78 | url = URI(sonatypeRepoUrl) 79 | credentials { 80 | username = sonatypeUsername 81 | password = sonatypePassword 82 | } 83 | 84 | } 85 | } 86 | } 87 | } 88 | 89 | if (project.hasProperty("signing.keyId")) { 90 | signing { 91 | sign(publishing.publications["mavenJava"]) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /duration/src/main/kotlin/eu/markov/kotlin/lucidtime/duration/Days.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | inline val Double.days: Duration get() = (24.0 * this).hours 4 | inline val Float.days: Duration get() = this.toDouble().days 5 | inline val Long.days: Duration get() = (24L * this).hours 6 | inline val Number.days: Duration get() = this.toLong().days 7 | 8 | inline val Double.day: Duration get() = days 9 | inline val Float.day: Duration get() = days 10 | inline val Long.day: Duration get() = days 11 | inline val Number.day: Duration get() = days 12 | -------------------------------------------------------------------------------- /duration/src/main/kotlin/eu/markov/kotlin/lucidtime/duration/Duration.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | inline class Duration(val nanos: Long) : Comparable { 4 | operator fun plus(other: Duration) = Duration(nanos + other.nanos) 5 | operator fun minus(other: Duration) = Duration(nanos - other.nanos) 6 | 7 | override fun compareTo(other: Duration): Int { 8 | return nanos.compareTo(other.nanos) 9 | } 10 | 11 | override fun toString(): String { 12 | return "Duration(nanos=$nanos)" 13 | } 14 | } 15 | 16 | inline val Duration.toNanos: Long get() = nanos 17 | inline val Duration.toMicros: Long get() = toNanos / 1000 18 | inline val Duration.toMillis: Long get() = toMicros / 1000 19 | inline val Duration.toSeconds: Long get() = toMillis / 1000 20 | inline val Duration.toMinutes: Long get() = toSeconds / 60 21 | inline val Duration.toHours: Long get() = toMinutes / 60 22 | inline val Duration.toDays: Long get() = toHours / 24 23 | inline val Duration.toWeeks: Long get() = toDays / 7 24 | -------------------------------------------------------------------------------- /duration/src/main/kotlin/eu/markov/kotlin/lucidtime/duration/Hours.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | inline val Double.hours: Duration get() = (60.0 * this).minutes 4 | inline val Float.hours: Duration get() = this.toDouble().hours 5 | inline val Long.hours: Duration get() = (60L * this).minutes 6 | inline val Number.hours: Duration get() = this.toLong().hours 7 | 8 | inline val Double.hour: Duration get() = hours 9 | inline val Float.hour: Duration get() = hours 10 | inline val Long.hour: Duration get() = hours 11 | inline val Number.hour: Duration get() = hours 12 | -------------------------------------------------------------------------------- /duration/src/main/kotlin/eu/markov/kotlin/lucidtime/duration/Microseconds.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | inline val Double.micros: Duration get() = (1000.0 * this).nanos 4 | inline val Float.micros: Duration get() = this.toDouble().micros 5 | inline val Long.micros: Duration get() = (1000L * this).nanos 6 | inline val Number.micros: Duration get() = this.toLong().micros 7 | 8 | inline val Double.micro: Duration get() = micros 9 | inline val Float.micro: Duration get() = micros 10 | inline val Long.micro: Duration get() = micros 11 | inline val Number.micro: Duration get() = micros 12 | -------------------------------------------------------------------------------- /duration/src/main/kotlin/eu/markov/kotlin/lucidtime/duration/Milliseconds.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | inline val Double.millis: Duration get() = (1000.0 * this).micros 4 | inline val Float.millis: Duration get() = this.toDouble().millis 5 | inline val Long.millis: Duration get() = (1000L * this).micros 6 | inline val Number.millis: Duration get() = this.toLong().millis 7 | 8 | inline val Double.milli: Duration get() = millis 9 | inline val Float.milli: Duration get() = millis 10 | inline val Long.milli: Duration get() = millis 11 | inline val Number.milli: Duration get() = millis 12 | -------------------------------------------------------------------------------- /duration/src/main/kotlin/eu/markov/kotlin/lucidtime/duration/Minutes.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | inline val Double.minutes: Duration get() = (60.0 * this).seconds 4 | inline val Float.minutes: Duration get() = this.toDouble().minutes 5 | inline val Long.minutes: Duration get() = (60L * this).seconds 6 | inline val Number.minutes: Duration get() = this.toLong().minutes 7 | 8 | inline val Double.minute: Duration get() = minutes 9 | inline val Float.minute: Duration get() = minutes 10 | inline val Long.minute: Duration get() = minutes 11 | inline val Number.minute: Duration get() = minutes 12 | -------------------------------------------------------------------------------- /duration/src/main/kotlin/eu/markov/kotlin/lucidtime/duration/Nanoseconds.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | inline val Number.nanos: Duration get() = Duration(this.toLong()) 4 | inline val Number.nano: Duration get() = nanos 5 | -------------------------------------------------------------------------------- /duration/src/main/kotlin/eu/markov/kotlin/lucidtime/duration/Seconds.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | inline val Double.seconds: Duration get() = (1000.0 * this).millis 4 | inline val Float.seconds: Duration get() = this.toDouble().seconds 5 | inline val Long.seconds: Duration get() = (1000L * this).millis 6 | inline val Number.seconds: Duration get() = this.toLong().seconds 7 | 8 | inline val Double.second: Duration get() = seconds 9 | inline val Float.second: Duration get() = seconds 10 | inline val Long.second: Duration get() = seconds 11 | inline val Number.second: Duration get() = seconds 12 | -------------------------------------------------------------------------------- /duration/src/main/kotlin/eu/markov/kotlin/lucidtime/duration/Weeks.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | inline val Double.weeks: Duration get() = (7.0 * this).days 4 | inline val Float.weeks: Duration get() = this.toDouble().weeks 5 | inline val Long.weeks: Duration get() = (7L * this).days 6 | inline val Number.weeks: Duration get() = this.toLong().weeks 7 | 8 | inline val Double.week: Duration get() = weeks 9 | inline val Float.week: Duration get() = weeks 10 | inline val Long.week: Duration get() = weeks 11 | inline val Number.week: Duration get() = weeks 12 | -------------------------------------------------------------------------------- /duration/src/test/kotlin/eu/markov/kotlin/lucidtime/duration/DaysTest.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | import org.junit.Test 4 | import java.util.concurrent.TimeUnit 5 | import kotlin.test.assertEquals 6 | 7 | class DaysTest { 8 | 9 | @Test 10 | fun one_day_is_24_hours() { 11 | assertEquals(24.hours, 1.day) 12 | } 13 | 14 | @Test 15 | fun half_a_day_is_12_hours() { 16 | assertEquals(12.hours, 0.5f.days) 17 | } 18 | 19 | @Test 20 | fun addition_of_other_duration_calculates_correctly() { 21 | assertEquals(1.5.days, 1.day + 12.hours) 22 | assertEquals(1.5.days, 12.hours + 1.day) 23 | } 24 | 25 | @Test 26 | fun subtraction_of_other_duration_calculates_correctly() { 27 | assertEquals(0.5.days, 1.day - 12.hours) 28 | assertEquals(0.5.days, 24.hours - 0.5.day) 29 | } 30 | 31 | @Test 32 | fun is_equal_to_native_time_unit() { 33 | assertEquals(TimeUnit.DAYS.toNanos(42), 42.days.toNanos) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /duration/src/test/kotlin/eu/markov/kotlin/lucidtime/duration/DurationTest.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | import org.junit.Test 4 | import kotlin.test.assertEquals 5 | import kotlin.test.assertNotSame 6 | import kotlin.test.assertNull 7 | 8 | private const val half_an_hour_string = "Half an hour" 9 | private const val half_a_day_string = "Half a day" 10 | 11 | class DurationTest { 12 | 13 | @Test 14 | fun max_nanos_is_about_292_years() { 15 | assertEquals(292, Long.MAX_VALUE.nanos.toDays / 365) 16 | } 17 | 18 | @Test 19 | fun min_nanos_is_about_negative_292_years() { 20 | assertEquals(-292, Long.MIN_VALUE.nanos.toDays / 365) 21 | } 22 | 23 | @Test 24 | fun any_duration_subtype_can_be_used_as_a_map_key() { 25 | val map = 26 | mapOf( 27 | 0.5.hours to half_an_hour_string, 28 | 0.5.days to half_a_day_string 29 | ) 30 | assertEquals(half_an_hour_string, map[0.5.hours]) 31 | assertEquals(half_an_hour_string, map[30.minutes]) 32 | 33 | assertEquals(half_a_day_string, map[0.5.days]) 34 | assertEquals(half_a_day_string, map[12.hours]) 35 | 36 | assertNull(map[0.5.hours + 1.nano]) 37 | assertNull(map[0.5.days + 1.nano]) 38 | } 39 | 40 | @Test 41 | fun equality_across_subtypes() { 42 | assertEquals(30.minutes, 0.5.hours) 43 | } 44 | 45 | @Test 46 | fun hash_code_equality_across_subtypes() { 47 | assertEquals(30.minutes.hashCode(), 0.5.hours.hashCode()) 48 | } 49 | 50 | @Test 51 | fun sorting_order_with_different_subtypes() { 52 | val expected = listOf( 53 | 1.nano, 54 | 1.micro, 55 | 1.milli, 56 | 1.second, 57 | 1.minute, 58 | 1.hour, 59 | 1.day, 60 | 1.week 61 | ) 62 | val sorted = expected.reversed().sorted() 63 | 64 | assertNotSame(expected, sorted) 65 | assertEquals(expected, sorted) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /duration/src/test/kotlin/eu/markov/kotlin/lucidtime/duration/HoursTest.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | import org.junit.Test 4 | import java.util.concurrent.TimeUnit 5 | import kotlin.test.assertEquals 6 | 7 | class HoursTest { 8 | 9 | @Test 10 | fun one_hour_is_60_minutes() { 11 | assertEquals(60.minutes, 1.hour) 12 | } 13 | 14 | @Test 15 | fun half_a_hour_is_30_minutes() { 16 | assertEquals(30.minutes, 0.5f.hours) 17 | } 18 | 19 | @Test 20 | fun addition_of_other_duration_calculates_correctly() { 21 | assertEquals(1.5.hours, 1.hour + 30.minutes) 22 | assertEquals(1.5.hours, 30.minutes + 1.hour) 23 | } 24 | 25 | @Test 26 | fun subtraction_of_other_duration_calculates_correctly() { 27 | assertEquals(0.5.hours, 1.hour - 30.minutes) 28 | assertEquals(0.5.hours, 60.minutes - 0.5.hour) 29 | } 30 | 31 | @Test 32 | fun is_equal_to_native_time_unit() { 33 | assertEquals(TimeUnit.HOURS.toNanos(42), 42.hours.toNanos) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /duration/src/test/kotlin/eu/markov/kotlin/lucidtime/duration/MicrosecondsTest.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | import org.junit.Test 4 | import java.util.concurrent.TimeUnit 5 | import kotlin.test.assertEquals 6 | 7 | class MicrosecondsTest { 8 | 9 | @Test 10 | fun one_microsecond_is_1000_nanos() { 11 | assertEquals(1000.nanos, 1.micro) 12 | } 13 | 14 | @Test 15 | fun half_a_microsecond_is_500_nanos() { 16 | assertEquals(500.nanos, 0.5f.micros) 17 | } 18 | 19 | @Test 20 | fun addition_of_other_duration_calculates_correctly() { 21 | assertEquals(1.5.micros, 1.micro + 500.nanos) 22 | assertEquals(1.5.micros, 500.nanos + 1.micro) 23 | } 24 | 25 | @Test 26 | fun subtraction_of_other_duration_calculates_correctly() { 27 | assertEquals(0.5.micros, 1.micro - 500.nanos) 28 | assertEquals(0.5.micros, 1000.nanos - 0.5f.micro) 29 | } 30 | 31 | @Test 32 | fun is_equal_to_native_time_unit() { 33 | assertEquals(TimeUnit.MICROSECONDS.toNanos(42), 42.micros.toNanos) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /duration/src/test/kotlin/eu/markov/kotlin/lucidtime/duration/MillisecondsTest.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | import org.junit.Test 4 | import java.util.concurrent.TimeUnit 5 | import kotlin.test.assertEquals 6 | 7 | class MillisecondsTest { 8 | 9 | @Test 10 | fun one_millis_is_1000_micros() { 11 | assertEquals(1000.micros, 1.milli) 12 | } 13 | 14 | @Test 15 | fun half_a_millis_is_500_micros() { 16 | assertEquals(500.micros, 0.5f.millis) 17 | } 18 | 19 | @Test 20 | fun addition_of_other_duration_calculates_correctly() { 21 | assertEquals(1.5.millis, 1.milli + 500.micros) 22 | assertEquals(1.5.millis, 500.micros + 1.milli) 23 | } 24 | 25 | @Test 26 | fun subtraction_of_other_duration_calculates_correctly() { 27 | assertEquals(0.5.millis, 1.milli - 500.micros) 28 | assertEquals(0.5.millis, 1000.micros - 0.5f.millis) 29 | } 30 | 31 | @Test 32 | fun is_equal_to_native_time_unit() { 33 | assertEquals(TimeUnit.MILLISECONDS.toNanos(42), 42.millis.toNanos) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /duration/src/test/kotlin/eu/markov/kotlin/lucidtime/duration/MinutesTest.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | import org.junit.Test 4 | import java.util.concurrent.TimeUnit 5 | import kotlin.test.assertEquals 6 | 7 | class MinutesTest { 8 | 9 | @Test 10 | fun one_minute_is_60_seconds() { 11 | assertEquals(60.seconds, 1.minute) 12 | } 13 | 14 | @Test 15 | fun half_a_minute_is_30_seconds() { 16 | assertEquals(30.seconds, 0.5f.minutes) 17 | } 18 | 19 | @Test 20 | fun addition_of_other_duration_calculates_correctly() { 21 | assertEquals(1.5.minutes, 1.minute + 30.seconds) 22 | assertEquals(1.5.minutes, 30.seconds + 1.minute) 23 | } 24 | 25 | @Test 26 | fun subtraction_of_other_duration_calculates_correctly() { 27 | assertEquals(0.5.minutes, 1.minute - 30.seconds) 28 | assertEquals(0.5.minutes, 60.seconds - 0.5.minute) 29 | } 30 | 31 | @Test 32 | fun is_equal_to_native_time_unit() { 33 | assertEquals(TimeUnit.MINUTES.toNanos(42), 42.minutes.toNanos) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /duration/src/test/kotlin/eu/markov/kotlin/lucidtime/duration/NanosecondsTest.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | import org.junit.Test 4 | import java.util.concurrent.TimeUnit 5 | import kotlin.test.assertEquals 6 | 7 | class NanosecondsTest { 8 | 9 | @Test 10 | fun float_to_int_equal_results() { 11 | assertEquals(30.0f.nanos, 30.nanos) 12 | } 13 | 14 | @Test 15 | fun double_to_long_equal_results() { 16 | assertEquals(1.0.nano, 1L.nano) 17 | } 18 | 19 | @Test 20 | fun no_sub_nano_precision() { 21 | assertEquals(1, 1.nano.toNanos) 22 | assertEquals(0, 0.999.nanos.toNanos) 23 | } 24 | 25 | @Test 26 | fun is_equal_to_native_time_unit() { 27 | assertEquals(TimeUnit.NANOSECONDS.toNanos(42), 42.nano.toNanos) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /duration/src/test/kotlin/eu/markov/kotlin/lucidtime/duration/SecondsTest.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | import org.junit.Test 4 | import java.util.concurrent.TimeUnit 5 | import kotlin.test.assertEquals 6 | 7 | class SecondsTest { 8 | 9 | @Test 10 | fun one_second_is_1000_millis() { 11 | assertEquals(1000.millis, 1.second) 12 | } 13 | 14 | @Test 15 | fun half_a_second_is_500_millis() { 16 | assertEquals(500.millis, 0.5f.seconds) 17 | } 18 | 19 | @Test 20 | fun addition_of_other_duration_calculates_correctly() { 21 | assertEquals(1.5.seconds, 1.second + 500.millis) 22 | assertEquals(1.5.seconds, 500.millis + 1.second) 23 | } 24 | 25 | @Test 26 | fun subtraction_of_other_duration_calculates_correctly() { 27 | assertEquals(0.5.seconds, 1.second - 500.millis) 28 | assertEquals(0.5.seconds, 1000.millis - 0.5f.seconds) 29 | } 30 | 31 | @Test 32 | fun is_equal_to_native_time_unit() { 33 | assertEquals(TimeUnit.SECONDS.toNanos(42), 42.seconds.toNanos) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /duration/src/test/kotlin/eu/markov/kotlin/lucidtime/duration/WeeksTest.kt: -------------------------------------------------------------------------------- 1 | package eu.markov.kotlin.lucidtime.duration 2 | 3 | import org.junit.Test 4 | import java.util.concurrent.TimeUnit 5 | import kotlin.test.assertEquals 6 | 7 | class WeeksTest { 8 | 9 | @Test 10 | fun one_week_is_7_days() { 11 | assertEquals(7.days, 1.week) 12 | } 13 | 14 | @Test 15 | fun half_a_week_is_3_and_a_half_days() { 16 | assertEquals(3.5.days, 0.5f.weeks) 17 | } 18 | 19 | @Test 20 | fun addition_of_other_duration_calculates_correctly() { 21 | assertEquals(1.5.weeks, 1.week + 3.5.days) 22 | assertEquals(1.5.weeks, 3.5.days + 1.week) 23 | } 24 | 25 | @Test 26 | fun subtraction_of_other_duration_calculates_correctly() { 27 | assertEquals(0.5.weeks, 1.week - 3.5.days) 28 | assertEquals(0.5.weeks, 7.days - 0.5.week) 29 | } 30 | 31 | @Test 32 | fun is_equal_to_native_time_unit() { 33 | assertEquals(TimeUnit.DAYS.toNanos(7 * 42), 42.weeks.toNanos) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2g \ 2 | -XX:MaxPermSize=256m \ 3 | -XX:+HeapDumpOnOutOfMemoryError \ 4 | -Dfile.encoding=UTF-8 \ 5 | -Duser.language=en \ 6 | -Duser.country=US 7 | 8 | org.gradle.parallel=true 9 | org.gradle.caching=true 10 | org.gradle.daemon=true 11 | org.gradle.configureondemand=true 12 | 13 | # Kotlin code style for this project: "official" or "obsolete": 14 | kotlin.code.style=official 15 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markov/lucid-time/43b19665b1e5a5c910633e5c9c9507f1e21585b4/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | include(":duration") 2 | --------------------------------------------------------------------------------