├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.gradle.kts ├── copyright.txt ├── detekt.yml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── java-time ├── README.md ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── github │ │ └── debop │ │ └── javatimes │ │ ├── AbstractTemporalInterval.kt │ │ ├── DateIterator.kt │ │ ├── DurationExtensions.kt │ │ ├── InstantExtensions.kt │ │ ├── Javatimex.kt │ │ ├── LocalDateTimeExntesions.kt │ │ ├── MutableTemporalInterval.kt │ │ ├── PeriodExtensions.kt │ │ ├── Quarter.kt │ │ ├── ReadableTemporalInterval.kt │ │ ├── TemporalExtensions.kt │ │ ├── TemporalInterval.kt │ │ ├── TemporalIntervalExtensions.kt │ │ ├── TemporalIterator.kt │ │ ├── TimeSpec.kt │ │ ├── YearQuarter.kt │ │ ├── ZonedDateTimeExtensions.kt │ │ └── ranges │ │ ├── DateProgression.kt │ │ ├── DateRange.kt │ │ ├── ProgressionExtensions.kt │ │ ├── RangeExtensions.kt │ │ ├── TemporalProgression.kt │ │ ├── TemporalRange.kt │ │ ├── TemporalRangeExtensions.kt │ │ ├── TimeProgression.kt │ │ ├── TimeRange.kt │ │ ├── TimestampProgression.kt │ │ ├── TimestampRange.kt │ │ └── reactive │ │ └── ReactiveRangeExtensions.kt │ └── test │ ├── kotlin │ └── com │ │ └── github │ │ └── debop │ │ └── javatimes │ │ ├── AbstractJavaTimesTest.kt │ │ ├── InstantExtensionsTest.kt │ │ ├── JavatimexTest.kt │ │ ├── TemporalIntervalChunkTest.kt │ │ ├── TemporalIntervalExtensionsTest.kt │ │ ├── TemporalIntervalTest.kt │ │ ├── TemporalIntervalWindowedTest.kt │ │ ├── TemporalIntervalZipWithNextTest.kt │ │ └── ranges │ │ ├── DateProgressionTest.kt │ │ ├── DateRangeTest.kt │ │ ├── InstantProgressionTest.kt │ │ ├── InstantRangeTest.kt │ │ ├── TemporalProgressionTest.kt │ │ ├── TemporalRangeChunkTest.kt │ │ ├── TemporalRangeTest.kt │ │ └── reactive │ │ └── ReactiveRangesTest.kt │ └── resources │ ├── junit-platform.properties │ └── logback-test.xml ├── koda-time ├── README.md ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── github │ │ └── debop │ │ └── kodatimes │ │ ├── KodaTimex.kt │ │ ├── PeriodUnit.kt │ │ ├── ReadableInstantIterator.kt │ │ ├── TimeIntervalx.kt │ │ ├── TimestampZoneText.kt │ │ ├── package-info.md │ │ └── ranges │ │ ├── Progressions.kt │ │ ├── Ranges.kt │ │ └── reactive │ │ └── ReactiveRangeExtensions.kt │ └── test │ ├── kotlin │ └── com │ │ └── github │ │ └── debop │ │ └── kodatimes │ │ ├── AbstractKodaTimesTest.kt │ │ ├── DateTimeTest.kt │ │ ├── DurationTest.kt │ │ ├── ParsingTest.kt │ │ ├── TimeIntervalChunkTest.kt │ │ ├── TimeIntervalWindowedTest.kt │ │ ├── TimeIntervalxTest.kt │ │ └── ranges │ │ ├── DateTimeProgressTest.kt │ │ ├── DateTimeRangeTest.kt │ │ ├── InstantProgressionTest.kt │ │ ├── InstantRangeTest.kt │ │ └── reactive │ │ └── ReactiveRangeExtensionsTest.kt │ └── resources │ ├── junit-platform.properties │ └── logback-test.xml └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | *.iml 15 | .idea/ 16 | out/ 17 | 18 | !gradle/wrapper/gradle-wrapper.jar 19 | build/ 20 | .gradle 21 | 22 | # eclipse 23 | bin/ 24 | .classpath 25 | .project 26 | 27 | .gradletasknamecache 28 | 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | sudo: false 7 | 8 | install: true 9 | 10 | script: ./gradlew clean build 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # koda-times change Logs 2 | 3 | # 2.0.0 4 | 5 | > release 2019-01-12 6 | 7 | * Split two module 8 | * koda-time (joda-time extensios) support JVM 6 or higher 9 | * java-time (java.time extensions) support JVM 8 or higher 10 | * Bump up kotlin-stdlib 1.3.11 11 | * Bump up kotlinx-coroutines-core 1.1.0 12 | 13 | # 1.2.2 14 | 15 | > release 2018-03-24 16 | 17 | * Bump up kotlin-stdlib 1.2.30 18 | * Bump up kotlinx-coroutines 0.22.5 19 | * add TemporalRange, TemporalProgression 20 | * add RxJava2 Flowable convert method for DateTimeRange, InstanceRange ... 21 | * add java 8 LocalDateTime 22 | 23 | #1.2.1 24 | 25 | > release 2017-11-19 26 | 27 | * add DateTimeRange, InstantRange for joda-time 28 | * add DateTimeProgression, InstantProgression for joda-time 29 | * add DateRange, InstantRange for java 8 time 30 | * add DateProgression, InstantProgression for java 8 time 31 | 32 | # 1.2 33 | 34 | * add ReadableInterval.chunk(), windowed(), zipWithNext() methods 35 | * Upgrade kotlin standard library 1.1.51 36 | * Improve ReadableInterval.buildSequence 37 | 38 | # 1.1.3 39 | 40 | * Upgrade kotlin standard library 1.1.3 41 | * Change Target JVM to 1.8 42 | * Support Java 8 Time (see Javatimex.kt) 43 | * Change build tool to Gradle 44 | 45 | # 1.1.2 46 | 47 | * Change build tool to [Kobalt](http://beust.com/kobalt) (Build system with Kotlin syntax) 48 | 49 | # 1.1.1 50 | 51 | * Upgrade kotlin standard library to 1.1.1 52 | * Upgrade gradle version (3.5) 53 | * Remove assertj-core, use kotlin test lib. 54 | 55 | # 1.1.0 56 | 57 | * Add arithmatic operation for Period, Instace, LocalDateTime, LocalDate, LocalTime 58 | * Remove DurationBuilder class 59 | * Upgrade kotlin library to 1.1 60 | 61 | # 1.0.0 62 | 63 | > First release 64 | 65 | * Supply joda-time DateTime extension functions -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # koda-time 2 | [![Build Status](https://travis-ci.org/debop/koda-time.png)](https://travis-ci.org/debop/koda-time) 3 | [![Download](https://api.bintray.com/packages/debop/maven/koda-time/images/download.svg)](https://bintray.com/debop/maven/koda-time/_latestVersion) 4 | 5 | [Joda Time](http://joda.org) Extensions in Kotlin 6 | 7 | supply arithmetic operation (+,-,*,/) for joda-time `DateTime` in Kotlin language 8 | 9 | ### Usage 10 | 11 | ```kotlin 12 | 13 | val now = DateTime.now() 14 | val start = now + 5.minutes() 15 | val end = now + 15.minutes() 16 | val interval = start .. end 17 | ``` 18 | 19 | and supply Extension functions of `DateTime`, `Duration`, `Instance`, `Period` 20 | 21 | ### Setup 22 | 23 | ##### Maven 24 | 25 | add dependency 26 | 27 | ```xml 28 | 29 | com.github.debop 30 | koda-time 31 | 1.2.1 32 | 33 | ``` 34 | 35 | add repository 36 | 37 | ```xml 38 | 39 | 40 | jcenter 41 | http://jcenter.bintray.com 42 | 43 | 44 | ``` 45 | or 46 | ```xml 47 | 48 | 49 | debop-releases-bintray 50 | http://dl.bintray.com/debop/maven 51 | 52 | 53 | ``` 54 | 55 | ##### Gradle 56 | 57 | ``` 58 | repository { 59 | jcenter() 60 | } 61 | dependencies { 62 | compile "com.github.debop:koda-time:1.2.1" 63 | } 64 | ``` 65 | 66 | 67 | 68 | ### Build 69 | 70 | build by Gradle 71 | 72 | ``` 73 | $ ./gradlew clean build 74 | ``` -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016. Sunghyouk Bae 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | import com.jfrog.bintray.gradle.BintrayExtension 17 | import io.gitlab.arturbosch.detekt.detekt 18 | import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformJvmPlugin 19 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 20 | import org.jetbrains.dokka.gradle.DokkaTask 21 | 22 | plugins { 23 | base 24 | kotlin("jvm") version "1.3.11" apply false 25 | id("io.gitlab.arturbosch.detekt") version "1.0.0-RC12" apply false 26 | 27 | id("org.jetbrains.dokka") version "0.9.17" apply false 28 | id("com.jfrog.bintray") version "1.8.4" apply false 29 | } 30 | 31 | allprojects { 32 | val kodaTime: String by extra 33 | val snapshot: String by extra 34 | 35 | group = "com.github.debop" 36 | version = kodaTime + snapshot 37 | 38 | repositories { 39 | mavenCentral() 40 | jcenter() 41 | } 42 | } 43 | 44 | dependencies { 45 | subprojects.forEach { 46 | archives(it) 47 | } 48 | } 49 | 50 | subprojects { 51 | apply { 52 | plugin("kotlin") 53 | plugin("io.gitlab.arturbosch.detekt") 54 | plugin("jacoco") 55 | 56 | plugin("org.jetbrains.dokka") 57 | plugin("maven-publish") 58 | plugin("com.jfrog.bintray") 59 | } 60 | 61 | val sourceSets = project.the() 62 | 63 | val sourcesJar by tasks.creating(Jar::class) { 64 | from(sourceSets["main"].allSource) 65 | classifier = "sources" 66 | } 67 | 68 | // Configure existing Dokka task to output HTML to typical Javadoc directory 69 | tasks.withType { 70 | outputFormat = "html" 71 | outputDirectory = "$buildDir/javadoc" 72 | } 73 | 74 | // Create dokka Jar task from dokka task output 75 | val dokkaJar by tasks.creating(Jar::class) { 76 | group = JavaBasePlugin.DOCUMENTATION_GROUP 77 | description = "Assembles Kotlin docs with Dokka" 78 | classifier = "javadoc" 79 | // dependsOn(tasks.dokka) not needed; dependency automatically inferred by from(tasks.dokka) 80 | from(tasks["dokka"]) 81 | } 82 | 83 | tasks.withType { 84 | useJUnitPlatform() 85 | 86 | testLogging { 87 | events("FAILED") 88 | } 89 | 90 | maxParallelForks = Runtime.getRuntime().availableProcessors() 91 | setForkEvery(1L) 92 | } 93 | 94 | detekt { 95 | description = "Runs a failfast detekt build." 96 | 97 | input = files("src/main/kotlin") 98 | config = files("../detekt.yml") 99 | filters = ".*/build/.*" 100 | 101 | reports { 102 | xml.enabled = false 103 | html.enabled = true 104 | } 105 | } 106 | 107 | configure { 108 | publications { 109 | register(project.name, MavenPublication::class) { 110 | from(components["java"]) 111 | artifact(sourcesJar) 112 | artifact(dokkaJar) 113 | groupId = project.group as String 114 | artifactId = project.name 115 | version = project.version as String 116 | } 117 | } 118 | } 119 | 120 | // bintray 121 | configure { 122 | user = findProperty("BINTRAY_USER") as? String 123 | key = findProperty("BINTRAY_KEY") as? String 124 | setPublications(project.name) 125 | publish = true 126 | pkg.apply { 127 | repo = "maven" 128 | name = "Koda Time" 129 | desc = "The modelling for success/failure of operations in Kotlin" 130 | userOrg = "debop" 131 | websiteUrl = "https://github.com/debop/koda-time" 132 | vcsUrl = "https://github.com/debop/koda-time" 133 | setLicenses("Apache-2.0") 134 | version.apply { 135 | name = project.version as String 136 | } 137 | } 138 | } 139 | 140 | // jacoco 141 | configure { 142 | toolVersion = extra.get("jacoco") as String 143 | } 144 | 145 | tasks.withType { 146 | reports { 147 | html.isEnabled = true 148 | xml.isEnabled = true 149 | csv.isEnabled = false 150 | } 151 | } 152 | 153 | tasks["clean"].doLast { 154 | delete("./.project") 155 | delete("./out") 156 | delete("./bin") 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /copyright.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016. Sunghyouk Bae 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. -------------------------------------------------------------------------------- /detekt.yml: -------------------------------------------------------------------------------- 1 | complexity: 2 | LongMethod: 3 | threshold: 32 4 | 5 | LongParameterList: 6 | threshold: 6 7 | ignoreDefaultParameters: true 8 | 9 | TooManyFunctions: 10 | thresholdInFiles: 60 11 | thresholdInClasses: 20 12 | 13 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2016. Sunghyouk Bae 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | org.gradle.daemon=true 17 | org.gradle.parallel=true 18 | org.gradle.caching = false 19 | 20 | kotlin.incremental=true 21 | java.incremental=true 22 | 23 | kodaTime = 2.0.0 24 | snapshot = -SNAPSHOT 25 | 26 | kotlin = 1.3.11 27 | coroutines = 1.1.0 28 | jodatime = 2.10 29 | rxjava2 = 2.2.4 30 | 31 | junitPlatform = 1.3.2 32 | junitJupiter = 5.3.2 33 | jacoco = 0.8.2 34 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debop/koda-time/cbb0efedaa53bdf9d77b230d8477cb0ae0d7abd7/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-5.0-bin.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='"-Xmx64m"' 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="-Xmx64m" 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 | -------------------------------------------------------------------------------- /java-time/README.md: -------------------------------------------------------------------------------- 1 | # java-time 2 | 3 | [![Build Status](https://travis-ci.org/debop/koda-time.png)](https://travis-ci.org/debop/koda-time) 4 | [![Download](https://api.bintray.com/packages/debop/maven/joda-time/images/download.svg)](https://bintray.com/debop/maven/joda-time/_latestVersion) 5 | 6 | Extension methods for Java 8 time. 7 | 8 | ## Features 9 | 10 | ### Support arithmetic operation (+,-,*,/) 11 | 12 | Easy to express manipulation of times 13 | 14 | ```kotlin 15 | 16 | val start = now() 17 | val end = current + 5.minutes() // after 5 minutes 18 | val interval = start .. end // TemporalInterval (start ~ endInclusive) 19 | ``` 20 | 21 | ### Period types 22 | 23 | #### Temporal Ranges 24 | 25 | Various range expression (Date, Time, Timestamp, Temporal ...) 26 | 27 | * DateRange 28 | * TimeRange 29 | * TimestampRange 30 | * TemporalRange 31 | * LocalDateRnage 32 | * LocalTimeRange 33 | * LocalDateTimeRange 34 | * OffsetDateTimeRange 35 | * ZonedDateTimeRange 36 | * InstantRange 37 | 38 | ```kotlin 39 | val start = Date() 40 | val end = start + 5.days() 41 | val period = DateRange.fromClosedRange(start, end) // changed to start .. end 42 | ``` 43 | 44 | #### Extension methods for Ranges 45 | 46 | * Chunk 47 | 48 | ```kotlin 49 | @Test 50 | open fun `chunk in week`() { 51 | val range = temporalRangeOf(start, (start + 5.weeks()) as T) 52 | 53 | val chunks = range.chunkWeek(3).toList() 54 | assertEquals(2, chunks.size) 55 | assertChunkInRange(range, chunks) 56 | } 57 | ``` 58 | 59 | #### Interval 60 | 61 | Class for Java 8 times that performs functions such as `Interval` of [joda-time](http://joda.org) 62 | 63 | #### Extension methods for Interval 64 | 65 | Also provide sequence, chunk, windowed, zipWithNext method for various interval 66 | 67 | * Sequence 68 | ```kotlin 69 | @Test 70 | fun `build day sequence`() { 71 | val start = nowZonedDateTime() 72 | val end = start + 42.days() 73 | 74 | val interval = temporalIntervalOf(start, end) 75 | 76 | val startDay = start.dayOfYear 77 | logger.debug { "startDay=$startDay" } 78 | 79 | assertEquals(listOf(startDay, startDay + 1, startDay + 2), 80 | interval.days(1).take(3).map { 81 | logger.debug { "produce $it, dayOfYear=${it.dayOfYear}" } 82 | it.dayOfYear 83 | }.toList()) 84 | 85 | assertEquals(listOf(startDay, startDay + 2, startDay + 4), 86 | interval.days(2).take(3).map { 87 | logger.debug { "produce $it, dayOfYear=${it.dayOfYear}" } 88 | it.dayOfYear 89 | }.toList()) 90 | } 91 | ``` 92 | 93 | * Chunk 94 | ```kotlin 95 | @Test 96 | fun `chunk week and aggregate`() { 97 | val start = nowZonedDateTime().startOfWeek() 98 | val endExclusive = start + 5.weekPeriod() 99 | 100 | val interval = start..endExclusive 101 | 102 | val chunks = interval.chunkWeek(3) 103 | .map { weeks -> weeks.first()..weeks.last() } 104 | .toList() 105 | 106 | assertEquals(2, chunks.size) 107 | chunks.forEach { 108 | assertTrue(it in interval) 109 | } 110 | } 111 | ``` 112 | 113 | * Windowed 114 | ```kotlin 115 | @Test 116 | fun `windowed days`() { 117 | val start = nowZonedDateTime().startOfDay() 118 | val endExclusive = start + 5.days() 119 | 120 | val interval = start..endExclusive 121 | 122 | logger.debug { "day interval=$interval" } 123 | 124 | val windowed = interval.windowedDay(3, 2) 125 | windowed.forEachIndexed { index, items -> 126 | logger.debug { "index=$index, items=$items" } 127 | assertTrue { items.first() in interval } 128 | assertTrue { items.last() in interval } 129 | } 130 | assertEquals(3, windowed.count()) 131 | 132 | assertFailsWith { 133 | interval.windowedDay(-1, 2) 134 | } 135 | assertFailsWith { 136 | interval.windowedDay(1, -2) 137 | } 138 | } 139 | ``` 140 | 141 | 142 | ## Setup 143 | 144 | #### Gradle 145 | 146 | ``` 147 | repository { 148 | jcenter() 149 | } 150 | dependencies { 151 | compile "com.github.debop:java-time:2.0.0" 152 | } 153 | ``` 154 | 155 | #### Maven 156 | 157 | add repository 158 | 159 | ```xml 160 | 161 | 162 | jcenter 163 | http://jcenter.bintray.com 164 | 165 | 166 | ``` 167 | 168 | add dependency 169 | 170 | ```xml 171 | 172 | com.github.debop 173 | java-time 174 | 2.0.0 175 | 176 | ``` 177 | 178 | 179 | -------------------------------------------------------------------------------- /java-time/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | tasks.withType { 4 | sourceCompatibility = "1.8" 5 | kotlinOptions.jvmTarget = "1.8" 6 | } 7 | 8 | dependencies { 9 | 10 | implementation(kotlin("stdlib")) 11 | 12 | compileOnly("io.reactivex.rxjava2:rxjava:${extra.get("rxjava2")}") 13 | compileOnly("org.jetbrains.kotlinx:kotlinx-coroutines-rx2:${extra.get("coroutines")}") 14 | 15 | testImplementation("io.reactivex.rxjava2:rxjava:${extra.get("rxjava2")}") 16 | testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-rx2:${extra.get("coroutines")}") 17 | 18 | implementation("io.github.microutils:kotlin-logging:1.6.22") 19 | implementation("org.slf4j:slf4j-api:1.7.25") 20 | testImplementation("ch.qos.logback:logback-classic:1.2.3") 21 | 22 | testImplementation("org.junit.jupiter:junit-jupiter-api:${extra.get("junitJupiter")}") 23 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${extra.get("junitJupiter")}") 24 | 25 | testImplementation("org.amshove.kluent:kluent:1.45") 26 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/AbstractTemporalInterval.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes 2 | 3 | import com.github.debop.javatimes.ReadableTemporalInterval.Companion.SEPARATOR 4 | import mu.KLogging 5 | import java.time.Duration 6 | import java.time.Instant 7 | import java.time.Period 8 | import java.time.ZoneId 9 | import java.time.ZonedDateTime 10 | import java.time.chrono.ChronoZonedDateTime 11 | import java.time.temporal.ChronoUnit 12 | import java.util.Objects 13 | 14 | /** 15 | * JodaTime's AbstractInterval class 를 구현했습니다. 16 | * 17 | * @author debop 18 | */ 19 | abstract class AbstractTemporalInterval : ReadableTemporalInterval { 20 | 21 | companion object : KLogging() 22 | 23 | override val zoneId: ZoneId 24 | get() = SystemZoneId 25 | 26 | override val startEpochMillis: Long 27 | get() = start.toEpochMillis() 28 | 29 | override val startDateTime: ZonedDateTime 30 | get() = ZonedDateTime.ofInstant(start, zoneId) 31 | 32 | override val endEpochMillis: Long 33 | get() = end.toEpochMillis() 34 | 35 | override val endDateTime: ZonedDateTime 36 | get() = ZonedDateTime.ofInstant(end, zoneId) 37 | 38 | override val isEmpty: Boolean 39 | get() = start == end 40 | 41 | /** 42 | * 두 Interval 이 연속해 있으면 true를 아니면 false 를 반환한다. 43 | * @param other 44 | */ 45 | override fun abuts(other: ReadableTemporalInterval): Boolean { 46 | return start == other.end || end == other.start 47 | } 48 | 49 | override fun gap(interval: ReadableTemporalInterval): ReadableTemporalInterval? { 50 | return when { 51 | overlaps(interval) -> null 52 | else -> temporalIntervalOf(maxOf(start, interval.start), minOf(end, interval.end), zoneId) 53 | } 54 | } 55 | 56 | override fun overlap(interval: ReadableTemporalInterval): ReadableTemporalInterval? { 57 | return when { 58 | overlaps(interval) -> TemporalInterval(maxOf(start, interval.start), minOf(end, interval.end), zoneId) 59 | else -> null 60 | } 61 | } 62 | 63 | override fun overlaps(other: ReadableTemporalInterval): Boolean { 64 | return overlaps(other.start) || overlaps(other.end) 65 | } 66 | 67 | override fun overlaps(moment: ChronoZonedDateTime<*>): Boolean { 68 | return overlaps(moment.toInstant()) 69 | } 70 | 71 | /** 72 | * the specific instant in start, end 73 | * @param instant time instant that is overlaps with temporal interval 74 | */ 75 | override fun overlaps(instant: Instant): Boolean { 76 | return instant in start..end 77 | } 78 | 79 | /** 80 | * given interval is inner inverval of this interval 81 | * @param other 82 | */ 83 | override operator fun contains(other: ReadableTemporalInterval): Boolean { 84 | return contains(other.start) && contains(other.end) 85 | } 86 | 87 | /** 88 | * the specific instant in [start, end) 89 | * @param instant 90 | */ 91 | override operator fun contains(instant: Instant): Boolean { 92 | return start <= instant && instant < end 93 | } 94 | 95 | override fun contains(datetime: ZonedDateTime): Boolean { 96 | return contains(datetime.toInstant()) 97 | } 98 | 99 | override fun contains(epochMillis: Long): Boolean { 100 | return contains(epochMillis.toInstant()) 101 | } 102 | 103 | /** 104 | * 현 Interval 이 `other` interval 보다 이전인가? 105 | * @param other 106 | */ 107 | override fun isBefore(other: ReadableTemporalInterval): Boolean = end < other.end 108 | 109 | /** 110 | * This interval is before to given instant 111 | * @param instant 112 | */ 113 | override fun isBefore(instant: Instant): Boolean = end < instant 114 | 115 | override fun isAfter(other: ReadableTemporalInterval): Boolean = start >= other.start 116 | 117 | override fun isAfter(instant: Instant): Boolean = start >= instant 118 | 119 | override fun withStartMillis(startMillis: Long): ReadableTemporalInterval = when { 120 | startMillis > endEpochMillis -> temporalIntervalOf(endEpochMillis, startMillis, zoneId) 121 | else -> temporalIntervalOf(startMillis, endEpochMillis, zoneId) 122 | } 123 | 124 | override fun withStart(start: Instant): ReadableTemporalInterval = when { 125 | start > this.end -> temporalIntervalOf(end, start, zoneId) 126 | else -> temporalIntervalOf(start, end, zoneId) 127 | } 128 | 129 | override fun withEndMillis(endMillis: Long): ReadableTemporalInterval = when { 130 | endMillis < startEpochMillis -> temporalIntervalOf(endMillis, startEpochMillis, zoneId) 131 | else -> temporalIntervalOf(startEpochMillis, endMillis, zoneId) 132 | } 133 | 134 | override fun withEndMillis(end: Instant): ReadableTemporalInterval = when { 135 | end < this.start -> temporalIntervalOf(end, start, zoneId) 136 | else -> temporalIntervalOf(start, end, zoneId) 137 | } 138 | 139 | override fun toDuration(): Duration = Duration.between(start, end) 140 | 141 | override fun toDurationMillis(): Long = endEpochMillis - startEpochMillis 142 | 143 | override fun toInterval(): ReadableTemporalInterval = temporalIntervalOf(start, end, zoneId) 144 | 145 | override fun toMutableInterval(): MutableTemporalInterval = mutableTemporalIntervalOf(start, end, zoneId) 146 | 147 | override fun toPeriod(): Period = Period.between(start.toLocalDate(), end.toLocalDate()) 148 | 149 | override fun toPeriod(unit: ChronoUnit): Period { 150 | return when(unit) { 151 | ChronoUnit.DAYS -> Period.ofDays(toPeriod().days) 152 | ChronoUnit.WEEKS -> Period.ofWeeks(toPeriod().days / 7) 153 | ChronoUnit.MONTHS -> Period.ofDays(toPeriod().months) 154 | ChronoUnit.YEARS -> Period.ofDays(toPeriod().years) 155 | else -> toPeriod() 156 | } 157 | } 158 | 159 | override fun compareTo(other: ReadableTemporalInterval): Int { 160 | return start.compareTo(other.start) 161 | } 162 | 163 | override fun equals(other: Any?): Boolean { 164 | if(other == null) return false 165 | if(other === this) return true 166 | 167 | return when(other) { 168 | is ReadableTemporalInterval -> 169 | start == other.start && end == other.end && zoneId == other.zoneId 170 | else -> false 171 | } 172 | } 173 | 174 | override fun hashCode(): Int { 175 | return Objects.hash(start, end, zoneId) 176 | } 177 | 178 | override fun toString(): String { 179 | return "$start$SEPARATOR$end" 180 | } 181 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/DateIterator.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes 2 | 3 | import java.util.Date 4 | 5 | /** 6 | * An iterator over a sequence of values of type `java.util.Date`. 7 | */ 8 | abstract class DateIterator : Iterator { 9 | 10 | final override fun next(): T = nextDate() 11 | 12 | /** Returns the next value in the sequence without boxing. */ 13 | abstract fun nextDate(): T 14 | 15 | } 16 | -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/DurationExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes 2 | 3 | import java.time.Duration 4 | import java.time.temporal.Temporal 5 | 6 | 7 | operator fun Duration.unaryMinus(): Duration = this.negated() 8 | 9 | val Duration.isPositive: Boolean get() = this > Duration.ZERO 10 | 11 | val Duration.isNotNegative: Boolean get() = this >= Duration.ZERO 12 | 13 | val Duration.millis: Long get() = seconds * 1000L + nano / 1_000_000L 14 | fun Duration.inNanos(): Long = seconds * NANO_PER_SECOND + nano 15 | 16 | fun durationOf(startInclusive: Temporal, endExclusive: Temporal): Duration = 17 | Duration.between(startInclusive, endExclusive) 18 | 19 | fun durationOfYear(year: Int): Duration = 20 | durationOf(zonedDateTimeOf(year), zonedDateTimeOf(year + 1)) 21 | 22 | fun durationOfQuarter(year: Int, quarter: Quarter): Duration { 23 | val startInclusive = startOfQuarter(year, quarter) 24 | val endExclusive = startInclusive.plusMonths(MonthsPerQuarter.toLong()) 25 | return durationOf(startInclusive, endExclusive) 26 | } 27 | 28 | fun durationOfMonth(year: Int, monthOfYear: Int): Duration { 29 | val startInclusive = startOfMonth(year, monthOfYear) 30 | val endExclusive = startInclusive.plusMonths(1) 31 | return durationOf(startInclusive, endExclusive) 32 | } 33 | 34 | fun durationOfWeek(week: Int): Duration = if(week == 0) Duration.ZERO else durationOfDay(week * DaysPerWeek) 35 | 36 | @JvmOverloads 37 | fun durationOfDay(days: Int, 38 | hours: Int = 0, 39 | minutes: Int = 0, 40 | seconds: Int = 0, 41 | nanos: Int = 0): Duration { 42 | var duration = days.days() 43 | 44 | if(hours != 0) 45 | duration += hours.hours() 46 | if(minutes != 0) 47 | duration += minutes.minutes() 48 | if(seconds != 0) 49 | duration += seconds.seconds() 50 | if(nanos != 0) 51 | duration += nanos.nanos() 52 | 53 | return duration 54 | } 55 | 56 | fun durationOfHour(hours: Int, 57 | minutes: Int = 0, 58 | seconds: Int = 0, 59 | nanos: Int = 0): Duration { 60 | var duration = hours.hours() 61 | 62 | if(minutes != 0) 63 | duration += minutes.minutes() 64 | if(seconds != 0) 65 | duration += seconds.seconds() 66 | if(nanos != 0) 67 | duration += nanos.nanos() 68 | 69 | return duration 70 | } 71 | 72 | fun durationOfMinute(minutes: Int, 73 | seconds: Int = 0, 74 | nanos: Int = 0): Duration { 75 | var duration = minutes.minutes() 76 | 77 | if(seconds != 0) 78 | duration += seconds.seconds() 79 | if(nanos != 0) 80 | duration += nanos.nanos() 81 | 82 | return duration 83 | } 84 | 85 | 86 | fun durationOfSecond(seconds: Int, 87 | nanos: Int = 0): Duration { 88 | var duration = seconds.seconds() 89 | 90 | if(nanos != 0) 91 | duration += nanos.nanos() 92 | 93 | return duration 94 | } 95 | 96 | fun durationOfNano(nanos: Long): Duration = Duration.ofNanos(nanos) -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/InstantExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes 2 | 3 | import java.time.Duration 4 | import java.time.Instant 5 | import java.time.LocalDate 6 | import java.time.LocalDateTime 7 | import java.time.OffsetDateTime 8 | import java.time.ZoneId 9 | import java.time.ZoneOffset 10 | import java.time.ZonedDateTime 11 | import java.time.temporal.ChronoField 12 | import java.time.temporal.ChronoUnit 13 | import java.util.Calendar 14 | import java.util.Date 15 | import java.util.TimeZone 16 | 17 | fun instantOf(epochMillis: Long): Instant = Instant.ofEpochMilli(epochMillis) 18 | 19 | @JvmField 20 | val EPOCH: Instant = Instant.EPOCH 21 | 22 | operator fun Instant.rangeTo(end: Instant) = temporalIntervalOf(this, end) 23 | 24 | operator fun Instant.rangeTo(duration: Duration) = temporalIntervalOf(this, duration) 25 | 26 | @JvmOverloads 27 | fun Instant.toLocalDate(zoneId: ZoneId = SystemZoneId): LocalDate = toLocalDateTime(zoneId).toLocalDate() 28 | 29 | @JvmOverloads 30 | fun Instant.toLocalDateTime(zoneId: ZoneId = SystemZoneId): LocalDateTime = LocalDateTime.ofInstant(this, zoneId) 31 | 32 | @JvmOverloads 33 | fun Instant.toOffsetDateTime(zoneId: ZoneId = SystemZoneId): OffsetDateTime = OffsetDateTime.ofInstant(this, zoneId) 34 | 35 | @JvmOverloads 36 | fun Instant.toZonedDateTime(zoneId: ZoneId = SystemZoneId): ZonedDateTime = ZonedDateTime.ofInstant(this, zoneId) 37 | 38 | fun Instant.toDate(): Date = Date.from(this) 39 | 40 | fun Instant.toCalendar(timeZone: TimeZone = TimeZone.getTimeZone(UtcZoneId)): Calendar = 41 | Calendar.Builder() 42 | .setInstant(this.toEpochMilli()) 43 | .setTimeZone(timeZone) 44 | .build() 45 | 46 | @JvmOverloads 47 | fun Instant.with(year: Int, 48 | monthOfYear: Int = 1, 49 | dayOfMonth: Int = 1, 50 | hourOfDay: Int = 0, 51 | minuteOfHour: Int = 0, 52 | secondOfMinute: Int = 0, 53 | millisOfSecond: Int = 0, 54 | zoneOffset: ZoneOffset = ZoneOffset.UTC): Instant = 55 | this.toLocalDateTime() 56 | .withYear(year) 57 | .withMonth(monthOfYear) 58 | .withDayOfMonth(dayOfMonth) 59 | .withHour(hourOfDay) 60 | .withMinute(minuteOfHour) 61 | .withSecond(secondOfMinute) 62 | .with(ChronoField.MILLI_OF_SECOND, millisOfSecond.toLong()) 63 | .toInstant(zoneOffset) 64 | 65 | fun Instant.startOfYear(): Instant = toLocalDateTime(UtcZoneId).startOfYear().toInstant(UtcOffset) 66 | fun Instant.startOfMonth(): Instant = toLocalDateTime(UtcZoneId).startOfMonth().toInstant(UtcOffset) 67 | fun Instant.startOfWeek(): Instant = toLocalDateTime(UtcZoneId).startOfWeek().toInstant(UtcOffset) 68 | fun Instant.startOfDay(): Instant = truncatedTo(ChronoUnit.DAYS) 69 | fun Instant.startOfHour(): Instant = truncatedTo(ChronoUnit.HOURS) 70 | fun Instant.startOfMinute(): Instant = truncatedTo(ChronoUnit.MINUTES) 71 | fun Instant.startOfSecond(): Instant = truncatedTo(ChronoUnit.SECONDS) 72 | fun Instant.startOfMillis(): Instant = truncatedTo(ChronoUnit.MILLIS) 73 | 74 | operator fun Instant.plus(millis: Long): Instant = plusMillis(millis) 75 | operator fun Instant.minus(millis: Long): Instant = minusMillis(millis) 76 | 77 | infix fun Instant?.min(that: Instant?): Instant? = when { 78 | this == null -> that 79 | that == null -> this 80 | this > that -> that 81 | else -> this 82 | } 83 | 84 | infix fun Instant?.max(that: Instant?): Instant? = when { 85 | this == null -> that 86 | that == null -> this 87 | this < that -> that 88 | else -> this 89 | } 90 | 91 | val Instant.yearInterval: ReadableTemporalInterval 92 | get() { 93 | val start = startOfYear() 94 | return start..(start + 1.yearPeriod()) 95 | } 96 | 97 | val Instant.monthInterval: ReadableTemporalInterval 98 | get() { 99 | val start = startOfMonth() 100 | return start..(start + 1.monthPeriod()) 101 | } 102 | 103 | val Instant.dayInterval: ReadableTemporalInterval 104 | get() { 105 | val start = this.startOfDay() 106 | return start..(start + 1.days()) 107 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/LocalDateTimeExntesions.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes 2 | 3 | import java.time.DayOfWeek 4 | import java.time.Instant 5 | import java.time.LocalDate 6 | import java.time.LocalDateTime 7 | import java.time.LocalTime 8 | import java.time.Month 9 | import java.time.OffsetDateTime 10 | import java.time.OffsetTime 11 | import java.time.Period 12 | import java.time.ZoneOffset 13 | import java.time.ZonedDateTime 14 | import java.time.temporal.ChronoUnit 15 | import java.util.Date 16 | 17 | @JvmOverloads 18 | fun localDateTimeOf(year: Int, 19 | monthOfYear: Int = 1, 20 | dayOfMonth: Int = 1, 21 | hourOfDay: Int = 0, 22 | minuteOfHour: Int = 0, 23 | secondOfMinute: Int = 0, 24 | milliOfSecond: Int = 0): LocalDateTime = 25 | LocalDateTime.of(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, milliOfSecond.millisToNanos()) 26 | 27 | fun LocalDateTime.toDate(): Date = Date.from(toInstant()) 28 | fun LocalDateTime.toInstant(): Instant = toOffsetDateTime().toInstant() 29 | fun LocalDateTime.toUtcInstant(): Instant = toOffsetDateTime().toUtcInstant() 30 | 31 | 32 | @JvmOverloads 33 | fun LocalDateTime.toOffsetDateTime(offset: ZoneOffset = SystemOffset): OffsetDateTime = 34 | OffsetDateTime.of(this, offset) 35 | 36 | fun LocalDateTime.toZonedDateTime(offset: ZoneOffset = SystemOffset): ZonedDateTime = 37 | ZonedDateTime.of(this, offset) 38 | 39 | fun LocalDateTime.startOfYear(): LocalDateTime = this.withDayOfYear(1).truncatedTo(ChronoUnit.DAYS) 40 | fun LocalDateTime.startOfMonth(): LocalDateTime = this.withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS) 41 | fun LocalDateTime.startOfWeek(): LocalDateTime = startOfDay() - (dayOfWeek.value - DayOfWeek.MONDAY.value).days() 42 | fun LocalDateTime.startOfDay(): LocalDateTime = this.truncatedTo(ChronoUnit.DAYS) 43 | 44 | 45 | @JvmOverloads 46 | fun localDateOf(year: Int, 47 | monthOfYear: Int = 1, 48 | dayOfMonth: Int = 1): LocalDate = LocalDate.of(year, monthOfYear, dayOfMonth) 49 | 50 | @JvmOverloads 51 | fun localDateOf(year: Int, 52 | month: Month, 53 | dayOfMonth: Int = 1): LocalDate = LocalDate.of(year, month, dayOfMonth) 54 | 55 | fun LocalDate.toDate(): Date = Date.from(toInstant()) 56 | fun LocalDate.toInstant(): Instant = Instant.from(this) 57 | fun LocalDate.startOfWeek(): LocalDate = this - (dayOfWeek.value - DayOfWeek.MONDAY.value).days() 58 | fun LocalDate.startOfMonth(): LocalDate = withDayOfMonth(1) 59 | fun LocalDate.startOfYear(): LocalDate = withDayOfYear(1) 60 | 61 | fun LocalDate.between(endExclusive: LocalDate): Period = Period.between(this, endExclusive) 62 | 63 | @JvmOverloads 64 | fun localTimeOf(hourOfDay: Int, 65 | minuteOfHour: Int = 0, 66 | secondOfMinute: Int = 0, 67 | milliOfSecond: Int): LocalTime = 68 | LocalTime.of(hourOfDay, minuteOfHour, secondOfMinute, milliOfSecond.millisToNanos()) 69 | 70 | fun LocalTime.toInstant(): Instant = Instant.from(this) 71 | 72 | @JvmOverloads 73 | fun offsetDateTimeOf(year: Int, 74 | monthOfYear: Int = 1, 75 | dayOfMonth: Int = 1, 76 | hourOfDay: Int = 0, 77 | minuteOfHour: Int = 0, 78 | secondOfMinute: Int = 0, 79 | milliOfSecond: Int = 0, 80 | offset: ZoneOffset = SystemOffset): OffsetDateTime = 81 | OffsetDateTime.of(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, milliOfSecond.millisToNanos(), offset) 82 | 83 | @JvmOverloads 84 | fun offsetDateTimeOf(localDate: LocalDate = LocalDate.ofEpochDay(0), 85 | localTime: LocalTime = LocalTime.ofSecondOfDay(0), 86 | offset: ZoneOffset = SystemOffset): OffsetDateTime = 87 | OffsetDateTime.of(localDate, localTime, offset) 88 | 89 | fun OffsetDateTime.toUtcInstant(): Instant = Instant.ofEpochSecond(this.toEpochSecond()) 90 | 91 | fun OffsetDateTime.startOfDay(): OffsetDateTime = this.truncatedTo(ChronoUnit.DAYS) 92 | fun OffsetDateTime.startOfWeek(): OffsetDateTime = startOfDay() - (dayOfWeek.value - DayOfWeek.MONDAY.value).days() 93 | fun OffsetDateTime.startOfMonth(): OffsetDateTime = withDayOfMonth(1) 94 | fun OffsetDateTime.startOfYear(): OffsetDateTime = withDayOfYear(1) 95 | 96 | fun OffsetTime.toInstant(): Instant = Instant.from(this) 97 | 98 | 99 | -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/MutableTemporalInterval.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes 2 | 3 | import java.time.Instant 4 | import java.time.ZoneId 5 | 6 | /** 7 | * Mutable [AbstractTemporalInterval] 8 | * 9 | * @author debop 10 | */ 11 | class MutableTemporalInterval(_start: Instant, _end: Instant, 12 | override val zoneId: ZoneId = UtcZoneId) : AbstractTemporalInterval() { 13 | 14 | constructor(other: ReadableTemporalInterval) : this(other.start, other.end, other.zoneId) 15 | 16 | override var start: Instant = _start 17 | set(value) { 18 | if(value.isAfter(end)) { 19 | field = end 20 | end = value 21 | } else { 22 | field = value 23 | } 24 | } 25 | 26 | override var end: Instant = _end 27 | set(value) { 28 | if(value.isBefore(start)) { 29 | field = start 30 | this.start = value 31 | 32 | } else { 33 | field = value 34 | } 35 | } 36 | 37 | override fun toMutableInterval(): MutableTemporalInterval { 38 | return MutableTemporalInterval(this) 39 | } 40 | 41 | override fun withStartMillis(startMillis: Long): MutableTemporalInterval = mutableTemporalIntervalOf(startMillis, this.endEpochMillis) 42 | override fun withStart(start: Instant): MutableTemporalInterval = mutableTemporalIntervalOf(start, this.end) 43 | override fun withEndMillis(endMillis: Long): MutableTemporalInterval = mutableTemporalIntervalOf(this.startEpochMillis, endMillis) 44 | override fun withEndMillis(end: Instant): MutableTemporalInterval = mutableTemporalIntervalOf(this.start, end) 45 | 46 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/PeriodExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes 2 | 3 | import java.time.Period 4 | import java.time.temporal.Temporal 5 | 6 | operator fun Period.unaryMinus(): Period = this.negated() 7 | 8 | @Suppress("UNCHECKED_CAST") 9 | operator fun Period.plus(instant: T): T = addTo(instant) as T 10 | 11 | @Suppress("UNCHECKED_CAST") 12 | operator fun Period.minus(instant: T): T = subtractFrom(instant) as T 13 | 14 | @JvmOverloads 15 | fun periodOf(years: Int, months: Int = 0, days: Int = 0): Period = Period.of(years, months, days) 16 | 17 | /** 18 | * year sequence of `Period` 19 | */ 20 | suspend fun Period.yearSequence(): Sequence = sequence { 21 | var year = 0 22 | val years = this@yearSequence.years 23 | if(years > 0) { 24 | while(year < years) { 25 | yield(year++) 26 | } 27 | } else { 28 | while(year > years) { 29 | yield(year--) 30 | } 31 | } 32 | } 33 | 34 | /** 35 | * month sequence of `java.time.Period` 36 | */ 37 | suspend fun Period.monthSequence(): Sequence = sequence { 38 | var month = 0 39 | val months = this@monthSequence.months 40 | if(months > 0) { 41 | while(month < months) { 42 | yield(month++) 43 | } 44 | } else { 45 | while(month > months) { 46 | yield(month--) 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * day sequence of `java.time.Period` 53 | */ 54 | suspend fun Period.daySequence(): Sequence = sequence { 55 | var day = 0 56 | val days = this@daySequence.days 57 | if(days > 0) { 58 | while(day < days) { 59 | yield(day++) 60 | } 61 | } else { 62 | while(day > days) { 63 | yield(day--) 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/Quarter.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes 2 | 3 | /** 4 | * 분기(Quarter) 를 나타내는 enum class 입니다. 5 | * 6 | * @autor debop 7 | * @since 18. 4. 19 8 | */ 9 | enum class Quarter(val value: Int) { 10 | 11 | FIRST(1), SECOND(2), THIRD(3), FOURTH(4); 12 | 13 | operator fun plus(delta: Int): Quarter { 14 | val amount = delta % 4 15 | return quarterArray[(ordinal + (amount + QuartersPerYear)) % QuartersPerYear] 16 | } 17 | 18 | val months: IntArray 19 | get() = when(this) { 20 | FIRST -> FirstQuarterMonths 21 | SECOND -> SecondQuarterMonths 22 | THIRD -> ThirdQuarterMonths 23 | FOURTH -> FourthQuarterMonths 24 | } 25 | 26 | val startMonth: Int = this.ordinal * MonthsPerQuarter + 1 27 | val endMonth: Int = value * MonthsPerQuarter 28 | 29 | companion object { 30 | 31 | @JvmStatic private val quarterArray: Array = Quarter.values() 32 | 33 | @JvmStatic 34 | fun of(q: Int): Quarter { 35 | if(q in 1..4) { 36 | return quarterArray[q - 1] 37 | } 38 | throw IllegalArgumentException("Invalid q for Quarter. need 1..4, q=$q") 39 | } 40 | 41 | @JvmStatic 42 | fun ofMonth(monthOfYear: Int): Quarter { 43 | return quarterArray[(monthOfYear - 1) / MonthsPerQuarter] 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/ReadableTemporalInterval.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes 2 | 3 | import java.io.Serializable 4 | import java.time.Duration 5 | import java.time.Instant 6 | import java.time.Period 7 | import java.time.ZoneId 8 | import java.time.ZonedDateTime 9 | import java.time.chrono.ChronoZonedDateTime 10 | import java.time.temporal.ChronoUnit 11 | 12 | /** 13 | * JodaTime 의 `ReadableTemporalInterval` 과 같은 기능을 수행합니다. 14 | * 15 | * @author debop 16 | */ 17 | interface ReadableTemporalInterval : Comparable, Serializable { 18 | 19 | companion object { 20 | const val SEPARATOR = "~" 21 | val EMPTY_INTERVAL = temporalIntervalOf(0L, 0L) 22 | } 23 | 24 | val zoneId: ZoneId 25 | 26 | val start: Instant 27 | val startEpochMillis: Long 28 | val startDateTime: ZonedDateTime 29 | 30 | val end: Instant 31 | val endEpochMillis: Long 32 | val endDateTime: ZonedDateTime 33 | 34 | val isEmpty: Boolean 35 | 36 | fun abuts(other: ReadableTemporalInterval): Boolean 37 | fun gap(interval: ReadableTemporalInterval): ReadableTemporalInterval? 38 | fun overlap(interval: ReadableTemporalInterval): ReadableTemporalInterval? 39 | 40 | fun overlaps(other: ReadableTemporalInterval): Boolean 41 | fun overlaps(moment: ChronoZonedDateTime<*>): Boolean 42 | fun overlaps(instant: Instant): Boolean 43 | 44 | operator fun contains(other: ReadableTemporalInterval): Boolean 45 | operator fun contains(instant: Instant): Boolean 46 | operator fun contains(datetime: ZonedDateTime): Boolean 47 | operator fun contains(epochMillis: Long): Boolean 48 | 49 | fun isBefore(other: ReadableTemporalInterval): Boolean 50 | fun isBefore(instant: Instant): Boolean 51 | 52 | fun isAfter(other: ReadableTemporalInterval): Boolean 53 | fun isAfter(instant: Instant): Boolean 54 | 55 | fun withStartMillis(startMillis: Long): ReadableTemporalInterval 56 | fun withStart(start: Instant): ReadableTemporalInterval 57 | 58 | fun withEndMillis(endMillis: Long): ReadableTemporalInterval 59 | fun withEndMillis(end: Instant): ReadableTemporalInterval 60 | 61 | fun toDuration(): Duration 62 | fun toDurationMillis(): Long 63 | 64 | fun toInterval(): ReadableTemporalInterval 65 | fun toMutableInterval(): MutableTemporalInterval 66 | 67 | fun toPeriod(): Period 68 | fun toPeriod(unit: ChronoUnit): Period 69 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/TemporalExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes 2 | 3 | import java.time.Duration 4 | import java.time.Instant 5 | import java.time.LocalDate 6 | import java.time.LocalDateTime 7 | import java.time.LocalTime 8 | import java.time.OffsetDateTime 9 | import java.time.OffsetTime 10 | import java.time.ZonedDateTime 11 | import java.time.chrono.ChronoLocalDate 12 | import java.time.temporal.ChronoUnit 13 | import java.time.temporal.Temporal 14 | 15 | fun Temporal.durationTo(endExclusive: Temporal): Duration = Duration.between(this, endExclusive) 16 | 17 | @Suppress("UNCHECKED_CAST") 18 | fun T.trimToMillis(millis: Long = 0L): T { 19 | return when(this) { 20 | is Instant -> truncatedTo(ChronoUnit.MILLIS).plusMillis(millis) as T 21 | is LocalTime -> truncatedTo(ChronoUnit.MILLIS).plusNanos(millis * NANO_PER_SECOND) as T 22 | is ChronoLocalDate -> this 23 | is LocalDateTime -> truncatedTo(ChronoUnit.MILLIS).plusNanos(millis * NANO_PER_SECOND) as T 24 | is OffsetTime -> truncatedTo(ChronoUnit.MILLIS).plusNanos(millis * NANO_PER_SECOND) as T 25 | is OffsetDateTime -> truncatedTo(ChronoUnit.MILLIS).plusNanos(millis * NANO_PER_SECOND) as T 26 | is ZonedDateTime -> truncatedTo(ChronoUnit.MILLIS).plusNanos(millis * NANO_PER_SECOND) as T 27 | 28 | else -> throw UnsupportedOperationException("Not supported for $javaClass") 29 | } 30 | } 31 | 32 | 33 | @Suppress("UNCHECKED_CAST") 34 | fun T.trimToSecond(seconds: Long = 0L): T { 35 | return when(this) { 36 | is LocalTime -> truncatedTo(ChronoUnit.SECONDS).plusSeconds(seconds) as T 37 | is LocalDate -> this 38 | is LocalDateTime -> truncatedTo(ChronoUnit.SECONDS).plusSeconds(seconds) as T 39 | is OffsetTime -> truncatedTo(ChronoUnit.SECONDS).plusSeconds(seconds) as T 40 | is OffsetDateTime -> truncatedTo(ChronoUnit.SECONDS).plusSeconds(seconds) as T 41 | is ZonedDateTime -> truncatedTo(ChronoUnit.SECONDS).plusSeconds(seconds) as T 42 | 43 | else -> throw UnsupportedOperationException("Not supported for $javaClass") 44 | } 45 | } 46 | 47 | @Suppress("UNCHECKED_CAST") 48 | fun T.trimToMinute(minutes: Long = 0L): T { 49 | return when(this) { 50 | is LocalTime -> truncatedTo(ChronoUnit.MINUTES).plusMinutes(minutes) as T 51 | is LocalDate -> this 52 | is LocalDateTime -> truncatedTo(ChronoUnit.MINUTES).plusMinutes(minutes) as T 53 | is OffsetTime -> truncatedTo(ChronoUnit.MINUTES).plusMinutes(minutes) as T 54 | is OffsetDateTime -> truncatedTo(ChronoUnit.MINUTES).plusMinutes(minutes) as T 55 | is ZonedDateTime -> truncatedTo(ChronoUnit.MINUTES).plusMinutes(minutes) as T 56 | 57 | else -> throw UnsupportedOperationException("Not supported for $javaClass") 58 | } 59 | } 60 | 61 | @Suppress("UNCHECKED_CAST") 62 | fun T.trimToHour(hours: Long = 0L): T { 63 | return when(this) { 64 | is LocalTime -> truncatedTo(ChronoUnit.HOURS).plusHours(hours) as T 65 | is LocalDate -> this 66 | is LocalDateTime -> truncatedTo(ChronoUnit.HOURS).plusHours(hours) as T 67 | is OffsetTime -> truncatedTo(ChronoUnit.HOURS).plusHours(hours) as T 68 | is OffsetDateTime -> truncatedTo(ChronoUnit.HOURS).plusHours(hours) as T 69 | is ZonedDateTime -> truncatedTo(ChronoUnit.HOURS).plusHours(hours) as T 70 | 71 | else -> throw UnsupportedOperationException("Not supported for $javaClass") 72 | } 73 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/TemporalInterval.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes 2 | 3 | import java.time.Instant 4 | import java.time.ZoneId 5 | import java.time.ZonedDateTime 6 | import java.time.format.DateTimeFormatter 7 | import java.time.temporal.TemporalAmount 8 | 9 | /** 10 | * joda-time의 Interval을 구현한 클래스입니다. 11 | * 12 | * 참고: https://gist.github.com/simon04/26f68a3f21f76dc0bc1ff012676432c9 13 | * 14 | * @autor debop 15 | * @since 18. 4. 16 16 | */ 17 | class TemporalInterval @JvmOverloads constructor( 18 | override val start: Instant, 19 | override val end: Instant, 20 | override val zoneId: ZoneId = UtcZoneId) : AbstractTemporalInterval() { 21 | 22 | init { 23 | check(start <= end) { "The end instant[$end] must be greater than the start instant[$start]." } 24 | } 25 | 26 | companion object { 27 | @JvmStatic 28 | fun parse(str: String): TemporalInterval { 29 | val (leftStr, rightStr) = str.split(ReadableTemporalInterval.SEPARATOR, limit = 2) 30 | 31 | val start = ZonedDateTime.parse(leftStr.trim()) 32 | val end = ZonedDateTime.parse(rightStr.trim()) 33 | 34 | return temporalIntervalOf(start, end) 35 | } 36 | 37 | @JvmStatic 38 | fun parseWithOffset(str: CharSequence): TemporalInterval { 39 | val (leftStr, rightStr) = str.split(ReadableTemporalInterval.SEPARATOR, limit = 2) 40 | 41 | val start = ZonedDateTime.parse(leftStr.trim(), DateTimeFormatter.ISO_OFFSET_DATE_TIME) 42 | val end = ZonedDateTime.parse(rightStr.trim(), DateTimeFormatter.ISO_OFFSET_DATE_TIME) 43 | 44 | return temporalIntervalOf(start, end) 45 | } 46 | } 47 | 48 | @JvmOverloads 49 | constructor(startMillis: Long, endMillis: Long, zoneId: ZoneId = UtcZoneId) 50 | : this(Instant.ofEpochMilli(startMillis), Instant.ofEpochMilli(endMillis), zoneId) 51 | 52 | constructor(start: ZonedDateTime, end: ZonedDateTime) 53 | : this(start.toInstant(), end.toInstant(), start.zone) { 54 | require(start.zone == end.zone) { "start timezone is different with end timezone" } 55 | } 56 | 57 | @JvmOverloads 58 | constructor(start: Instant, amount: TemporalAmount, zoneId: ZoneId = UtcZoneId) : this(start, start + amount, zoneId) 59 | 60 | @JvmOverloads 61 | constructor(amount: TemporalAmount, end: Instant, zoneId: ZoneId = UtcZoneId) : this(end - amount, end, zoneId) 62 | 63 | 64 | fun withAmountAfterStart(amount: TemporalAmount): TemporalInterval { 65 | return temporalIntervalOf(start, start + amount, zoneId) 66 | } 67 | 68 | fun withAmountBeforeEnd(amount: TemporalAmount): TemporalInterval { 69 | return temporalIntervalOf(end - amount, end, zoneId) 70 | } 71 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/TemporalIterator.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes 2 | 3 | import java.time.temporal.Temporal 4 | 5 | /** 6 | * An iterator over a buildSequence of values of type `java.util.Temporal`. 7 | */ 8 | abstract class TemporalIterator : Iterator { 9 | 10 | final override fun next(): T = nextTemporal() 11 | 12 | /** 13 | * Returns the next value in the buildSequence without boxing. 14 | */ 15 | abstract fun nextTemporal(): T 16 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/TimeSpec.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes 2 | 3 | import java.time.DayOfWeek 4 | import java.time.Duration 5 | import java.time.LocalDate 6 | import java.time.Year 7 | import java.time.ZonedDateTime 8 | 9 | const val MonthsPerYear = 12 10 | const val HalfyearsPerYear = 2 11 | const val QuartersPerYear = 4 12 | const val QuartersPerHalfyear = 2 13 | const val MonthsPerHalfyear = 6 14 | const val MonthsPerQuarter = 3 15 | const val MaxWeeksPerYear = 54 16 | const val MaxDaysPerMonth = 31 17 | const val DaysPerWeek = 7 18 | const val HoursPerDay = 24 19 | const val MinutesPerHour = 60 20 | const val SecondsPerMinute = 60 21 | 22 | const val MillisPerSecond = 1000L 23 | const val MillisPerMinute: Long = MillisPerSecond * SecondsPerMinute 24 | const val MillisPerHour: Long = MillisPerMinute * MinutesPerHour 25 | const val MillisPerDay: Long = MillisPerHour * HoursPerDay 26 | 27 | const val MicrosPerSecond = 1000L * MillisPerSecond 28 | const val MicrosPerMinute: Long = MicrosPerSecond * SecondsPerMinute 29 | const val MicrosPerHour: Long = MicrosPerMinute * MinutesPerHour 30 | const val MicrosPerDay: Long = MicrosPerHour * HoursPerDay 31 | 32 | const val NanosPerSecond = 1000L * MicrosPerSecond 33 | const val NanosPerMinute: Long = NanosPerSecond * SecondsPerMinute 34 | const val NanosPerHour: Long = NanosPerMinute * MinutesPerHour 35 | const val NanosPerDay: Long = NanosPerHour * HoursPerDay 36 | 37 | const val TicksPerMillisecond = 10000L 38 | const val TicksPerSecond = TicksPerMillisecond * MillisPerSecond 39 | const val TicksPerMinute = TicksPerSecond * SecondsPerMinute 40 | const val TicksPerHour = TicksPerMinute * MinutesPerHour 41 | const val TicksPerDay = TicksPerHour * HoursPerDay 42 | 43 | 44 | @JvmField val Weekdays = arrayOf(DayOfWeek.MONDAY, DayOfWeek.TUESDAY, DayOfWeek.WEDNESDAY, DayOfWeek.THURSDAY, DayOfWeek.FRIDAY) 45 | @JvmField val Weekends = arrayOf(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY) 46 | 47 | fun DayOfWeek.isWeekend(): Boolean = Weekends.contains(this) 48 | 49 | @JvmField val FirstDayOfWeek: DayOfWeek = DayOfWeek.MONDAY 50 | 51 | @JvmField val FirstHalfyearMonths = intArrayOf(1, 2, 3, 4, 5, 6) 52 | @JvmField val SecondHalfyearMonths = intArrayOf(7, 8, 9, 10, 11, 12) 53 | 54 | 55 | @JvmField val FirstQuarterMonths = intArrayOf(1, 2, 3) 56 | @JvmField val SecondQuarterMonths = intArrayOf(4, 5, 6) 57 | @JvmField val ThirdQuarterMonths = intArrayOf(7, 8, 9) 58 | @JvmField val FourthQuarterMonths = intArrayOf(10, 11, 12) 59 | 60 | @JvmField val EmptyDuration: Duration = Duration.ZERO 61 | @JvmField val MinDuration: Duration = 0.nanos() 62 | @JvmField val MaxDuration: Duration = Long.MAX_VALUE.seconds() 63 | @JvmField val MinPositiveDuration: Duration = 1.nanos() 64 | @JvmField val MinNegativeDuration: Duration = (-1).nanos() 65 | 66 | @JvmField val MinPeriodTime: ZonedDateTime = zonedDateTimeOf(LocalDate.MIN) 67 | @JvmField val MaxPeriodTime: ZonedDateTime = zonedDateTimeOf(Year.MAX_VALUE - 1, 12, 31) 68 | 69 | @JvmField val DefaultStartOffset: Duration = EmptyDuration 70 | @JvmField val DefaultEndOffset: Duration = MinNegativeDuration -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/YearQuarter.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes 2 | 3 | import java.io.Serializable 4 | import java.time.LocalDateTime 5 | import java.time.OffsetDateTime 6 | import java.time.ZonedDateTime 7 | 8 | /** 9 | * YearQuarter 10 | * @author debop 11 | * @since 2018. 4. 22. 12 | */ 13 | data class YearQuarter(val year: Int, val quarter: Quarter) : Serializable { 14 | 15 | constructor(moment: LocalDateTime) : this(moment.year, Quarter.ofMonth(moment.monthValue)) 16 | constructor(moment: OffsetDateTime) : this(moment.year, Quarter.ofMonth(moment.monthValue)) 17 | constructor(moment: ZonedDateTime) : this(moment.year, Quarter.ofMonth(moment.monthValue)) 18 | 19 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/ZonedDateTimeExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes 2 | 3 | import java.time.DayOfWeek 4 | import java.time.Instant 5 | import java.time.LocalDate 6 | import java.time.LocalTime 7 | import java.time.OffsetDateTime 8 | import java.time.YearMonth 9 | import java.time.ZoneId 10 | import java.time.ZonedDateTime 11 | import java.time.temporal.ChronoField 12 | import java.time.temporal.ChronoUnit 13 | import java.time.temporal.IsoFields 14 | import java.time.temporal.TemporalAdjusters 15 | import java.time.temporal.WeekFields 16 | 17 | @JvmOverloads 18 | fun zonedDateTimeOf(year: Int, 19 | monthOfYear: Int = 1, 20 | dayOfMonth: Int = 1, 21 | hourOfDay: Int = 0, 22 | minuteOfHour: Int = 0, 23 | secondOfMinute: Int = 0, 24 | nanoOfSecond: Int = 0, 25 | zoneId: ZoneId = SystemZoneId): ZonedDateTime = 26 | ZonedDateTime.of(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, nanoOfSecond, zoneId) 27 | 28 | @JvmOverloads 29 | fun zonedDateTimeOf(localDate: LocalDate = LocalDate.ofEpochDay(0), 30 | localTime: LocalTime = LocalTime.MIDNIGHT, 31 | zoned: ZoneId = SystemZoneId): ZonedDateTime { 32 | return ZonedDateTime.of(localDate, localTime, zoned) 33 | } 34 | 35 | operator fun ZonedDateTime.rangeTo(endExclusive: ZonedDateTime): ReadableTemporalInterval = temporalIntervalOf(this, endExclusive) 36 | 37 | val ZonedDateTime.weekyear: Int get() = this[WeekFields.ISO.weekBasedYear()] 38 | val ZonedDateTime.weekOfWeekyear: Int get() = this[WeekFields.ISO.weekOfWeekBasedYear()] 39 | val ZonedDateTime.weekOfMonth: Int get() = this[WeekFields.ISO.weekOfMonth()] 40 | 41 | val ZonedDateTime.seondsOfDay: Int get() = this[ChronoField.SECOND_OF_DAY] 42 | val ZonedDateTime.millisOfDay: Int get() = this[ChronoField.MILLI_OF_DAY] 43 | val ZonedDateTime.nanoOfDay: Long get() = this.getLong(ChronoField.NANO_OF_DAY) 44 | 45 | fun ZonedDateTime.toUtcInstant(): Instant = Instant.ofEpochSecond(this.toEpochSecond()) // toInstant().minusSeconds(offset.totalSeconds.toLong()) 46 | 47 | fun ZonedDateTime.startOfYear(): ZonedDateTime = zonedDateTimeOf(year, 1, 1) 48 | fun ZonedDateTime.endOfYear(): ZonedDateTime = endOfYear(year) 49 | fun ZonedDateTime.startOfQuarter(): ZonedDateTime = startOfQuarter(year, monthValue) 50 | fun ZonedDateTime.endOfQuarter(): ZonedDateTime = endOfQuarter(year, monthValue) 51 | fun ZonedDateTime.startOfMonth(): ZonedDateTime = zonedDateTimeOf(year, monthValue, 1) 52 | fun ZonedDateTime.endOfMonth(): ZonedDateTime = endOfMonth(year, monthValue) 53 | fun ZonedDateTime.startOfWeek(): ZonedDateTime = startOfWeek(year, monthValue, dayOfMonth) 54 | fun ZonedDateTime.endOfWeek(): ZonedDateTime = endOfWeek(year, monthValue, dayOfMonth) 55 | fun ZonedDateTime.startOfDay(): ZonedDateTime = truncatedTo(ChronoUnit.DAYS) 56 | fun ZonedDateTime.endOfDay(): ZonedDateTime = startOfDay().plusDays(1).minusNanos(1) 57 | fun ZonedDateTime.startOfHour(): ZonedDateTime = truncatedTo(ChronoUnit.HOURS) 58 | fun ZonedDateTime.endOfHour(): ZonedDateTime = startOfHour().plusHours(1L).minusNanos(1) 59 | fun ZonedDateTime.startOfMinute(): ZonedDateTime = truncatedTo(ChronoUnit.MINUTES) 60 | fun ZonedDateTime.endOfMinute(): ZonedDateTime = startOfMinute().plusMinutes(1).minusNanos(1) 61 | fun ZonedDateTime.startOfSecond(): ZonedDateTime = truncatedTo(ChronoUnit.SECONDS) 62 | fun ZonedDateTime.endOfSeconds(): ZonedDateTime = startOfSecond().plusSeconds(1).minusNanos(1) 63 | 64 | fun ZonedDateTime.startOfMillis(): ZonedDateTime = truncatedTo(ChronoUnit.MILLIS) 65 | fun ZonedDateTime.endOfMillis(): ZonedDateTime = startOfMillis().plusNanos(1_000_000L - 1L) 66 | 67 | fun startOfYear(year: Int): ZonedDateTime = 68 | zonedDateTimeOf(year, 1, 1) 69 | 70 | fun endOfYear(year: Int): ZonedDateTime = 71 | startOfYear(year).plusYears(1).minusNanos(1) 72 | 73 | fun startOfQuarter(year: Int, monthOfYear: Int): ZonedDateTime = 74 | startOfQuarter(year, Quarter.ofMonth(monthOfYear)) 75 | 76 | fun startOfQuarter(year: Int, quarter: Quarter): ZonedDateTime = 77 | zonedDateTimeOf(year, quarter.startMonth, 1) 78 | 79 | fun endOfQuarter(year: Int, monthOfYear: Int): ZonedDateTime = 80 | endOfQuarter(year, Quarter.ofMonth(monthOfYear)) 81 | 82 | fun endOfQuarter(year: Int, quarter: Quarter): ZonedDateTime = 83 | zonedDateTimeOf(year, quarter.endMonth, 1).plusMonths(1).minusNanos(1) 84 | 85 | fun startOfMonth(year: Int, monthOfYear: Int): ZonedDateTime = 86 | zonedDateTimeOf(year, monthOfYear, 1) 87 | 88 | fun endOfMonth(year: Int, monthOfYear: Int): ZonedDateTime = 89 | startOfMonth(year, monthOfYear).plusMonths(1).minusNanos(1) 90 | 91 | /** 해당 년/월의 Day 수 */ 92 | fun lengthOfMonth(year: Int, monthOfYear: Int): Int = 93 | YearMonth.of(year, monthOfYear).lengthOfMonth() 94 | 95 | fun startOfWeek(year: Int, monthOfYear: Int, dayOfMonth: Int): ZonedDateTime { 96 | val date = zonedDateTimeOf(year, monthOfYear, dayOfMonth) 97 | return date - (date.dayOfWeek.value - DayOfWeek.MONDAY.value).days() 98 | } 99 | 100 | fun endOfWeek(year: Int, monthOfYear: Int, dayOfMonth: Int): ZonedDateTime = 101 | startOfWeek(year, monthOfYear, dayOfMonth).plusDays(DaysPerWeek.toLong()).minusNanos(1) 102 | 103 | fun startOfWeekOfWeekyear(weekyear: Int, weekOfWeekyear: Int): ZonedDateTime { 104 | return ZonedDateTime.now() 105 | .with(IsoFields.WEEK_BASED_YEAR, weekyear.toLong()) 106 | .with(IsoFields.WEEK_OF_WEEK_BASED_YEAR, weekOfWeekyear.toLong()) 107 | .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) 108 | } 109 | 110 | fun endOfWeekOfWeekyear(weekyear: Int, weekOfWeekyear: Int): ZonedDateTime { 111 | return startOfWeekOfWeekyear(weekyear, weekOfWeekyear).plusDays(DaysPerWeek.toLong()).minusNanos(1) 112 | } 113 | 114 | 115 | fun ZonedDateTime.nextDayOfWeek(): ZonedDateTime = this.plusWeeks(1) 116 | fun ZonedDateTime.prevDayOfWeek(): ZonedDateTime = this.minusWeeks(1) 117 | 118 | infix fun ZonedDateTime?.min(that: ZonedDateTime?): ZonedDateTime? = when { 119 | this == null -> that 120 | that == null -> this 121 | this < that -> this 122 | else -> that 123 | } 124 | 125 | infix fun ZonedDateTime?.max(that: ZonedDateTime?): ZonedDateTime? = when { 126 | this == null -> that 127 | that == null -> this 128 | this > that -> this 129 | else -> that 130 | } 131 | 132 | fun ZonedDateTime.equalTo(that: OffsetDateTime): Boolean = this == that.toZonedDateTime() 133 | 134 | fun ZonedDateTime?.equalToSeconds(that: ZonedDateTime?): Boolean = when { 135 | this == null && that == null -> true 136 | this === that -> true 137 | (this == null) != (that == null) -> false 138 | else -> this!!.truncatedTo(ChronoUnit.SECONDS) == that!!.truncatedTo(ChronoUnit.SECONDS) 139 | } 140 | 141 | fun ZonedDateTime?.equalToMillis(that: ZonedDateTime?): Boolean = when { 142 | this == null && that == null -> true 143 | this === that -> true 144 | (this == null) != (that == null) -> false 145 | else -> this!!.truncatedTo(ChronoUnit.MILLIS) == that!!.truncatedTo(ChronoUnit.MILLIS) 146 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/ranges/DateProgression.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes.ranges 2 | 3 | import com.github.debop.javatimes.DateIterator 4 | import com.github.debop.javatimes.isPositive 5 | import com.github.debop.javatimes.plus 6 | import java.time.Duration 7 | import java.util.Date 8 | import java.util.NoSuchElementException 9 | import java.util.Objects 10 | 11 | /** 12 | * Create [DateProgression] instance 13 | */ 14 | @JvmOverloads 15 | fun dateProgressionOf(start: T, endInclusive: T, step: Duration = Duration.ofMillis(1)): DateProgression = 16 | DateProgression.fromClosedRange(start, endInclusive, step) 17 | 18 | /** 19 | * A progression of value of `java.util.Date` subclass 20 | * 21 | * @property first first value of progression 22 | * @property last last value of progression 23 | * @property step progression step 24 | */ 25 | open class DateProgression internal constructor(start: T, endInclusive: T, val step: Duration) : Iterable { 26 | init { 27 | require(step != Duration.ZERO) { "step must be non-zero" } 28 | } 29 | 30 | companion object { 31 | @JvmStatic 32 | @JvmOverloads 33 | fun fromClosedRange(start: T, endInclusive: T, step: Duration = Duration.ofMillis(1L)): DateProgression { 34 | require(step != Duration.ZERO) { "step must be non-zero" } 35 | return DateProgression(start, endInclusive, step) 36 | } 37 | } 38 | 39 | val first: T = start 40 | 41 | @Suppress("UNCHECKED_CAST") 42 | val last: T = getProgressionLastElement(start, endInclusive, step) as T 43 | 44 | open fun isEmpty(): Boolean = if(step > Duration.ZERO) first > last else first < last 45 | 46 | override fun iterator(): Iterator = DateProgressionIterator(first, last, step) 47 | 48 | override fun equals(other: Any?): Boolean = 49 | other is DateProgression<*> && 50 | ((isEmpty() && other.isEmpty()) || 51 | (first == other.first && last == other.last && step == other.step)) 52 | 53 | override fun hashCode(): Int = 54 | if(isEmpty()) -1 55 | else Objects.hash(first, last, step) 56 | 57 | override fun toString(): String = 58 | if(step > Duration.ZERO) "$first..$last step $step" 59 | else "$first downTo $last step ${step.negated()}" 60 | 61 | /** 62 | * An iterator over a progression of values of type [java.util.Date] 63 | * 64 | * @property step the number by which the value is incremented on each step. 65 | */ 66 | internal class DateProgressionIterator(first: T, last: T, val step: Duration) : DateIterator() { 67 | 68 | private val stepMillis = step.toMillis() 69 | private val _finalElement: T = last 70 | private var _hasNext: Boolean = if(step.isPositive) first <= last else first >= last 71 | private var _next: T = if(_hasNext) first else _finalElement 72 | 73 | override fun hasNext(): Boolean = _hasNext 74 | 75 | override fun nextDate(): T { 76 | val value = _next 77 | if(value == _finalElement) { 78 | if(!_hasNext) 79 | throw NoSuchElementException() 80 | _hasNext = false 81 | } else { 82 | @Suppress("UNCHECKED_CAST") 83 | _next = _next.plus(stepMillis) as T 84 | } 85 | return value 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/ranges/DateRange.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes.ranges 2 | 3 | import mu.KLogging 4 | import java.time.Duration 5 | import java.util.Date 6 | 7 | /** 8 | * A range of `java.util.Date` 9 | * 10 | * @autor debop 11 | * @since 18. 4. 16 12 | */ 13 | open class DateRange(start: Date, endInclusive: Date) 14 | : DateProgression(start, endInclusive, Duration.ofDays(1)), ClosedRange { 15 | 16 | companion object : KLogging() { 17 | @JvmField 18 | val EMPTY: DateRange = DateRange(Date(1L), Date(0L)) 19 | 20 | fun fromClosedRange(start: Date, end: Date): DateRange = 21 | DateRange(start, end) 22 | } 23 | 24 | override val start: Date get() = first 25 | override val endInclusive: Date get() = last 26 | 27 | override fun contains(value: Date): Boolean = first <= value && value <= last 28 | 29 | override fun isEmpty(): Boolean = first > last 30 | 31 | override fun toString(): String = "$first..$last" 32 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/ranges/ProgressionExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes.ranges 2 | 3 | import com.github.debop.javatimes.minus 4 | import com.github.debop.javatimes.plus 5 | import com.github.debop.javatimes.toEpochMillis 6 | import java.time.Duration 7 | import java.time.Instant 8 | import java.time.temporal.ChronoUnit 9 | import java.time.temporal.Temporal 10 | import java.util.Date 11 | 12 | 13 | private fun mod(a: Int, b: Int): Int { 14 | val mod = a % b 15 | return if (mod >= 0) mod else mod + b 16 | } 17 | 18 | private fun mod(a: Long, b: Long): Long { 19 | val mod = a % b 20 | return if (mod >= 0) mod else mod + b 21 | } 22 | 23 | private fun differenceModulo(a: Int, b: Int, c: Int): Int = 24 | mod(mod(a, c) - mod(b, c), c) 25 | 26 | private fun differenceModulo(a: Long, b: Long, c: Long): Long = 27 | mod(mod(a, c) - mod(b, c), c) 28 | 29 | internal fun getProgressionLastElement(start: Instant, end: Instant, step: Duration): Instant = when { 30 | step > Duration.ZERO -> end.minusMillis(differenceModulo(end.toEpochMilli(), start.toEpochMilli(), step.toMillis())) 31 | step < Duration.ZERO -> end.plusMillis(differenceModulo(start.toEpochMilli(), end.toEpochMilli(), -step.toMillis())) 32 | else -> throw IllegalArgumentException("step must not be zero") 33 | } 34 | 35 | internal fun getProgressionLastElement(start: Date, end: Date, step: Duration): Date = when { 36 | step > Duration.ZERO -> end - differenceModulo(end.time, start.time, step.toMillis()) 37 | step < Duration.ZERO -> end + differenceModulo(start.time, end.time, -step.toMillis()) 38 | else -> throw IllegalArgumentException("step must not be zero") 39 | } 40 | 41 | @Suppress("UNCHECKED_CAST") 42 | internal fun getProgressionLastElement(start: T, end: T, step: Duration): T 43 | where T : Temporal, T : Comparable = when { 44 | step > Duration.ZERO -> end.minus(differenceModulo(end.toEpochMillis(), start.toEpochMillis(), step.toMillis()), ChronoUnit.MILLIS) as T 45 | step < Duration.ZERO -> end.plus(differenceModulo(start.toEpochMillis(), end.toEpochMillis(), -step.toMillis()), ChronoUnit.MILLIS) as T 46 | else -> throw IllegalArgumentException("step must not be zero") 47 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/ranges/RangeExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes.ranges 2 | 3 | import java.time.Duration 4 | import java.time.Instant 5 | import java.time.LocalDate 6 | import java.time.LocalDateTime 7 | import java.time.LocalTime 8 | import java.time.OffsetDateTime 9 | import java.time.OffsetTime 10 | import java.time.Year 11 | import java.time.YearMonth 12 | import java.time.ZonedDateTime 13 | import java.time.temporal.Temporal 14 | import java.util.Date 15 | 16 | typealias YearRange = TemporalRange 17 | typealias YearMonthRange = TemporalRange 18 | 19 | typealias LocalDateRange = TemporalRange 20 | typealias LocalTimeRange = TemporalRange 21 | typealias LocalDateTimeRange = TemporalRange 22 | typealias OffsetTimeRange = TemporalRange 23 | typealias OffsetDateTimeRange = TemporalRange 24 | typealias ZonedDateTimeRange = TemporalRange 25 | 26 | typealias InstantRange = TemporalRange 27 | 28 | 29 | operator fun Year.rangeTo(endInclusive: Year): YearRange = YearRange(this, endInclusive) 30 | operator fun YearMonth.rangeTo(endInclusive: YearMonth): YearMonthRange = YearMonthRange(this, endInclusive) 31 | 32 | operator fun Date.rangeTo(endInclusive: Date): DateRange = DateRange(this, endInclusive) 33 | 34 | operator fun LocalDate.rangeTo(endInclusive: LocalDate): LocalDateRange = LocalDateRange(this, endInclusive) 35 | operator fun LocalTime.rangeTo(endInclusive: LocalTime): LocalTimeRange = LocalTimeRange(this, endInclusive) 36 | 37 | operator fun LocalDateTime.rangeTo(endInclusive: LocalDateTime): LocalDateTimeRange = 38 | LocalDateTimeRange(this, endInclusive) 39 | 40 | operator fun OffsetTime.rangeTo(endInclusive: OffsetTime): OffsetTimeRange = 41 | OffsetTimeRange(this, endInclusive) 42 | 43 | operator fun OffsetDateTime.rangeTo(endInclusive: OffsetDateTime): OffsetDateTimeRange = 44 | OffsetDateTimeRange(this, endInclusive) 45 | 46 | operator fun ZonedDateTime.rangeTo(endInclusive: ZonedDateTime): ZonedDateTimeRange = 47 | ZonedDateTimeRange(this, endInclusive) 48 | 49 | operator fun Instant.rangeTo(endInclusive: Instant): InstantRange = InstantRange(this, endInclusive) 50 | 51 | 52 | @Suppress("UNCHECKED_CAST") 53 | fun TemporalRange.asSequence(step: Duration): Sequence where T : Temporal, T : Comparable { 54 | return sequence { 55 | var current = first 56 | while(current <= last) { 57 | yield(current) 58 | current = current.plus(step) as T 59 | } 60 | } 61 | } 62 | 63 | fun YearMonthRange.asSequence(): Sequence = sequence { 64 | var current = first 65 | while(current <= last) { 66 | yield(current) 67 | current = current.plusMonths(1) 68 | } 69 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/ranges/TemporalProgression.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes.ranges 2 | 3 | import com.github.debop.javatimes.TemporalIterator 4 | import com.github.debop.javatimes.isPositive 5 | import java.io.Serializable 6 | import java.time.Duration 7 | import java.time.temporal.Temporal 8 | import java.util.Objects 9 | 10 | fun temporalProgressionOf(start: T, endInclusive: T, step: Duration = Duration.ofMillis(1)): TemporalProgression 11 | where T : Temporal, T : Comparable { 12 | return TemporalProgression.fromClosedRange(start, endInclusive, step) 13 | } 14 | 15 | /** 16 | * A Progression of value of [java.time.temporal.Temporal] 17 | * 18 | * @autor debop 19 | * @since 18. 4. 16 20 | */ 21 | open class TemporalProgression internal constructor(start: T, 22 | endInclusive: T, 23 | val step: Duration = Duration.ofMillis(1L)) 24 | : Iterable, Serializable where T : Temporal, T : Comparable { 25 | 26 | init { 27 | require(!step.isZero) { "step must be non-zero" } 28 | } 29 | 30 | companion object { 31 | @JvmStatic 32 | @JvmOverloads 33 | fun fromClosedRange(start: T, endInclusive: T, step: Duration = Duration.ofMillis(1)) 34 | : TemporalProgression where T : Temporal, T : Comparable { 35 | require(!step.isZero) { "step must be non-zero" } 36 | return TemporalProgression(start, endInclusive, step) 37 | } 38 | } 39 | 40 | val first: T = start 41 | val last: T = getProgressionLastElement(start, endInclusive, step) 42 | open fun isEmpty(): Boolean = if(step.isPositive) first > last else first < last 43 | 44 | override fun equals(other: Any?): Boolean = when(other) { 45 | null -> false 46 | !is TemporalProgression<*> -> false 47 | else -> (isEmpty() && other.isEmpty()) || 48 | (first == other.first && last == other.last && step == other.step) 49 | } 50 | 51 | override fun hashCode(): Int = if(isEmpty()) -1 else Objects.hash(first, last, step) 52 | 53 | override fun toString(): String = when { 54 | step.isZero -> "$first..$last" 55 | step.isPositive -> "$first..$last step $step" 56 | else -> "$first downTo $last step ${step.negated()}" 57 | } 58 | 59 | @Suppress("UNCHECKED_CAST") 60 | fun sequence(): Sequence = sequence { 61 | var current = first 62 | while(current <= last) { 63 | yield(current) 64 | current = current.plus(step) as T 65 | } 66 | } 67 | 68 | override fun iterator(): Iterator = TemporalProgressionIterator(first, last, step) 69 | 70 | /** 71 | * An iterator over a progression of values of type [java.time.temporal.Temporal]. 72 | * 73 | * @property step the number by which the value is incremented on each step. 74 | */ 75 | internal class TemporalProgressionIterator(first: T, 76 | last: T, 77 | val step: Duration = Duration.ofMillis(1L)) 78 | : TemporalIterator() where T : Temporal, T : Comparable { 79 | 80 | private val _finalElement: T = last 81 | private var _hasNext: Boolean = if(step.isPositive) first <= last else first >= last 82 | private var _next: T = if(_hasNext) first else _finalElement 83 | 84 | override fun hasNext(): Boolean = _hasNext 85 | 86 | override fun nextTemporal(): T { 87 | val value = _next 88 | if(value == _finalElement) { 89 | if(!_hasNext) 90 | throw NoSuchElementException() 91 | _hasNext = false 92 | } else { 93 | @Suppress("UNCHECKED_CAST") 94 | _next = _next.plus(step) as T 95 | } 96 | return value 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/ranges/TemporalRange.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes.ranges 2 | 3 | import mu.KLogging 4 | import java.io.Serializable 5 | import java.time.Duration 6 | import java.time.LocalDateTime 7 | import java.time.temporal.Temporal 8 | 9 | 10 | fun temporalRangeOf(start: T, endInclusive: T): TemporalRange 11 | where T : Temporal, T : Comparable { 12 | return TemporalRange.fromClosedRange(start, endInclusive) 13 | } 14 | 15 | @Suppress("UNCHECKED_CAST") 16 | fun TemporalRange.Companion.fromClosedRange(start: T, 17 | endInclusive: T): TemporalRange 18 | where T : Temporal, T : Comparable { 19 | return TemporalRange(start, endInclusive) 20 | } 21 | 22 | /** 23 | * A range of [Temporal] 24 | * 25 | * @autor debop 26 | * @since 18. 4. 16 27 | */ 28 | class TemporalRange(start: T, endInclusive: T) 29 | : TemporalProgression(start, endInclusive, Duration.ofMillis(1L)), ClosedRange, Serializable 30 | where T : Temporal, T : Comparable { 31 | 32 | companion object : KLogging() { 33 | @JvmField 34 | val EMPTY = TemporalRange(LocalDateTime.MAX, LocalDateTime.MIN) 35 | } 36 | 37 | override val start: T get() = first 38 | 39 | override val endInclusive: T get() = last 40 | 41 | override fun contains(value: T): Boolean = value in first..last 42 | 43 | override fun isEmpty(): Boolean = first > last 44 | 45 | override fun toString(): String = "$first..$last" 46 | 47 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/ranges/TemporalRangeExtensions.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("UNCHECKED_CAST") 2 | 3 | package com.github.debop.javatimes.ranges 4 | 5 | import com.github.debop.javatimes.days 6 | import com.github.debop.javatimes.hours 7 | import com.github.debop.javatimes.minutes 8 | import com.github.debop.javatimes.monthPeriod 9 | import com.github.debop.javatimes.seconds 10 | import com.github.debop.javatimes.weekPeriod 11 | import com.github.debop.javatimes.yearPeriod 12 | import java.time.temporal.Temporal 13 | 14 | fun TemporalRange.chunkYear(size: Int): Sequence> where T : Temporal, T : Comparable { 15 | require(size > 0) { "size must be positive number. [$size]" } 16 | 17 | return sequence { 18 | var current = start 19 | val increment = size.yearPeriod() 20 | while(current <= endInclusive) { 21 | yield(List(size) { (current + it.yearPeriod()) as T }.takeWhile { it <= endInclusive }) 22 | current = (current + increment) as T 23 | } 24 | } 25 | } 26 | 27 | fun TemporalRange.chunkMonth(size: Int): Sequence> where T : Temporal, T : Comparable { 28 | require(size > 0) { "size must be positive number. [$size]" } 29 | 30 | return sequence { 31 | var current = start 32 | val increment = size.monthPeriod() 33 | while(current <= endInclusive) { 34 | yield(List(size) { (current + it.monthPeriod()) as T }.takeWhile { it <= endInclusive }) 35 | current = (current + increment) as T 36 | } 37 | } 38 | } 39 | 40 | fun TemporalRange.chunkWeek(size: Int): Sequence> where T : Temporal, T : Comparable { 41 | require(size > 0) { "size must be positive number. [$size]" } 42 | 43 | return sequence { 44 | var current = start 45 | val increment = size.weekPeriod() 46 | while(current <= endInclusive) { 47 | yield(List(size) { (current + it.weekPeriod()) as T }.takeWhile { it <= endInclusive }) 48 | current = (current + increment) as T 49 | } 50 | } 51 | } 52 | 53 | 54 | fun TemporalRange.chunkDay(size: Int): Sequence> where T : Temporal, T : Comparable { 55 | require(size > 0) { "size must be positive number. [$size]" } 56 | 57 | return sequence { 58 | var current = start 59 | val increment = size.days() 60 | while(current <= endInclusive) { 61 | yield(List(size) { (current + it.days()) as T }.takeWhile { it <= endInclusive }) 62 | current = (current + increment) as T 63 | } 64 | } 65 | } 66 | 67 | fun TemporalRange.chunkHour(size: Int): Sequence> where T : Temporal, T : Comparable { 68 | require(size > 0) { "size must be positive number. [$size]" } 69 | 70 | return sequence { 71 | var current = start 72 | val increment = size.hours() 73 | while(current <= endInclusive) { 74 | yield(List(size) { (current + it.hours()) as T }.takeWhile { it <= endInclusive }) 75 | current = (current + increment) as T 76 | } 77 | } 78 | } 79 | 80 | fun TemporalRange.chunkMinute(size: Int): Sequence> where T : Temporal, T : Comparable { 81 | require(size > 0) { "size must be positive number. [$size]" } 82 | 83 | return sequence { 84 | var current = start 85 | val increment = size.minutes() 86 | while(current <= endInclusive) { 87 | yield(List(size) { (current + it.minutes()) as T }.takeWhile { it <= endInclusive }) 88 | current = (current + increment) as T 89 | } 90 | } 91 | } 92 | 93 | 94 | fun TemporalRange.chunkSecond(size: Int): Sequence> where T : Temporal, T : Comparable { 95 | require(size > 0) { "size must be positive number. [$size]" } 96 | 97 | return sequence { 98 | var current = start 99 | val increment = size.seconds() 100 | while(current <= endInclusive) { 101 | yield(List(size) { (current + it.seconds()) as T }.takeWhile { it <= endInclusive }) 102 | current = (current + increment) as T 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /java-time/src/main/kotlin/com/github/debop/javatimes/ranges/TimeProgression.kt: -------------------------------------------------------------------------------- 1 | package com.github.debop.javatimes.ranges 2 | 3 | import java.sql.Time 4 | import java.time.Duration 5 | 6 | /** 7 | * A Progression of value of [Time] 8 | * 9 | * @autor debop 10 | * @since 18. 4. 16 11 | */ 12 | class TimeProgression(start: Time, endInclusive: Time, step: Duration) : DateProgression