├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle ├── time-android ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── kotlin │ └── com │ │ └── kizitonwose │ │ └── timeandroid │ │ └── AndroidExtensions.kt │ └── res │ └── values │ └── strings.xml └── time ├── .gitignore ├── build.gradle └── src ├── main └── kotlin │ └── com │ └── kizitonwose │ └── time │ ├── Extensions.kt │ └── Time.kt └── test └── kotlin └── com └── kizitonwose └── time └── TimeTest.kt /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.war 15 | *.ear 16 | *.zip 17 | *.tar.gz 18 | *.rar 19 | 20 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 21 | hs_err_pid* 22 | 23 | /local.properties 24 | /.gradle 25 | /.idea 26 | /build 27 | /out 28 | *.iml 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: oraclejdk8 3 | android: 4 | components: 5 | - tools 6 | - platform-tools 7 | - build-tools-29.0.3 8 | - android-29 9 | before_script: 10 | - chmod +x gradlew 11 | script: 12 | - ./gradlew test jacocoFullReport 13 | after_success: 14 | - bash <(curl -s https://codecov.io/bash) 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Kizito Nwose 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Time 2 | 3 | [![JitPack](https://jitpack.io/v/kizitonwose/Time.svg)](https://jitpack.io/#kizitonwose/Time) 4 | [![Build Status](https://travis-ci.org/kizitonwose/Time.svg?branch=master)](https://travis-ci.org/kizitonwose/Time) 5 | [![Coverage](https://img.shields.io/codecov/c/github/kizitonwose/Time/master.svg)](https://codecov.io/gh/kizitonwose/Time) 6 | [![Codacy](https://api.codacy.com/project/badge/Grade/26b009748a0849f3973887fbbcd84900)](https://www.codacy.com/app/kizitonwose/Time) 7 | [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/kizitonwose/Time/blob/master/LICENSE.md) 8 | 9 | This library is made for you if you have ever written something like this: 10 | 11 | ```kotlin 12 | val duration = 10 * 1000 13 | ``` 14 | to represent a duration of 10 seconds(in milliseconds) because most methods in Kotlin/Java take duration parameters in milliseconds. 15 | 16 | ## Usage 17 | 18 | ### Showcase 19 | 20 | ```kotlin 21 | val tenSeconds = 10.seconds 22 | val fiveMinutes = 5.minutes 23 | val twoHours = 2.hours 24 | val threeDays = 3.days 25 | val tenMinutesFromNow = Calendar.getInstance() + 10.minutes 26 | val tenSecondsInMilliseconds = 10.seconds.inMilliseconds 27 | ``` 28 | 29 | ### Basics 30 | 31 | The main advantage of the library is that all time units are *strongly-typed*. So, for example: 32 | 33 | ```kotlin 34 | val tenMinutes = 10.minutes 35 | ``` 36 | 37 | In the example above, `tenMinutes` will be of type `Interval`. There are seven time units available, from nanoseconds to days: 38 | 39 | ```kotlin 40 | val tenNanoseconds = 10.nanoseconds 41 | // type is Interval 42 | ``` 43 | ```kotlin 44 | val tenMicroseconds = 10.microseconds 45 | // type is Interval 46 | ``` 47 | ```kotlin 48 | val tenMilliseconds = 10.milliseconds 49 | // type is Interval 50 | ``` 51 | ```kotlin 52 | val tenSeconds = 10.seconds 53 | // type is Interval 54 | ``` 55 | ```kotlin 56 | val tenMinutes = 10.minutes 57 | // type is Interval 58 | ``` 59 | ```kotlin 60 | val tenHours = 10.hours 61 | // type is Interval 62 | ``` 63 | ```kotlin 64 | val tenDays = 10.days 65 | // type is Interval 66 | ``` 67 | 68 | ### Operations 69 | 70 | You can perform all basic arithmetic operations on time intervals, even of different units: 71 | 72 | ```kotlin 73 | val duration = 10.minutes + 15.seconds - 3.minutes + 2.hours // Interval 74 | val doubled = duration * 2 75 | 76 | val seconds = 10.seconds + 3.minutes // Interval 77 | ``` 78 | 79 | You can also use these operations with the `Calendar` class: 80 | 81 | ```kotlin 82 | val twoHoursLater = Calendar.getInstance() + 2.hours 83 | ``` 84 | 85 | ### Conversions 86 | 87 | Time intervals are easily convertible: 88 | 89 | ```kotlin 90 | val twoMinutesInSeconds = 2.minutes.inSeconds // Interval 91 | val fourDaysInHours = 4.days.inHours // Interval 92 | ``` 93 | 94 | You can also use the `converted()` method, although you would rarely need to: 95 | 96 | ```kotlin 97 | val tenMinutesInSeconds: Interval = 10.minutes.converted() 98 | ``` 99 | 100 | ### Comparison 101 | 102 | You can compare different time units as well: 103 | 104 | ```kotlin 105 | 50.seconds < 2.hours // true 106 | 120.minutes == 2.hours // true 107 | 100.milliseconds > 2.seconds // false 108 | 48.hours in 2.days // true 109 | ``` 110 | 111 | ### Creating your own time units 112 | 113 | If, for some reason, you need to create your own time unit, that's super easy to do: 114 | 115 | ```kotlin 116 | class Week : TimeUnit { 117 | // number of seconds in one week 118 | override val timeIntervalRatio = 604800.0 119 | } 120 | ``` 121 | 122 | Now you can use it like any other time unit: 123 | 124 | ```kotlin 125 | val fiveWeeks = Interval(5) 126 | ``` 127 | 128 | For the sake of convenience, don't forget to write those handy extensions: 129 | 130 | ```kotlin 131 | class Week : TimeUnit { 132 | override val timeIntervalRatio = 604800.0 133 | } 134 | 135 | val Number.weeks: Interval 136 | get() = Interval(this) 137 | 138 | val Interval.inWeeks: Interval 139 | get() = converted() 140 | ``` 141 | Now you can write: 142 | 143 | ```kotlin 144 | val fiveWeeks = 5.weeks // Interval 145 | ``` 146 | You can also easily convert to weeks: 147 | 148 | ```kotlin 149 | val valueInWeeks = 14.days.inWeeks // Interval 150 | ``` 151 | 152 | ### Extras 153 | 154 | The library includes some handy extensions for some classes: 155 | 156 | ```kotlin 157 | val now = Calendar.getInstance() 158 | val sixHoursLater = now + 6.hours 159 | val fourDaysAgo = now - 4.days 160 | ``` 161 | 162 | ```kotlin 163 | val timer = Timer() 164 | timer.schedule(10.seconds) { 165 | println("This block will be called in 10 seconds") 166 | } 167 | ``` 168 | 169 | The library also includes extensions for Android's Handler class, this is only available if you compile the "time-android" module. 170 | 171 | ```kotlin 172 | val handler = Handler() 173 | handler.postDelayed({ 174 | Log.i("TAG", "This will be printed to the Logcat in 2 minutes") 175 | }, 2.minutes) 176 | ``` 177 | More extensions will be added to the library in the future. 178 | 179 | ### Conversion safety everywhere 180 | 181 | For time-related methods in other third-party libraries in your project, if such methods are frequently used, it's best to write extention functions that let you use the time units in this libary in those methods. This is mostly just one line of code. 182 | 183 | If such methods aren't frequently used, you can still benefit from the conversion safety that comes with this library. 184 | 185 | An example method in a third-party library that does something after a delay period in milliseconds: 186 | 187 | ```kotlin 188 | class Person { 189 | fun doSomething(delayMillis: Long) { 190 | // method body 191 | } 192 | } 193 | ``` 194 | 195 | To call the method above with a value of 5 minutes, one would usually write: 196 | 197 | ```kotlin 198 | val person = Person() 199 | person.doSomething(5 * 60 * 1000) 200 | ``` 201 | 202 | The above line can be written in a safer and clearer way using this library: 203 | 204 | ```kotlin 205 | val person = Person() 206 | person.doSomething(5.minutes.inMilliseconds.longValue) 207 | ``` 208 | 209 | If the method is frequently used, you can write an extension function: 210 | 211 | ```kotlin 212 | fun Person.doSomething(delay: Interval) { 213 | doSomething(delay.inMilliseconds.longValue) 214 | } 215 | ``` 216 | Now you can write: 217 | 218 | ```kotlin 219 | val person = Person() 220 | person.doSomething(5.minutes) 221 | ``` 222 | 223 | ## Installation 224 | 225 | Add the JitPack repository to your `build.gradle`: 226 | 227 | ```groovy 228 | allprojects { 229 | repositories { 230 | maven { url "https://jitpack.io" } 231 | } 232 | } 233 | ``` 234 | 235 | Add the dependency to your `build.gradle`: 236 | 237 | - For **non-Android** projects: 238 | 239 | ```groovy 240 | dependencies { 241 | compile 'com.github.kizitonwose.time:time:' 242 | } 243 | ``` 244 | 245 | - For **Android** projects: 246 | 247 | ```groovy 248 | dependencies { 249 | compile 'com.github.kizitonwose.time:time-android:' 250 | } 251 | ``` 252 | 253 | ## Contributing 254 | The goal is for the library to be used wherever possible. If there are extension functions or features you think the library should have, feel free to add them and send a pull request or open an issue. Core Kotlin extensions belong in the "[time][time-core-module-url]" module while Android extensions belong in "[time-android][time-android-module-url]" module. 255 | 256 | 257 | ## Inspiration 258 | Time was inspired by a Swift library of the same name - [Time][time-swift-url]. 259 | 260 | The API of Time(Kotlin) has been designed to be as close as possible to Time(Swift) for consistency and because the two languages have many similarities. Check out [Time(Swift)][time-swift-url] if you want a library with the same functionality in Swift. 261 | 262 | 263 | ## License 264 | Time is distributed under the MIT license. [See LICENSE](https://github.com/kizitonwose/Time/blob/master/LICENSE.md) for details. 265 | 266 | 267 | [time-swift-url]: https://github.com/dreymonde/Time 268 | [time-core-module-url]: https://github.com/kizitonwose/Time/tree/master/time 269 | [time-android-module-url]: https://github.com/kizitonwose/Time/tree/master/time-android 270 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.70' 3 | 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.6.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 12 | classpath 'com.palantir:jacoco-coverage:0.4.0' 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | jcenter() 20 | } 21 | } 22 | 23 | apply plugin: 'com.palantir.jacoco-full-report' -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kizitonwose/Time/ef4027fb965d59ea87957fe08da7cc4d3a91d145/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Mar 24 12:36:26 CET 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /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: -------------------------------------------------------------------------------- 1 | include ':time', ':time-android' 2 | rootProject.name = 'Time' 3 | 4 | -------------------------------------------------------------------------------- /time-android/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /time-android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'com.github.dcendents.android-maven' 4 | 5 | group = 'com.github.kizitonwose' 6 | 7 | android { 8 | compileSdkVersion 29 9 | buildToolsVersion "29.0.3" 10 | 11 | defaultConfig { 12 | minSdkVersion 14 13 | targetSdkVersion 29 14 | versionCode 1 15 | versionName "1.0" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | sourceSets { 26 | main { 27 | java.srcDirs += 'src/main/kotlin' 28 | } 29 | } 30 | } 31 | 32 | 33 | dependencies { 34 | implementation fileTree(dir: 'libs', include: ['*.jar']) 35 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 36 | api project(':time') 37 | } 38 | -------------------------------------------------------------------------------- /time-android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /time-android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /time-android/src/main/kotlin/com/kizitonwose/timeandroid/AndroidExtensions.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | package com.kizitonwose.timeandroid 3 | 4 | import android.os.Handler 5 | import com.kizitonwose.time.* 6 | 7 | /** 8 | * Created by Kizito Nwose on 19/10/2017 9 | */ 10 | 11 | fun Handler.postDelayed(r: Runnable, delay: Interval) 12 | = postDelayed(r, delay.inMilliseconds.longValue) 13 | 14 | fun Handler.postDelayed(r: () -> Unit, delay: Interval) 15 | = postDelayed(r, delay.inMilliseconds.longValue) 16 | 17 | -------------------------------------------------------------------------------- /time-android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Time Android 3 | 4 | -------------------------------------------------------------------------------- /time/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /time/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'kotlin' 2 | apply plugin: 'maven' 3 | apply plugin: 'jacoco' 4 | 5 | group = 'com.github.kizitonwose' 6 | 7 | dependencies { 8 | implementation fileTree(dir: 'libs', include: ['*.jar']) 9 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 10 | testImplementation 'junit:junit:4.12' 11 | } 12 | 13 | compileKotlin { 14 | kotlinOptions.jvmTarget = "1.8" 15 | } 16 | compileTestKotlin { 17 | kotlinOptions.jvmTarget = "1.8" 18 | } 19 | -------------------------------------------------------------------------------- /time/src/main/kotlin/com/kizitonwose/time/Extensions.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | package com.kizitonwose.time 3 | 4 | import java.util.* 5 | import kotlin.concurrent.schedule 6 | import kotlin.concurrent.scheduleAtFixedRate 7 | 8 | /** 9 | * Created by Kizito Nwose on 15/10/2017 10 | */ 11 | 12 | //region Calendar 13 | operator fun Calendar.plus(other: Interval): Calendar = (clone() as Calendar).apply { 14 | timeInMillis += other.inMilliseconds.longValue 15 | } 16 | 17 | operator fun Calendar.minus(other: Interval): Calendar = (clone() as Calendar).apply { 18 | timeInMillis -= other.inMilliseconds.longValue 19 | } 20 | //endregion 21 | 22 | 23 | //region Thread 24 | fun Thread.sleep(interval: Interval) 25 | = Thread.sleep(interval.inMilliseconds.longValue) 26 | //endregion 27 | 28 | 29 | //region Java Timer 30 | fun Timer.schedule(task: TimerTask, period: Interval) 31 | = schedule(task, period.inMilliseconds.longValue) 32 | 33 | fun Timer.schedule(task: TimerTask, delay: Interval, period: Interval) 34 | = schedule(task, delay.inMilliseconds.longValue, period.inMilliseconds.longValue) 35 | 36 | fun Timer.schedule(task: TimerTask, firstTime: Date, period: Interval) 37 | = schedule(task, firstTime, period.inMilliseconds.longValue) 38 | 39 | fun Timer.scheduleAtFixedRate(task: TimerTask, delay: Interval, period: Interval) 40 | = scheduleAtFixedRate(task, delay.inMilliseconds.longValue, period.inMilliseconds.longValue) 41 | 42 | fun Timer.scheduleAtFixedRate(task: TimerTask, firstTime: Date, period: Interval) 43 | = scheduleAtFixedRate(task, firstTime, period.inMilliseconds.longValue) 44 | //endregion 45 | 46 | 47 | //region Kotlin Timer 48 | inline fun Timer.schedule(delay: Interval, crossinline action: TimerTask.() -> Unit) 49 | = schedule(delay.inMilliseconds.longValue, action) 50 | 51 | inline fun Timer.schedule(delay: Interval, period: Interval, crossinline action: TimerTask.() -> Unit) 52 | = schedule(delay.inMilliseconds.longValue, period.inMilliseconds.longValue, action) 53 | 54 | inline fun Timer.schedule(time: Date, period: Interval, crossinline action: TimerTask.() -> Unit) 55 | = schedule(time, period.inMilliseconds.longValue, action) 56 | 57 | inline fun Timer.scheduleAtFixedRate(delay: Interval, period: Interval, crossinline action: TimerTask.() -> Unit) 58 | = scheduleAtFixedRate(delay.inMilliseconds.longValue, period.inMilliseconds.longValue, action) 59 | 60 | inline fun Timer.scheduleAtFixedRate(time: Date, period: Interval, crossinline action: TimerTask.() -> Unit) 61 | = scheduleAtFixedRate(time, period.inMilliseconds.longValue, action) 62 | //endregion 63 | -------------------------------------------------------------------------------- /time/src/main/kotlin/com/kizitonwose/time/Time.kt: -------------------------------------------------------------------------------- 1 | package com.kizitonwose.time 2 | 3 | import java.io.Serializable 4 | import java.util.* 5 | import kotlin.math.roundToLong 6 | 7 | /** 8 | * Created by Kizito Nwose on 14/10/2017 9 | */ 10 | 11 | 12 | interface TimeUnit { 13 | val timeIntervalRatio: Double 14 | fun conversionRate(otherTimeUnit: OtherUnit): Double { 15 | return timeIntervalRatio / otherTimeUnit.timeIntervalRatio 16 | } 17 | } 18 | 19 | 20 | class Interval(value: Number, factory: () -> T) : Serializable { 21 | 22 | companion object { 23 | inline operator fun invoke(value: Number) = Interval(value) { 24 | K::class.java.newInstance() 25 | } 26 | } 27 | 28 | val unit: T = factory() 29 | 30 | val value = value.toDouble() 31 | 32 | val longValue = this.value.roundToLong() 33 | 34 | val inDays: Interval 35 | get() = converted() 36 | 37 | val inHours: Interval 38 | get() = converted() 39 | 40 | val inMinutes: Interval 41 | get() = converted() 42 | 43 | val inSeconds: Interval 44 | get() = converted() 45 | 46 | val inMilliseconds: Interval 47 | get() = converted() 48 | 49 | val inMicroseconds: Interval 50 | get() = converted() 51 | 52 | val inNanoseconds: Interval 53 | get() = converted() 54 | 55 | 56 | inline fun converted(): Interval { 57 | val otherInstance = OtherUnit::class.java.newInstance() 58 | return Interval(value * unit.conversionRate(otherInstance)) 59 | } 60 | 61 | operator fun plus(other: Interval): Interval { 62 | val newValue = value + other.value * other.unit.conversionRate(unit) 63 | return Interval(newValue) { unit } 64 | } 65 | 66 | operator fun minus(other: Interval): Interval { 67 | val newValue = value - other.value * other.unit.conversionRate(unit) 68 | return Interval(newValue) { unit } 69 | } 70 | 71 | operator fun times(other: Number): Interval { 72 | return Interval(value * other.toDouble()) { unit } 73 | } 74 | 75 | operator fun div(other: Number): Interval { 76 | return Interval(value / other.toDouble()) { unit } 77 | } 78 | 79 | operator fun inc() = Interval(value + 1) { unit } 80 | 81 | operator fun dec() = Interval(value - 1) { unit } 82 | 83 | operator fun compareTo(other: Interval) = inMilliseconds.value.compareTo(other.inMilliseconds.value) 84 | 85 | operator fun contains(other: Interval) = inMilliseconds.value >= other.inMilliseconds.value 86 | 87 | override fun equals(other: Any?): Boolean { 88 | if (other == null || other !is Interval) return false 89 | return compareTo(other) == 0 90 | } 91 | 92 | override fun hashCode() = inMilliseconds.value.hashCode() 93 | 94 | override fun toString(): String { 95 | val unitString = unit::class.java.simpleName.toLowerCase(Locale.ENGLISH) 96 | val isWhole = value % 1 == 0.0 97 | return (if (isWhole) longValue.toString() else value.toString()) 98 | .plus(" ") 99 | .plus(if (value == 1.0) unitString else unitString.plus("s")) 100 | } 101 | } 102 | 103 | 104 | class Day : TimeUnit { 105 | override val timeIntervalRatio = 86400.0 106 | } 107 | 108 | class Hour : TimeUnit { 109 | override val timeIntervalRatio = 3600.0 110 | } 111 | 112 | class Minute : TimeUnit { 113 | override val timeIntervalRatio = 60.0 114 | } 115 | 116 | class Second : TimeUnit { 117 | override val timeIntervalRatio = 1.0 118 | } 119 | 120 | class Millisecond : TimeUnit { 121 | override val timeIntervalRatio = 0.001 122 | } 123 | 124 | class Microsecond : TimeUnit { 125 | override val timeIntervalRatio = 0.000001 126 | } 127 | 128 | class Nanosecond : TimeUnit { 129 | override val timeIntervalRatio = 1e-9 130 | } 131 | 132 | 133 | val Number.days: Interval 134 | get() = Interval(this) 135 | 136 | val Number.hours: Interval 137 | get() = Interval(this) 138 | 139 | val Number.minutes: Interval 140 | get() = Interval(this) 141 | 142 | val Number.seconds: Interval 143 | get() = Interval(this) 144 | 145 | val Number.milliseconds: Interval 146 | get() = Interval(this) 147 | 148 | val Number.microseconds: Interval 149 | get() = Interval(this) 150 | 151 | val Number.nanoseconds: Interval 152 | get() = Interval(this) 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /time/src/test/kotlin/com/kizitonwose/time/TimeTest.kt: -------------------------------------------------------------------------------- 1 | package com.kizitonwose.time 2 | 3 | import org.hamcrest.core.IsEqual.equalTo 4 | import org.junit.Assert.* 5 | import org.junit.Test 6 | import java.util.* 7 | 8 | /** 9 | * Created by Kizito Nwose on 15/10/2017 10 | */ 11 | 12 | class TimeTest { 13 | 14 | @Test 15 | fun `time comparisons work as expected`() { 16 | assertTrue(5.minutes > 120.seconds) 17 | assertTrue(2.days < 48.5.hours) 18 | assertTrue(1000.microseconds > 2000.nanoseconds) 19 | assertEquals(60.seconds, 60000.milliseconds) 20 | } 21 | 22 | @Test 23 | fun `time conversions work as expected`() { 24 | val twentyFourHours = 24.hours 25 | 26 | val valueInDays = twentyFourHours.inSeconds.inMinutes.inNanoseconds 27 | .inMicroseconds.inHours.inMilliseconds.inDays 28 | 29 | assertThat(valueInDays.value, equalTo(1.0)) 30 | } 31 | 32 | @Test 33 | fun `basic time operators work as expected`() { 34 | val sixtySecs = 60.seconds 35 | 36 | var newValue = sixtySecs + 2.minutes 37 | newValue -= 20.seconds 38 | newValue += 10.seconds 39 | 40 | assertThat(newValue, equalTo(170.seconds)) 41 | } 42 | 43 | @Test 44 | fun `time "in" operator works as expected`() { 45 | assertTrue(60.minutes in 4.hours) 46 | assertFalse(2.days in 24.hours) 47 | assertTrue(120.seconds in 2.minutes) 48 | } 49 | 50 | @Test 51 | fun `time operators(multiplication and division) work as expected`() { 52 | val sixtySecs = 60.seconds 53 | 54 | val multiplied = sixtySecs * 2 55 | val divided = sixtySecs / 2 56 | 57 | assertEquals(multiplied, 120.seconds) 58 | assertEquals(divided, 30.seconds) 59 | } 60 | 61 | @Test 62 | fun `time operators(increment and decrement) work as expected`() { 63 | var days = 2.days 64 | 65 | days++ 66 | assertEquals(days, 3.days) 67 | 68 | days-- 69 | assertEquals(days, 2.days) 70 | } 71 | 72 | @Test 73 | fun `ten minutes in the future is greater than now`() { 74 | val now = Calendar.getInstance() 75 | 76 | val tenMinutesLater = now + 10.minutes 77 | 78 | assertTrue(tenMinutesLater > now) 79 | } 80 | 81 | @Test 82 | fun `ten days ago is less than now`() { 83 | val now = Calendar.getInstance() 84 | 85 | val tenDaysAgo = now - 10.days 86 | 87 | assertTrue(tenDaysAgo < now) 88 | } 89 | 90 | @Test 91 | fun `custom time units work as expected`() { 92 | val twoWeeks = 2.weeks 93 | val fourteenDays = 14.days 94 | 95 | assertEquals(twoWeeks, fourteenDays) 96 | assertEquals(336.hours.inWeeks, twoWeeks) 97 | } 98 | } 99 | 100 | 101 | // Custom time unit. 102 | class Week : TimeUnit { 103 | override val timeIntervalRatio = 604800.0 104 | } 105 | 106 | val Number.weeks: Interval 107 | get() = Interval(this.toDouble()) 108 | 109 | val Interval.inWeeks: Interval 110 | get() = converted() 111 | --------------------------------------------------------------------------------