├── .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 | [](https://travis-ci.org/debop/koda-time)
3 | [](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 | [](https://travis-ci.org/debop/koda-time)
4 | [](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