diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 00000000..ff30e87e
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,5 @@
+github: jodastephen
+open_collective: joda
+tidelift: maven/org.threeten:threeten-extra
+
+# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository
diff --git a/.github/SECURITY.md b/.github/SECURITY.md
new file mode 100644
index 00000000..f8ff1f13
--- /dev/null
+++ b/.github/SECURITY.md
@@ -0,0 +1,10 @@
+# Security Policy
+
+## Supported Versions
+
+If a security issue occurs, only the latest version is guaranteed to be patched.
+
+## Reporting a Vulnerability
+
+To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security).
+Tidelift will coordinate the fix and disclosure.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..26aa42f9
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,10 @@
+# Dependabot config
+
+version: 2
+updates:
+- package-ecosystem: "maven"
+ directory: "/"
+ schedule:
+ interval: weekly
+ time: "02:30"
+ open-pull-requests-limit: 20
diff --git a/.github/maven-settings.xml b/.github/maven-settings.xml
new file mode 100644
index 00000000..5fbfc607
--- /dev/null
+++ b/.github/maven-settings.xml
@@ -0,0 +1,13 @@
+
+
+
+ github
+
+ ${GITHUB_TOKEN}
+
+
+
+
diff --git a/.github/website.sh b/.github/website.sh
new file mode 100644
index 00000000..d112a446
--- /dev/null
+++ b/.github/website.sh
@@ -0,0 +1,24 @@
+
+echo "## setup..."
+git config --global user.name "Stephen Colebourne (CI)"
+git config --global user.email "scolebourne@joda.org"
+cd target
+
+echo "## clone..."
+git clone https://${GITHUB_TOKEN}@github.com/ThreeTen/threeten.github.io.git
+cd threeten.github.io
+git status
+
+echo "## copy..."
+rm -rf threeten-extra/
+cp -R ../site threeten-extra/
+
+echo "## update..."
+git add -A
+git status
+git commit --message "Update threeten-extra from Travis: Build $TRAVIS_BUILD_NUMBER"
+
+echo "## push..."
+git push origin main
+
+echo "## done"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..5eb595c8
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,66 @@
+name: Build
+
+on:
+ push:
+ branches:
+ - '*'
+ tags:
+ - 'v*'
+ - 'website*'
+ pull_request:
+ branches:
+ - 'main'
+ schedule:
+ - cron: '41 19 * * 2'
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ permissions:
+ security-events: write # for github/codeql-action
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ java: [8, 11]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Set up JDK
+ uses: actions/setup-java@v3
+ with:
+ java-version: ${{ matrix.java }}
+ distribution: 'temurin'
+ cache: 'maven'
+
+ - name: Maven version
+ run: |
+ mkdir -p ./.mvn
+ echo '-e -B -DtrimStackTrace=false' > ./.mvn/maven.config
+ mvn --version
+ mkdir -p target
+
+ - name: Initialize CodeQL
+ if: matrix.java == '11'
+ uses: github/codeql-action/init@v2
+ with:
+ languages: java
+
+ - name: Maven build
+ run: |
+ mvn install site
+
+ - name: Perform CodeQL Analysis
+ if: matrix.java == '11'
+ uses: github/codeql-action/analyze@v2
+
+ - name: Website
+ if: matrix.java == '11' && github.event_name == 'push' && (startsWith(github.ref, 'refs/tags/website') || startsWith(github.ref, 'refs/tags/v'))
+ env:
+ GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN_GH }}
+ run: |
+ chmod +x ./.github/website.sh
+ .github/website.sh
diff --git a/.gitignore b/.gitignore
index 8470331c..bfab9001 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,5 @@
/*.iml
/*.ipr
/*.iws
+/pom.xml.releaseBackup
+/release.properties
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index c0898988..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file enables the Travis continuous integration system, which
-# automatically builds and tests the project for each GitHub commit or
-# pull request on three separate JDKs.
-#
-# For more information, see https://travis-ci.org
-
-language: java
-
-jdk:
- - oraclejdk8
diff --git a/LICENSE.txt b/LICENSE.txt
index fcdfc8f0..f8f6e594 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,31 +1,29 @@
-/*
- * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * * Neither the name of JSR-310 nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
+Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos.
+
+All rights reserved.
+
+* Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of JSR-310 nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
index bfce2239..12a4219b 100644
--- a/README.md
+++ b/README.md
@@ -11,19 +11,44 @@ This project provides some of those additional classes as a well-tested and reli
### Documentation
Various documentation is available:
-* The [home page](http://www.threeten.org/threeten-extra/)
-* The [user guide](http://www.threeten.org/threeten-extra/userguide.html)
-* The [Javadoc](http://www.threeten.org/threeten-extra/apidocs/index.html)
+* The [home page](https://www.threeten.org/threeten-extra/)
+* The [user guide](https://www.threeten.org/threeten-extra/userguide.html)
+* The [Javadoc](https://www.threeten.org/threeten-extra/apidocs/org.threeten.extra/module-summary.html)
### Releases
-Release 1.0 is the current release.
-This release is considered stable and worthy of the 1.x tag.
+Release 1.8.0 is the current release.
+This release is considered stable and worthy of the 1.x tag as per [SemVer](https://semver.org/spec/v2.0.0.html).
ThreeTen-Extra requires Java SE 8 or later and has no dependencies.
-Available in the [Maven Central repository](http://search.maven.org/#artifactdetails|org.threeten|threeten-extra|1.0|jar)
+Available in the [Maven Central repository](https://search.maven.org/search?q=g:org.threeten%20AND%20a:threeten-extra&core=gav)
+
+```
+
+ org.threeten
+ threeten-extra
+ 1.8.0
+
+```
+
+
### Support
-Please use GitHub [issues](https://github.com/ThreeTen/threeten-extra/issues) and Pull Requests for support.
+Please use [Stack Overflow](https://stackoverflow.com/search?q=threeten-extra) for general usage questions.
+GitHub [issues](https://github.com/ThreeTen/threeten-extra/issues) and [pull requests](https://github.com/ThreeTen/threeten-extra/pulls)
+should be used when you want to help advance the project.
+Commercial support is available via the
+[Tidelift subscription](https://tidelift.com/subscription/pkg/maven-org-threeten-threeten-extra?utm_source=maven-org-threeten-threeten-extra&utm_medium=referral&utm_campaign=readme).
+
+To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security).
+Tidelift will coordinate the fix and disclosure.
+
+
+### Release process
+
+* Update version (README.md, index.md, changes.xml)
+* Commit and push
+* Run `mvn clean release:clean release:prepare release:perform` on Java 11
+* Website will be built and released by GitHub Actions
diff --git a/pom.xml b/pom.xml
index 391cd616..84075d26 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,16 +1,17 @@
-
+
+
+
+
+
4.0.0org.threetenthreeten-extrajarThreeTen-Extra
- 1.1-SNAPSHOT
- Additional functionality that enhances JSR-310 dates and times in JDK 8
+ 1.8.1-SNAPSHOT
+ Additional functionality that enhances JSR-310 dates and times in Java SE 8 and laterhttps://www.threeten.org/threeten-extra
@@ -25,7 +26,6 @@
jodastephenStephen Colebourne
- Project Lead
@@ -38,53 +38,145 @@
Carlo Daporhttps://github.com/catull
+
+ Octavi Fornés
+ https://github.com/ofornes
+
+
+ Nick Glorioso
+ https://github.com/nglorioso
+ Jim Goughhttps://github.com/jpgough
+
+ Monica Guzik
+ https://github.com/monicagg
+ Christian Heinemannhttps://github.com/cheinema
+
+ John Hill
+ https://github.com/jjcard
+
+
+ Michael Hixson
+ https://github.com/michaelhixson
+ Stephen A. Imhoffhttps://github.com/Clockwork-Muse
+
+ Johannes Jensen
+ https://github.com/spand
+
+
+ M. Justin
+ https://github.com/mjustin
+
+
+ Bruno P. Kinoshita
+ https://github.com/kinow
+
+
+ Kurt Alfred Kluever
+ https://github.com/kluever
+
+
+ Martin Kröning
+ https://github.com/mwkroening
+
+
+ Harald Kuhr
+ https://github.com/haraldk
+
+
+ Sebastian Lövdahl
+ https://github.com/slovdahl
+ Steven McCoyhttps://github.com/steve-o
- Bjorn Raupach
+ JB Nizet
+ https://github.com/jnizet
+
+
+ Steven Paligo
+ https://github.com/stevenpaligo
+
+
+ Bjørn Erik Pedersen
+ https://github.com/bep
+
+
+ Erik van Paassen
+ https://github.com/evpaassen
+
+
+ Max Poliakov
+ https://github.com/Jaimies
+
+
+ Björn Raupachhttps://github.com/raupachz
+
+ Michel Schudel
+ https://github.com/MichelSchudel
+
+
+ Michał Sobkiewicz
+ https://github.com/perceptron8
+ Nils Sommerhttps://github.com/nsommer
+
+ Tristan Swadell
+ https://github.com/TristonianJones
+
+
+ Roberto Tyley
+ https://github.com/rtyley
+
+
+ Dimo Velev
+ https://github.com/dimovelev
+ BSD 3-clause
- https://raw.githubusercontent.com/ThreeTen/threeten-extra/master/LICENSE.txt
+ https://raw.githubusercontent.com/ThreeTen/threeten-extra/main/LICENSE.txtrepo
- scm:git:git@github.com:ThreeTen/threeten-extra.git
- scm:git:git@github.com:ThreeTen/threeten-extra.git
+ scm:git:https://github.com/ThreeTen/threeten-extra.git
+ scm:git:https://github.com/ThreeTen/threeten-extra.githttps://github.com/ThreeTen/threeten-extra
+ HEADThreeTen.org
- http://www.threeten.org
+ https://www.threeten.org
+
+ src/main/resources
+ META-INF${project.basedir}
@@ -92,69 +184,44 @@
LICENSE.txt
-
- ${basedir}/src/main/resources
-
+
org.apache.maven.pluginsmaven-checkstyle-plugin
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
- run-checkstyle
- process-sources
+ enforce-maven
- checkstyle
+ enforce
+
+
+
+ 3.6.0
+
+
+
-
- org.apache.maven.plugins
- maven-surefire-plugin
-
-
- **/Test*.java
-
- -Xmx2G
- classes
- 4
-
-
-
- usedefaultlisteners
- false
-
-
- listener
- org.testng.reporters.ExitCodeListener
-
-
- reporter
- org.testng.reporters.FailedReporter,org.testng.reporters.XMLReporter,org.testng.reporters.JUnitReportReporter
-
-
-
-
-
- org.apache.maven.plugins
- maven-jar-plugin
-
-
- ${project.build.outputDirectory}/META-INF/MANIFEST.MF
-
- true
- true
-
-
-
-
+
org.apache.felixmaven-bundle-plugin
- 2.4.0
+ ${maven-bundle-plugin.version}bundle-manifest
@@ -162,22 +229,30 @@
manifest
+
+
+ org.threeten.extra,org.threeten.extra.chrono,org.threeten.extra.scale
+ osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"
+
+
+
org.apache.maven.plugins
- maven-javadoc-plugin
-
-
- attach-javadocs
- package
-
- jar
-
-
-
+ maven-jar-plugin
+
+
+ ${project.build.outputDirectory}/META-INF/MANIFEST.MF
+
+ true
+ true
+
+
+
+
org.apache.maven.pluginsmaven-source-plugin
@@ -191,48 +266,6 @@
-
- org.apache.maven.plugins
- maven-site-plugin
-
- true
-
-
-
- lt.velykis.maven.skins
- reflow-velocity-tools
- 1.1.1
-
-
- org.apache.velocity
- velocity
- 1.7
-
-
-
-
- com.github.github
- site-maven-plugin
- 0.12
-
-
- github-site
-
- site
-
- site-deploy
-
-
-
- Create website for ${project.artifactId} v${project.version}
- threeten-extra
- true
- github
- ThreeTen
- threeten.github.io
- refs/heads/master
-
-
@@ -268,6 +301,11 @@
maven-dependency-plugin${maven-dependency-plugin.version}
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ ${maven-enforcer-plugin.version}
+ org.apache.maven.pluginsmaven-gpg-plugin
@@ -318,11 +356,6 @@
maven-resources-plugin${maven-resources-plugin.version}
-
- org.apache.maven.plugins
- maven-site-plugin
- ${maven-site-plugin.version}
- org.apache.maven.pluginsmaven-source-plugin
@@ -343,6 +376,94 @@
maven-toolchains-plugin${maven-toolchains-plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-release-plugin
+ ${maven-release-plugin.version}
+
+ -Doss.repo
+ true
+ v@{project.version}
+ true
+
+
+
+ org.kohsuke
+ github-api
+ ${github-api.version}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${maven-checkstyle-plugin.version}
+
+
+ run-checkstyle
+ process-sources
+
+ checkstyle
+
+
+
+
+ module-info.java
+
+
+
+ com.puppycrawl.tools
+ checkstyle
+ ${checkstyle.version}
+
+
+
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+ ${spotbugs-maven-plugin.version}
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco-maven-plugin.version}
+
+
+ jacoco-initialize
+
+ prepare-agent
+
+
+
+ jacoco-site
+ package
+
+ report
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-site-plugin
+ ${maven-site-plugin.version}
+
+ true
+
+
+
+ org.joda.external
+ reflow-velocity-tools
+ ${reflow-velocity-tools.version}
+
+
+
+
org.eclipse.m2elifecycle-mapping
@@ -354,56 +475,88 @@
org.apache.felixmaven-bundle-plugin
- [2.4.0,)
+ [2.5.4,)manifest
-
-
-
-
-
- org.apache.maven.plugins
- maven-toolchains-plugin
- [1.0,)
-
- toolchain
-
-
-
-
+
-
- org.apache.maven.plugins
- maven-checkstyle-plugin
- ${maven-checkstyle-plugin.version}
-
-
- 3.0.4
-
+
+
+
+ org.junit
+ junit-bom
+ ${junit.version}
+ pom
+ import
+
+
+
+
+ org.joda
+ joda-convert
+ ${joda-convert.version}
+ compile
+ true
+ com.google.guavaguava
- 19.0
+ ${guava.version}test
+
+
+ com.google.code.findbugs
+ jsr305
+
+
+ org.checkerframework
+ checker-qual
+
+
+ com.google.errorprone
+ error_prone_annotations
+
+
+ com.google.j2objc
+ j2objc-annotations
+
+
- org.testng
- testng
- 6.10
+ com.google.guava
+ guava-testlib
+ ${guava.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.junit-pioneer
+ junit-pioneer
+ ${pioneer.version}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${assertj.version}test
@@ -411,24 +564,39 @@
+
org.apache.maven.pluginsmaven-project-info-reports-plugin
- ${maven-project-info-plugin.version}
+ ${maven-project-info-reports-plugin.version}
+ ci-managementdependenciesdependency-info
- issue-tracking
- license
- project-team
+ issue-management
+ licenses
+ teamscmsummary
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${maven-checkstyle-plugin.version}
+
+ false
+ false
+ false
+ module-info.java
+
+
+
org.apache.maven.pluginsmaven-javadoc-plugin
@@ -441,14 +609,16 @@
+
org.apache.maven.pluginsmaven-surefire-report-plugin${maven-surefire-report-plugin.version}
- true
+ true
+
org.apache.maven.pluginsmaven-changes-plugin
@@ -461,26 +631,34 @@
+
org.apache.maven.plugins
- maven-jxr-plugin
- ${maven-jxr-plugin.version}
-
-
-
- jxr
-
-
-
+ maven-pmd-plugin
+ ${maven-pmd-plugin.version}
+
+ 100
+ ${maven.compiler.target}
+
+ module-info.java
+
+
+
- org.apache.maven.plugins
- maven-checkstyle-plugin
- ${maven-checkstyle-plugin.version}
+ com.github.spotbugs
+ spotbugs-maven-plugin
+ ${spotbugs-maven-plugin.version}
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco-maven-plugin.version}
- checkstyle
+ report
@@ -493,83 +671,209 @@
sonatype-threeten-stagingSonatype OSS staging repository
- http://oss.sonatype.org/service/local/staging/deploy/maven2/
+ https://oss.sonatype.org/service/local/staging/deploy/maven2/defaultfalsesonatype-threeten-snapshotSonatype OSS snapshot repository
- http://oss.sonatype.org/content/repositories/threeten-snapshots
+ https://oss.sonatype.org/content/repositories/threeten-snapshotsdefault
- http://oss.sonatype.org/content/repositories/threeten-releases
+ https://oss.sonatype.org/content/repositories/threeten-releases
+
- activate-jdk8
+ java8
-
- jdk8
-
+ [1.6,9)
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ module-info.java
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ module-info.java
+
+
+
+
+
+
+
+
org.apache.maven.plugins
- maven-toolchains-plugin
+ maven-javadoc-plugin
+
+ true
+
+
+
+
+
+
+
+ java9plus
+
+ [9,)
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-dependencies
+ compile
+
+ copy-dependencies
+
+
+ ${project.build.directory}/dependencies
+ true
+ true
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ default-test
+
+ true
+ --add-modules org.joda.convert --module-path ${project.build.directory}/dependencies ${argLine}
+
+
+
+ test-without-modules
+ test
+
+ test
+
+
+ false
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ attach-javadocs
+ package
+
+ jar
+
+
+
+
+ 11
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
- validate
+ default-compile
+
+ 9
+
+
+
+
+ base-compile
- toolchain
+ compile
+
+
+ module-info.java
+
+
+
-
-
- 1.8
- oracle
-
-
+ 8
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+ 11
+
+
+
+
+
- repo-sign-artifacts
+ release-artifactsoss.repo
- true
+
org.apache.maven.plugins
- maven-toolchains-plugin
+ maven-enforcer-plugin
- validate
+ enforce-java
- toolchain
+ enforce
+
+
+
+ [9,)
+
+
+
-
-
-
- 1.8
- oracle
-
-
-
+
org.apache.maven.pluginsmaven-gpg-plugin
@@ -583,6 +887,43 @@
+
+
+
+
+ de.jutzig
+ github-release-plugin
+ ${github-release-plugin.version}
+
+ Release v${project.version}
+ See the [change notes](https://www.threeten.org/threeten-extra/changes-report.html) for more information.
+ v${project.version}
+ true
+
+
+
+ github-releases
+ deploy
+
+ release
+
+
+
+
+
+
+ org.sonatype.plugins
+ nexus-staging-maven-plugin
+ ${nexus-staging-maven-plugin.version}
+ true
+
+ https://oss.sonatype.org/
+ sonatype-joda-staging
+ true
+ true
+ 20
+
+
@@ -590,43 +931,58 @@
+
+ 3.24.2
+ 2.2.3
+ 5.10.1
+ 1.9.1
+ 32.1.3-jre
- 2.5.5
- 2.11
- 2.16
- 2.6.1
- 3.3
- 2.8.2
- 2.10
- 1.6
- 2.5.2
- 2.6
- 2.10.3
- 2.5
- 3.4
- 3.5
- 2.8
+ 3.7.1
+ 5.1.8
+ 2.12.1
+ 3.2.0
+ 3.3.2
+ 3.13.0
+ 3.1.1
+ 3.6.1
+ 3.4.1
+ 3.2.3
+ 3.1.0
+ 3.4.0
+ 3.6.3
+ 3.3.0
+ 3.12.0
+ 3.21.2
+ 3.5.0
+ 3.0.12.4
- 2.7
- 3.4
- 2.4
- 2.18.1
- 2.18.1
- 1.1
+ 3.3.1
+ 3.12.1
+ 3.2.1
+ 3.2.5
+ 3.2.5
+ 3.1.0
+
+ 1.321
+ 1.4.0
+ 0.8.12
+ 1.6.13
+ 1.2
+ 4.8.4.01.81.81.8true
- true
- true
- truefalsetrue
- -Xdoclint:none
+ none
- ${project.basedir}/src/main/checkstyle/checkstyle.xml
+ 8.41
+ src/main/checkstyle/checkstyle.xml
+ falseUTF-8UTF-8
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 79c6a7f6..359abbc3 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -7,7 +7,247 @@
-
+
+
+ Add HourMinute. New class representing time but constrained to hours and minutes only.
+
+
+ Add YearHalf. New class representing a half-year, such as 2024-H1.
+
+
+ Add Interval.of(duration, end), providing another way to create an interval.
+
+
+
+
+ Adds offset for AccountingChronology, which is essential for some retail calendars, particularly the NRF.
+ Fixes #223, #201.
+
+
+ Update CodeQL.
+
+
+ Switch master to main.
+
+
+
+
+ Add utilities to work with durations as numbers.
+ Fixes #147.
+
+
+ Fix YearWeek.isSupported.
+ Fixes #192.
+
+
+ Fix InternationalFixedEra era value.
+ Fixes #205.
+
+
+
+
+
+ Additional comparison methods on UtcInstant/TaiInstant.
+ Fixes #189.
+
+
+ Additional comparison methods on Interval.
+ Fixes #174.
+
+
+ Meaningful factory methods for unbounded intervals.
+ Fixes #174.
+
+
+ Add Go-compatible duration parsing.
+ Fixes #182.
+
+
+ Cache UtcInstant::toString().
+ Fixes #177.
+
+
+ Enhance OffsetDate tests.
+ Fixes #181.
+
+
+
+
+ Add OffsetDate.
+ Fixes #137.
+
+
+ Add factory methods taking Year and Quarter objects.
+ Fixes #155, #156.
+
+
+ Add isZero(), isPositive(), isNegative() to temporal amount classes.
+ Fixes #148.
+
+
+ Make YearWeek implement Temporal.
+ Fixes #165, #163, #115.
+
+
+ Fix UtcInstant.isLeapSecond().
+ Fixes #153.
+
+
+ Add Farsi translations for word-based formatting.
+ Fixes #131.
+
+
+ Add Bulgarian translations for word-based formatting.
+ Fixes #129.
+
+
+ Add Finnish translations for word-based formatting.
+ Fixes #127.
+
+
+ Add Swedish translations for word-based formatting.
+ Fixes #126.
+
+
+ Add Norwegian Bokml and Norwegian Nynorsk translations for word-based formatting.
+ Fixes #125.
+
+
+ Fix Interval Javadoc.
+ Fixes #171.
+
+
+ Fix Interval Javadoc.
+ Fixes #159.
+
+
+
+
+ Add stream-returning method YearQuarter.quartersUntil(YearQuarter).
+ Fixes #122.
+
+
+ Add word-based period formatting.
+ Note that textual data can only be altered by PRs to ThreeTen-Extra.
+ Based on original code from Joda-Time.
+ Fixes #113, #41.
+
+
+ Add Catalan translation for word-based formatting.
+ Fixes #123.
+
+
+ Add Joda-Convert annotations.
+ The additional Joda-Convert dependency is optional (except that on Scala it is apparently mandatory).
+
+
+ Add Tidelift commercial support and security policy.
+
+
+
+
+ Enhance LocalDateRange.
+ Add more factory methods for empty and unbounded.
+ Ensure that unbounded ranges are more clearly specified.
+ Reject certain ranges near LocalDate.MIN/LocalDate.MAX.
+ Alter behaviour of lengthInDays() and toPeriod().
+ Fixes #100.
+
+
+ Fix build for Java 9.
+ Resource files cannot be read from other modules in Java 9.
+ As such, the `LeapSeconds.txt` file has moved to be under META-INF,
+ `META-INF/org/threeten/extra/scale/LeapSeconds.txt`.
+
+
+ Fix OSGi for Java 9.
+ Now that the build is on Java 9, the OSGi data had to be updated.
+ See #92, #94.
+
+
+ Add Temporals.nextWorkingDayOrSame() and Temporals.previousWorkingDayOrSame().
+ Fixes #101.
+
+
+ Fix test parameter order.
+ See #98, #99.
+
+
+
+
+ Fix build for Java 8.
+ Found actual issue with Javac was in the pom.xml.
+ See #91.
+
+
+
+
+ Fix build for Java 8.
+ Javac release flag is not correctly ignoring new overloaded methods.
+ Fixes #91.
+
+
+
+
+ Support Java 9.
+ Update and redesign build.
+
+
+ Switch from TestNG to JUnit 4.
+
+
+ Error message and Javadoc fixes in Interval.
+ See #89.
+
+
+ Interval.parse now handles Instant.MIN/MAX.
+ See #80.
+
+
+ YearWeek.atDay now correctly handles the end of the year.
+ See #87.
+
+
+ Add MutableClock.
+ See #83, #84.
+
+
+
+
+ Add plusYears/minusYears to YearWeek.
+ See #78.
+
+
+ Add plusWeeks/minusWeeks to YearWeek.
+ See #78.
+
+
+
+
+ Add PeriodDuration, combining Period and Duration.
+ See #74.
+
+
+ Fix incorrect method name in Hours.
+ toPeriod() should have been toDuration().
+ Fixes #76.
+
+
+ Extend formats parsed by Hours, Minutes and Seconds.
+ Fixes #77.
+
+
+ Extend formats parsed by Interval, allowing end instant to have offset inferred from start instant.
+ See #75.
+
+
+ Extend formats parsed by Interval, allowing years, months, weeks and days.
+ See #70.
+
+
+ Add Seconds temporal amount class.
+ See #73.
+
Add Temporals.parseFirstMatching().
This allows text to be parsed against a number of different formats.
diff --git a/src/main/checkstyle/checkstyle.xml b/src/main/checkstyle/checkstyle.xml
index 27af249f..f7a7b6d5 100644
--- a/src/main/checkstyle/checkstyle.xml
+++ b/src/main/checkstyle/checkstyle.xml
@@ -5,7 +5,6 @@
-
@@ -31,12 +30,6 @@
-
-
-
-
-
-
@@ -44,11 +37,6 @@
-
-
-
-
-
@@ -61,6 +49,9 @@
+
+
+
@@ -118,25 +109,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
new file mode 100644
index 00000000..60a3e7a6
--- /dev/null
+++ b/src/main/java/module-info.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of JSR-310 nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * ThreeTen-Extra provides additional date-time classes that complement those in Java SE 8.
+ *
+ * Not every piece of date/time logic is destined for the JDK. Some concepts are too
+ * specialized or too bulky to make it in. This project provides some of those additional
+ * classes as a well-tested and reliable module.
+ */
+module org.threeten.extra {
+
+ // only annotations are used, thus they are optional
+ requires static org.joda.convert;
+
+ // export all packages
+ exports org.threeten.extra;
+ exports org.threeten.extra.chrono;
+ exports org.threeten.extra.scale;
+
+ // provide the services
+ provides java.time.chrono.Chronology
+ with org.threeten.extra.chrono.BritishCutoverChronology,
+ org.threeten.extra.chrono.CopticChronology,
+ org.threeten.extra.chrono.DiscordianChronology,
+ org.threeten.extra.chrono.EthiopicChronology,
+ org.threeten.extra.chrono.InternationalFixedChronology,
+ org.threeten.extra.chrono.JulianChronology,
+ org.threeten.extra.chrono.PaxChronology,
+ org.threeten.extra.chrono.Symmetry010Chronology,
+ org.threeten.extra.chrono.Symmetry454Chronology;
+
+}
diff --git a/src/main/java/org/threeten/extra/AmountFormats.java b/src/main/java/org/threeten/extra/AmountFormats.java
new file mode 100644
index 00000000..facbf480
--- /dev/null
+++ b/src/main/java/org/threeten/extra/AmountFormats.java
@@ -0,0 +1,693 @@
+/*
+ * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of JSR-310 nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.threeten.extra;
+
+import java.time.Duration;
+import java.time.Period;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.TemporalAmount;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.ResourceBundle;
+import java.util.function.Function;
+import java.util.function.IntPredicate;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+/**
+ * Provides the ability to format a temporal amount.
+ *
+ * This allows a {@link TemporalAmount}, such as {@link Duration} or {@link Period},
+ * to be formatted. Only selected formatting options are provided.
+ *
+ *
Implementation Requirements:
+ * This class is immutable and thread-safe.
+ */
+public final class AmountFormats {
+
+ /**
+ * The number of days per week.
+ */
+ private static final int DAYS_PER_WEEK = 7;
+ /**
+ * The number of hours per day.
+ */
+ private static final int HOURS_PER_DAY = 24;
+ /**
+ * The number of minutes per hour.
+ */
+ private static final int MINUTES_PER_HOUR = 60;
+ /**
+ * The number of seconds per minute.
+ */
+ private static final int SECONDS_PER_MINUTE = 60;
+ /**
+ * The number of nanosecond per millisecond.
+ */
+ private static final int NANOS_PER_MILLIS = 1000_000;
+ /**
+ * The resource bundle name.
+ */
+ private static final String BUNDLE_NAME = "org.threeten.extra.wordbased";
+ /**
+ * The pattern to split lists with.
+ */
+ private static final Pattern SPLITTER = Pattern.compile("[|][|][|]");
+ /**
+ * The property file key for the separator ", ".
+ */
+ private static final String WORDBASED_COMMASPACE = "WordBased.commaspace";
+ /**
+ * The property file key for the separator " and ".
+ */
+ private static final String WORDBASED_SPACEANDSPACE = "WordBased.spaceandspace";
+ /**
+ * The property file key for the word "year".
+ */
+ private static final String WORDBASED_YEAR = "WordBased.year";
+ /**
+ * The property file key for the word "month".
+ */
+ private static final String WORDBASED_MONTH = "WordBased.month";
+ /**
+ * The property file key for the word "week".
+ */
+ private static final String WORDBASED_WEEK = "WordBased.week";
+ /**
+ * The property file key for the word "day".
+ */
+ private static final String WORDBASED_DAY = "WordBased.day";
+ /**
+ * The property file key for the word "hour".
+ */
+ private static final String WORDBASED_HOUR = "WordBased.hour";
+ /**
+ * The property file key for the word "minute".
+ */
+ private static final String WORDBASED_MINUTE = "WordBased.minute";
+ /**
+ * The property file key for the word "second".
+ */
+ private static final String WORDBASED_SECOND = "WordBased.second";
+ /**
+ * The property file key for the word "millisecond".
+ */
+ private static final String WORDBASED_MILLISECOND = "WordBased.millisecond";
+ /**
+ * The predicate that matches 1 or -1.
+ */
+ private static final IntPredicate PREDICATE_1 = value -> value == 1 || value == -1;
+ /**
+ * The predicate that matches numbers ending 1 but not ending 11.
+ */
+ private static final IntPredicate PREDICATE_END1_NOT11 = value -> {
+ int abs = Math.abs(value);
+ int last = abs % 10;
+ int secondLast = (abs % 100) / 10;
+ return (last == 1 && secondLast != 1);
+ };
+ /**
+ * The predicate that matches numbers ending 2, 3 or 4, but not ending 12, 13 or 14.
+ */
+ private static final IntPredicate PREDICATE_END234_NOTTEENS = value -> {
+ int abs = Math.abs(value);
+ int last = abs % 10;
+ int secondLast = (abs % 100) / 10;
+ return (last >= 2 && last <= 4 && secondLast != 1);
+ };
+ /**
+ * List of DurationUnit values ordered by longest suffix first.
+ */
+ private static final List DURATION_UNITS =
+ Arrays.asList(new DurationUnit("ns", Duration.ofNanos(1)),
+ new DurationUnit("µs", Duration.ofNanos(1000)), // U+00B5 = micro symbol
+ new DurationUnit("μs", Duration.ofNanos(1000)), // U+03BC = Greek letter mu
+ new DurationUnit("us", Duration.ofNanos(1000)),
+ new DurationUnit("ms", Duration.ofMillis(1)),
+ new DurationUnit("s", Duration.ofSeconds(1)),
+ new DurationUnit("m", Duration.ofMinutes(1)),
+ new DurationUnit("h", Duration.ofHours(1)));
+ /**
+ * Zero value for an absent fractional component of a numeric duration string.
+ */
+ private static final FractionScalarPart EMPTY_FRACTION = new FractionScalarPart(0, 0);
+
+ //-----------------------------------------------------------------------
+ /**
+ * Formats a period and duration to a string in ISO-8601 format.
+ *
+ * To obtain the ISO-8601 format of a {@code Period} or {@code Duration}
+ * individually, simply call {@code toString()}.
+ * See also {@link PeriodDuration}.
+ *
+ * @param period the period to format
+ * @param duration the duration to format
+ * @return the ISO-8601 format for the period and duration
+ */
+ public static String iso8601(Period period, Duration duration) {
+ Objects.requireNonNull(period, "period must not be null");
+ Objects.requireNonNull(duration, "duration must not be null");
+ if (period.isZero()) {
+ return duration.toString();
+ }
+ if (duration.isZero()) {
+ return period.toString();
+ }
+ return period.toString() + duration.toString().substring(1);
+ }
+
+ //-------------------------------------------------------------------------
+ /**
+ * Formats a period to a string in a localized word-based format.
+ *
+ * This returns a word-based format for the period.
+ * The year and month are printed as supplied unless the signs differ, in which case they are normalized.
+ * The words are configured in a resource bundle text file -
+ * {@code org.threeten.extra.wordbased.properties} - with overrides per language.
+ *
+ * @param period the period to format
+ * @param locale the locale to use
+ * @return the localized word-based format for the period
+ */
+ public static String wordBased(Period period, Locale locale) {
+ Objects.requireNonNull(period, "period must not be null");
+ Objects.requireNonNull(locale, "locale must not be null");
+ ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_NAME, locale);
+ UnitFormat[] formats = {
+ UnitFormat.of(bundle, WORDBASED_YEAR),
+ UnitFormat.of(bundle, WORDBASED_MONTH),
+ UnitFormat.of(bundle, WORDBASED_WEEK),
+ UnitFormat.of(bundle, WORDBASED_DAY)};
+ WordBased wb = new WordBased(formats, bundle.getString(WORDBASED_COMMASPACE), bundle.getString(WORDBASED_SPACEANDSPACE));
+
+ Period normPeriod = oppositeSigns(period.getMonths(), period.getYears()) ? period.normalized() : period;
+ int weeks = 0;
+ int days = 0;
+ if (normPeriod.getDays() % DAYS_PER_WEEK == 0) {
+ weeks = normPeriod.getDays() / DAYS_PER_WEEK;
+ } else {
+ days = normPeriod.getDays();
+ }
+ int[] values = {normPeriod.getYears(), normPeriod.getMonths(), weeks, days};
+ return wb.format(values);
+ }
+
+ /**
+ * Formats a duration to a string in a localized word-based format.
+ *
+ * This returns a word-based format for the duration.
+ * The words are configured in a resource bundle text file -
+ * {@code org.threeten.extra.wordbased.properties} - with overrides per language.
+ *
+ * @param duration the duration to format
+ * @param locale the locale to use
+ * @return the localized word-based format for the duration
+ */
+ public static String wordBased(Duration duration, Locale locale) {
+ Objects.requireNonNull(duration, "duration must not be null");
+ Objects.requireNonNull(locale, "locale must not be null");
+ ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_NAME, locale);
+ UnitFormat[] formats = {
+ UnitFormat.of(bundle, WORDBASED_HOUR),
+ UnitFormat.of(bundle, WORDBASED_MINUTE),
+ UnitFormat.of(bundle, WORDBASED_SECOND),
+ UnitFormat.of(bundle, WORDBASED_MILLISECOND)};
+ WordBased wb = new WordBased(formats, bundle.getString(WORDBASED_COMMASPACE), bundle.getString(WORDBASED_SPACEANDSPACE));
+
+ long hours = duration.toHours();
+ long mins = duration.toMinutes() % MINUTES_PER_HOUR;
+ long secs = duration.getSeconds() % SECONDS_PER_MINUTE;
+ int millis = duration.getNano() / NANOS_PER_MILLIS;
+ int[] values = {(int) hours, (int) mins, (int) secs, millis};
+ return wb.format(values);
+ }
+
+ /**
+ * Formats a period and duration to a string in a localized word-based format.
+ *
+ * This returns a word-based format for the period.
+ * The year and month are printed as supplied unless the signs differ, in which case they are normalized.
+ * The words are configured in a resource bundle text file -
+ * {@code org.threeten.extra.wordbased.properties} - with overrides per language.
+ *
+ * @param period the period to format
+ * @param duration the duration to format
+ * @param locale the locale to use
+ * @return the localized word-based format for the period and duration
+ */
+ public static String wordBased(Period period, Duration duration, Locale locale) {
+ Objects.requireNonNull(period, "period must not be null");
+ Objects.requireNonNull(duration, "duration must not be null");
+ Objects.requireNonNull(locale, "locale must not be null");
+ ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_NAME, locale);
+ UnitFormat[] formats = {
+ UnitFormat.of(bundle, WORDBASED_YEAR),
+ UnitFormat.of(bundle, WORDBASED_MONTH),
+ UnitFormat.of(bundle, WORDBASED_WEEK),
+ UnitFormat.of(bundle, WORDBASED_DAY),
+ UnitFormat.of(bundle, WORDBASED_HOUR),
+ UnitFormat.of(bundle, WORDBASED_MINUTE),
+ UnitFormat.of(bundle, WORDBASED_SECOND),
+ UnitFormat.of(bundle, WORDBASED_MILLISECOND)};
+ WordBased wb = new WordBased(formats, bundle.getString(WORDBASED_COMMASPACE), bundle.getString(WORDBASED_SPACEANDSPACE));
+
+ Period normPeriod = oppositeSigns(period.getMonths(), period.getYears()) ? period.normalized() : period;
+ int weeks = 0;
+ int days = 0;
+ if (normPeriod.getDays() % DAYS_PER_WEEK == 0) {
+ weeks = normPeriod.getDays() / DAYS_PER_WEEK;
+ } else {
+ days = normPeriod.getDays();
+ }
+ long totalHours = duration.toHours();
+ days += (int) (totalHours / HOURS_PER_DAY);
+ int hours = (int) (totalHours % HOURS_PER_DAY);
+ int mins = (int) (duration.toMinutes() % MINUTES_PER_HOUR);
+ int secs = (int) (duration.getSeconds() % SECONDS_PER_MINUTE);
+ int millis = duration.getNano() / NANOS_PER_MILLIS;
+ int[] values = {
+ normPeriod.getYears(), normPeriod.getMonths(), weeks, days,
+ (int) hours, mins, secs, millis};
+ return wb.format(values);
+ }
+
+ // are the signs opposite
+ private static boolean oppositeSigns(int a, int b) {
+ return a < 0 ? (b >= 0) : (b < 0);
+ }
+
+ // -------------------------------------------------------------------------
+ /**
+ * Parses formatted durations based on units.
+ *
+ * The behaviour matches the Golang
+ * duration parser, however, infinite durations are not supported.
+ *
+ * The duration format is a possibly signed sequence of decimal numbers, each with optional
+ * fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are
+ * "ns", "us" (or "µs"), "ms", "s", "m", "h".
+ *
+ * Note, the value "0" is specially supported as {@code Duration.ZERO}.
+ *
+ * @param durationText the formatted unit-based duration string.
+ * @return the {@code Duration} value represented by the string, if possible.
+ */
+ public static Duration parseUnitBasedDuration(CharSequence durationText) {
+ Objects.requireNonNull(durationText, "durationText must not be null");
+
+ // variables for tracking error positions during parsing.
+ int offset = 0;
+ CharSequence original = durationText;
+
+ // consume the leading sign - or + if one is present.
+ int sign = 1;
+ Optional updatedText = consumePrefix(durationText, '-');
+ if (updatedText.isPresent()) {
+ sign = -1;
+ offset += 1;
+ durationText = updatedText.get();
+ } else {
+ updatedText = consumePrefix(durationText, '+');
+ if (updatedText.isPresent()) {
+ offset += 1;
+ }
+ durationText = updatedText.orElse(durationText);
+ }
+ // special case for a string of "0"
+ if (durationText.equals("0")) {
+ return Duration.ZERO;
+ }
+ // special case, empty string as an invalid duration.
+ if (durationText.length() == 0) {
+ throw new DateTimeParseException("Not a numeric value", original, 0);
+ }
+
+ Duration value = Duration.ZERO;
+ int durationTextLength = durationText.length();
+ while (durationTextLength > 0) {
+ ParsedUnitPart integerPart =
+ consumeDurationLeadingInt(durationText, original, offset);
+ offset += (durationText.length() - integerPart.remainingText().length());
+ durationText = integerPart.remainingText();
+ DurationScalar leadingInt = integerPart;
+ DurationScalar fraction = EMPTY_FRACTION;
+ Optional dot = consumePrefix(durationText, '.');
+ if (dot.isPresent()) {
+ offset += 1;
+ durationText = dot.get();
+ ParsedUnitPart fractionPart =
+ consumeDurationFraction(durationText, original, offset);
+ // update the remaining string and fraction.
+ offset += (durationText.length() - fractionPart.remainingText().length());
+ durationText = fractionPart.remainingText();
+ fraction = fractionPart;
+ }
+
+ Optional optUnit = findUnit(durationText);
+ if (!optUnit.isPresent()) {
+ throw new DateTimeParseException(
+ "Invalid duration unit", original, offset);
+ }
+ DurationUnit unit = optUnit.get();
+ try {
+ Duration unitValue = leadingInt.applyTo(unit);
+ Duration fractionValue = fraction.applyTo(unit);
+ unitValue = unitValue.plus(fractionValue);
+ value = value.plus(unitValue);
+ } catch (ArithmeticException e) {
+ throw new DateTimeParseException(
+ "Duration string exceeds valid numeric range",
+ original, offset, e);
+ }
+ // update the remaining text and text length.
+ CharSequence remainingText = unit.consumeDurationUnit(durationText);
+ offset += (durationText.length() - remainingText.length());
+ durationText = remainingText;
+ durationTextLength = durationText.length();
+ }
+ return sign < 0 ? value.negated() : value;
+ }
+
+ // consume the fractional part of a unit-based duration, e.g.
+ // ..
+ private static ParsedUnitPart consumeDurationLeadingInt(CharSequence text,
+ CharSequence original, int offset) {
+ long integerPart = 0;
+ int i = 0;
+ int valueLength = text.length();
+ for ( ; i < valueLength; i++) {
+ char c = text.charAt(i);
+ if (c < '0' || c > '9') {
+ break;
+ }
+ // overflow of a single numeric specifier for a duration.
+ if (integerPart > Long.MAX_VALUE / 10) {
+ throw new DateTimeParseException(
+ "Duration string exceeds valid numeric range",
+ original, i + offset);
+ }
+ integerPart *= 10;
+ integerPart += (long) (c - '0');
+ // overflow of a single numeric specifier for a duration.
+ if (integerPart < 0) {
+ throw new DateTimeParseException(
+ "Duration string exceeds valid numeric range",
+ original, i + offset);
+ }
+ }
+ // if no text was consumed, return empty.
+ if (i == 0) {
+ throw new DateTimeParseException("Missing leading integer", original, offset);
+ }
+ return new ParsedUnitPart(text.subSequence(i, text.length()),
+ new IntegerScalarPart(integerPart));
+ }
+
+ // consume the fractional part of a unit-based duration, e.g.
+ // ..
+ private static ParsedUnitPart consumeDurationFraction(CharSequence text,
+ CharSequence original, int offset) {
+ int i = 0;
+ long fraction = 0;
+ long scale = 1;
+ boolean overflow = false;
+ for ( ; i < text.length(); i++) {
+ char c = text.charAt(i);
+ if (c < '0' || c > '9') {
+ break;
+ }
+ // for the fractional part, it's possible to overflow; however,
+ // this does not invalidate the duration, but rather it means that
+ // the precision of the fractional part is truncated to 999,999,999.
+ if (overflow || fraction > Long.MAX_VALUE / 10) {
+ continue;
+ }
+ long tmp = fraction * 10 + (long) (c - '0');
+ if (tmp < 0) {
+ overflow = true;
+ continue;
+ }
+ fraction = tmp;
+ scale *= 10;
+ }
+ if (i == 0) {
+ throw new DateTimeParseException(
+ "Missing numeric fraction after '.'", original, offset);
+ }
+ return new ParsedUnitPart(text.subSequence(i, text.length()),
+ new FractionScalarPart(fraction, scale));
+ }
+
+ // find the duration unit at the beginning of the input text, if present.
+ private static Optional findUnit(CharSequence text) {
+ return DURATION_UNITS.stream()
+ .sequential()
+ .filter(du -> du.prefixMatchesUnit(text))
+ .findFirst();
+ }
+
+ // consume the indicated {@code prefix} if it exists at the beginning of the
+ // text, returning the
+ // remaining string if the prefix was consumed.
+ private static Optional consumePrefix(CharSequence text, char prefix) {
+ if (text.length() > 0 && text.charAt(0) == prefix) {
+ return Optional.of(text.subSequence(1, text.length()));
+ }
+ return Optional.empty();
+ }
+
+ private AmountFormats() {
+ }
+
+ //-------------------------------------------------------------------------
+ // data holder for word-based formats
+ static final class WordBased {
+ private final UnitFormat[] units;
+ private final String separator;
+ private final String lastSeparator;
+
+ public WordBased(UnitFormat[] units, String separator, String lastSeparator) {
+ this.units = units;
+ this.separator = separator;
+ this.lastSeparator = lastSeparator;
+ }
+
+ String format(int[] values) {
+ StringBuilder buf = new StringBuilder(32);
+ int nonZeroCount = 0;
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] != 0) {
+ nonZeroCount++;
+ }
+ }
+ int count = 0;
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] != 0 || (count == 0 && i == values.length - 1)) {
+ units[i].formatTo(values[i], buf);
+ if (count < nonZeroCount - 2) {
+ buf.append(separator);
+ } else if (count == nonZeroCount - 2) {
+ buf.append(lastSeparator);
+ }
+ count++;
+ }
+ }
+ return buf.toString();
+ }
+ }
+
+ // data holder for single/plural formats
+ static interface UnitFormat {
+
+ static UnitFormat of(ResourceBundle bundle, String keyStem) {
+ if (bundle.containsKey(keyStem + "s.predicates")) {
+ String predicateList = bundle.getString(keyStem + "s.predicates");
+ String textList = bundle.getString(keyStem + "s.list");
+ String[] regexes = SPLITTER.split(predicateList);
+ String[] text = SPLITTER.split(textList);
+ return new PredicateFormat(regexes, text);
+ } else {
+ String single = bundle.getString(keyStem);
+ String plural = bundle.getString(keyStem + "s");
+ return new SinglePluralFormat(single, plural);
+ }
+ }
+
+ void formatTo(int value, StringBuilder buf);
+ }
+
+ // data holder for single/plural formats
+ static final class SinglePluralFormat implements UnitFormat {
+ private final String single;
+ private final String plural;
+
+ SinglePluralFormat(String single, String plural) {
+ this.single = single;
+ this.plural = plural;
+ }
+
+ @Override
+ public void formatTo(int value, StringBuilder buf) {
+ buf.append(value).append(value == 1 || value == -1 ? single : plural);
+ }
+ }
+
+ // data holder for predicate formats
+ static final class PredicateFormat implements UnitFormat {
+ private final IntPredicate[] predicates;
+ private final String[] text;
+
+ PredicateFormat(String[] predicateStrs, String[] text) {
+ if (predicateStrs.length + 1 != text.length) {
+ throw new IllegalStateException("Invalid word-based resource");
+ }
+ this.predicates = Stream.of(predicateStrs)
+ .map(predicateStr -> findPredicate(predicateStr))
+ .toArray(IntPredicate[]::new);
+ this.text = text;
+ }
+
+ private IntPredicate findPredicate(String predicateStr) {
+ switch (predicateStr) {
+ case "One": return PREDICATE_1;
+ case "End234NotTeens": return PREDICATE_END234_NOTTEENS;
+ case "End1Not11": return PREDICATE_END1_NOT11;
+ default: throw new IllegalStateException("Invalid word-based resource");
+ }
+ }
+
+ @Override
+ public void formatTo(int value, StringBuilder buf) {
+ for (int i = 0; i < predicates.length; i++) {
+ if (predicates[i].test(value)) {
+ buf.append(value).append(text[i]);
+ return;
+ }
+ }
+ buf.append(value).append(text[predicates.length]);
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // data holder for a duration unit string and its associated Duration value.
+ static final class DurationUnit {
+ private final String abbrev;
+ private final Duration value;
+
+ private DurationUnit(String abbrev, Duration value) {
+ this.abbrev = abbrev;
+ this.value = value;
+ }
+
+ // whether the input text starts with the unit abbreviation.
+ boolean prefixMatchesUnit(CharSequence text) {
+ return text.length() >= abbrev.length()
+ && abbrev.equals(text.subSequence(0, abbrev.length()));
+ }
+
+ // consume the duration unit and returning the remaining text.
+ CharSequence consumeDurationUnit(CharSequence text) {
+ return text.subSequence(abbrev.length(), text.length());
+ }
+
+ // scale the unit by the input scalingFunction, returning a value if
+ // one is produced, or an empty result when the operation results in an
+ // arithmetic overflow.
+ Duration scaleBy(Function scaleFunc) {
+ return scaleFunc.apply(value);
+ }
+ }
+
+ // interface for computing a duration from a duration unit and a scalar.
+ static interface DurationScalar {
+ // returns a duration value on a successful computation, and an empty
+ // result otherwise.
+ Duration applyTo(DurationUnit unit);
+ }
+
+ // data holder for parsed fragments of a floating point duration scalar.
+ static final class ParsedUnitPart implements DurationScalar {
+ private final CharSequence remainingText;
+ private final DurationScalar scalar;
+
+ private ParsedUnitPart(CharSequence remainingText, DurationScalar scalar) {
+ this.remainingText = remainingText;
+ this.scalar = scalar;
+ }
+
+ @Override
+ public Duration applyTo(DurationUnit unit) {
+ return scalar.applyTo(unit);
+ }
+
+ CharSequence remainingText() {
+ return remainingText;
+ }
+ }
+
+ // data holder for the leading integer value of a duration scalar.
+ static final class IntegerScalarPart implements DurationScalar {
+ private final long value;
+
+ private IntegerScalarPart(long value) {
+ this.value = value;
+ }
+
+ @Override
+ public Duration applyTo(DurationUnit unit) {
+ return unit.scaleBy(d -> d.multipliedBy(value));
+ }
+ }
+
+ // data holder for the fractional floating point value of a duration
+ // scalar.
+ static final class FractionScalarPart implements DurationScalar {
+ private final long value;
+ private final long scale;
+
+ private FractionScalarPart(long value, long scale) {
+ this.value = value;
+ this.scale = scale;
+ }
+
+ @Override
+ public Duration applyTo(DurationUnit unit) {
+ if (value == 0) {
+ return Duration.ZERO;
+ }
+ return unit.scaleBy(d -> d.multipliedBy(value).dividedBy(scale));
+ }
+ }
+}
diff --git a/src/main/java/org/threeten/extra/Days.java b/src/main/java/org/threeten/extra/Days.java
index e0724cad..8ae5a269 100644
--- a/src/main/java/org/threeten/extra/Days.java
+++ b/src/main/java/org/threeten/extra/Days.java
@@ -48,6 +48,9 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.joda.convert.FromString;
+import org.joda.convert.ToString;
+
/**
* A day-based amount of time, such as '12 days'.
*
@@ -203,11 +206,12 @@ public static Days from(TemporalAmount amount) {
* @return the parsed period, not null
* @throws DateTimeParseException if the text cannot be parsed to a period
*/
+ @FromString
public static Days parse(CharSequence text) {
Objects.requireNonNull(text, "text");
Matcher matcher = PATTERN.matcher(text);
if (matcher.matches()) {
- int negate = ("-".equals(matcher.group(1)) ? -1 : 1);
+ int negate = "-".equals(matcher.group(1)) ? -1 : 1;
String weeksStr = matcher.group(2);
String daysStr = matcher.group(3);
if (weeksStr != null || daysStr != null) {
@@ -281,7 +285,7 @@ private Object readResolve() {
*/
@Override
public long get(TemporalUnit unit) {
- if (unit == ChronoUnit.DAYS) {
+ if (unit == DAYS) {
return days;
}
throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
@@ -312,6 +316,33 @@ public int getAmount() {
return days;
}
+ /**
+ * Checks if the amount is negative.
+ *
+ * @return true if the amount is negative, false if the amount is zero or positive
+ */
+ public boolean isNegative() {
+ return getAmount() < 0;
+ }
+
+ /**
+ * Checks if the amount is zero.
+ *
+ * @return true if the amount is zero, false if not
+ */
+ public boolean isZero() {
+ return getAmount() == 0;
+ }
+
+ /**
+ * Checks if the amount is positive.
+ *
+ * @return true if the amount is positive, false if the amount is zero or negative
+ */
+ public boolean isPositive() {
+ return getAmount() > 0;
+ }
+
//-----------------------------------------------------------------------
/**
* Returns a copy of this amount with the specified amount added.
@@ -353,13 +384,13 @@ public Days plus(int days) {
*
* This instance is immutable and unaffected by this method call.
*
- * @param amountToAdd the amount to add, not null
+ * @param amountToSubtract the amount to subtract, not null
* @return a {@code Days} based on this instance with the requested amount subtracted, not null
* @throws DateTimeException if the specified amount contains an invalid unit
* @throws ArithmeticException if numeric overflow occurs
*/
- public Days minus(TemporalAmount amountToAdd) {
- return minus(Days.from(amountToAdd).getAmount());
+ public Days minus(TemporalAmount amountToSubtract) {
+ return minus(Days.from(amountToSubtract).getAmount());
}
/**
@@ -574,6 +605,7 @@ public int hashCode() {
* @return the number of days in ISO-8601 string format
*/
@Override
+ @ToString
public String toString() {
return "P" + days + "D";
}
diff --git a/src/main/java/org/threeten/extra/Half.java b/src/main/java/org/threeten/extra/Half.java
new file mode 100644
index 00000000..99d63561
--- /dev/null
+++ b/src/main/java/org/threeten/extra/Half.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of JSR-310 nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.threeten.extra;
+
+import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
+import static org.threeten.extra.TemporalFields.HALF_OF_YEAR;
+import static org.threeten.extra.TemporalFields.HALF_YEARS;
+
+import java.time.DateTimeException;
+import java.time.LocalDate;
+import java.time.Month;
+import java.time.chrono.Chronology;
+import java.time.chrono.IsoChronology;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.TextStyle;
+import java.time.temporal.ChronoField;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalAdjuster;
+import java.time.temporal.TemporalField;
+import java.time.temporal.TemporalQueries;
+import java.time.temporal.TemporalQuery;
+import java.time.temporal.UnsupportedTemporalTypeException;
+import java.time.temporal.ValueRange;
+import java.util.Locale;
+
+/**
+ * A half-of-year, such as 'H2'.
+ *
+ * {@code Half} is an enum representing the 2 halves of the year - H1 and H2.
+ * These are defined as January to June and July to December.
+ *
+ * The {@code int} value follows the half, from 1 (H1) to 2 (H2).
+ * It is recommended that applications use the enum rather than the {@code int} value
+ * to ensure code clarity.
+ *
+ * Do not use {@code ordinal()} to obtain the numeric representation of {@code Half}.
+ * Use {@code getValue()} instead.
+ *
+ *
Implementation Requirements:
+ * This is an immutable and thread-safe enum.
+ */
+public enum Half implements TemporalAccessor, TemporalAdjuster {
+
+ /**
+ * The singleton instance for the first half-of-year, from January to June.
+ * This has the numeric value of {@code 1}.
+ */
+ H1,
+ /**
+ * The singleton instance for the second half-of-year, from July to December.
+ * This has the numeric value of {@code 2}.
+ */
+ H2;
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains an instance of {@code Half} from an {@code int} value.
+ *
+ * {@code Half} is an enum representing the 2 halves of the year.
+ * This factory allows the enum to be obtained from the {@code int} value.
+ * The {@code int} value follows the half, from 1 (H1) to 2 (H2).
+ *
+ * @param halfOfYear the half-of-year to represent, from 1 (H1) to 2 (H2)
+ * @return the half-of-year, not null
+ * @throws DateTimeException if the half-of-year is invalid
+ */
+ public static Half of(int halfOfYear) {
+ switch (halfOfYear) {
+ case 1:
+ return H1;
+ case 2:
+ return H2;
+ default:
+ throw new DateTimeException("Invalid value for Half: " + halfOfYear);
+ }
+ }
+
+ /**
+ * Obtains an instance of {@code Half} from a month-of-year.
+ *
+ * {@code Half} is an enum representing the 2 halves of the year.
+ * This factory allows the enum to be obtained from the {@code Month} value.
+ *
+ * January to June are H1 and July to December are H2.
+ *
+ * @param monthOfYear the month-of-year to convert from, from 1 to 12
+ * @return the half-of-year, not null
+ * @throws DateTimeException if the month-of-year is invalid
+ */
+ public static Half ofMonth(int monthOfYear) {
+ MONTH_OF_YEAR.range().checkValidValue(monthOfYear, MONTH_OF_YEAR);
+ return of(monthOfYear <= 6 ? 1 : 2);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains an instance of {@code Half} from a temporal object.
+ *
+ * This obtains a half based on the specified temporal.
+ * A {@code TemporalAccessor} represents an arbitrary set of date and time information,
+ * which this factory converts to an instance of {@code Half}.
+ *
+ * The conversion extracts the {@link TemporalFields#HALF_OF_YEAR HALF_OF_YEAR} field.
+ * The extraction is only permitted if the temporal object has an ISO
+ * chronology, or can be converted to a {@code LocalDate}.
+ *
+ * This method matches the signature of the functional interface {@link TemporalQuery}
+ * allowing it to be used in queries via method reference, {@code Half::from}.
+ *
+ * @param temporal the temporal-time object to convert, not null
+ * @return the half-of-year, not null
+ * @throws DateTimeException if unable to convert to a {@code Half}
+ */
+ public static Half from(TemporalAccessor temporal) {
+ if (temporal instanceof Half) {
+ return (Half) temporal;
+ } else if (temporal instanceof Month) {
+ Month month = (Month) temporal;
+ return of(month.ordinal() / 6 + 1);
+ }
+ try {
+ TemporalAccessor adjusted =
+ !IsoChronology.INSTANCE.equals(Chronology.from(temporal)) ? LocalDate.from(temporal) : temporal;
+ // need to use getLong() as JDK Parsed class get() doesn't work properly
+ int qoy = Math.toIntExact(adjusted.getLong(HALF_OF_YEAR));
+ return of(qoy);
+ } catch (DateTimeException ex) {
+ throw new DateTimeException("Unable to obtain Half from TemporalAccessor: " +
+ temporal + " of type " + temporal.getClass().getName(), ex);
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the half-of-year {@code int} value.
+ *
+ * The values are numbered following the ISO-8601 standard,
+ * from 1 (H1) to 2 (H2).
+ *
+ * @return the half-of-year, from 1 (H1) to 2 (H2)
+ */
+ public int getValue() {
+ return ordinal() + 1;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the textual representation, such as 'H1' or '4th half'.
+ *
+ * This returns the textual name used to identify the half-of-year,
+ * suitable for presentation to the user.
+ * The parameters control the style of the returned text and the locale.
+ *
+ * If no textual mapping is found then the {@link #getValue() numeric value} is returned.
+ *
+ * @param style the length of the text required, not null
+ * @param locale the locale to use, not null
+ * @return the text value of the half-of-year, not null
+ */
+ public String getDisplayName(TextStyle style, Locale locale) {
+ return new DateTimeFormatterBuilder().appendText(HALF_OF_YEAR, style).toFormatter(locale).format(this);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Checks if the specified field is supported.
+ *
+ * This checks if this half-of-year can be queried for the specified field.
+ * If false, then calling the {@link #range(TemporalField) range} and
+ * {@link #get(TemporalField) get} methods will throw an exception.
+ *
+ * If the field is {@link TemporalFields#HALF_OF_YEAR HALF_OF_YEAR} then
+ * this method returns true.
+ * All {@code ChronoField} instances will return false.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.isSupportedBy(TemporalAccessor)}
+ * passing {@code this} as the argument.
+ * Whether the field is supported is determined by the field.
+ *
+ * @param field the field to check, null returns false
+ * @return true if the field is supported on this half-of-year, false if not
+ */
+ @Override
+ public boolean isSupported(TemporalField field) {
+ if (field == HALF_OF_YEAR) {
+ return true;
+ } else if (field instanceof ChronoField) {
+ return false;
+ }
+ return field != null && field.isSupportedBy(this);
+ }
+
+ /**
+ * Gets the range of valid values for the specified field.
+ *
+ * The range object expresses the minimum and maximum valid values for a field.
+ * This half is used to enhance the accuracy of the returned range.
+ * If it is not possible to return the range, because the field is not supported
+ * or for some other reason, an exception is thrown.
+ *
+ * If the field is {@link TemporalFields#HALF_OF_YEAR HALF_OF_YEAR} then the
+ * range of the half-of-year, from 1 to 2, will be returned.
+ * All {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)}
+ * passing {@code this} as the argument.
+ * Whether the range can be obtained is determined by the field.
+ *
+ * @param field the field to query the range for, not null
+ * @return the range of valid values for the field, not null
+ * @throws DateTimeException if the range for the field cannot be obtained
+ * @throws UnsupportedTemporalTypeException if the field is not supported
+ */
+ @Override
+ public ValueRange range(TemporalField field) {
+ if (field == HALF_OF_YEAR) {
+ return field.range();
+ } else if (field instanceof ChronoField) {
+ throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
+ }
+ return TemporalAccessor.super.range(field);
+ }
+
+ /**
+ * Gets the value of the specified field from this half-of-year as an {@code int}.
+ *
+ * This queries this half for the value for the specified field.
+ * The returned value will always be within the valid range of values for the field.
+ * If it is not possible to return the value, because the field is not supported
+ * or for some other reason, an exception is thrown.
+ *
+ * If the field is {@link TemporalFields#HALF_OF_YEAR HALF_OF_YEAR} then the
+ * value of the half-of-year, from 1 to 2, will be returned.
+ * All {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}
+ * passing {@code this} as the argument. Whether the value can be obtained,
+ * and what the value represents, is determined by the field.
+ *
+ * @param field the field to get, not null
+ * @return the value for the field, within the valid range of values
+ * @throws DateTimeException if a value for the field cannot be obtained or
+ * the value is outside the range of valid values for the field
+ * @throws UnsupportedTemporalTypeException if the field is not supported or
+ * the range of values exceeds an {@code int}
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public int get(TemporalField field) {
+ if (field == HALF_OF_YEAR) {
+ return getValue();
+ } else if (field instanceof ChronoField) {
+ throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
+ }
+ return TemporalAccessor.super.get(field);
+ }
+
+ /**
+ * Gets the value of the specified field from this half-of-year as a {@code long}.
+ *
+ * This queries this half for the value for the specified field.
+ * If it is not possible to return the value, because the field is not supported
+ * or for some other reason, an exception is thrown.
+ *
+ * If the field is {@link TemporalFields#HALF_OF_YEAR HALF_OF_YEAR} then the
+ * value of the half-of-year, from 1 to 2, will be returned.
+ * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}
+ * passing {@code this} as the argument. Whether the value can be obtained,
+ * and what the value represents, is determined by the field.
+ *
+ * @param field the field to get, not null
+ * @return the value for the field
+ * @throws DateTimeException if a value for the field cannot be obtained
+ * @throws UnsupportedTemporalTypeException if the field is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public long getLong(TemporalField field) {
+ if (field == HALF_OF_YEAR) {
+ return getValue();
+ } else if (field instanceof ChronoField) {
+ throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
+ }
+ return field.getFrom(this);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns the half that is the specified number of halves after this one.
+ *
+ * The calculation rolls around the end of the year from H2 to H1.
+ * The specified period may be negative.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param halves the halves to add, positive or negative
+ * @return the resulting half, not null
+ */
+ public Half plus(long halves) {
+ int amount = (int) halves % 2;
+ return values()[(ordinal() + (amount + 2)) % 2];
+ }
+
+ /**
+ * Returns the half that is the specified number of halves before this one.
+ *
+ * The calculation rolls around the start of the year from H1 to H2.
+ * The specified period may be negative.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param halves the halves to subtract, positive or negative
+ * @return the resulting half, not null
+ */
+ public Half minus(long halves) {
+ return plus(-(halves % 2));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the length of this half in days.
+ *
+ * This takes a flag to determine whether to return the length for a leap year or not.
+ *
+ * H1 has 181 in a standard year and 182 days in a leap year.
+ * H2 has 184 days.
+ *
+ * @param leapYear true if the length is required for a leap year
+ * @return the length of this month in days, 181, 182 or 184
+ */
+ public int length(boolean leapYear) {
+ return this == H1 ? (leapYear ? 182 : 181) : 184;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the first of the six months that this half refers to.
+ *
+ * H1 will return January.
+ * H2 will return July.
+ *
+ * @return the first month in the half, not null
+ */
+ public Month firstMonth() {
+ return this == H1 ? Month.JANUARY : Month.JULY;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Queries this half-of-year using the specified query.
+ *
+ * This queries this half-of-year using the specified query strategy object.
+ * The {@code TemporalQuery} object defines the logic to be used to
+ * obtain the result. Read the documentation of the query to understand
+ * what the result of this method will be.
+ *
+ * The result of this method is obtained by invoking the
+ * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the
+ * specified query passing {@code this} as the argument.
+ *
+ * @param the type of the result
+ * @param query the query to invoke, not null
+ * @return the query result, null may be returned (defined by the query)
+ * @throws DateTimeException if unable to query (defined by the query)
+ * @throws ArithmeticException if numeric overflow occurs (defined by the query)
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public R query(TemporalQuery query) {
+ if (query == TemporalQueries.chronology()) {
+ return (R) IsoChronology.INSTANCE;
+ } else if (query == TemporalQueries.precision()) {
+ return (R) HALF_YEARS;
+ }
+ return TemporalAccessor.super.query(query);
+ }
+
+ /**
+ * Adjusts the specified temporal object to have this half-of-year.
+ *
+ * This returns a temporal object of the same observable type as the input
+ * with the half-of-year changed to be the same as this.
+ *
+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)}
+ * passing {@link TemporalFields#HALF_OF_YEAR} as the field.
+ * If the specified temporal object does not use the ISO calendar system then
+ * a {@code DateTimeException} is thrown.
+ *
+ * In most cases, it is clearer to reverse the calling pattern by using
+ * {@link Temporal#with(TemporalAdjuster)}:
+ *
+ * // these two lines are equivalent, but the second approach is recommended
+ * temporal = thisHalf.adjustInto(temporal);
+ * temporal = temporal.with(thisHalf);
+ *
+ *
+ * For example, given a date in May, the following are output:
+ *
+ * dateInMay.with(H1); // no change
+ * dateInMay.with(H2); // six months later
+ *
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param temporal the target object to be adjusted, not null
+ * @return the adjusted object, not null
+ * @throws DateTimeException if unable to make the adjustment
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public Temporal adjustInto(Temporal temporal) {
+ if (Chronology.from(temporal).equals(IsoChronology.INSTANCE) == false) {
+ throw new DateTimeException("Adjustment only supported on ISO date-time");
+ }
+ return temporal.with(HALF_OF_YEAR, getValue());
+ }
+
+}
diff --git a/src/main/java/org/threeten/extra/HourMinute.java b/src/main/java/org/threeten/extra/HourMinute.java
new file mode 100644
index 00000000..d91de31b
--- /dev/null
+++ b/src/main/java/org/threeten/extra/HourMinute.java
@@ -0,0 +1,1090 @@
+/*
+ * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of JSR-310 nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.threeten.extra;
+
+import static java.time.temporal.ChronoField.AMPM_OF_DAY;
+import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM;
+import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY;
+import static java.time.temporal.ChronoField.HOUR_OF_AMPM;
+import static java.time.temporal.ChronoField.HOUR_OF_DAY;
+import static java.time.temporal.ChronoField.MINUTE_OF_DAY;
+import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
+import static java.time.temporal.ChronoUnit.HALF_DAYS;
+import static java.time.temporal.ChronoUnit.HOURS;
+import static java.time.temporal.ChronoUnit.MINUTES;
+
+import java.io.Serializable;
+import java.time.Clock;
+import java.time.DateTimeException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.OffsetTime;
+import java.time.Period;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalAdjuster;
+import java.time.temporal.TemporalAmount;
+import java.time.temporal.TemporalField;
+import java.time.temporal.TemporalQueries;
+import java.time.temporal.TemporalQuery;
+import java.time.temporal.TemporalUnit;
+import java.time.temporal.UnsupportedTemporalTypeException;
+import java.time.temporal.ValueRange;
+import java.util.Objects;
+
+import org.joda.convert.FromString;
+import org.joda.convert.ToString;
+
+/**
+ * An hour-minute, such as {@code 12:31}.
+ *
+ * This class is similar to {@link LocalTime} but has a precision of minutes.
+ * Seconds and nanoseconds cannot be represented by this class.
+ *
+ *
Implementation Requirements:
+ * This class is immutable and thread-safe.
+ *
+ * This class must be treated as a value type. Do not synchronize, rely on the
+ * identity hash code or use the distinction between equals() and ==.
+ */
+public final class HourMinute
+ implements Temporal, TemporalAdjuster, Comparable, Serializable {
+
+ /**
+ * The time of midnight at the start of the day, '00:00'.
+ */
+ public static final HourMinute MIDNIGHT = new HourMinute(0, 0);
+
+ /**
+ * Serialization version.
+ */
+ private static final long serialVersionUID = -2532872925L;
+ /**
+ * Parser.
+ */
+ private static final DateTimeFormatter PARSER = new DateTimeFormatterBuilder()
+ .appendValue(HOUR_OF_DAY, 2)
+ .appendLiteral(':')
+ .appendValue(MINUTE_OF_HOUR, 2)
+ .toFormatter();
+ /**
+ * Hours per day.
+ */
+ private static final int HOURS_PER_DAY = 24;
+ /**
+ * Minutes per hour.
+ */
+ private static final int MINUTES_PER_HOUR = 60;
+ /**
+ * Minutes per day.
+ */
+ private static final int MINUTES_PER_DAY = MINUTES_PER_HOUR * HOURS_PER_DAY;
+
+ /**
+ * The hour-of-day.
+ */
+ private final int hour;
+ /**
+ * The minute-of-hour.
+ */
+ private final int minute;
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains the current hour-minute from the system clock in the default time-zone.
+ *
+ * This will query the {@link java.time.Clock#systemDefaultZone() system clock} in the default
+ * time-zone to obtain the current hour-minute.
+ * The zone and offset will be set based on the time-zone in the clock.
+ *
+ * Using this method will prevent the ability to use an alternate clock for testing
+ * because the clock is hard-coded.
+ *
+ * @return the current hour-minute using the system clock and default time-zone, not null
+ */
+ public static HourMinute now() {
+ return now(Clock.systemDefaultZone());
+ }
+
+ /**
+ * Obtains the current hour-minute from the system clock in the specified time-zone.
+ *
+ * This will query the {@link Clock#system(java.time.ZoneId) system clock} to obtain the current hour-minute.
+ * Specifying the time-zone avoids dependence on the default time-zone.
+ *
+ * Using this method will prevent the ability to use an alternate clock for testing
+ * because the clock is hard-coded.
+ *
+ * @param zone the zone ID to use, not null
+ * @return the current hour-minute using the system clock, not null
+ */
+ public static HourMinute now(ZoneId zone) {
+ return now(Clock.system(zone));
+ }
+
+ /**
+ * Obtains the current hour-minute from the specified clock.
+ *
+ * This will query the specified clock to obtain the current hour-minute.
+ * Using this method allows the use of an alternate clock for testing.
+ * The alternate clock may be introduced using {@link Clock dependency injection}.
+ *
+ * @param clock the clock to use, not null
+ * @return the current hour-minute, not null
+ */
+ public static HourMinute now(Clock clock) {
+ final LocalTime now = LocalTime.now(clock); // called once
+ return HourMinute.of(now.getHour(), now.getMinute());
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains an instance of {@code HourMinute} from a hour and minute.
+ *
+ * @param hour the hour to represent, from 0 to 23
+ * @param minute the minute-of-hour to represent, from 0 to 59
+ * @return the hour-minute, not null
+ * @throws DateTimeException if either field value is invalid
+ */
+ public static HourMinute of(int hour, int minute) {
+ HOUR_OF_DAY.checkValidValue(hour);
+ MINUTE_OF_HOUR.checkValidValue(minute);
+ return new HourMinute(hour, minute);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains an instance of {@code HourMinute} from a temporal object.
+ *
+ * This obtains a hour-minute based on the specified temporal.
+ * A {@code TemporalAccessor} represents an arbitrary set of date and time information,
+ * which this factory converts to an instance of {@code HourMinute}.
+ *
+ * The conversion extracts the {@link ChronoField#HOUR_OF_DAY HOUR_OF_DAY} and
+ * {@link ChronoField#MINUTE_OF_HOUR MINUTE_OF_HOUR} fields.
+ *
+ * This method matches the signature of the functional interface {@link TemporalQuery}
+ * allowing it to be used in queries via method reference, {@code HourMinute::from}.
+ *
+ * @param temporal the temporal object to convert, not null
+ * @return the hour-minute, not null
+ * @throws DateTimeException if unable to convert to a {@code HourMinute}
+ */
+ public static HourMinute from(TemporalAccessor temporal) {
+ if (temporal instanceof HourMinute) {
+ return (HourMinute) temporal;
+ }
+ Objects.requireNonNull(temporal, "temporal");
+ try {
+ // need to use getLong() as JDK Parsed class get() doesn't work properly
+ int hour = Math.toIntExact(temporal.getLong(HOUR_OF_DAY));
+ int minute = Math.toIntExact(temporal.getLong(MINUTE_OF_HOUR));
+ return of(hour, minute);
+ } catch (DateTimeException ex) {
+ throw new DateTimeException("Unable to obtain HourMinute from TemporalAccessor: " +
+ temporal + " of type " + temporal.getClass().getName(), ex);
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains an instance of {@code HourMinute} from a text string such as {@code 12:31}.
+ *
+ * The string must represent a valid hour-minute.
+ * The format must be {@code HH:mm}.
+ *
+ * @param text the text to parse such as "12:31", not null
+ * @return the parsed hour-minute, not null
+ * @throws DateTimeParseException if the text cannot be parsed
+ */
+ @FromString
+ public static HourMinute parse(CharSequence text) {
+ return parse(text, PARSER);
+ }
+
+ /**
+ * Obtains an instance of {@code HourMinute} from a text string using a specific formatter.
+ *
+ * The text is parsed using the formatter, returning a hour-minute.
+ *
+ * @param text the text to parse, not null
+ * @param formatter the formatter to use, not null
+ * @return the parsed hour-minute, not null
+ * @throws DateTimeParseException if the text cannot be parsed
+ */
+ public static HourMinute parse(CharSequence text, DateTimeFormatter formatter) {
+ Objects.requireNonNull(formatter, "formatter");
+ return formatter.parse(text, HourMinute::from);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Constructor.
+ *
+ * @param hour the hour to represent, validated from 0 to 23
+ * @param minute the minute-of-hour to represent, validated from 0 to 59
+ */
+ private HourMinute(int hour, int minute) {
+ this.hour = hour;
+ this.minute = minute;
+ }
+
+ /**
+ * Validates the input.
+ *
+ * @return the valid object, not null
+ */
+ private Object readResolve() {
+ return of(hour, minute);
+ }
+
+ /**
+ * Returns a copy of this hour-minute with the new hour and minute, checking
+ * to see if a new object is in fact required.
+ *
+ * @param newYear the hour to represent, validated from 0 to 23
+ * @param newMinute the minute-of-hour to represent, validated from 0 to 59
+ * @return the hour-minute, not null
+ */
+ private HourMinute with(int newYear, int newMinute) {
+ if (hour == newYear && minute == newMinute) {
+ return this;
+ }
+ return new HourMinute(newYear, newMinute);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Checks if the specified field is supported.
+ *
+ * This checks if this hour-minute can be queried for the specified field.
+ * If false, then calling the {@link #range(TemporalField) range},
+ * {@link #get(TemporalField) get} and {@link #with(TemporalField, long)}
+ * methods will throw an exception.
+ *
+ * If the field is a {@link ChronoField} then the query is implemented here.
+ * The supported fields are:
+ *
+ *
{@code MINUTE_OF_HOUR}
+ *
{@code MINUTE_OF_DAY}
+ *
{@code HOUR_OF_AMPM}
+ *
{@code CLOCK_HOUR_OF_AMPM}
+ *
{@code HOUR_OF_DAY}
+ *
{@code CLOCK_HOUR_OF_DAY}
+ *
{@code AMPM_OF_DAY}
+ *
+ * All other {@code ChronoField} instances will return false.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.isSupportedBy(TemporalAccessor)}
+ * passing {@code this} as the argument.
+ * Whether the field is supported is determined by the field.
+ *
+ * @param field the field to check, null returns false
+ * @return true if the field is supported on this hour-minute, false if not
+ */
+ @Override
+ public boolean isSupported(TemporalField field) {
+ if (field instanceof ChronoField) {
+ return field == MINUTE_OF_HOUR ||
+ field == MINUTE_OF_DAY ||
+ field == HOUR_OF_AMPM ||
+ field == CLOCK_HOUR_OF_AMPM ||
+ field == HOUR_OF_DAY ||
+ field == CLOCK_HOUR_OF_DAY ||
+ field == AMPM_OF_DAY;
+ }
+ return field != null && field.isSupportedBy(this);
+ }
+
+ /**
+ * Checks if the specified unit is supported.
+ *
+ * This checks if the specified unit can be added to, or subtracted from, this hour-minute.
+ * If false, then calling the {@link #plus(long, TemporalUnit)} and
+ * {@link #minus(long, TemporalUnit) minus} methods will throw an exception.
+ *
+ * If the unit is a {@link ChronoUnit} then the query is implemented here.
+ * The supported units are:
+ *
+ *
{@code MINUTES}
+ *
{@code HOURS}
+ *
{@code HALF_DAYS}
+ *
+ * All other {@code ChronoUnit} instances will return false.
+ *
+ * If the unit is not a {@code ChronoUnit}, then the result of this method
+ * is obtained by invoking {@code TemporalUnit.isSupportedBy(Temporal)}
+ * passing {@code this} as the argument.
+ * Whether the unit is supported is determined by the unit.
+ *
+ * @param unit the unit to check, null returns false
+ * @return true if the unit can be added/subtracted, false if not
+ */
+ @Override
+ public boolean isSupported(TemporalUnit unit) {
+ if (unit instanceof ChronoUnit) {
+ return unit == MINUTES || unit == HOURS || unit == HALF_DAYS;
+ }
+ return unit != null && unit.isSupportedBy(this);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the range of valid values for the specified field.
+ *
+ * The range object expresses the minimum and maximum valid values for a field.
+ * If it is not possible to return the range, because the field is not supported
+ * or for some other reason, an exception is thrown.
+ *
+ * If the field is a {@link ChronoField} then the query is implemented here.
+ * The {@link #isSupported(TemporalField) supported fields} will return
+ * appropriate range instances.
+ * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)}
+ * passing {@code this} as the argument.
+ * Whether the range can be obtained is determined by the field.
+ *
+ * @param field the field to query the range for, not null
+ * @return the range of valid values for the field, not null
+ * @throws DateTimeException if the range for the field cannot be obtained
+ * @throws UnsupportedTemporalTypeException if the field is not supported
+ */
+ @Override
+ public ValueRange range(TemporalField field) {
+ return Temporal.super.range(field);
+ }
+
+ /**
+ * Gets the value of the specified field from this hour-minute as an {@code int}.
+ *
+ * This queries this hour-minute for the value for the specified field.
+ * The returned value will always be within the valid range of values for the field.
+ * If it is not possible to return the value, because the field is not supported
+ * or for some other reason, an exception is thrown.
+ *
+ * If the field is a {@link ChronoField} then the query is implemented here.
+ * The {@link #isSupported(TemporalField) supported fields} will return valid
+ * values based on this hour-minute,.
+ * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}
+ * passing {@code this} as the argument. Whether the value can be obtained,
+ * and what the value represents, is determined by the field.
+ *
+ * @param field the field to get, not null
+ * @return the value for the field
+ * @throws DateTimeException if a value for the field cannot be obtained or
+ * the value is outside the range of valid values for the field
+ * @throws UnsupportedTemporalTypeException if the field is not supported or
+ * the range of values exceeds an {@code int}
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public int get(TemporalField field) {
+ if (field instanceof ChronoField) {
+ return get0(field);
+ }
+ return Temporal.super.get(field);
+ }
+
+ /**
+ * Gets the value of the specified field from this hour-minute as a {@code long}.
+ *
+ * This queries this hour-minute for the value for the specified field.
+ * If it is not possible to return the value, because the field is not supported
+ * or for some other reason, an exception is thrown.
+ *
+ * If the field is a {@link ChronoField} then the query is implemented here.
+ * The {@link #isSupported(TemporalField) supported fields} will return valid
+ * values based on this hour-minute.
+ * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}
+ * passing {@code this} as the argument. Whether the value can be obtained,
+ * and what the value represents, is determined by the field.
+ *
+ * @param field the field to get, not null
+ * @return the value for the field
+ * @throws DateTimeException if a value for the field cannot be obtained
+ * @throws UnsupportedTemporalTypeException if the field is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public long getLong(TemporalField field) {
+ if (field instanceof ChronoField) {
+ return get0(field);
+ }
+ return field.getFrom(this);
+ }
+
+ private int get0(TemporalField field) {
+ switch ((ChronoField) field) {
+ case MINUTE_OF_HOUR:
+ return minute;
+ case MINUTE_OF_DAY:
+ return hour * 60 + minute;
+ case HOUR_OF_AMPM:
+ return hour % 12;
+ case CLOCK_HOUR_OF_AMPM:
+ int ham = hour % 12;
+ return (ham % 12 == 0 ? 12 : ham);
+ case HOUR_OF_DAY:
+ return hour;
+ case CLOCK_HOUR_OF_DAY:
+ return (hour == 0 ? 24 : hour);
+ case AMPM_OF_DAY:
+ return hour / 12;
+ default:
+ throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the hour field, from 0 to 23.
+ *
+ * This method returns the hour as an {@code int} from 0 to 23.
+ *
+ * @return the hour, from 0 to 23
+ */
+ public int getHour() {
+ return hour;
+ }
+
+ /**
+ * Gets the minute-of-hour field from 0 to 59.
+ *
+ * This method returns the minute as an {@code int} from 0 to 59.
+ *
+ * @return the minute-of-hour, from 0 to 59
+ */
+ public int getMinute() {
+ return minute;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns an adjusted copy of this hour-minute.
+ *
+ * This returns a {@code HourMinute} based on this one, with the hour-minute adjusted.
+ * The adjustment takes place using the specified adjuster strategy object.
+ * Read the documentation of the adjuster to understand what adjustment will be made.
+ *
+ * The result of this method is obtained by invoking the
+ * {@link TemporalAdjuster#adjustInto(Temporal)} method on the
+ * specified adjuster passing {@code this} as the argument.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param adjuster the adjuster to use, not null
+ * @return a {@code HourMinute} based on {@code this} with the adjustment made, not null
+ * @throws DateTimeException if the adjustment cannot be made
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public HourMinute with(TemporalAdjuster adjuster) {
+ return (HourMinute) adjuster.adjustInto(this);
+ }
+
+ /**
+ * Returns a copy of this hour-minute with the specified field set to a new value.
+ *
+ * This returns a {@code HourMinute} based on this one, with the value
+ * for the specified field changed.
+ * This can be used to change any supported field, such as the hour or minute.
+ * If it is not possible to set the value, because the field is not supported or for
+ * some other reason, an exception is thrown.
+ *
+ * If the field is a {@link ChronoField} then the adjustment is implemented here.
+ * The supported fields behave as follows:
+ *
+ *
{@code MINUTE_OF_HOUR} -
+ * Returns a {@code HourMinute} with the specified minute-of-hour.
+ * The hour will be unchanged.
+ *
{@code MINUTE_OF_DAY} -
+ * Returns a {@code HourMinute} with the specified minute-of-day.
+ *
{@code HOUR_OF_AMPM} -
+ * Returns a {@code HourMinute} with the specified hour-of-am-pm.
+ * The AM/PM and minute-of-hour will be unchanged.
+ *
{@code CLOCK_HOUR_OF_AMPM} -
+ * Returns a {@code HourMinute} with the specified clock-hour-of-am-pm.
+ * The AM/PM and minute-of-hour will be unchanged.
+ *
{@code HOUR_OF_DAY} -
+ * Returns a {@code HourMinute} with the specified hour-of-day.
+ * The minute-of-hour will be unchanged.
+ *
{@code CLOCK_HOUR_OF_DAY} -
+ * Returns a {@code HourMinute} with the specified clock-hour-of-day.
+ * The minute-of-hour will be unchanged.
+ *
{@code AMPM_OF_DAY} -
+ * Returns a {@code HourMinute} with the specified AM/PM.
+ * The hour-of-am-pm and minute-of-hour will be unchanged.
+ *
+ *
+ * In all cases, if the new value is outside the valid range of values for the field
+ * then a {@code DateTimeException} will be thrown.
+ *
+ * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)}
+ * passing {@code this} as the argument. In this case, the field determines
+ * whether and how to adjust the instant.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param field the field to set in the result, not null
+ * @param newValue the new value of the field in the result
+ * @return a {@code HourMinute} based on {@code this} with the specified field set, not null
+ * @throws DateTimeException if the field cannot be set
+ * @throws UnsupportedTemporalTypeException if the field is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public HourMinute with(TemporalField field, long newValue) {
+ if (field instanceof ChronoField) {
+ ChronoField f = (ChronoField) field;
+ f.checkValidValue(newValue);
+ switch (f) {
+ case MINUTE_OF_HOUR:
+ return withMinute((int) newValue);
+ case MINUTE_OF_DAY:
+ return plusMinutes(newValue - (hour * MINUTES_PER_HOUR + minute));
+ case HOUR_OF_AMPM:
+ return plusHours(newValue - (hour % 12));
+ case CLOCK_HOUR_OF_AMPM:
+ return plusHours((newValue == 12 ? 0 : newValue) - (hour % 12));
+ case HOUR_OF_DAY:
+ return withHour((int) newValue);
+ case CLOCK_HOUR_OF_DAY:
+ return withHour((int) (newValue == 24 ? 0 : newValue));
+ case AMPM_OF_DAY:
+ return plusHours((newValue - (hour / 12)) * 12);
+ default:
+ throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
+ }
+ }
+ return field.adjustInto(this, newValue);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this {@code HourMinute} with the hour altered.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param hour the hour to set in the returned hour-minute, from 0 to 23
+ * @return a {@code HourMinute} based on this hour-minute with the requested hour, not null
+ * @throws DateTimeException if the hour value is invalid
+ */
+ public HourMinute withHour(int hour) {
+ HOUR_OF_DAY.checkValidValue(hour);
+ return with(hour, minute);
+ }
+
+ /**
+ * Returns a copy of this {@code HourMinute} with the minute-of-hour altered.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param minute the minute-of-hour to set in the returned hour-minute, from 0 to 59
+ * @return a {@code HourMinute} based on this hour-minute with the requested minute, not null
+ * @throws DateTimeException if the minute-of-hour value is invalid
+ */
+ public HourMinute withMinute(int minute) {
+ MINUTE_OF_HOUR.checkValidValue(minute);
+ return with(hour, minute);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this hour-minute with the specified amount added.
+ *
+ * This returns a {@code HourMinute} based on this one, with the specified amount added.
+ * The amount is typically {@link Period} but may be any other type implementing
+ * the {@link TemporalAmount} interface.
+ *
+ * The calculation is delegated to the amount object by calling
+ * {@link TemporalAmount#addTo(Temporal)}. The amount implementation is free
+ * to implement the addition in any way it wishes, however it typically
+ * calls back to {@link #plus(long, TemporalUnit)}. Consult the documentation
+ * of the amount implementation to determine if it can be successfully added.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToAdd the amount to add, not null
+ * @return a {@code HourMinute} based on this hour-minute with the addition made, not null
+ * @throws DateTimeException if the addition cannot be made
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public HourMinute plus(TemporalAmount amountToAdd) {
+ return (HourMinute) amountToAdd.addTo(this);
+ }
+
+ /**
+ * Returns a copy of this hour-minute with the specified amount added.
+ *
+ * This returns a {@code HourMinute} based on this one, with the amount
+ * in terms of the unit added. If it is not possible to add the amount, because the
+ * unit is not supported or for some other reason, an exception is thrown.
+ *
+ * If the field is a {@link ChronoUnit} then the addition is implemented here.
+ * The supported fields behave as follows:
+ *
+ *
{@code MINUTES} -
+ * Returns a {@code LocalTime} with the specified number of minutes added.
+ * This is equivalent to {@link #plusMinutes(long)}.
+ *
{@code HOURS} -
+ * Returns a {@code LocalTime} with the specified number of hours added.
+ * This is equivalent to {@link #plusHours(long)}.
+ *
{@code HALF_DAYS} -
+ * Returns a {@code LocalTime} with the specified number of half-days added.
+ * This is equivalent to {@link #plusHours(long)} with the amount
+ * multiplied by 12.
+ *
+ *
+ * All other {@code ChronoUnit} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoUnit}, then the result of this method
+ * is obtained by invoking {@code TemporalUnit.addTo(Temporal, long)}
+ * passing {@code this} as the argument. In this case, the unit determines
+ * whether and how to perform the addition.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToAdd the amount of the unit to add to the result, may be negative
+ * @param unit the unit of the amount to add, not null
+ * @return a {@code HourMinute} based on this hour-minute with the specified amount added, not null
+ * @throws DateTimeException if the addition cannot be made
+ * @throws UnsupportedTemporalTypeException if the unit is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public HourMinute plus(long amountToAdd, TemporalUnit unit) {
+ if (unit instanceof ChronoUnit) {
+ switch ((ChronoUnit) unit) {
+ case MINUTES:
+ return plusMinutes(amountToAdd);
+ case HOURS:
+ return plusHours(amountToAdd);
+ case HALF_DAYS:
+ return plusHours((amountToAdd % 2) * 12);
+ default:
+ throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
+ }
+ }
+ return unit.addTo(this, amountToAdd);
+ }
+
+ /**
+ * Returns a copy of this {@code HourMinute} with the specified number of hours added.
+ *
+ * This adds the specified number of hours to this time, returning a new time.
+ * The calculation wraps around midnight.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param hoursToAdd the hours to add, may be negative
+ * @return an {@code HourMinute} based on this time with the hours added, not null
+ */
+ public HourMinute plusHours(long hoursToAdd) {
+ if (hoursToAdd == 0) {
+ return this;
+ }
+ int newHour = ((int) (hoursToAdd % HOURS_PER_DAY) + hour + HOURS_PER_DAY) % HOURS_PER_DAY;
+ return with(newHour, minute);
+ }
+
+ /**
+ * Returns a copy of this {@code HourMinute} with the specified number of minutes added.
+ *
+ * This adds the specified number of minutes to this time, returning a new time.
+ * The calculation wraps around midnight.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param minutesToAdd the minutes to add, may be negative
+ * @return an {@code HourMinute} based on this time with the minutes added, not null
+ */
+ public HourMinute plusMinutes(long minutesToAdd) {
+ if (minutesToAdd == 0) {
+ return this;
+ }
+ int mofd = hour * MINUTES_PER_HOUR + minute;
+ int newMofd = ((int) (minutesToAdd % MINUTES_PER_DAY) + mofd + MINUTES_PER_DAY) % MINUTES_PER_DAY;
+ if (mofd == newMofd) {
+ return this;
+ }
+ int newHour = newMofd / MINUTES_PER_HOUR;
+ int newMinute = newMofd % MINUTES_PER_HOUR;
+ return with(newHour, newMinute);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this hour-minute with the specified amount subtracted.
+ *
+ * This returns a {@code HourMinute} based on this one, with the specified amount subtracted.
+ * The amount is typically {@link Period} but may be any other type implementing
+ * the {@link TemporalAmount} interface.
+ *
+ * The calculation is delegated to the amount object by calling
+ * {@link TemporalAmount#subtractFrom(Temporal)}. The amount implementation is free
+ * to implement the subtraction in any way it wishes, however it typically
+ * calls back to {@link #minus(long, TemporalUnit)}. Consult the documentation
+ * of the amount implementation to determine if it can be successfully subtracted.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToSubtract the amount to subtract, not null
+ * @return a {@code HourMinute} based on this hour-minute with the subtraction made, not null
+ * @throws DateTimeException if the subtraction cannot be made
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public HourMinute minus(TemporalAmount amountToSubtract) {
+ return (HourMinute) amountToSubtract.subtractFrom(this);
+ }
+
+ /**
+ * Returns a copy of this hour-minute with the specified amount subtracted.
+ *
+ * This returns a {@code HourMinute} based on this one, with the amount
+ * in terms of the unit subtracted. If it is not possible to subtract the amount,
+ * because the unit is not supported or for some other reason, an exception is thrown.
+ *
+ * This method is equivalent to {@link #plus(long, TemporalUnit)} with the amount negated.
+ * See that method for a full description of how addition, and thus subtraction, works.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToSubtract the amount of the unit to subtract from the result, may be negative
+ * @param unit the unit of the amount to subtract, not null
+ * @return a {@code HourMinute} based on this hour-minute with the specified amount subtracted, not null
+ * @throws DateTimeException if the subtraction cannot be made
+ * @throws UnsupportedTemporalTypeException if the unit is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public HourMinute minus(long amountToSubtract, TemporalUnit unit) {
+ return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit));
+ }
+
+ /**
+ * Returns a copy of this hour-minute with the specified period in hours subtracted.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param hoursToSubtract the hours to subtract, may be negative
+ * @return a {@code HourMinute} based on this hour-minute with the hours subtracted, not null
+ * @throws DateTimeException if the result exceeds the supported range
+ */
+ public HourMinute minusHours(long hoursToSubtract) {
+ return (hoursToSubtract == Long.MIN_VALUE ? plusHours(Long.MAX_VALUE).plusHours(1) : plusHours(-hoursToSubtract));
+ }
+
+ /**
+ * Returns a copy of this hour-minute with the specified period in minutes subtracted.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param minutesToSubtract the minutes to subtract, may be negative
+ * @return a {@code HourMinute} based on this hour-minute with the minutes subtracted, not null
+ * @throws DateTimeException if the result exceeds the supported range
+ */
+ public HourMinute minusMinutes(long minutesToSubtract) {
+ return (minutesToSubtract == Long.MIN_VALUE ? plusMinutes(Long.MAX_VALUE).plusMinutes(1) : plusMinutes(-minutesToSubtract));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Queries this hour-minute using the specified query.
+ *
+ * This queries this hour-minute using the specified query strategy object.
+ * The {@code TemporalQuery} object defines the logic to be used to
+ * obtain the result. Read the documentation of the query to understand
+ * what the result of this method will be.
+ *
+ * The result of this method is obtained by invoking the
+ * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the
+ * specified query passing {@code this} as the argument.
+ *
+ * @param the type of the result
+ * @param query the query to invoke, not null
+ * @return the query result, null may be returned (defined by the query)
+ * @throws DateTimeException if unable to query (defined by the query)
+ * @throws ArithmeticException if numeric overflow occurs (defined by the query)
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public R query(TemporalQuery query) {
+ if (query == TemporalQueries.localTime()) {
+ return (R) toLocalTime();
+ } else if (query == TemporalQueries.precision()) {
+ return (R) MINUTES;
+ }
+ return Temporal.super.query(query);
+ }
+
+ /**
+ * Adjusts the specified temporal object to have this hour-minute.
+ * Note that if the target has a second or nanosecond field, that is not altered by this method.
+ *
+ * This returns a temporal object of the same observable type as the input
+ * with the hour and minute changed to be the same as this.
+ *
+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)}
+ * passing {@link ChronoField#MINUTE_OF_DAY} as the field.
+ * Note that this does not affect any second/nanosecond field in the target.
+ *
+ * In most cases, it is clearer to reverse the calling pattern by using
+ * {@link Temporal#with(TemporalAdjuster)}:
+ *
+ * // these two lines are equivalent, but the second approach is recommended
+ * temporal = thisHourMinute.adjustInto(temporal);
+ * temporal = temporal.with(thisHourMinute);
+ *
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param temporal the target object to be adjusted, not null
+ * @return the adjusted object, not null
+ * @throws DateTimeException if unable to make the adjustment
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public Temporal adjustInto(Temporal temporal) {
+ return temporal.with(MINUTE_OF_DAY, hour * MINUTES_PER_HOUR + minute);
+ }
+
+ /**
+ * Calculates the amount of time until another hour-minute in terms of the specified unit.
+ *
+ * This calculates the amount of time between two {@code HourMinute}
+ * objects in terms of a single {@code TemporalUnit}.
+ * The start and end points are {@code this} and the specified hour-minute.
+ * The result will be negative if the end is before the start.
+ * The {@code Temporal} passed to this method is converted to a
+ * {@code HourMinute} using {@link #from(TemporalAccessor)}.
+ * For example, the period in hours between two hour-minutes can be calculated
+ * using {@code startHourMinute.until(endHourMinute, YEARS)}.
+ *
+ * The calculation is implemented in this method for {@link ChronoUnit}.
+ * The units {@code MINUTES}, {@code HOURS} and {@code HALF_DAYS} are supported.
+ * Other {@code ChronoUnit} values will throw an exception.
+ *
+ * If the unit is not a {@code ChronoUnit}, then the result of this method
+ * is obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)}
+ * passing {@code this} as the first argument and the converted input temporal
+ * as the second argument.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param endExclusive the end date, exclusive, which is converted to a {@code HourMinute}, not null
+ * @param unit the unit to measure the amount in, not null
+ * @return the amount of time between this hour-minute and the end hour-minute
+ * @throws DateTimeException if the amount cannot be calculated, or the end
+ * temporal cannot be converted to a {@code HourMinute}
+ * @throws UnsupportedTemporalTypeException if the unit is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public long until(Temporal endExclusive, TemporalUnit unit) {
+ HourMinute end = HourMinute.from(endExclusive);
+ long minutesUntil = (end.hour * MINUTES_PER_HOUR + end.minute) - (hour * MINUTES_PER_HOUR + minute); // no overflow
+ if (unit instanceof ChronoUnit) {
+ switch ((ChronoUnit) unit) {
+ case MINUTES:
+ return minutesUntil;
+ case HOURS:
+ return minutesUntil / MINUTES_PER_HOUR;
+ case HALF_DAYS:
+ return minutesUntil / (12 * MINUTES_PER_HOUR);
+ default:
+ throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
+ }
+ }
+ return unit.between(this, end);
+ }
+
+ /**
+ * Formats this hour-minute using the specified formatter.
+ *
+ * This hour-minute will be passed to the formatter to produce a string.
+ *
+ * @param formatter the formatter to use, not null
+ * @return the formatted hour-minute string, not null
+ * @throws DateTimeException if an error occurs during printing
+ */
+ public String format(DateTimeFormatter formatter) {
+ Objects.requireNonNull(formatter, "formatter");
+ return formatter.format(this);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Combines this time with a date to create a {@code LocalDateTime}.
+ *
+ * This returns a {@code LocalDateTime} formed from this time at the specified date.
+ * All possible combinations of date and time are valid.
+ *
+ * @param date the date to combine with, not null
+ * @return the local date-time formed from this time and the specified date, not null
+ */
+ public LocalDateTime atDate(LocalDate date) {
+ return LocalDateTime.of(date, toLocalTime());
+ }
+
+ /**
+ * Combines this time with an offset to create an {@code OffsetTime}.
+ *
+ * This returns an {@code OffsetTime} formed from this time at the specified offset.
+ * All possible combinations of time and offset are valid.
+ *
+ * @param offset the offset to combine with, not null
+ * @return the offset time formed from this time and the specified offset, not null
+ */
+ public OffsetTime atOffset(ZoneOffset offset) {
+ return OffsetTime.of(toLocalTime(), offset);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns the equivalent {@code LocalTime}.
+ *
+ * This returns a {@code LocalTime} formed from this hour and minute.
+ *
+ * @return the equivalent local time, not null
+ */
+ public LocalTime toLocalTime() {
+ return LocalTime.of(hour, minute);
+ }
+
+ //-------------------------------------------------------------------------
+ /**
+ * Compares this hour-minute to another
+ *
+ * The comparison is based first on the value of the hour, then on the value of the minute.
+ * It is "consistent with equals", as defined by {@link Comparable}.
+ *
+ * @param other the other hour-minute to compare to, not null
+ * @return the comparator value, negative if less, positive if greater
+ */
+ @Override
+ public int compareTo(HourMinute other) {
+ int cmp = (hour - other.hour);
+ if (cmp == 0) {
+ cmp = (minute - other.minute);
+ }
+ return cmp;
+ }
+
+ /**
+ * Is this hour-minute after the specified hour-minute.
+ *
+ * @param other the other hour-minute to compare to, not null
+ * @return true if this is after the specified hour-minute
+ */
+ public boolean isAfter(HourMinute other) {
+ return compareTo(other) > 0;
+ }
+
+ /**
+ * Is this hour-minute before the specified hour-minute.
+ *
+ * @param other the other hour-minute to compare to, not null
+ * @return true if this point is before the specified hour-minute
+ */
+ public boolean isBefore(HourMinute other) {
+ return compareTo(other) < 0;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Checks if this hour-minute is equal to another hour-minute.
+ *
+ * The comparison is based on the time-line position of the hour-minute.
+ *
+ * @param obj the object to check, null returns false
+ * @return true if this is equal to the other hour-minute
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof HourMinute) {
+ HourMinute other = (HourMinute) obj;
+ return hour == other.hour && minute == other.minute;
+ }
+ return false;
+ }
+
+ /**
+ * A hash code for this hour-minute.
+ *
+ * @return a suitable hash code
+ */
+ @Override
+ public int hashCode() {
+ return hour * MINUTES_PER_HOUR + minute;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Outputs this hour-minute as a {@code String}, such as {@code 12:31}.
+ *
+ * @return a string representation of this hour-minute, not null
+ */
+ @Override
+ @ToString
+ public String toString() {
+ return new StringBuilder(5)
+ .append(hour < 10 ? "0" : "").append(hour)
+ .append(minute < 10 ? ":0" : ":").append(minute)
+ .toString();
+ }
+
+}
diff --git a/src/main/java/org/threeten/extra/Hours.java b/src/main/java/org/threeten/extra/Hours.java
index 5d829167..75e1420a 100644
--- a/src/main/java/org/threeten/extra/Hours.java
+++ b/src/main/java/org/threeten/extra/Hours.java
@@ -48,6 +48,9 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.joda.convert.FromString;
+import org.joda.convert.ToString;
+
/**
* A hour-based amount of time, such as '4 hours'.
*
@@ -75,12 +78,19 @@ public final class Hours
*/
private static final long serialVersionUID = -8494096666041369608L;
+ /**
+ * The number of hours per day.
+ */
+ private static final int HOURS_PER_DAY = 24;
+
/**
* The pattern for parsing.
*/
private static final Pattern PATTERN =
- Pattern.compile("([-+]?)PT"
- + "(?:([-+]?[0-9]+)H)?", Pattern.CASE_INSENSITIVE);
+ Pattern.compile("([-+]?)P"
+ + "(?:([-+]?[0-9]+)D)?"
+ + "(?:T"
+ + "(?:([-+]?[0-9]+)H)?)?", Pattern.CASE_INSENSITIVE);
/**
* The number of hours.
@@ -145,19 +155,25 @@ public static Hours from(TemporalAmount amount) {
/**
* Obtains a {@code Hours} from a text string such as {@code PTnH}.
*
- * This will parse the string produced by {@code toString()} which is
- * based on the ISO-8601 period format {@code PTnH}.
+ * This will parse the string produced by {@code toString()} and other
+ * related formats based on ISO-8601 {@code PnDTnH}.
*
* The string starts with an optional sign, denoted by the ASCII negative
* or positive symbol. If negative, the whole amount is negated.
- * The ASCII letters "P" and "T" are next in upper or lower case.
- * The section has the suffix "H" in ASCII in either upper or lower case.
- * The number part must consist of ASCII digits. The number may be prefixed
- * by the ASCII negative or positive symbol. The number must parse to an
- * {@code int}.
- *
- * The leading plus/minus sign, and negative value for hours is not part of
- * the ISO-8601 standard.
+ * The ASCII letter "P" is next in upper or lower case.
+ * There are two sections consisting of a number and a suffix.
+ * There is one section for days suffixed by "D",
+ * followed by one section for hours suffixed by "H".
+ * At least one section must be present.
+ * If the hours section is present it must be prefixed by "T".
+ * If the hours section is omitted the "T" must be omitted.
+ * Letters must be in ASCII upper or lower case.
+ * The number part of each section must consist of ASCII digits.
+ * The number may be prefixed by the ASCII negative or positive symbol.
+ * The number must parse to an {@code int}.
+ *
+ * The leading plus/minus sign, and negative values for days and hours
+ * are not part of the ISO-8601 standard.
*
*
* @param text the text to parse, not null
* @return the parsed period, not null
* @throws DateTimeParseException if the text cannot be parsed to a period
*/
+ @FromString
public static Hours parse(CharSequence text) {
Objects.requireNonNull(text, "text");
Matcher matcher = PATTERN.matcher(text);
if (matcher.matches()) {
- int negate = ("-".equals(matcher.group(1)) ? -1 : 1);
- String hoursStr = matcher.group(2);
- if (hoursStr != null) {
- try {
- int hours = Integer.parseInt(hoursStr);
- return of(negate * hours);
- } catch (NumberFormatException ex) {
- throw new DateTimeParseException("Text cannot be parsed to Hours, non-numeric hours", text, 0, ex);
+ int negate = "-".equals(matcher.group(1)) ? -1 : 1;
+ String daysStr = matcher.group(2);
+ String hoursStr = matcher.group(3);
+ if (daysStr != null || hoursStr != null) {
+ int hours = 0;
+ if (hoursStr != null) {
+ try {
+ hours = Integer.parseInt(hoursStr);
+ } catch (NumberFormatException ex) {
+ throw new DateTimeParseException("Text cannot be parsed to Hours, non-numeric hours", text, 0, ex);
+ }
+ }
+ if (daysStr != null) {
+ try {
+ int daysAsHours = Math.multiplyExact(Integer.parseInt(daysStr), HOURS_PER_DAY);
+ hours = Math.addExact(hours, daysAsHours);
+ } catch (NumberFormatException ex) {
+ throw new DateTimeParseException("Text cannot be parsed to Hours, non-numeric days", text, 0, ex);
+ }
}
+ return of(Math.multiplyExact(hours, negate));
}
}
- throw new DateTimeParseException("Text cannot be parsed to a Hours", text, 0);
+ throw new DateTimeParseException("Text cannot be parsed to Hours", text, 0);
}
//-----------------------------------------------------------------------
/**
- * Obtains a {@code Hours} consisting of the number of hours between two dates.
+ * Obtains a {@code Hours} consisting of the number of hours between two temporals.
*
- * The start date is included, but the end date is not.
+ * The start temporal is included, but the end temporal is not.
* The result of this method can be negative if the end is before the start.
*
- * @param startDateInclusive the start date, inclusive, not null
- * @param endDateExclusive the end date, exclusive, not null
- * @return the number of hours between this date and the end date, not null
+ * @param startInclusive the start temporal, inclusive, not null
+ * @param endExclusive the end temporal, exclusive, not null
+ * @return the number of hours between the start and end temporals, not null
*/
- public static Hours between(Temporal startDateInclusive, Temporal endDateExclusive) {
- return of(Math.toIntExact(HOURS.between(startDateInclusive, endDateExclusive)));
+ public static Hours between(Temporal startInclusive, Temporal endExclusive) {
+ return of(Math.toIntExact(HOURS.between(startInclusive, endExclusive)));
}
//-----------------------------------------------------------------------
@@ -254,7 +285,7 @@ public long get(TemporalUnit unit) {
*/
@Override
public List getUnits() {
- return Collections.singletonList(ChronoUnit.HOURS);
+ return Collections.singletonList(HOURS);
}
//-----------------------------------------------------------------------
@@ -267,6 +298,33 @@ public int getAmount() {
return hours;
}
+ /**
+ * Checks if the amount is negative.
+ *
+ * @return true if the amount is negative, false if the amount is zero or positive
+ */
+ public boolean isNegative() {
+ return getAmount() < 0;
+ }
+
+ /**
+ * Checks if the amount is zero.
+ *
+ * @return true if the amount is zero, false if not
+ */
+ public boolean isZero() {
+ return getAmount() == 0;
+ }
+
+ /**
+ * Checks if the amount is positive.
+ *
+ * @return true if the amount is positive, false if the amount is zero or negative
+ */
+ public boolean isPositive() {
+ return getAmount() > 0;
+ }
+
//-----------------------------------------------------------------------
/**
* Returns a copy of this amount with the specified amount added.
@@ -308,13 +366,13 @@ public Hours plus(int hours) {
*
* This instance is immutable and unaffected by this method call.
*
- * @param amountToAdd the amount to add, not null
+ * @param amountToSubtract the amount to subtract, not null
* @return a {@code Hours} based on this instance with the requested amount subtracted, not null
* @throws DateTimeException if the specified amount contains an invalid unit
* @throws ArithmeticException if numeric overflow occurs
*/
- public Hours minus(TemporalAmount amountToAdd) {
- return minus(Hours.from(amountToAdd).getAmount());
+ public Hours minus(TemporalAmount amountToSubtract) {
+ return minus(Hours.from(amountToSubtract).getAmount());
}
/**
@@ -400,14 +458,28 @@ public Hours abs() {
/**
* Gets the number of hours as a {@code Duration}.
*
- * This returns a duration with the same number of months.
+ * This returns a duration with the same number of hours.
*
* @return the equivalent duration, not null
+ * @deprecated Use {@link #toDuration()}
*/
+ @Deprecated
public Duration toPeriod() {
return Duration.ofHours(hours);
}
+ //-------------------------------------------------------------------------
+ /**
+ * Gets the number of hours as a {@code Duration}.
+ *
+ * This returns a duration with the same number of hours.
+ *
+ * @return the equivalent duration, not null
+ */
+ public Duration toDuration() {
+ return Duration.ofHours(hours);
+ }
+
//-----------------------------------------------------------------------
/**
* Adds this amount to the specified temporal object.
@@ -529,6 +601,7 @@ public int hashCode() {
* @return the number of hours in ISO-8601 string format
*/
@Override
+ @ToString
public String toString() {
return "PT" + hours + "H";
}
diff --git a/src/main/java/org/threeten/extra/Interval.java b/src/main/java/org/threeten/extra/Interval.java
index f4b900fb..3b50b7c4 100644
--- a/src/main/java/org/threeten/extra/Interval.java
+++ b/src/main/java/org/threeten/extra/Interval.java
@@ -35,10 +35,17 @@
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
+import java.time.LocalDateTime;
import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
+import java.time.temporal.TemporalAccessor;
import java.util.Objects;
+import org.joda.convert.FromString;
+import org.joda.convert.ToString;
+
/**
* An immutable interval of time between two instants.
*
@@ -86,8 +93,8 @@ public final class Interval
*
* The end instant must not be before the start instant.
*
- * @param startInclusive the start instant, inclusive, MIN_DATE treated as unbounded, not null
- * @param endExclusive the end instant, exclusive, MAX_DATE treated as unbounded, not null
+ * @param startInclusive the start instant, inclusive, {@link Instant#MIN} treated as unbounded, not null
+ * @param endExclusive the end instant, exclusive, {@link Instant#MAX} treated as unbounded, not null
* @return the half-open interval, not null
* @throws DateTimeException if the end is before the start
*/
@@ -95,7 +102,7 @@ public static Interval of(Instant startInclusive, Instant endExclusive) {
Objects.requireNonNull(startInclusive, "startInclusive");
Objects.requireNonNull(endExclusive, "endExclusive");
if (endExclusive.isBefore(startInclusive)) {
- throw new DateTimeException("End instant must on or after start instant");
+ throw new DateTimeException("End instant must be equal or after start instant");
}
return new Interval(startInclusive, endExclusive);
}
@@ -117,59 +124,161 @@ public static Interval of(Instant startInclusive, Duration duration) {
Objects.requireNonNull(startInclusive, "startInclusive");
Objects.requireNonNull(duration, "duration");
if (duration.isNegative()) {
- throw new DateTimeException("Duration must not be zero or negative");
+ throw new DateTimeException("Duration must not be negative");
}
return new Interval(startInclusive, startInclusive.plus(duration));
}
+ /**
+ * Obtains an instance of {@code Interval} from the duration and the end.
+ *
+ * The start instant is calculated as the end minus the duration.
+ * The duration must not be negative.
+ *
+ * @param duration the duration from the start to the end, not null
+ * @param endExclusive the end instant, exclusive, not null
+ * @return the interval, not null
+ * @throws DateTimeException if the end is before the start,
+ * or if the duration addition cannot be made
+ * @throws ArithmeticException if numeric overflow occurs when subtracting the duration
+ */
+ public static Interval of(Duration duration, Instant endExclusive) {
+ Objects.requireNonNull(duration, "duration");
+ Objects.requireNonNull(endExclusive, "endExclusive");
+ if (duration.isNegative()) {
+ throw new DateTimeException("Duration must not be negative");
+ }
+ return new Interval(endExclusive.minus(duration), endExclusive);
+ }
+
+ /**
+ * Obtains an instance of {@code Interval} with the specified start instant and unbounded end.
+ *
+ * @param startInclusive the start instant, inclusive, not null
+ * @return a new {@code Instant} with the specified start instant.
+ */
+ public static Interval startingAt(Instant startInclusive) {
+ Objects.requireNonNull(startInclusive, "startInclusive");
+ return Interval.ALL.withStart(startInclusive);
+ }
+
+ /**
+ * Obtains an instance of {@code Interval} with unbounded start and the specified end instant.
+ *
+ * @param endExclusive the end instant, exclusive, not null
+ * @return a new {@code Instant} with the specified end instant.
+ */
+ public static Interval endingAt(Instant endExclusive) {
+ Objects.requireNonNull(endExclusive, "endExclusive");
+ return Interval.ALL.withEnd(endExclusive);
+ }
+
//-----------------------------------------------------------------------
/**
* Obtains an instance of {@code Interval} from a text string such as
* {@code 2007-12-03T10:15:30Z/2007-12-04T10:15:30Z}, where the end instant is exclusive.
*
- * The string must consist of one of the following three formats:
+ * The string must consist of one of the following four formats:
*
*
a representations of an {@link OffsetDateTime}, followed by a forward slash,
* followed by a representation of a {@link OffsetDateTime}
+ *
a representations of an {@link OffsetDateTime}, followed by a forward slash,
+ * followed by a representation of a {@link LocalDateTime}, where the end offset is implied.
*
a representation of an {@link OffsetDateTime}, followed by a forward slash,
- * followed by a representation of a {@link Duration}
- *
a representation of a {@link Duration}, followed by a forward slash,
+ * followed by a representation of a {@link PeriodDuration}
+ *
a representation of a {@link PeriodDuration}, followed by a forward slash,
* followed by a representation of an {@link OffsetDateTime}
*
- *
+ *
+ * ISO-8601 supports a very wide range of possible inputs, many of which are not supported here.
+ * For example, basic format, week-based dates, ordinal dates and date-style period formats are not supported.
*
* @param text the text to parse, not null
* @return the parsed interval, not null
* @throws DateTimeParseException if the text cannot be parsed
*/
+ @FromString
public static Interval parse(CharSequence text) {
Objects.requireNonNull(text, "text");
for (int i = 0; i < text.length(); i++) {
if (text.charAt(i) == '/') {
- char firstChar = text.charAt(0);
- if (firstChar == 'P' || firstChar == 'p') {
- // duration followed by instant
- Duration duration = Duration.parse(text.subSequence(0, i));
- Instant end = OffsetDateTime.parse(text.subSequence(i + 1, text.length())).toInstant();
- return Interval.of(end.minus(duration), end);
- } else {
- // instant followed by instant or duration
- Instant start = OffsetDateTime.parse(text.subSequence(0, i)).toInstant();
- if (i + 1 < text.length()) {
- char c = text.charAt(i + 1);
- if (c == 'P' || c == 'p') {
- Duration duration = Duration.parse(text.subSequence(i + 1, text.length()));
- return Interval.of(start, start.plus(duration));
- }
- }
- Instant end = OffsetDateTime.parse(text.subSequence(i + 1, text.length())).toInstant();
- return Interval.of(start, end);
- }
+ return parseSplit(text.subSequence(0, i), text.subSequence(i + 1, text.length()));
}
}
throw new DateTimeParseException("Interval cannot be parsed, no forward slash found", text, 0);
}
+ private static Interval parseSplit(CharSequence startStr, CharSequence endStr) {
+ char firstChar = startStr.charAt(0);
+ if (firstChar == 'P' || firstChar == 'p') {
+ // duration followed by instant
+ PeriodDuration amount = PeriodDuration.parse(startStr);
+ try {
+ OffsetDateTime end = OffsetDateTime.parse(endStr);
+ return Interval.of(end.minus(amount).toInstant(), end.toInstant());
+ } catch (DateTimeParseException ex) {
+ // handle case where Instant is outside the bounds of OffsetDateTime
+ Instant end = Instant.parse(endStr);
+ // addition of PeriodDuration only supported by OffsetDateTime,
+ // but to make that work need to move point being subtracted from closer to EPOCH
+ long move = end.isBefore(Instant.EPOCH) ? 1000 * 86400 : -1000 * 86400;
+ Instant start = end.plusSeconds(move).atOffset(ZoneOffset.UTC).minus(amount).toInstant().minusSeconds(move);
+ return Interval.of(start, end);
+ }
+ }
+ // instant followed by instant or duration
+ OffsetDateTime start;
+ try {
+ start = OffsetDateTime.parse(startStr);
+ } catch (DateTimeParseException ex) {
+ return parseStartExtended(startStr, endStr);
+ }
+ if (endStr.length() > 0) {
+ char c = endStr.charAt(0);
+ if (c == 'P' || c == 'p') {
+ PeriodDuration amount = PeriodDuration.parse(endStr);
+ return Interval.of(start.toInstant(), start.plus(amount).toInstant());
+ }
+ }
+ return parseEndDateTime(start.toInstant(), start.getOffset(), endStr);
+ }
+
+ // handle case where Instant is outside the bounds of OffsetDateTime
+ private static Interval parseStartExtended(CharSequence startStr, CharSequence endStr) {
+ Instant start = Instant.parse(startStr);
+ if (endStr.length() > 0) {
+ char c = endStr.charAt(0);
+ if (c == 'P' || c == 'p') {
+ PeriodDuration amount = PeriodDuration.parse(endStr);
+ // addition of PeriodDuration only supported by OffsetDateTime,
+ // but to make that work need to move point being added to closer to EPOCH
+ long move = start.isBefore(Instant.EPOCH) ? 1000 * 86400 : -1000 * 86400;
+ Instant end = start.plusSeconds(move).atOffset(ZoneOffset.UTC).plus(amount).toInstant().minusSeconds(move);
+ return Interval.of(start, end);
+ }
+ }
+ // infer offset from start if not specified by end
+ return parseEndDateTime(start, ZoneOffset.UTC, endStr);
+ }
+
+ // parse when there are two date-times
+ private static Interval parseEndDateTime(Instant start, ZoneOffset offset, CharSequence endStr) {
+ try {
+ TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest(endStr, OffsetDateTime::from, LocalDateTime::from);
+ if (temporal instanceof OffsetDateTime) {
+ OffsetDateTime odt = (OffsetDateTime) temporal;
+ return Interval.of(start, odt.toInstant());
+ } else {
+ // infer offset from start if not specified by end
+ LocalDateTime ldt = (LocalDateTime) temporal;
+ return Interval.of(start, ldt.toInstant(offset));
+ }
+ } catch (DateTimeParseException ex) {
+ Instant end = Instant.parse(endStr);
+ return Interval.of(start, end);
+ }
+ }
+
//-----------------------------------------------------------------------
/**
* Constructor.
@@ -195,7 +304,7 @@ public Instant getStart() {
return start;
}
- /**
+ /**
* Gets the end of this time interval, exclusive.
*
* This will return {@link Instant#MAX} if the range is unbounded at the end.
@@ -212,7 +321,7 @@ public Instant getEnd() {
* Checks if the range is empty.
*
* An empty range occurs when the start date equals the inclusive end date.
- *
+ *
* @return true if the range is empty
*/
public boolean isEmpty() {
@@ -221,7 +330,7 @@ public boolean isEmpty() {
/**
* Checks if the start of the interval is unbounded.
- *
+ *
* @return true if start is unbounded
*/
public boolean isUnboundedStart() {
@@ -230,7 +339,7 @@ public boolean isUnboundedStart() {
/**
* Checks if the end of the interval is unbounded.
- *
+ *
* @return true if end is unbounded
*/
public boolean isUnboundedEnd() {
@@ -261,22 +370,6 @@ public Interval withEnd(Instant end) {
}
//-----------------------------------------------------------------------
- /**
- * Checks if this interval contains the specified instant.
- *
- * This checks if the specified instant is within the bounds of this interval.
- * If this range has an unbounded start then {@code contains(Instant#MIN)} returns true.
- * If this range has an unbounded end then {@code contains(Instant#MAX)} returns true.
- * If this range is empty then this method always returns false.
- *
- * @param instant the instant, not null
- * @return true if this interval contains the instant
- */
- public boolean contains(Instant instant) {
- Objects.requireNonNull(instant, "instant");
- return start.compareTo(instant) <= 0 && (instant.compareTo(end) < 0 || isUnboundedEnd());
- }
-
/**
* Checks if this interval encloses the specified interval.
*
@@ -324,7 +417,7 @@ public boolean isConnected(Interval other) {
/**
* Checks if this interval overlaps the specified interval.
*
- * The result is true if the the two intervals share some part of the time-line.
+ * The result is true if the two intervals share some part of the time-line.
* An empty interval overlaps itself.
*
* This is equivalent to {@code (isConnected(other) && !abuts(other))}.
@@ -343,7 +436,7 @@ public boolean overlaps(Interval other) {
*
* This finds the intersection of two intervals.
* This throws an exception if the two intervals are not {@linkplain #isConnected(Interval) connected}.
- *
+ *
* @param other the other interval to check for, not null
* @return the interval that is the intersection of the two intervals
* @throws DateTimeException if the intervals do not connect
@@ -371,7 +464,7 @@ public Interval intersection(Interval other) {
*
* This finds the union of two intervals.
* This throws an exception if the two intervals are not {@linkplain #isConnected(Interval) connected}.
- *
+ *
* @param other the other interval to check for, not null
* @return the interval that is the union of the two intervals
* @throws DateTimeException if the intervals do not connect
@@ -399,7 +492,7 @@ public Interval union(Interval other) {
*
* The result of this method will {@linkplain #encloses(Interval) enclose}
* this interval and the specified interval.
- *
+ *
* @param other the other interval to check for, not null
* @return the interval that spans the two intervals
*/
@@ -414,62 +507,197 @@ public Interval span(Interval other) {
//-------------------------------------------------------------------------
/**
- * Checks if this interval is after the specified instant.
+ * Checks if this interval is after the specified interval.
*
- * The result is true if the this instant starts after the specified instant.
+ * The result is true if this interval starts after the end of the specified interval.
+ * Since intervals do not include their end points, this will return true if the
+ * two intervals abut.
* An empty interval behaves as though it is an instant for comparison purposes.
*
- * @param instant the other instant to compare to, not null
- * @return true if the start of this interval is after the specified instant
+ * @param interval the other interval to compare to, not null
+ * @return true if this interval is after the specified interval
*/
- public boolean isAfter(Instant instant) {
- return start.compareTo(instant) > 0;
+ public boolean isAfter(Interval interval) {
+ return start.compareTo(interval.end) >= 0 && !interval.equals(this);
}
/**
- * Checks if this interval is before the specified instant.
+ * Checks if this interval is before the specified interval.
*
- * The result is true if the this instant ends before the specified instant.
+ * The result is true if this interval ends before the start of the specified interval.
* Since intervals do not include their end points, this will return true if the
- * instant equals the end of the interval.
+ * two intervals abut.
* An empty interval behaves as though it is an instant for comparison purposes.
*
- * @param instant the other instant to compare to, not null
- * @return true if the start of this interval is before the specified instant
+ * @param interval the other interval to compare to, not null
+ * @return true if this interval is before the specified interval
*/
- public boolean isBefore(Instant instant) {
- return end.compareTo(instant) <= 0 && start.compareTo(instant) < 0;
+ public boolean isBefore(Interval interval) {
+ return end.compareTo(interval.start) <= 0 && !interval.equals(this);
}
//-------------------------------------------------------------------------
/**
- * Checks if this interval is after the specified interval.
+ * Checks if this interval starts on or before the specified instant.
*
- * The result is true if the this instant starts after the end of the specified interval.
- * Since intervals do not include their end points, this will return true if the
- * instant equals the end of the interval.
+ * This method compares the start of the interval to the instant.
+ * An interval with an unbounded start is considered to start at {@code Instant.MIN}.
+ *
+ * @param instant the instant, not null
+ * @return true if this interval starts before the instant
+ */
+ public boolean startsBefore(Instant instant) {
+ Objects.requireNonNull(instant, "instant");
+ return start.compareTo(instant) < 0;
+ }
+
+ /**
+ * Checks if this interval starts at or before the specified instant.
+ *
+ * This method compares the start of the interval to the instant.
+ * An interval with an unbounded start is considered to start at {@code Instant.MIN}.
+ *
+ * @param instant the instant, not null
+ * @return true if this interval starts at or before the instant
+ */
+ public boolean startsAtOrBefore(Instant instant) {
+ Objects.requireNonNull(instant, "instant");
+ return start.compareTo(instant) <= 0;
+ }
+
+ /**
+ * Checks if this interval starts on or after the specified instant.
+ *
+ * This method compares the start of the interval to the instant.
+ * An interval with an unbounded start is considered to start at {@code Instant.MIN}.
+ *
+ * @param instant the instant, not null
+ * @return true if this interval starts after the instant
+ */
+ public boolean startsAfter(Instant instant) {
+ Objects.requireNonNull(instant, "instant");
+ return start.compareTo(instant) > 0;
+ }
+
+ /**
+ * Checks if this interval starts at or after the specified instant.
+ *
+ * This method compares the start of the interval to the instant.
+ * An interval with an unbounded start is considered to start at {@code Instant.MIN}.
+ *
+ * @param instant the instant, not null
+ * @return true if this interval starts at or after the instant
+ */
+ public boolean startsAtOrAfter(Instant instant) {
+ Objects.requireNonNull(instant, "instant");
+ return start.compareTo(instant) >= 0;
+ }
+
+ //-------------------------------------------------------------------------
+ /**
+ * Checks if this interval ends before the specified instant.
+ *
+ * This method compares the end of the interval to the instant.
+ * An interval with an unbounded end is considered to end after {@code Instant.MAX}.
+ *
+ * @param instant the instant, not null
+ * @return true if this interval ends before the instant
+ */
+ public boolean endsBefore(Instant instant) {
+ Objects.requireNonNull(instant, "instant");
+ return end.compareTo(instant) < 0 && !isUnboundedEnd();
+ }
+
+ /**
+ * Checks if this interval ends at or before the specified instant.
+ *
+ * This method compares the end of the interval to the instant.
+ * An interval with an unbounded end is considered to end after {@code Instant.MAX}.
+ *
+ * @param instant the instant, not null
+ * @return true if this interval ends at or before the instant
+ */
+ public boolean endsAtOrBefore(Instant instant) {
+ Objects.requireNonNull(instant, "instant");
+ return end.compareTo(instant) <= 0 && !isUnboundedEnd();
+ }
+
+ /**
+ * Checks if this interval ends after the specified instant.
+ *
+ * This method compares the end of the interval to the instant.
+ * An interval with an unbounded end is considered to end after {@code Instant.MAX}.
+ *
+ * @param instant the instant, not null
+ * @return true if this interval ends after the instant
+ */
+ public boolean endsAfter(Instant instant) {
+ Objects.requireNonNull(instant, "instant");
+ return end.compareTo(instant) > 0 || isUnboundedEnd();
+ }
+
+ /**
+ * Checks if this interval ends after the specified instant.
+ *
+ * This method compares the end of the interval to the instant.
+ * An interval with an unbounded end is considered to end after {@code Instant.MAX}.
+ *
+ * @param instant the instant, not null
+ * @return true if this interval ends at or after the instant
+ */
+ public boolean endsAtOrAfter(Instant instant) {
+ Objects.requireNonNull(instant, "instant");
+ return end.compareTo(instant) >= 0 || isUnboundedEnd();
+ }
+
+ //-------------------------------------------------------------------------
+ /**
+ * Checks if this interval contains the specified instant.
+ *
+ * This checks if the specified instant is within the bounds of this interval.
+ * If this interval has an unbounded start then {@code contains(Instant#MIN)} returns true.
+ * If this interval has an unbounded end then {@code contains(Instant#MAX)} returns true.
+ * Otherwise, if this interval is empty then this method returns false.
+ *
+ * This is equivalent to {@link #startsAtOrBefore(Instant)} {@code &&} {@link #endsAfter(Instant)}.
+ *
+ * @param instant the instant, not null
+ * @return true if this interval contains the instant
+ */
+ public boolean contains(Instant instant) {
+ return startsAtOrBefore(instant) && endsAfter(instant);
+ }
+
+ /**
+ * Checks if this interval is after the specified instant.
+ *
+ * The result is true if this interval starts after the specified instant.
* An empty interval behaves as though it is an instant for comparison purposes.
+ *
+ * This is equivalent to {@link #startsAfter(Instant)}.
*
- * @param interval the other interval to compare to, not null
- * @return true if this instant is after the specified instant
+ * @param instant the other instant to compare to, not null
+ * @return true if the start of this interval is after the specified instant
*/
- public boolean isAfter(Interval interval) {
- return start.compareTo(interval.end) >= 0 && !interval.equals(this);
+ public boolean isAfter(Instant instant) {
+ return startsAfter(instant);
}
/**
- * Checks if this interval is before the specified interval.
+ * Checks if this interval is before the specified instant.
*
- * The result is true if the this instant ends before the start of the specified interval.
+ * The result is true if this interval ends before the specified instant.
* Since intervals do not include their end points, this will return true if the
- * two intervals abut.
+ * instant equals the end of the interval.
* An empty interval behaves as though it is an instant for comparison purposes.
+ *
+ * This is equivalent to {@link #endsAtOrBefore(Instant)} {@code &&} {@link #startsBefore(Instant)}.
*
- * @param interval the other interval to compare to, not null
- * @return true if this instant is before the specified instant
+ * @param instant the other instant to compare to, not null
+ * @return true if the end of this interval is before or equal to the specified instant
*/
- public boolean isBefore(Interval interval) {
- return end.compareTo(interval.start) <= 0 && !interval.equals(this);
+ public boolean isBefore(Instant instant) {
+ return endsAtOrBefore(instant) && startsBefore(instant);
}
//-----------------------------------------------------------------------
@@ -528,6 +756,7 @@ public int hashCode() {
* @return a string representation of this instant, not null
*/
@Override
+ @ToString
public String toString() {
return start.toString() + '/' + end.toString();
}
diff --git a/src/main/java/org/threeten/extra/LocalDateRange.java b/src/main/java/org/threeten/extra/LocalDateRange.java
index 5e397bea..5e168552 100644
--- a/src/main/java/org/threeten/extra/LocalDateRange.java
+++ b/src/main/java/org/threeten/extra/LocalDateRange.java
@@ -37,13 +37,17 @@
import java.time.Period;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAdjuster;
-import java.util.Iterator;
+import java.util.Comparator;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
+import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
+import org.joda.convert.FromString;
+import org.joda.convert.ToString;
+
/**
* A range of local dates.
*
@@ -52,12 +56,17 @@
* Internally, the class stores the start and end dates, with the start inclusive and the end exclusive.
* The end date is always greater than or equal to the start date.
*
- * The constants {@link LocalDate#MIN} and {@link LocalDate#MAX} can be used
+ * The constants {@code LocalDate.MIN} and {@code LocalDate.MAX} can be used
* to indicate an unbounded far-past or far-future. Note that there is no difference
- * between a half-open and a closed range when the end is {@link LocalDate#MAX}.
+ * between a half-open and a closed range when the end is {@code LocalDate.MAX}.
+ * Empty ranges are allowed.
+ *
+ * No range can end at {@code LocalDate.MIN} or {@code LocalDate.MIN.plusDays(1)}.
+ * No range can start at {@code LocalDate.MAX} or {@code LocalDate.MAX.minusDays(1)}.
+ * No empty range can exist at {@code LocalDate.MIN} or {@code LocalDate.MAX}.
*
* Date ranges are not comparable. To compare the length of two ranges, it is
- * generally recommended to compare the number of days the contain.
+ * generally recommended to compare the number of days they contain.
*
*
Implementation Requirements:
* This class is immutable and thread-safe.
@@ -68,6 +77,14 @@
public final class LocalDateRange
implements Serializable {
+ /**
+ * The day after the MIN date.
+ */
+ private static final LocalDate MINP1 = LocalDate.MIN.plusDays(1);
+ /**
+ * The day before the MAX date.
+ */
+ private static final LocalDate MAXM1 = LocalDate.MAX.minusDays(1);
/**
* A range over the whole time-line.
*/
@@ -91,22 +108,27 @@ public final class LocalDateRange
/**
* Obtains a half-open range of dates, including the start and excluding the end.
*
- * The range includes the start date and excludes the end date, unless the end
- * is {@link LocalDate#MAX}.
+ * The range includes the start date and excludes the end date, unless the end is {@code LocalDate.MAX}.
* The end date must be equal to or after the start date.
* This definition permits an empty range located at a specific date.
+ *
+ * The constants {@code LocalDate.MIN} and {@code LocalDate.MAX} can be used
+ * to indicate an unbounded far-past or far-future.
+ *
+ * The start inclusive date must not be {@code LocalDate.MAX} or {@code LocalDate.MAX.minusDays(1)}.
+ * The end inclusive date must not be {@code LocalDate.MIN} or {@code LocalDate.MIN.plusDays(1)}.
+ * No empty range can exist at {@code LocalDate.MIN} or {@code LocalDate.MAX}.
*
- * @param startInclusive the start date, inclusive, LocalDate.MIN treated as unbounded, not null
- * @param endExclusive the end date, exclusive, LocalDate.MAX treated as unbounded, not null
+ * @param startInclusive the inclusive start date, not null
+ * @param endExclusive the exclusive end date, not null
* @return the half-open range, not null
- * @throws DateTimeException if the end is before the start
+ * @throws DateTimeException if the end is before the start,
+ * or the start date is {@code LocalDate.MAX} or {@code LocalDate.MAX.minusDays(1)},
+ * or the end date is {@code LocalDate.MIN} or {@code LocalDate.MIN.plusDays(1)}
*/
public static LocalDateRange of(LocalDate startInclusive, LocalDate endExclusive) {
Objects.requireNonNull(startInclusive, "startInclusive");
Objects.requireNonNull(endExclusive, "endExclusive");
- if (endExclusive.isBefore(startInclusive)) {
- throw new DateTimeException("End date must on or after start date");
- }
return new LocalDateRange(startInclusive, endExclusive);
}
@@ -115,17 +137,26 @@ public static LocalDateRange of(LocalDate startInclusive, LocalDate endExclusive
*
* The range includes the start date and the end date.
* The end date must be equal to or after the start date.
+ *
+ * The constants {@code LocalDate.MIN} and {@code LocalDate.MAX} can be used
+ * to indicate an unbounded far-past or far-future. In addition, an end date of
+ * {@code LocalDate.MAX.minusDays(1)} will also create an unbounded far-future range.
+ *
+ * The start inclusive date must not be {@code LocalDate.MAX} or {@code LocalDate.MAX.minusDays(1)}.
+ * The end inclusive date must not be {@code LocalDate.MIN}.
*
- * @param startInclusive the inclusive start date, LocalDate.MIN treated as unbounded, not null
- * @param endInclusive the inclusive end date, LocalDate.MAX treated as unbounded, not null
+ * @param startInclusive the inclusive start date, not null
+ * @param endInclusive the inclusive end date, not null
* @return the closed range
- * @throws DateTimeException if the end is before the start
+ * @throws DateTimeException if the end is before the start,
+ * or the start date is {@code LocalDate.MAX} or {@code LocalDate.MAX.minusDays(1)},
+ * or the end date is {@code LocalDate.MIN}
*/
public static LocalDateRange ofClosed(LocalDate startInclusive, LocalDate endInclusive) {
Objects.requireNonNull(startInclusive, "startInclusive");
Objects.requireNonNull(endInclusive, "endInclusive");
if (endInclusive.isBefore(startInclusive)) {
- throw new DateTimeException("Start date must on or before end date");
+ throw new DateTimeException("Start date must be on or before end date");
}
LocalDate end = (endInclusive.equals(LocalDate.MAX) ? LocalDate.MAX : endInclusive.plusDays(1));
return new LocalDateRange(startInclusive, end);
@@ -136,8 +167,12 @@ public static LocalDateRange ofClosed(LocalDate startInclusive, LocalDate endInc
*
* The end date is calculated as the start plus the duration.
* The period must not be negative.
+ *
+ * The constant {@code LocalDate.MIN} can be used to indicate an unbounded far-past.
+ *
+ * The period must not be zero or one day when the start date is {@code LocalDate.MIN}.
*
- * @param startInclusive the start date, inclusive, not null
+ * @param startInclusive the inclusive start date, not null
* @param period the period from the start to the end, not null
* @return the range, not null
* @throws DateTimeException if the end is before the start,
@@ -153,6 +188,60 @@ public static LocalDateRange of(LocalDate startInclusive, Period period) {
return new LocalDateRange(startInclusive, startInclusive.plus(period));
}
+ /**
+ * Obtains an empty date range located at the specified date.
+ *
+ * The empty range has zero length and contains no other dates or ranges.
+ * An empty range cannot be located at {@code LocalDate.MIN}, {@code LocalDate.MIN.plusDays(1)},
+ * {@code LocalDate.MAX} or {@code LocalDate.MAX.minusDays(1)}.
+ *
+ * @param date the date where the empty range is located, not null
+ * @return the empty range, not null
+ * @throws DateTimeException if the date is {@code LocalDate.MIN}, {@code LocalDate.MIN.plusDays(1)},
+ * {@code LocalDate.MAX} or {@code LocalDate.MAX.minusDays(1)}
+ */
+ public static LocalDateRange ofEmpty(LocalDate date) {
+ Objects.requireNonNull(date, "date");
+ return new LocalDateRange(date, date);
+ }
+
+ /**
+ * Obtains a range that is unbounded at the start and end.
+ *
+ * @return the range, with an unbounded start and unbounded end
+ */
+ public static LocalDateRange ofUnbounded() {
+ return ALL;
+ }
+
+ /**
+ * Obtains a range up to, but not including, the specified end date.
+ *
+ * The range includes all dates from the unbounded start, denoted by {@code LocalDate.MIN}, to the end date.
+ * The end date is exclusive and cannot be {@code LocalDate.MIN} or {@code LocalDate.MIN.plusDays(1)}.
+ *
+ * @param endExclusive the exclusive end date, {@code LocalDate.MAX} treated as unbounded, not null
+ * @return the range, with an unbounded start
+ * @throws DateTimeException if the end date is {@code LocalDate.MIN} or {@code LocalDate.MIN.plusDays(1)}
+ */
+ public static LocalDateRange ofUnboundedStart(LocalDate endExclusive) {
+ return LocalDateRange.of(LocalDate.MIN, endExclusive);
+ }
+
+ /**
+ * Obtains a range from and including the specified start date.
+ *
+ * The range includes all dates from the start date to the unbounded end, denoted by {@code LocalDate.MAX}.
+ * The start date is inclusive and cannot be {@code LocalDate.MAX} or {@code LocalDate.MAX.minusDays(1)}.
+ *
+ * @param startInclusive the inclusive start date, {@code LocalDate.MIN} treated as unbounded, not null
+ * @return the range, with an unbounded end
+ * @throws DateTimeException if the start date is {@code LocalDate.MAX} or {@code LocalDate.MAX.minusDays(1)}
+ */
+ public static LocalDateRange ofUnboundedEnd(LocalDate startInclusive) {
+ return LocalDateRange.of(startInclusive, LocalDate.MAX);
+ }
+
//-----------------------------------------------------------------------
/**
* Obtains an instance of {@code LocalDateRange} from a text string such as
@@ -172,6 +261,7 @@ public static LocalDateRange of(LocalDate startInclusive, Period period) {
* @return the parsed range, not null
* @throws DateTimeParseException if the text cannot be parsed
*/
+ @FromString
public static LocalDateRange parse(CharSequence text) {
Objects.requireNonNull(text, "text");
for (int i = 0; i < text.length(); i++) {
@@ -208,6 +298,18 @@ public static LocalDateRange parse(CharSequence text) {
* @param endExclusive the end date, exclusive, validated not null
*/
private LocalDateRange(LocalDate startInclusive, LocalDate endExclusive) {
+ if (endExclusive.isBefore(startInclusive)) {
+ throw new DateTimeException("End date must be on or after start date");
+ }
+ if (startInclusive.equals(MAXM1)) {
+ throw new DateTimeException("Range must not start at LocalDate.MAX.minusDays(1)");
+ }
+ if (endExclusive.equals(MINP1)) {
+ throw new DateTimeException("Range must not end at LocalDate.MIN.plusDays(1)");
+ }
+ if (endExclusive.equals(LocalDate.MIN) || startInclusive.equals(LocalDate.MAX)) {
+ throw new DateTimeException("Empty range must not be at LocalDate.MIN or LocalDate.MAX");
+ }
this.start = startInclusive;
this.end = endExclusive;
}
@@ -216,8 +318,10 @@ private LocalDateRange(LocalDate startInclusive, LocalDate endExclusive) {
/**
* Gets the start date of this range, inclusive.
*
- * This will return {@link LocalDate#MIN} if the range is unbounded at the start.
+ * This will return {@code LocalDate#MIN} if the range is unbounded at the start.
* In this case, the range includes all dates into the far-past.
+ *
+ * This never returns {@code LocalDate.MAX} or {@code LocalDate.MAX.minusDays(1)}.
*
* @return the start date
*/
@@ -228,8 +332,10 @@ public LocalDate getStart() {
/**
* Gets the end date of this range, exclusive.
*
- * This will return {@link LocalDate#MAX} if the range is unbounded at the end.
+ * This will return {@code LocalDate.MAX} if the range is unbounded at the end.
* In this case, the range includes all dates into the far-future.
+ *
+ * This never returns {@code LocalDate.MIN} or {@code LocalDate.MIN.plusDays(1)}.
*
* @return the end date, exclusive
*/
@@ -240,8 +346,12 @@ public LocalDate getEnd() {
/**
* Gets the end date of this range, inclusive.
*
- * This will return {@link LocalDate#MAX} if the range is unbounded at the end.
+ * This will return {@code LocalDate.MAX} if the range is unbounded at the end.
* In this case, the range includes all dates into the far-future.
+ *
+ * This returns the date before the end date.
+ *
+ * This never returns {@code LocalDate.MIN}.
*
* @return the end date, inclusive
*/
@@ -249,9 +359,6 @@ public LocalDate getEndInclusive() {
if (isUnboundedEnd()) {
return LocalDate.MAX;
}
- if (end.equals(LocalDate.MIN)) {
- return LocalDate.MIN;
- }
return end.minusDays(1);
}
@@ -259,7 +366,9 @@ public LocalDate getEndInclusive() {
/**
* Checks if the range is empty.
*
- * An empty range occurs when the start date equals the inclusive end date.
+ * An empty range occurs when the start date equals the end date.
+ *
+ * An empty range is never unbounded.
*
* @return true if the range is empty
*/
@@ -269,6 +378,8 @@ public boolean isEmpty() {
/**
* Checks if the start of the range is unbounded.
+ *
+ * An unbounded range is never empty.
*
* @return true if start is unbounded
*/
@@ -278,6 +389,8 @@ public boolean isUnboundedStart() {
/**
* Checks if the end of the range is unbounded.
+ *
+ * An unbounded range is never empty.
*
* @return true if end is unbounded
*/
@@ -295,7 +408,7 @@ public boolean isUnboundedEnd() {
*
* For example, to adjust the start to one week earlier:
*
- * range = range.withStart(date -> date.minus(1, ChronoUnit.WEEKS));
+ * range = range.withStart(date -> date.minus(1, ChronoUnit.WEEKS));
*
*
* @param adjuster the adjuster to use, not null
@@ -315,7 +428,7 @@ public LocalDateRange withStart(TemporalAdjuster adjuster) {
*
* For example, to adjust the end to one week later:
*
- * range = range.withEnd(date -> date.plus(1, ChronoUnit.WEEKS));
+ * range = range.withEnd(date -> date.plus(1, ChronoUnit.WEEKS));
*
*
* @param adjuster the adjuster to use, not null
@@ -331,9 +444,9 @@ public LocalDateRange withEnd(TemporalAdjuster adjuster) {
* Checks if this range contains the specified date.
*
* This checks if the specified date is within the bounds of this range.
- * If this range has an unbounded start then {@code contains(LocalDate#MIN)} returns true.
- * If this range has an unbounded end then {@code contains(LocalDate#MAX)} returns true.
* If this range is empty then this method always returns false.
+ * Else if this range has an unbounded start then {@code contains(LocalDate#MIN)} returns true.
+ * Else if this range has an unbounded end then {@code contains(LocalDate#MAX)} returns true.
*
* @param date the date to check for, not null
* @return true if this range contains the date
@@ -390,7 +503,7 @@ public boolean isConnected(LocalDateRange other) {
/**
* Checks if this range overlaps the specified range.
*
- * The result is true if the the two ranges share some part of the time-line.
+ * The result is true if the two ranges share some part of the time-line.
* An empty range overlaps itself.
*
* This is equivalent to {@code (isConnected(other) && !abuts(other))}.
@@ -488,25 +601,36 @@ public LocalDateRange span(LocalDateRange other) {
* @return the stream of dates from the start to the end
*/
public Stream stream() {
- Iterator it = new Iterator() {
- private LocalDate current = start;
+ long count = end.toEpochDay() - start.toEpochDay() + (isUnboundedEnd() ? 1 : 0);
+ Spliterator spliterator = new Spliterators.AbstractSpliterator(
+ count,
+ Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.DISTINCT | Spliterator.ORDERED |
+ Spliterator.SORTED | Spliterator.SIZED | Spliterator.SUBSIZED) {
+ private LocalDate current = start;
+
@Override
- public LocalDate next() {
- LocalDate result = current;
- current = current.plusDays(1);
- return result;
+ public boolean tryAdvance(Consumer super LocalDate> action) {
+ if (current != null) {
+ if (current.isBefore(end)) {
+ action.accept(current);
+ current = current.plusDays(1);
+ return true;
+ }
+ if (current.equals(LocalDate.MAX)) {
+ action.accept(LocalDate.MAX);
+ current = null;
+ return true;
+ }
+ }
+ return false;
}
-
+
@Override
- public boolean hasNext() {
- return current.isBefore(end);
+ public Comparator super LocalDate> getComparator() {
+ return null;
}
};
- long count = end.toEpochDay() - start.toEpochDay() + 1;
- Spliterator spliterator = Spliterators.spliterator(it, count,
- Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.DISTINCT | Spliterator.ORDERED |
- Spliterator.SORTED | Spliterator.SIZED | Spliterator.SUBSIZED);
return StreamSupport.stream(spliterator, false);
}
@@ -569,23 +693,33 @@ public boolean isBefore(LocalDateRange range) {
* Obtains the length of this range in days.
*
* This returns the number of days between the start and end dates.
+ * If the range is too large, the length will be {@code Integer.MAX_VALUE}.
+ * Unbounded ranges return {@code Integer.MAX_VALUE}.
*
- * @return the length in days
- * @throws ArithmeticException if the length exceeds the capacity of an {@code int}
+ * @return the length in days, Integer.MAX_VALUE if unbounded or too large
*/
public int lengthInDays() {
- return Math.toIntExact(end.toEpochDay() - start.toEpochDay());
+ if (isUnboundedStart() || isUnboundedEnd()) {
+ return Integer.MAX_VALUE;
+ }
+ long length = end.toEpochDay() - start.toEpochDay();
+ return length > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) length;
}
/**
* Obtains the length of this range as a period.
*
* This returns the {@link Period} between the start and end dates.
+ * Unbounded ranges throw {@link ArithmeticException}.
*
* @return the period of the range
- * @throws ArithmeticException if the calculation exceeds the capacity of {@code Period}
+ * @throws ArithmeticException if the calculation exceeds the capacity of {@code Period},
+ * or the range is unbounded
*/
public Period toPeriod() {
+ if (isUnboundedStart() || isUnboundedEnd()) {
+ throw new ArithmeticException("Unbounded range cannot be converted to a Period");
+ }
return Period.between(start, end);
}
@@ -631,6 +765,7 @@ public int hashCode() {
* @return a string representation of this date, not null
*/
@Override
+ @ToString
public String toString() {
return start.toString() + '/' + end.toString();
}
diff --git a/src/main/java/org/threeten/extra/Minutes.java b/src/main/java/org/threeten/extra/Minutes.java
index 87e3d588..e3994d0b 100644
--- a/src/main/java/org/threeten/extra/Minutes.java
+++ b/src/main/java/org/threeten/extra/Minutes.java
@@ -48,6 +48,9 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.joda.convert.FromString;
+import org.joda.convert.ToString;
+
/**
* A minute-based amount of time, such as '8 minutes'.
*
@@ -75,6 +78,10 @@ public final class Minutes
*/
private static final long serialVersionUID = 2602801843170589407L;
+ /**
+ * The number of minutes per day.
+ */
+ private static final int MINUTES_PER_DAY = 24 * 60;
/**
* The number of minutes per hour.
*/
@@ -84,9 +91,11 @@ public final class Minutes
* The pattern for parsing.
*/
private static final Pattern PATTERN =
- Pattern.compile("([-+]?)PT"
+ Pattern.compile("([-+]?)P"
+ + "(?:([-+]?[0-9]+)D)?"
+ + "(?:T"
+ "(?:([-+]?[0-9]+)H)?"
- + "(?:([-+]?[0-9]+)M)?", Pattern.CASE_INSENSITIVE);
+ + "(?:([-+]?[0-9]+)M)?)?", Pattern.CASE_INSENSITIVE);
/**
* The number of minutes.
@@ -168,22 +177,26 @@ public static Minutes from(TemporalAmount amount) {
/**
* Obtains a {@code Minutes} from a text string such as {@code PTnM}.
*
- * This will parse the string produced by {@code toString()} which is
- * based on the ISO-8601 period format {@code PTnHnM}.
+ * This will parse the string produced by {@code toString()} and other
+ * related formats based on ISO-8601 {@code PnDTnHnM}.
*
* The string starts with an optional sign, denoted by the ASCII negative
* or positive symbol. If negative, the whole amount is negated.
- * The ASCII letters "P" and "T" are next in upper or lower case.
- * There are then two sections, each consisting of a number and a suffix.
- * At least one of the two sections must be present.
- * The sections have suffixes in ASCII of "H" and "M" for hours and minutes,
- * accepted in upper or lower case. The suffixes must occur in order.
+ * The ASCII letter "P" is next in upper or lower case.
+ * There are three sections consisting of a number and a suffix.
+ * There is one section for days suffixed by "D",
+ * followed by one section for hours suffixed by "H",
+ * followed by one section for minutes suffixed by "M".
+ * At least one section must be present.
+ * If the hours or minutes section is present it must be prefixed by "T".
+ * If the hours or minutes section is omitted the "T" must be omitted.
+ * Letters must be in ASCII upper or lower case.
* The number part of each section must consist of ASCII digits.
* The number may be prefixed by the ASCII negative or positive symbol.
* The number must parse to an {@code int}.
*
- * The leading plus/minus sign, and negative values for hours and minutes are
- * not part of the ISO-8601 standard.
+ * The leading plus/minus sign, and negative values for days, hours and
+ * minutes are not part of the ISO-8601 standard.
*
*
* @param text the text to parse, not null
* @return the parsed period, not null
* @throws DateTimeParseException if the text cannot be parsed to a period
*/
+ @FromString
public static Minutes parse(CharSequence text) {
Objects.requireNonNull(text, "text");
Matcher matcher = PATTERN.matcher(text);
if (matcher.matches()) {
- int negate = ("-".equals(matcher.group(1)) ? -1 : 1);
- String hoursStr = matcher.group(2);
- String minutesStr = matcher.group(3);
- if (hoursStr != null || minutesStr != null) {
+ int negate = "-".equals(matcher.group(1)) ? -1 : 1;
+ String daysStr = matcher.group(2);
+ String hoursStr = matcher.group(3);
+ String minutesStr = matcher.group(4);
+ if (daysStr != null || hoursStr != null || minutesStr != null) {
int minutes = 0;
if (minutesStr != null) {
try {
minutes = Integer.parseInt(minutesStr);
} catch (NumberFormatException ex) {
- throw new DateTimeParseException("Text cannot be parsed to a Minutes, non-numeric minutes", text, 0, ex);
+ throw new DateTimeParseException("Text cannot be parsed to Minutes, non-numeric minutes", text, 0, ex);
}
}
if (hoursStr != null) {
try {
- int hours = Math.multiplyExact(Integer.parseInt(hoursStr), MINUTES_PER_HOUR);
- minutes = Math.addExact(minutes, hours);
+ int hoursAsMins = Math.multiplyExact(Integer.parseInt(hoursStr), MINUTES_PER_HOUR);
+ minutes = Math.addExact(minutes, hoursAsMins);
+ } catch (NumberFormatException ex) {
+ throw new DateTimeParseException("Text cannot be parsed to Minutes, non-numeric hours", text, 0, ex);
+ }
+ }
+ if (daysStr != null) {
+ try {
+ int daysAsMins = Math.multiplyExact(Integer.parseInt(daysStr), MINUTES_PER_DAY);
+ minutes = Math.addExact(minutes, daysAsMins);
} catch (NumberFormatException ex) {
- throw new DateTimeParseException("Text cannot be parsed to a Minutes, non-numeric minutes", text, 0, ex);
+ throw new DateTimeParseException("Text cannot be parsed to Minutes, non-numeric days", text, 0, ex);
}
}
return of(Math.multiplyExact(minutes, negate));
}
}
- throw new DateTimeParseException("Text cannot be parsed to a Minutes", text, 0);
+ throw new DateTimeParseException("Text cannot be parsed to Minutes", text, 0);
}
//-----------------------------------------------------------------------
/**
- * Obtains a {@code Minutes} consisting of the number of minutes between two dates.
+ * Obtains a {@code Minutes} consisting of the number of minutes between two temporals.
*
- * The start date is included, but the end date is not.
+ * The start temporal is included, but the end temporal is not.
* The result of this method can be negative if the end is before the start.
*
- * @param startDateInclusive the start date, inclusive, not null
- * @param endDateExclusive the end date, exclusive, not null
- * @return the number of minutes between this date and the end date, not null
+ * @param startInclusive the start temporal, inclusive, not null
+ * @param endExclusive the end temporal, exclusive, not null
+ * @return the number of minutes between the start and end temporals, not null
*/
- public static Minutes between(Temporal startDateInclusive, Temporal endDateExclusive) {
- return of(Math.toIntExact(MINUTES.between(startDateInclusive, endDateExclusive)));
+ public static Minutes between(Temporal startInclusive, Temporal endExclusive) {
+ return of(Math.toIntExact(MINUTES.between(startInclusive, endExclusive)));
}
//-----------------------------------------------------------------------
@@ -307,6 +332,33 @@ public int getAmount() {
return minutes;
}
+ /**
+ * Checks if the amount is negative.
+ *
+ * @return true if the amount is negative, false if the amount is zero or positive
+ */
+ public boolean isNegative() {
+ return getAmount() < 0;
+ }
+
+ /**
+ * Checks if the amount is zero.
+ *
+ * @return true if the amount is zero, false if not
+ */
+ public boolean isZero() {
+ return getAmount() == 0;
+ }
+
+ /**
+ * Checks if the amount is positive.
+ *
+ * @return true if the amount is positive, false if the amount is zero or negative
+ */
+ public boolean isPositive() {
+ return getAmount() > 0;
+ }
+
//-----------------------------------------------------------------------
/**
* Returns a copy of this amount with the specified amount added.
@@ -348,13 +400,13 @@ public Minutes plus(int minutes) {
*
* This instance is immutable and unaffected by this method call.
*
- * @param amountToAdd the amount to add, not null
+ * @param amountToSubtract the amount to subtract, not null
* @return a {@code Minutes} based on this instance with the requested amount subtracted, not null
* @throws DateTimeException if the specified amount contains an invalid unit
* @throws ArithmeticException if numeric overflow occurs
*/
- public Minutes minus(TemporalAmount amountToAdd) {
- return minus(Minutes.from(amountToAdd).getAmount());
+ public Minutes minus(TemporalAmount amountToSubtract) {
+ return minus(Minutes.from(amountToSubtract).getAmount());
}
/**
@@ -569,6 +621,7 @@ public int hashCode() {
* @return the number of minutes in ISO-8601 string format
*/
@Override
+ @ToString
public String toString() {
return "PT" + minutes + "M";
}
diff --git a/src/main/java/org/threeten/extra/Months.java b/src/main/java/org/threeten/extra/Months.java
index 39bd5da1..542b9393 100644
--- a/src/main/java/org/threeten/extra/Months.java
+++ b/src/main/java/org/threeten/extra/Months.java
@@ -48,6 +48,9 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.joda.convert.FromString;
+import org.joda.convert.ToString;
+
/**
* A month-based amount of time, such as '12 months'.
*
@@ -203,11 +206,12 @@ public static Months from(TemporalAmount amount) {
* @return the parsed period, not null
* @throws DateTimeParseException if the text cannot be parsed to a period
*/
+ @FromString
public static Months parse(CharSequence text) {
Objects.requireNonNull(text, "text");
Matcher matcher = PATTERN.matcher(text);
if (matcher.matches()) {
- int negate = ("-".equals(matcher.group(1)) ? -1 : 1);
+ int negate = "-".equals(matcher.group(1)) ? -1 : 1;
String weeksStr = matcher.group(2);
String daysStr = matcher.group(3);
if (weeksStr != null || daysStr != null) {
@@ -281,7 +285,7 @@ private Object readResolve() {
*/
@Override
public long get(TemporalUnit unit) {
- if (unit == ChronoUnit.MONTHS) {
+ if (unit == MONTHS) {
return months;
}
throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
@@ -312,6 +316,33 @@ public int getAmount() {
return months;
}
+ /**
+ * Checks if the amount is negative.
+ *
+ * @return true if the amount is negative, false if the amount is zero or positive
+ */
+ public boolean isNegative() {
+ return getAmount() < 0;
+ }
+
+ /**
+ * Checks if the amount is zero.
+ *
+ * @return true if the amount is zero, false if not
+ */
+ public boolean isZero() {
+ return getAmount() == 0;
+ }
+
+ /**
+ * Checks if the amount is positive.
+ *
+ * @return true if the amount is positive, false if the amount is zero or negative
+ */
+ public boolean isPositive() {
+ return getAmount() > 0;
+ }
+
//-----------------------------------------------------------------------
/**
* Returns a copy of this amount with the specified amount added.
@@ -353,13 +384,13 @@ public Months plus(int months) {
*
* This instance is immutable and unaffected by this method call.
*
- * @param amountToAdd the amount to add, not null
+ * @param amountToSubtract the amount to subtract, not null
* @return a {@code Months} based on this instance with the requested amount subtracted, not null
* @throws DateTimeException if the specified amount contains an invalid unit
* @throws ArithmeticException if numeric overflow occurs
*/
- public Months minus(TemporalAmount amountToAdd) {
- return minus(Months.from(amountToAdd).getAmount());
+ public Months minus(TemporalAmount amountToSubtract) {
+ return minus(Months.from(amountToSubtract).getAmount());
}
/**
@@ -574,6 +605,7 @@ public int hashCode() {
* @return the number of months in ISO-8601 string format
*/
@Override
+ @ToString
public String toString() {
return "P" + months + "M";
}
diff --git a/src/main/java/org/threeten/extra/MutableClock.java b/src/main/java/org/threeten/extra/MutableClock.java
new file mode 100644
index 00000000..bd0b5362
--- /dev/null
+++ b/src/main/java/org/threeten/extra/MutableClock.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of JSR-310 nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.threeten.extra;
+
+import static java.time.Instant.EPOCH;
+import static java.time.ZoneOffset.UTC;
+
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.time.Clock;
+import java.time.DateTimeException;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.temporal.TemporalAdjuster;
+import java.time.temporal.TemporalAmount;
+import java.time.temporal.TemporalField;
+import java.time.temporal.TemporalUnit;
+import java.time.temporal.UnsupportedTemporalTypeException;
+import java.util.Objects;
+
+/**
+ * A clock that does not advance on its own and that must be updated manually.
+ *
+ * This class is designed for testing clock-sensitive components by simulating
+ * the passage of time. This class differs from {@link
+ * Clock#fixed(Instant, ZoneId)} and {@link Clock#offset(Clock, Duration)} in
+ * that it permits arbitrary, unrestricted updates to its instant. This allows
+ * for testing patterns that are not well-supported by the {@code fixed} and
+ * {@code offset} clocks such as the following pattern:
+ *
+ *
Create the clock-sensitive component to be tested
+ *
Verify some behavior of the component in the initial state
+ *
Advance the clock without recreating the component
+ *
Verify that the component behaves as expected given the (artificial)
+ * delta in clock time since the initial state
+ *
+ *
+ * This class is mutable. The time-zone of the clock is fixed, but the instant
+ * may be updated at will.
+ *
+ * The instant may be set to any value even if that new value is less than the
+ * previous value. Caution should be exercised when moving the clock backwards,
+ * since clock-sensitive components are likely to assume that time is
+ * monotonically increasing.
+ *
+ * Update semantics are expressed in terms of {@link ZonedDateTime}. The steps
+ * of each update are as follows:
+ *
+ *
The clock captures its own state in a {@code ZonedDateTime} via {@link
+ * ZonedDateTime#now(Clock)} (or the equivalent thereof)
+ *
The update operation is applied to that {@code ZonedDateTime}, producing
+ * a new {@code ZonedDateTime}
+ *
The resulting {@code ZonedDateTime} is converted to an instant via {@link
+ * ZonedDateTime#toInstant()} (or the equivalent thereof)
+ *
The clock's instant is set to that new instant
+ *
+ *
+ * Therefore, whenever there is a question about what argument types, units,
+ * fields, or values an update operation supports, or what the result will be,
+ * refer to the corresponding method of {@code ZonedDateTime}. Links are
+ * provided from the documentation of each update operation of this class to the
+ * corresponding method of {@code ZonedDateTime}.
+ *
+ *
Implementation Requirements:
+ * This class is thread-safe. Updates are atomic and synchronized.
+ *
+ * While update semantics are expressed in terms of {@code ZonedDateTime}, that
+ * imposes no requirements on implementation details. The implementation may
+ * avoid using {@code ZonedDateTime} completely or only sometimes, for
+ * convenience, efficiency, or any other reason.
+ *
+ * @serial exclude
+ */
+public final class MutableClock
+ extends Clock
+ implements Serializable {
+
+ /**
+ * Serialization version.
+ */
+ private static final long serialVersionUID = -6152029959790119695L;
+
+ /**
+ * The mutable instant of this clock.
+ */
+ private final transient InstantHolder instantHolder;
+
+ /**
+ * The fixed time-zone of this clock.
+ */
+ private final transient ZoneId zone;
+
+ /**
+ * Obtains a new {@code MutableClock} set to the epoch of
+ * 1970-01-01T00:00:00Z, converting to date and time using the UTC
+ * time-zone.
+ *
+ * Use this method when a {@code MutableClock} is needed and neither its
+ * initial value nor its time-zone are important. This is often true when
+ * testing behavior that depends on elapsed relative time rather
+ * than absolute time.
+ *
+ * @return a new {@code MutableClock}, not null
+ */
+ public static MutableClock epochUTC() {
+ return MutableClock.of(EPOCH, UTC);
+ }
+
+ /**
+ * Obtains a new {@code MutableClock} set to the specified instant,
+ * converting to date and time using the specified time-zone.
+ *
+ * @param instant the initial value for the clock, not null
+ * @param zone the time-zone to use, not null
+ * @return a new {@code MutableClock}, not null
+ */
+ public static MutableClock of(Instant instant, ZoneId zone) {
+ Objects.requireNonNull(instant, "instant");
+ Objects.requireNonNull(zone, "zone");
+ return new MutableClock(new InstantHolder(instant), zone);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param instantHolder the mutable instant, validated not null
+ * @param zone the fixed time-zone, validated not null
+ */
+ private MutableClock(InstantHolder instantHolder, ZoneId zone) {
+ this.instantHolder = instantHolder;
+ this.zone = zone;
+ }
+
+ /**
+ * Overrides the instant of this clock with the specified value.
+ *
+ * @param instant the new instant for this clock, not null
+ */
+ public void setInstant(Instant instant) {
+ Objects.requireNonNull(instant, "instant");
+ instantHolder.set(instant);
+ }
+
+ /**
+ * Adds the specified amount to this clock.
+ *
+ * Atomically updates this clock to the value of the following expression:
+ *
+ *
+ * @param amountToAdd the amount of the specified unit to add, may be negative
+ * @param unit the unit of the amount to add, not null
+ * @throws DateTimeException if the unit cannot be added
+ * @throws UnsupportedTemporalTypeException if the unit is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ * @see ZonedDateTime#plus(long, TemporalUnit)
+ */
+ public void add(long amountToAdd, TemporalUnit unit) {
+ Objects.requireNonNull(unit, "unit");
+ synchronized (instantHolder) {
+ ZonedDateTime current = ZonedDateTime.ofInstant(instantHolder.get(), zone);
+ ZonedDateTime result = current.plus(amountToAdd, unit);
+ instantHolder.set(result.toInstant());
+ }
+ }
+
+ /**
+ * Adjusts this clock.
+ *
+ * Atomically updates this clock to the value of the following expression:
+ *
+ *
+ * @param field the field to set, not null
+ * @param newValue the new value of the field
+ * @throws DateTimeException if the field cannot be set
+ * @throws UnsupportedTemporalTypeException if the field is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ * @see ZonedDateTime#with(TemporalField, long)
+ */
+ public void set(TemporalField field, long newValue) {
+ Objects.requireNonNull(field, "field");
+ synchronized (instantHolder) {
+ ZonedDateTime current = ZonedDateTime.ofInstant(instantHolder.get(), zone);
+ ZonedDateTime result = current.with(field, newValue);
+ instantHolder.set(result.toInstant());
+ }
+ }
+
+ @Override
+ public ZoneId getZone() {
+ return zone;
+ }
+
+ /**
+ * Returns a {@code MutableClock} that uses the specified time-zone and that
+ * has shared updates with this clock.
+ *
+ * Two clocks with shared updates always have the same instant, and all
+ * updates applied to either clock affect both clocks.
+ *
+ * @param zone the time-zone to use for the returned clock, not null
+ * @return a view of this clock in the specified time-zone, not null
+ */
+ @Override
+ public MutableClock withZone(ZoneId zone) {
+ Objects.requireNonNull(zone, "zone");
+ if (zone.equals(this.zone)) {
+ return this;
+ }
+ return new MutableClock(instantHolder, zone);
+ }
+
+ @Override
+ public Instant instant() {
+ return instantHolder.get();
+ }
+
+ /**
+ * Returns {@code true} if {@code obj} is a {@code MutableClock} that uses
+ * the same time-zone as this clock and has shared updates with this clock.
+ *
+ * Two clocks with shared updates always have the same instant, and all
+ * updates applied to either clock affect both clocks.
+ *
+ * A deserialized {@code MutableClock} is not equal to the original clock
+ * that was serialized, since the two clocks do not have shared updates.
+ *
+ * @param obj the object to check, null returns {@code false}
+ * @return {@code true} if this is equal to the other clock
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof MutableClock) {
+ MutableClock other = (MutableClock) obj;
+ return instantHolder == other.instantHolder && zone.equals(other.zone);
+ }
+ return false;
+ }
+
+ /**
+ * A hash code for this clock, which is constant for this instance.
+ *
+ * @return a constant hash code for this instance
+ */
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(instantHolder) ^ zone.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "MutableClock[" + instant() + "," + getZone() + "]";
+ }
+
+ /**
+ * Returns the serialization proxy to replace this {@code MutableClock}.
+ *
+ * @return the serialization proxy, not null
+ */
+ private Object writeReplace() {
+ return new SerializationProxy(this);
+ }
+
+ /**
+ * Throws {@link InvalidObjectException}.
+ *
+ * @param s ignored
+ * @throws InvalidObjectException always
+ */
+ private void readObject(ObjectInputStream s) throws InvalidObjectException {
+ throw new InvalidObjectException("Proxy required");
+ }
+
+ /**
+ * The serialized form of a {@code MutableClock}.
+ *
+ * @serial include
+ */
+ private static final class SerializationProxy
+ implements Serializable {
+
+ /**
+ * Serialization version.
+ */
+ private static final long serialVersionUID = 8602110640241828260L;
+
+ /**
+ * A snapshot of the instant of the {@code MutableClock}, taken when the
+ * clock was serialized, not null.
+ *
+ * @serial
+ */
+ private final Instant instant;
+
+ /**
+ * The time-zone of the {@code MutableClock}, not null.
+ *
+ * @serial
+ */
+ private final ZoneId zone;
+
+ /**
+ * Constructor.
+ *
+ * @param clock the {@code MutableClock} to be serialized, not null
+ */
+ SerializationProxy(MutableClock clock) {
+ instant = clock.instant();
+ zone = clock.getZone();
+ }
+
+ /**
+ * Returns the {@code MutableClock} to replace this serialization proxy.
+ *
+ * @return the {@code MutableClock}, not null
+ * @throws InvalidObjectException if the instant or time-zone is null
+ */
+ private Object readResolve() throws InvalidObjectException {
+ if (instant == null) {
+ throw new InvalidObjectException("null instant");
+ }
+ if (zone == null) {
+ throw new InvalidObjectException("null zone");
+ }
+ return MutableClock.of(instant, zone);
+ }
+ }
+
+ /**
+ * An identity-having holder object for a mutable instant value.
+ *
+ * Clocks have shared updates when they share a holder object. Clocks rely
+ * on the identity of the holder object in their {@code equals} and {@code
+ * hashCode} methods.
+ *
+ * Reads of the value are volatile and are never stale. Blind writes to the
+ * value are volatile and do not need to synchronize. Atomic read-and-write
+ * operations must synchronize on the holder object instance.
+ */
+ private static final class InstantHolder {
+ /**
+ * The current value.
+ */
+ private volatile Instant value;
+
+ /**
+ * Constructor.
+ *
+ * @param value the initial value, validated not null
+ */
+ InstantHolder(Instant value) {
+ this.value = value;
+ }
+
+ /**
+ * Reads the value.
+ *
+ * @return the current value, not null
+ */
+ Instant get() {
+ return value;
+ }
+
+ /**
+ * Writes the value.
+ *
+ * @param value the new value, validated not null
+ */
+ void set(Instant value) {
+ this.value = value;
+ }
+ }
+}
diff --git a/src/main/java/org/threeten/extra/OffsetDate.java b/src/main/java/org/threeten/extra/OffsetDate.java
new file mode 100644
index 00000000..1f3731d7
--- /dev/null
+++ b/src/main/java/org/threeten/extra/OffsetDate.java
@@ -0,0 +1,1368 @@
+/*
+ * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of JSR-310 nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.threeten.extra;
+
+import static java.time.temporal.ChronoField.EPOCH_DAY;
+import static java.time.temporal.ChronoField.OFFSET_SECONDS;
+import static java.time.temporal.ChronoUnit.DAYS;
+
+import java.io.Serializable;
+import java.time.Clock;
+import java.time.DateTimeException;
+import java.time.DayOfWeek;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.Month;
+import java.time.MonthDay;
+import java.time.OffsetDateTime;
+import java.time.Period;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.chrono.IsoChronology;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalAdjuster;
+import java.time.temporal.TemporalAdjusters;
+import java.time.temporal.TemporalAmount;
+import java.time.temporal.TemporalField;
+import java.time.temporal.TemporalQueries;
+import java.time.temporal.TemporalQuery;
+import java.time.temporal.TemporalUnit;
+import java.time.temporal.UnsupportedTemporalTypeException;
+import java.time.temporal.ValueRange;
+import java.time.zone.ZoneRules;
+import java.util.Objects;
+
+import org.joda.convert.FromString;
+import org.joda.convert.ToString;
+
+/**
+ * A date with an offset from UTC/Greenwich in the ISO-8601 calendar system,
+ * such as {@code 2007-12-03+01:00}.
+ *
+ * {@code OffsetDate} is an immutable date-time object that represents a date, often viewed
+ * as year-month-day-offset. This object can also access other date fields such as
+ * day-of-year, day-of-week and week-of-year.
+ *
+ * This class does not store or represent a time.
+ * For example, the value "2nd October 2007 +02:00" can be stored
+ * in an {@code OffsetDate}.
+ *
+ *
Implementation Requirements:
+ * This class is immutable and thread-safe.
+ *
+ * This class must be treated as a value type. Do not synchronize, rely on the
+ * identity hash code or use the distinction between equals() and ==.
+ */
+public final class OffsetDate
+ implements Temporal, TemporalAdjuster, Comparable, Serializable {
+
+ /**
+ * The minimum supported {@code OffsetDate}, '-999999999-01-01+18:00'.
+ * This is the minimum local date in the maximum offset
+ * (larger offsets are earlier on the time-line).
+ * This combines {@link LocalDate#MIN} and {@link ZoneOffset#MAX}.
+ * This could be used by an application as a "far past" date.
+ */
+ public static final OffsetDate MIN = OffsetDate.of(LocalDate.MIN, ZoneOffset.MAX);
+ /**
+ * The maximum supported {@code OffsetDate}, '+999999999-12-31-18:00'.
+ * This is the maximum local date in the minimum offset
+ * (larger negative offsets are later on the time-line).
+ * This combines {@link LocalDate#MAX} and {@link ZoneOffset#MIN}.
+ * This could be used by an application as a "far future" date.
+ */
+ public static final OffsetDate MAX = OffsetDate.of(LocalDate.MAX, ZoneOffset.MIN);
+
+ /**
+ * Serialization version.
+ */
+ private static final long serialVersionUID = -4382054179074397774L;
+
+ /**
+ * The number of seconds per day.
+ */
+ private static final long SECONDS_PER_DAY = 86400;
+
+ /**
+ * The local date.
+ */
+ private final LocalDate date;
+ /**
+ * The offset from UTC/Greenwich.
+ */
+ private final ZoneOffset offset;
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains the current date from the system clock in the default time-zone.
+ *
+ * This will query the {@link Clock#systemDefaultZone() system clock} in the default
+ * time-zone to obtain the current date.
+ * The offset will be calculated from the time-zone in the clock.
+ *
+ * Using this method will prevent the ability to use an alternate clock for testing
+ * because the clock is hard-coded.
+ *
+ * @return the current date using the system clock, not null
+ */
+ public static OffsetDate now() {
+ return now(Clock.systemDefaultZone());
+ }
+
+ /**
+ * Obtains the current date from the system clock in the specified time-zone.
+ *
+ * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current date.
+ * Specifying the time-zone avoids dependence on the default time-zone.
+ * The offset will be calculated from the specified time-zone.
+ *
+ * Using this method will prevent the ability to use an alternate clock for testing
+ * because the clock is hard-coded.
+ *
+ * @param zone the zone ID to use, not null
+ * @return the current date using the system clock, not null
+ */
+ public static OffsetDate now(ZoneId zone) {
+ return now(Clock.system(zone));
+ }
+
+ /**
+ * Obtains the current date from the specified clock.
+ *
+ * This will query the specified clock to obtain the current date - today.
+ * The offset will be calculated from the time-zone in the clock.
+ *
+ * Using this method allows the use of an alternate clock for testing.
+ * The alternate clock may be introduced using {@link Clock dependency injection}.
+ *
+ * @param clock the clock to use, not null
+ * @return the current date, not null
+ */
+ public static OffsetDate now(Clock clock) {
+ Objects.requireNonNull(clock, "clock");
+ final Instant now = clock.instant(); // called once
+ return ofInstant(now, clock.getZone().getRules().getOffset(now));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains an instance of {@code OffsetDate} from a local date and an offset.
+ *
+ * @param date the local date, not null
+ * @param offset the zone offset, not null
+ * @return the offset date, not null
+ */
+ public static OffsetDate of(LocalDate date, ZoneOffset offset) {
+ return new OffsetDate(date, offset);
+ }
+
+ /**
+ * Obtains an instance of {@code OffsetDate} from a year, month, day
+ * and offset.
+ *
+ * This creates an offset date with the four specified fields.
+ *
+ * This method exists primarily for writing test cases.
+ * Non test-code will typically use other methods to create an offset time.
+ *
+ * @param year the year to represent, from MIN_YEAR to MAX_YEAR
+ * @param month the month-of-year to represent, from 1 (January) to 12 (December)
+ * @param dayOfMonth the day-of-month to represent, from 1 to 31
+ * @param offset the zone offset, not null
+ * @return the offset date, not null
+ * @throws DateTimeException if the value of any field is out of range, or
+ * if the day-of-month is invalid for the month-year
+ */
+ public static OffsetDate of(int year, int month, int dayOfMonth, ZoneOffset offset) {
+ LocalDate d = LocalDate.of(year, month, dayOfMonth);
+ return new OffsetDate(d, offset);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains an instance of {@code OffsetDate} from an {@code Instant} and zone ID.
+ *
+ * This creates an offset date with the same instant as midnight at the
+ * start of day of the instant specified.
+ * Finding the offset from UTC/Greenwich is simple as there is only one valid
+ * offset for each instant.
+ *
+ * @param instant the instant to create the time from, not null
+ * @param zone the time-zone, which may be an offset, not null
+ * @return the offset time, not null
+ */
+ public static OffsetDate ofInstant(Instant instant, ZoneId zone) {
+ Objects.requireNonNull(instant, "instant");
+ Objects.requireNonNull(zone, "zone");
+ ZoneRules rules = zone.getRules();
+ ZoneOffset offset = rules.getOffset(instant);
+ long epochSec = instant.getEpochSecond() + offset.getTotalSeconds(); // overflow caught later
+ long epochDay = Math.floorDiv(epochSec, SECONDS_PER_DAY);
+ LocalDate date = LocalDate.ofEpochDay(epochDay);
+ return new OffsetDate(date, offset);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains an instance of {@code OffsetDate} from a temporal object.
+ *
+ * A {@code TemporalAccessor} represents some form of date and time information.
+ * This factory converts the arbitrary temporal object to an instance of {@code OffsetDate}.
+ *
+ * The conversion extracts and combines {@code LocalDate} and {@code ZoneOffset}.
+ *
+ * This method matches the signature of the functional interface {@link TemporalQuery}
+ * allowing it to be used in queries via method reference, {@code OffsetDate::from}.
+ *
+ * @param temporal the temporal object to convert, not null
+ * @return the offset date, not null
+ * @throws DateTimeException if unable to convert to an {@code OffsetDate}
+ */
+ public static OffsetDate from(TemporalAccessor temporal) {
+ if (temporal instanceof OffsetDate) {
+ return (OffsetDate) temporal;
+ }
+ try {
+ LocalDate date = LocalDate.from(temporal);
+ ZoneOffset offset = ZoneOffset.from(temporal);
+ return new OffsetDate(date, offset);
+ } catch (DateTimeException ex) {
+ throw new DateTimeException("Unable to obtain OffsetDate from TemporalAccessor: " + temporal.getClass(), ex);
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains an instance of {@code OffsetDate} from a text string such as {@code 2007-12-03+01:00}.
+ *
+ * The string must represent a valid date and is parsed using
+ * {@link DateTimeFormatter#ISO_OFFSET_DATE}.
+ *
+ * @param text the text to parse such as "2007-12-03+01:00", not null
+ * @return the parsed offset date, not null
+ * @throws DateTimeParseException if the text cannot be parsed
+ */
+ @FromString
+ public static OffsetDate parse(CharSequence text) {
+ return parse(text, DateTimeFormatter.ISO_OFFSET_DATE);
+ }
+
+ /**
+ * Obtains an instance of {@code OffsetDate} from a text string using a specific formatter.
+ *
+ * The text is parsed using the formatter, returning a date.
+ *
+ * @param text the text to parse, not null
+ * @param formatter the formatter to use, not null
+ * @return the parsed offset date, not null
+ * @throws DateTimeParseException if the text cannot be parsed
+ */
+ public static OffsetDate parse(CharSequence text, DateTimeFormatter formatter) {
+ Objects.requireNonNull(formatter, "formatter");
+ return formatter.parse(text, OffsetDate::from);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Constructor.
+ *
+ * @param date the local date, not null
+ * @param offset the zone offset, not null
+ */
+ private OffsetDate(LocalDate date, ZoneOffset offset) {
+ this.date = Objects.requireNonNull(date, "date");
+ this.offset = Objects.requireNonNull(offset, "offset");
+ }
+
+ /**
+ * Validates the input.
+ *
+ * @return the valid object, not null
+ */
+ private Object readResolve() {
+ return of(date, offset);
+ }
+
+ /**
+ * Returns a new date based on this one, returning {@code this} where possible.
+ *
+ * @param date the date to create with, not null
+ * @param offset the zone offset to create with, not null
+ */
+ private OffsetDate with(LocalDate date, ZoneOffset offset) {
+ if (this.date == date && this.offset.equals(offset)) {
+ return this;
+ }
+ return new OffsetDate(date, offset);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Checks if the specified field is supported.
+ *
+ * This checks if this date can be queried for the specified field.
+ * If false, then calling the {@link #range(TemporalField) range},
+ * {@link #get(TemporalField) get} and {@link #with(TemporalField, long)}
+ * methods will throw an exception.
+ *
+ * If the field is a {@link ChronoField} then the query is implemented here.
+ * The supported fields are:
+ *
+ *
{@code DAY_OF_WEEK}
+ *
{@code ALIGNED_DAY_OF_WEEK_IN_MONTH}
+ *
{@code ALIGNED_DAY_OF_WEEK_IN_YEAR}
+ *
{@code DAY_OF_MONTH}
+ *
{@code DAY_OF_YEAR}
+ *
{@code EPOCH_DAY}
+ *
{@code ALIGNED_WEEK_OF_MONTH}
+ *
{@code ALIGNED_WEEK_OF_YEAR}
+ *
{@code MONTH_OF_YEAR}
+ *
{@code PROLEPTIC_MONTH}
+ *
{@code YEAR_OF_ERA}
+ *
{@code YEAR}
+ *
{@code ERA}
+ *
{@code OFFSET_SECONDS}
+ *
+ * All other {@code ChronoField} instances will return false.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.isSupportedBy(TemporalAccessor)}
+ * passing {@code this} as the argument.
+ * Whether the field is supported is determined by the field.
+ *
+ * @param field the field to check, null returns false
+ * @return true if the field is supported on this date, false if not
+ */
+ @Override
+ public boolean isSupported(TemporalField field) {
+ if (field instanceof ChronoField) {
+ return field.isDateBased() || field == OFFSET_SECONDS;
+ }
+ return field != null && field.isSupportedBy(this);
+ }
+
+ /**
+ * Checks if the specified unit is supported.
+ *
+ * This checks if the specified unit can be added to, or subtracted from, this date.
+ * If false, then calling the {@link #plus(long, TemporalUnit)} and
+ * {@link #minus(long, TemporalUnit) minus} methods will throw an exception.
+ *
+ * If the unit is a {@link ChronoUnit} then the query is implemented here.
+ * The supported units are:
+ *
+ *
{@code DAYS}
+ *
{@code WEEKS}
+ *
{@code MONTHS}
+ *
{@code YEARS}
+ *
{@code DECADES}
+ *
{@code CENTURIES}
+ *
{@code MILLENNIA}
+ *
{@code ERAS}
+ *
+ * All other {@code ChronoUnit} instances will return false.
+ *
+ * If the unit is not a {@code ChronoUnit}, then the result of this method
+ * is obtained by invoking {@code TemporalUnit.isSupportedBy(Temporal)}
+ * passing {@code this} as the argument.
+ * Whether the unit is supported is determined by the unit.
+ *
+ * @param unit the unit to check, null returns false
+ * @return true if the unit can be added/subtracted, false if not
+ */
+ @Override
+ public boolean isSupported(TemporalUnit unit) {
+ if (unit instanceof ChronoUnit) {
+ return unit.isDateBased();
+ }
+ return unit != null && unit.isSupportedBy(this);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the range of valid values for the specified field.
+ *
+ * The range object expresses the minimum and maximum valid values for a field.
+ * This date is used to enhance the accuracy of the returned range.
+ * If it is not possible to return the range, because the field is not supported
+ * or for some other reason, an exception is thrown.
+ *
+ * If the field is a {@link ChronoField} then the query is implemented here.
+ * The {@link #isSupported(TemporalField) supported fields} will return
+ * appropriate range instances.
+ * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)}
+ * passing {@code this} as the argument.
+ * Whether the range can be obtained is determined by the field.
+ *
+ * @param field the field to query the range for, not null
+ * @return the range of valid values for the field, not null
+ * @throws DateTimeException if the range for the field cannot be obtained
+ * @throws UnsupportedTemporalTypeException if the field is not supported
+ */
+ @Override
+ public ValueRange range(TemporalField field) {
+ if (field instanceof ChronoField) {
+ if (field == OFFSET_SECONDS) {
+ return field.range();
+ }
+ return date.range(field);
+ }
+ return field.rangeRefinedBy(this);
+ }
+
+ /**
+ * Gets the value of the specified field from this date as an {@code int}.
+ *
+ * This queries this date for the value for the specified field.
+ * The returned value will always be within the valid range of values for the field.
+ * If it is not possible to return the value, because the field is not supported
+ * or for some other reason, an exception is thrown.
+ *
+ * If the field is a {@link ChronoField} then the query is implemented here.
+ * The {@link #isSupported(TemporalField) supported fields} will return valid
+ * values based on this date, except {@code EPOCH_DAY} and {@code PROLEPTIC_MONTH}
+ * which are too large to fit in an {@code int} and throw a {@code DateTimeException}.
+ * All other {@code ChronoField} instances will throw a {@code DateTimeException}.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}
+ * passing {@code this} as the argument. Whether the value can be obtained,
+ * and what the value represents, is determined by the field.
+ *
+ * @param field the field to get, not null
+ * @return the value for the field
+ * @throws DateTimeException if a value for the field cannot be obtained or
+ * the value is outside the range of valid values for the field
+ * @throws UnsupportedTemporalTypeException if the field is not supported or
+ * the range of values exceeds an {@code int}
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override // override for Javadoc
+ public int get(TemporalField field) {
+ return Temporal.super.get(field);
+ }
+
+ /**
+ * Gets the value of the specified field from this date as a {@code long}.
+ *
+ * This queries this date for the value for the specified field.
+ * If it is not possible to return the value, because the field is not supported
+ * or for some other reason, an exception is thrown.
+ *
+ * If the field is a {@link ChronoField} then the query is implemented here.
+ * The {@link #isSupported(TemporalField) supported fields} will return valid
+ * values based on this date.
+ * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}
+ * passing {@code this} as the argument. Whether the value can be obtained,
+ * and what the value represents, is determined by the field.
+ *
+ * @param field the field to get, not null
+ * @return the value for the field
+ * @throws DateTimeException if a value for the field cannot be obtained
+ * @throws UnsupportedTemporalTypeException if the field is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public long getLong(TemporalField field) {
+ if (field instanceof ChronoField) {
+ if (field == OFFSET_SECONDS) {
+ return getOffset().getTotalSeconds();
+ }
+ return date.getLong(field);
+ }
+ return field.getFrom(this);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the zone offset, such as '+01:00'.
+ *
+ * This is the offset of the local date from UTC/Greenwich.
+ *
+ * @return the zone offset, not null
+ */
+ public ZoneOffset getOffset() {
+ return offset;
+ }
+
+ /**
+ * Returns a copy of this {@code OffsetDate} with the specified offset ensuring
+ * that the result has the same local date.
+ *
+ * This method returns an object with the same {@code LocalDate} and the specified {@code ZoneOffset}.
+ * No calculation is needed or performed.
+ * For example, if this time represents {@code 2007-12-03+02:00} and the offset specified is
+ * {@code +03:00}, then this method will return {@code 2007-12-03+03:00}.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param offset the zone offset to change to, not null
+ * @return an {@code OffsetDate} based on this date with the requested offset, not null
+ */
+ public OffsetDate withOffsetSameLocal(ZoneOffset offset) {
+ Objects.requireNonNull(offset, "offset");
+ return with(date, offset);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the {@code LocalDate} part of this date.
+ *
+ * This returns a {@code LocalDate} with the same year, month and day
+ * as this date.
+ *
+ * @return the date part of this date, not null
+ */
+ public LocalDate toLocalDate() {
+ return date;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the year field.
+ *
+ * This method returns the primitive {@code int} value for the year.
+ *
+ * The year returned by this method is proleptic as per {@code get(YEAR)}.
+ * To obtain the year-of-era, use {@code get(YEAR_OF_ERA)}.
+ *
+ * @return the year, from MIN_YEAR to MAX_YEAR
+ */
+ public int getYear() {
+ return date.getYear();
+ }
+
+ /**
+ * Gets the month-of-year field from 1 to 12.
+ *
+ * This method returns the month as an {@code int} from 1 to 12.
+ * Application code is frequently clearer if the enum {@link Month}
+ * is used by calling {@link #getMonth()}.
+ *
+ * @return the month-of-year, from 1 to 12
+ * @see #getMonth()
+ */
+ public int getMonthValue() {
+ return date.getMonthValue();
+ }
+
+ /**
+ * Gets the month-of-year field using the {@code Month} enum.
+ *
+ * This method returns the enum {@link Month} for the month.
+ * This avoids confusion as to what {@code int} values mean.
+ * If you need access to the primitive {@code int} value then the enum
+ * provides the {@link Month#getValue() int value}.
+ *
+ * @return the month-of-year, not null
+ * @see #getMonthValue()
+ */
+ public Month getMonth() {
+ return date.getMonth();
+ }
+
+ /**
+ * Gets the day-of-month field.
+ *
+ * This method returns the primitive {@code int} value for the day-of-month.
+ *
+ * @return the day-of-month, from 1 to 31
+ */
+ public int getDayOfMonth() {
+ return date.getDayOfMonth();
+ }
+
+ /**
+ * Gets the day-of-year field.
+ *
+ * This method returns the primitive {@code int} value for the day-of-year.
+ *
+ * @return the day-of-year, from 1 to 365, or 366 in a leap year
+ */
+ public int getDayOfYear() {
+ return date.getDayOfYear();
+ }
+
+ /**
+ * Gets the day-of-week field, which is an enum {@code DayOfWeek}.
+ *
+ * This method returns the enum {@link DayOfWeek} for the day-of-week.
+ * This avoids confusion as to what {@code int} values mean.
+ * If you need access to the primitive {@code int} value then the enum
+ * provides the {@link DayOfWeek#getValue() int value}.
+ *
+ * Additional information can be obtained from the {@code DayOfWeek}.
+ * This includes textual names of the values.
+ *
+ * @return the day-of-week, not null
+ */
+ public DayOfWeek getDayOfWeek() {
+ return date.getDayOfWeek();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns an adjusted copy of this date.
+ *
+ * This returns an {@code OffsetDate} based on this one, with the date adjusted.
+ * The adjustment takes place using the specified adjuster strategy object.
+ * Read the documentation of the adjuster to understand what adjustment will be made.
+ *
+ * A simple adjuster might simply set the one of the fields, such as the year field.
+ * A more complex adjuster might set the date to the last day of the month.
+ * A selection of common adjustments is provided in {@link TemporalAdjusters}.
+ * These include finding the "last day of the month" and "next Wednesday".
+ * Key date-time classes also implement the {@code TemporalAdjuster} interface,
+ * such as {@link Month} and {@link MonthDay MonthDay}.
+ * The adjuster is responsible for handling special cases, such as the varying
+ * lengths of month and leap years.
+ *
+ * For example this code returns a date on the last day of July:
+ *
+ * The classes {@link LocalDate} and {@link ZoneOffset} implement {@code TemporalAdjuster},
+ * thus this method can be used to change the date or offset:
+ *
+ * result = offsetDate.with(date);
+ * result = offsetDate.with(offset);
+ *
+ *
+ * The result of this method is obtained by invoking the
+ * {@link TemporalAdjuster#adjustInto(Temporal)} method on the
+ * specified adjuster passing {@code this} as the argument.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param adjuster the adjuster to use, not null
+ * @return an {@code OffsetDate} based on {@code this} with the adjustment made, not null
+ * @throws DateTimeException if the adjustment cannot be made
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public OffsetDate with(TemporalAdjuster adjuster) {
+ // optimizations
+ if (adjuster instanceof LocalDate) {
+ return with((LocalDate) adjuster, offset);
+ } else if (adjuster instanceof ZoneOffset) {
+ return with(date, (ZoneOffset) adjuster);
+ } else if (adjuster instanceof OffsetDate) {
+ return (OffsetDate) adjuster;
+ }
+ return (OffsetDate) adjuster.adjustInto(this);
+ }
+
+ /**
+ * Returns a copy of this date with the specified field set to a new value.
+ *
+ * This returns an {@code OffsetDate} based on this one, with the value
+ * for the specified field changed.
+ * This can be used to change any supported field, such as the year, month or day-of-month.
+ * If it is not possible to set the value, because the field is not supported or for
+ * some other reason, an exception is thrown.
+ *
+ * In some cases, changing the specified field can cause the resulting date to become invalid,
+ * such as changing the month from 31st January to February would make the day-of-month invalid.
+ * In cases like this, the field is responsible for resolving the date. Typically it will choose
+ * the previous valid date, which would be the last valid day of February in this example.
+ *
+ * If the field is a {@link ChronoField} then the adjustment is implemented here.
+ *
+ * The {@code OFFSET_SECONDS} field will return a date with the specified offset.
+ * The local date is unaltered. If the new offset value is outside the valid range
+ * then a {@code DateTimeException} will be thrown.
+ *
+ * The other {@link #isSupported(TemporalField) supported fields} will behave as per
+ * the matching method on {@link LocalDate#with(TemporalField, long)} LocalDate}.
+ * In this case, the offset is not part of the calculation and will be unchanged.
+ *
+ * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)}
+ * passing {@code this} as the argument. In this case, the field determines
+ * whether and how to adjust the instant.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param field the field to set in the result, not null
+ * @param newValue the new value of the field in the result
+ * @return an {@code OffsetDate} based on {@code this} with the specified field set, not null
+ * @throws DateTimeException if the field cannot be set
+ * @throws UnsupportedTemporalTypeException if the field is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public OffsetDate with(TemporalField field, long newValue) {
+ if (field instanceof ChronoField) {
+ if (field == OFFSET_SECONDS) {
+ ChronoField f = (ChronoField) field;
+ return with(date, ZoneOffset.ofTotalSeconds(f.checkValidIntValue(newValue)));
+ }
+ return with(date.with(field, newValue), offset);
+ }
+ return field.adjustInto(this, newValue);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this {@code OffsetDate} with the year altered.
+ *
+ * The offset does not affect the calculation and will be the same in the result.
+ * If the day-of-month is invalid for the year, it will be changed to the last valid day of the month.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param year the year to set in the result, from MIN_YEAR to MAX_YEAR
+ * @return an {@code OffsetDate} based on this date with the requested year, not null
+ * @throws DateTimeException if the year value is invalid
+ */
+ public OffsetDate withYear(int year) {
+ return with(date.withYear(year), offset);
+ }
+
+ /**
+ * Returns a copy of this {@code OffsetDate} with the month-of-year altered.
+ *
+ * The offset does not affect the calculation and will be the same in the result.
+ * If the day-of-month is invalid for the year, it will be changed to the last valid day of the month.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param month the month-of-year to set in the result, from 1 (January) to 12 (December)
+ * @return an {@code OffsetDate} based on this date with the requested month, not null
+ * @throws DateTimeException if the month-of-year value is invalid
+ */
+ public OffsetDate withMonth(int month) {
+ return with(date.withMonth(month), offset);
+ }
+
+ /**
+ * Returns a copy of this {@code OffsetDate} with the day-of-month altered.
+ *
+ * If the resulting date is invalid, an exception is thrown.
+ * The offset does not affect the calculation and will be the same in the result.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param dayOfMonth the day-of-month to set in the result, from 1 to 28-31
+ * @return an {@code OffsetDate} based on this date with the requested day, not null
+ * @throws DateTimeException if the day-of-month value is invalid,
+ * or if the day-of-month is invalid for the month-year
+ */
+ public OffsetDate withDayOfMonth(int dayOfMonth) {
+ return with(date.withDayOfMonth(dayOfMonth), offset);
+ }
+
+ /**
+ * Returns a copy of this {@code OffsetDate} with the day-of-year altered.
+ *
+ * If the resulting date is invalid, an exception is thrown.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param dayOfYear the day-of-year to set in the result, from 1 to 365-366
+ * @return an {@code OffsetDate} based on this date with the requested day, not null
+ * @throws DateTimeException if the day-of-year value is invalid,
+ * or if the day-of-year is invalid for the year
+ */
+ public OffsetDate withDayOfYear(int dayOfYear) {
+ return with(date.withDayOfYear(dayOfYear), offset);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this date with the specified period added.
+ *
+ * This returns an {@code OffsetDate} based on this one, with the specified amount added.
+ * The amount is typically {@link Period} but may be any other type implementing
+ * the {@link TemporalAmount} interface.
+ *
+ * This uses {@link TemporalAmount#addTo(Temporal)} to perform the calculation.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToAdd the amount to add, not null
+ * @return an {@code OffsetDate} based on this date with the addition made, not null
+ * @throws DateTimeException if the addition cannot be made
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public OffsetDate plus(TemporalAmount amountToAdd) {
+ return (OffsetDate) amountToAdd.addTo(this);
+ }
+
+ /**
+ * Returns a copy of this date with the specified amount added.
+ *
+ * This returns an {@code OffsetDate} based on this one, with the amount
+ * in terms of the unit added. If it is not possible to add the amount, because the
+ * unit is not supported or for some other reason, an exception is thrown.
+ *
+ * If the field is a {@link ChronoUnit} then the addition is implemented by
+ * {@link LocalDate#plus(long, TemporalUnit)}.
+ * The offset is not part of the calculation and will be unchanged in the result.
+ *
+ * If the field is not a {@code ChronoUnit}, then the result of this method
+ * is obtained by invoking {@code TemporalUnit.addTo(Temporal, long)}
+ * passing {@code this} as the argument. In this case, the unit determines
+ * whether and how to perform the addition.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToAdd the amount of the unit to add to the result, may be negative
+ * @param unit the unit of the amount to add, not null
+ * @return an {@code OffsetDate} based on this date with the specified amount added, not null
+ * @throws DateTimeException if the addition cannot be made
+ * @throws UnsupportedTemporalTypeException if the unit is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public OffsetDate plus(long amountToAdd, TemporalUnit unit) {
+ if (unit instanceof ChronoUnit) {
+ return with(date.plus(amountToAdd, unit), offset);
+ }
+ return unit.addTo(this, amountToAdd);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this {@code OffsetDate} with the specified number of years added.
+ *
+ * This uses {@link LocalDate#plusYears(long)} to add the years.
+ * The offset does not affect the calculation and will be the same in the result.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param years the years to add, may be negative
+ * @return an {@code OffsetDate} based on this date with the years added, not null
+ * @throws DateTimeException if the result exceeds the supported date range
+ */
+ public OffsetDate plusYears(long years) {
+ return with(date.plusYears(years), offset);
+ }
+
+ /**
+ * Returns a copy of this {@code OffsetDate} with the specified number of months added.
+ *
+ * This uses {@link LocalDate#plusMonths(long)} to add the months.
+ * The offset does not affect the calculation and will be the same in the result.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param months the months to add, may be negative
+ * @return an {@code OffsetDate} based on this date with the months added, not null
+ * @throws DateTimeException if the result exceeds the supported date range
+ */
+ public OffsetDate plusMonths(long months) {
+ return with(date.plusMonths(months), offset);
+ }
+
+ /**
+ * Returns a copy of this {@code OffsetDate} with the specified number of weeks added.
+ *
+ * This uses {@link LocalDate#plusWeeks(long)} to add the weeks.
+ * The offset does not affect the calculation and will be the same in the result.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param weeks the weeks to add, may be negative
+ * @return an {@code OffsetDate} based on this date with the weeks added, not null
+ * @throws DateTimeException if the result exceeds the supported date range
+ */
+ public OffsetDate plusWeeks(long weeks) {
+ return with(date.plusWeeks(weeks), offset);
+ }
+
+ /**
+ * Returns a copy of this {@code OffsetDate} with the specified number of days added.
+ *
+ * This uses {@link LocalDate#plusDays(long)} to add the days.
+ * The offset does not affect the calculation and will be the same in the result.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param days the days to add, may be negative
+ * @return an {@code OffsetDate} based on this date with the days added, not null
+ * @throws DateTimeException if the result exceeds the supported date range
+ */
+ public OffsetDate plusDays(long days) {
+ return with(date.plusDays(days), offset);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this date with the specified amount subtracted.
+ *
+ * This returns am {@code OffsetDate} based on this one, with the specified amount subtracted.
+ * The amount is typically {@link Period} but may be any other type implementing
+ * the {@link TemporalAmount} interface.
+ *
+ * This uses {@link TemporalAmount#subtractFrom(Temporal)} to perform the calculation.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToSubtract the amount to subtract, not null
+ * @return an {@code OffsetDate} based on this date with the subtraction made, not null
+ * @throws DateTimeException if the subtraction cannot be made
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public OffsetDate minus(TemporalAmount amountToSubtract) {
+ return (OffsetDate) amountToSubtract.subtractFrom(this);
+ }
+
+ /**
+ * Returns a copy of this date with the specified amount subtracted.
+ *
+ * This returns an {@code OffsetDate} based on this one, with the amount
+ * in terms of the unit subtracted. If it is not possible to subtract the amount,
+ * because the unit is not supported or for some other reason, an exception is thrown.
+ *
+ * This method is equivalent to {@link #plus(long, TemporalUnit)} with the amount negated.
+ * See that method for a full description of how addition, and thus subtraction, works.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToSubtract the amount of the unit to subtract from the result, may be negative
+ * @param unit the unit of the amount to subtract, not null
+ * @return an {@code OffsetDate} based on this date with the specified amount subtracted, not null
+ * @throws DateTimeException if the subtraction cannot be made
+ * @throws UnsupportedTemporalTypeException if the unit is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public OffsetDate minus(long amountToSubtract, TemporalUnit unit) {
+ return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this {@code OffsetDate} with the specified number of years subtracted.
+ *
+ * This uses {@link LocalDate#minusYears(long)} to subtract the years.
+ * The offset does not affect the calculation and will be the same in the result.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param years the years to subtract, may be negative
+ * @return an {@code OffsetDate} based on this date with the years subtracted, not null
+ * @throws DateTimeException if the result exceeds the supported date range
+ */
+ public OffsetDate minusYears(long years) {
+ return with(date.minusYears(years), offset);
+ }
+
+ /**
+ * Returns a copy of this {@code OffsetDate} with the specified number of months subtracted.
+ *
+ * This uses {@link LocalDate#minusMonths(long)} to subtract the months.
+ * The offset does not affect the calculation and will be the same in the result.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param months the months to subtract, may be negative
+ * @return an {@code OffsetDate} based on this date with the months subtracted, not null
+ * @throws DateTimeException if the result exceeds the supported date range
+ */
+ public OffsetDate minusMonths(long months) {
+ return with(date.minusMonths(months), offset);
+ }
+
+ /**
+ * Returns a copy of this {@code OffsetDate} with the specified number of weeks subtracted.
+ *
+ * This uses {@link LocalDate#minusWeeks(long)} to subtract the weeks.
+ * The offset does not affect the calculation and will be the same in the result.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param weeks the weeks to subtract, may be negative
+ * @return an {@code OffsetDate} based on this date with the weeks subtracted, not null
+ * @throws DateTimeException if the result exceeds the supported date range
+ */
+ public OffsetDate minusWeeks(long weeks) {
+ return with(date.minusWeeks(weeks), offset);
+ }
+
+ /**
+ * Returns a copy of this {@code OffsetDate} with the specified number of days subtracted.
+ *
+ * This uses {@link LocalDate#minusDays(long)} to subtract the days.
+ * The offset does not affect the calculation and will be the same in the result.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param days the days to subtract, may be negative
+ * @return an {@code OffsetDate} based on this date with the days subtracted, not null
+ * @throws DateTimeException if the result exceeds the supported date range
+ */
+ public OffsetDate minusDays(long days) {
+ return with(date.minusDays(days), offset);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Queries this date using the specified query.
+ *
+ * This queries this date using the specified query strategy object.
+ * The {@code TemporalQuery} object defines the logic to be used to
+ * obtain the result. Read the documentation of the query to understand
+ * what the result of this method will be.
+ *
+ * The result of this method is obtained by invoking the
+ * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the
+ * specified query passing {@code this} as the argument.
+ *
+ * @param the type of the result
+ * @param query the query to invoke, not null
+ * @return the query result, null may be returned (defined by the query)
+ * @throws DateTimeException if unable to query (defined by the query)
+ * @throws ArithmeticException if numeric overflow occurs (defined by the query)
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public R query(TemporalQuery query) {
+ if (query == TemporalQueries.chronology()) {
+ return (R) IsoChronology.INSTANCE;
+ } else if (query == TemporalQueries.precision()) {
+ return (R) DAYS;
+ } else if (query == TemporalQueries.offset() || query == TemporalQueries.zone()) {
+ return (R) getOffset();
+ }
+ return Temporal.super.query(query);
+ }
+
+ /**
+ * Adjusts the specified temporal object to have the same offset and date
+ * as this object.
+ *
+ * This returns a temporal object of the same observable type as the input
+ * with the offset and date changed to be the same as this.
+ *
+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)}
+ * twice, passing {@link ChronoField#OFFSET_SECONDS} and
+ * {@link ChronoField#EPOCH_DAY} as the fields.
+ *
+ * In most cases, it is clearer to reverse the calling pattern by using
+ * {@link Temporal#with(TemporalAdjuster)}:
+ *
+ * // these two lines are equivalent, but the second approach is recommended
+ * temporal = thisOffsetDate.adjustInto(temporal);
+ * temporal = temporal.with(thisOffsetDate);
+ *
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param temporal the target object to be adjusted, not null
+ * @return the adjusted object, not null
+ * @throws DateTimeException if unable to make the adjustment
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public Temporal adjustInto(Temporal temporal) {
+ return temporal
+ .with(OFFSET_SECONDS, getOffset().getTotalSeconds())
+ .with(EPOCH_DAY, toLocalDate().toEpochDay());
+ }
+
+ /**
+ * Calculates the period between this date and another date in
+ * terms of the specified unit.
+ *
+ * This calculates the period between two dates in terms of a single unit.
+ * The start and end points are {@code this} and the specified date.
+ * The result will be negative if the end is before the start.
+ * For example, the period in days between two dates can be calculated
+ * using {@code startDate.until(endDate, DAYS)}.
+ *
+ * The {@code Temporal} passed to this method is converted to a
+ * {@code OffsetDate} using {@link #from(TemporalAccessor)}.
+ * If the offset differs between the two times, then the specified
+ * end time is normalized to have the same offset as this time.
+ *
+ * The calculation returns a whole number, representing the number of
+ * complete units between the two dates.
+ * For example, the period in months between 2012-06-15Z and 2012-08-14Z
+ * will only be one month as it is one day short of two months.
+ *
+ * There are two equivalent ways of using this method.
+ * The first is to invoke this method.
+ * The second is to use {@link TemporalUnit#between(Temporal, Temporal)}:
+ *
+ * // these two lines are equivalent
+ * amount = start.until(end, DAYS);
+ * amount = DAYS.between(start, end);
+ *
+ * The choice should be made based on which makes the code more readable.
+ *
+ * The calculation is implemented in this method for {@link ChronoUnit}.
+ * The units {@code DAYS}, {@code WEEKS}, {@code MONTHS}, {@code YEARS},
+ * {@code DECADES}, {@code CENTURIES}, {@code MILLENNIA} and {@code ERAS}
+ * are supported. Other {@code ChronoUnit} values will throw an exception.
+ *
+ * If the unit is not a {@code ChronoUnit}, then the result of this method
+ * is obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)}
+ * passing {@code this} as the first argument and the converted input temporal
+ * as the second argument.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param endExclusive the end time, exclusive, which is converted to an {@code OffsetDate}, not null
+ * @param unit the unit to measure the amount in, not null
+ * @return the amount of time between this date and the end date
+ * @throws DateTimeException if the amount cannot be calculated, or the end
+ * temporal cannot be converted to an {@code OffsetDate}
+ * @throws UnsupportedTemporalTypeException if the unit is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public long until(Temporal endExclusive, TemporalUnit unit) {
+ OffsetDate end = OffsetDate.from(endExclusive);
+ if (unit instanceof ChronoUnit) {
+ long offsetDiff = end.offset.getTotalSeconds() - offset.getTotalSeconds();
+ LocalDate endLocal = end.date.plusDays(Math.floorDiv(-offsetDiff, SECONDS_PER_DAY));
+ return date.until(endLocal, unit);
+ }
+ return unit.between(this, end);
+ }
+
+ /**
+ * Formats this date using the specified formatter.
+ *
+ * This date will be passed to the formatter to produce a string.
+ *
+ * @param formatter the formatter to use, not null
+ * @return the formatted date string, not null
+ * @throws DateTimeException if an error occurs during printing
+ */
+ public String format(DateTimeFormatter formatter) {
+ Objects.requireNonNull(formatter, "formatter");
+ return formatter.format(this);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns an offset date-time formed from this date at the specified time.
+ *
+ * This combines this date with the specified time to form an {@code OffsetDateTime}.
+ * All possible combinations of date and time are valid.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param time the time to combine with, not null
+ * @return the offset date-time formed from this date and the specified time, not null
+ */
+ public OffsetDateTime atTime(LocalTime time) {
+ return OffsetDateTime.of(date, time, offset);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Converts this date to midnight at the start of day in epoch seconds.
+ *
+ * @return the epoch seconds value
+ */
+ private long toEpochSecond() {
+ long epochDay = date.toEpochDay();
+ long secs = epochDay * SECONDS_PER_DAY;
+ return secs - offset.getTotalSeconds();
+ }
+
+ /**
+ * Converts this {@code OffsetDate} to the number of seconds since the epoch
+ * of 1970-01-01T00:00:00Z.
+ *
+ * This combines this offset date with the specified time
+ * to calculate the epoch-second value, which is the
+ * number of elapsed seconds from 1970-01-01T00:00:00Z.
+ * Instants on the time-line after the epoch are positive, earlier
+ * are negative.
+ *
+ * @param time the local time, not null
+ * @return the number of seconds since the epoch of 1970-01-01T00:00:00Z, may be negative
+ */
+ public long toEpochSecond(LocalTime time) {
+ Objects.requireNonNull(time, "time");
+ return toEpochSecond() + time.toSecondOfDay();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this {@code OffsetDate} to another date.
+ *
+ * The comparison is based first on the UTC equivalent instant, then on the local date.
+ * It is "consistent with equals", as defined by {@link Comparable}.
+ *
+ * For example, the following is the comparator order:
+ *
+ *
2008-06-29-11:00
+ *
2008-06-29-12:00
+ *
2008-06-30+12:00
+ *
2008-06-29-13:00
+ *
+ * Values #2 and #3 represent the same instant on the time-line.
+ * When two values represent the same instant, the local date is compared
+ * to distinguish them. This step is needed to make the ordering
+ * consistent with {@code equals()}.
+ *
+ * To compare the underlying local date of two {@code TemporalAccessor} instances,
+ * use {@link ChronoField#EPOCH_DAY} as a comparator.
+ *
+ * @param other the other date to compare to, not null
+ * @return the comparator value, negative if less, positive if greater
+ */
+ @Override
+ public int compareTo(OffsetDate other) {
+ if (offset.equals(other.offset)) {
+ return date.compareTo(other.date);
+ }
+ int compare = Long.compare(toEpochSecond(), other.toEpochSecond());
+ if (compare == 0) {
+ compare = date.compareTo(other.date);
+ }
+ return compare;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Checks if the instant of midnight at the start of this {@code OffsetDate}
+ * is after midnight at the start of the specified date.
+ *
+ * This method differs from the comparison in {@link #compareTo} in that it
+ * only compares the instant of the date. This is equivalent to using
+ * {@code date1.toEpochSecond().isAfter(date2.toEpochSecond())}.
+ *
+ * @param other the other date to compare to, not null
+ * @return true if this is after the instant of the specified date
+ */
+ public boolean isAfter(OffsetDate other) {
+ return toEpochSecond() > other.toEpochSecond();
+ }
+
+ /**
+ * Checks if the instant of midnight at the start of this {@code OffsetDate}
+ * is before midnight at the start of the specified date.
+ *
+ * This method differs from the comparison in {@link #compareTo} in that it
+ * only compares the instant of the date. This is equivalent to using
+ * {@code date1.toEpochSecond().isBefore(date2.toEpochSecond())}.
+ *
+ * @param other the other date to compare to, not null
+ * @return true if this is before the instant of the specified date
+ */
+ public boolean isBefore(OffsetDate other) {
+ return toEpochSecond() < other.toEpochSecond();
+ }
+
+ /**
+ * Checks if the instant of midnight at the start of this {@code OffsetDate}
+ * equals midnight at the start of the specified date.
+ *
+ * This method differs from the comparison in {@link #compareTo} and {@link #equals}
+ * in that it only compares the instant of the date. This is equivalent to using
+ * {@code date1.toEpochSecond().equals(date2.toEpochSecond())}.
+ *
+ * @param other the other date to compare to, not null
+ * @return true if the instant equals the instant of the specified date
+ */
+ public boolean isEqual(OffsetDate other) {
+ return toEpochSecond() == other.toEpochSecond();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Checks if this date is equal to another date.
+ *
+ * The comparison is based on the local-date and the offset.
+ * To compare for the same instant on the time-line, use {@link #isEqual(OffsetDate)}.
+ *
+ * Only objects of type {@code OffsetDate} are compared, other types return false.
+ * To compare the underlying local date of two {@code TemporalAccessor} instances,
+ * use {@link ChronoField#EPOCH_DAY} as a comparator.
+ *
+ * @param obj the object to check, null returns false
+ * @return true if this is equal to the other date
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof OffsetDate) {
+ OffsetDate other = (OffsetDate) obj;
+ return date.equals(other.date) && offset.equals(other.offset);
+ }
+ return false;
+ }
+
+ /**
+ * A hash code for this date.
+ *
+ * @return a suitable hash code
+ */
+ @Override
+ public int hashCode() {
+ return date.hashCode() ^ offset.hashCode();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Outputs this date as a {@code String}, such as {@code 2007-12-03+01:00}.
+ *
+ * The output will be in the ISO-8601 format {@code yyyy-MM-ddXXXXX}.
+ *
+ * @return a string representation of this date, not null
+ */
+ @Override
+ @ToString
+ public String toString() {
+ return date.toString() + offset.toString();
+ }
+
+}
diff --git a/src/main/java/org/threeten/extra/PackedFields.java b/src/main/java/org/threeten/extra/PackedFields.java
index 91b34e91..cd7fa1cb 100644
--- a/src/main/java/org/threeten/extra/PackedFields.java
+++ b/src/main/java/org/threeten/extra/PackedFields.java
@@ -155,7 +155,7 @@ public boolean isSupportedBy(TemporalAccessor temporal) {
@Override
public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
- if (isSupportedBy(temporal) == false) {
+ if (!temporal.isSupported(this)) {
throw new DateTimeException("Unsupported field: " + this);
}
return range();
@@ -260,7 +260,7 @@ public boolean isSupportedBy(TemporalAccessor temporal) {
@Override
public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
- if (isSupportedBy(temporal) == false) {
+ if (!temporal.isSupported(this)) {
throw new DateTimeException("Unsupported field: " + this);
}
return range();
@@ -351,7 +351,7 @@ public boolean isSupportedBy(TemporalAccessor temporal) {
@Override
public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
- if (isSupportedBy(temporal) == false) {
+ if (!temporal.isSupported(this)) {
throw new DateTimeException("Unsupported field: " + this);
}
return range();
diff --git a/src/main/java/org/threeten/extra/PeriodDuration.java b/src/main/java/org/threeten/extra/PeriodDuration.java
new file mode 100644
index 00000000..136a1944
--- /dev/null
+++ b/src/main/java/org/threeten/extra/PeriodDuration.java
@@ -0,0 +1,674 @@
+/*
+ * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of JSR-310 nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.threeten.extra;
+
+import static java.time.temporal.ChronoUnit.DAYS;
+import static java.time.temporal.ChronoUnit.MONTHS;
+import static java.time.temporal.ChronoUnit.NANOS;
+import static java.time.temporal.ChronoUnit.SECONDS;
+import static java.time.temporal.ChronoUnit.YEARS;
+
+import java.io.Serializable;
+import java.time.DateTimeException;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.Period;
+import java.time.chrono.ChronoPeriod;
+import java.time.chrono.IsoChronology;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.IsoFields;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAmount;
+import java.time.temporal.TemporalQueries;
+import java.time.temporal.TemporalUnit;
+import java.time.temporal.UnsupportedTemporalTypeException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+import org.joda.convert.FromString;
+import org.joda.convert.ToString;
+
+/**
+ * An amount of time in the ISO-8601 calendar system that combines a period and a duration.
+ *
+ * This class models a quantity or amount of time in terms of a {@code Period} and {@code Duration}.
+ * A period is a date-based amount of time, consisting of years, months and days.
+ * A duration is a time-based amount of time, consisting of seconds and nanoseconds.
+ * See the {@link Period} and {@link Duration} classes for more details.
+ *
+ * The days in a period take account of daylight saving changes (23 or 25 hour days).
+ * When performing calculations, the period is added first, then the duration.
+ *
+ * The model is of a directed amount, meaning that the amount may be negative.
+ *
+ *
Implementation Requirements:
+ * This class is immutable and thread-safe.
+ *
+ * This class must be treated as a value type. Do not synchronize, rely on the
+ * identity hash code or use the distinction between equals() and ==.
+ */
+public final class PeriodDuration
+ implements TemporalAmount, Serializable {
+
+ /**
+ * A constant for a duration of zero.
+ */
+ public static final PeriodDuration ZERO = new PeriodDuration(Period.ZERO, Duration.ZERO);
+
+ /**
+ * A serialization identifier for this class.
+ */
+ private static final long serialVersionUID = 8815521625671589L;
+ /**
+ * The supported units.
+ */
+ private static final List SUPPORTED_UNITS =
+ Collections.unmodifiableList(Arrays.asList(YEARS, MONTHS, DAYS, SECONDS, NANOS));
+ /**
+ * The number of seconds per day.
+ */
+ private static final long SECONDS_PER_DAY = 86400;
+
+ /**
+ * The period.
+ */
+ private final Period period;
+ /**
+ * The duration.
+ */
+ private final Duration duration;
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains an instance based on a period and duration.
+ *
+ * The total amount of time of the resulting instance is the period plus the duration.
+ *
+ * @param period the period, not null
+ * @param duration the duration, not null
+ * @return the combined period-duration, not null
+ */
+ public static PeriodDuration of(Period period, Duration duration) {
+ Objects.requireNonNull(period, "The period must not be null");
+ Objects.requireNonNull(duration, "The duration must not be null");
+ return new PeriodDuration(period, duration);
+ }
+
+ /**
+ * Obtains an instance based on a period.
+ *
+ * The duration will be zero.
+ *
+ * @param period the period, not null
+ * @return the combined period-duration, not null
+ */
+ public static PeriodDuration of(Period period) {
+ Objects.requireNonNull(period, "The period must not be null");
+ return new PeriodDuration(period, Duration.ZERO);
+ }
+
+ /**
+ * Obtains an instance based on a duration.
+ *
+ * The period will be zero.
+ *
+ * @param duration the duration, not null
+ * @return the combined period-duration, not null
+ */
+ public static PeriodDuration of(Duration duration) {
+ Objects.requireNonNull(duration, "The duration must not be null");
+ return new PeriodDuration(Period.ZERO, duration);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains an instance from a temporal amount.
+ *
+ * This obtains an instance based on the specified amount.
+ * A {@code TemporalAmount} represents an amount of time which this factory
+ * extracts to a {@code PeriodDuration}.
+ *
+ * The result is calculated by looping around each unit in the specified amount.
+ * Any amount that is zero is ignore.
+ * If a unit has an exact duration, it will be totalled using {@link Duration#plus(Duration)}.
+ * If the unit is days or weeks, it will be totalled into the days part of the period.
+ * If the unit is months or quarters, it will be totalled into the months part of the period.
+ * If the unit is years, decades, centuries or millennia, it will be totalled into the years part of the period.
+ *
+ * @param amount the temporal amount to convert, not null
+ * @return the equivalent duration, not null
+ * @throws DateTimeException if unable to convert to a {@code Duration}
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ public static PeriodDuration from(TemporalAmount amount) {
+ if (amount instanceof PeriodDuration) {
+ return (PeriodDuration) amount;
+ }
+ if (amount instanceof Period) {
+ return PeriodDuration.of((Period) amount);
+ }
+ if (amount instanceof Duration) {
+ return PeriodDuration.of((Duration) amount);
+ }
+ if (amount instanceof ChronoPeriod) {
+ if (IsoChronology.INSTANCE.equals(((ChronoPeriod) amount).getChronology()) == false) {
+ throw new DateTimeException("Period requires ISO chronology: " + amount);
+ }
+ }
+ Objects.requireNonNull(amount, "amount");
+ int years = 0;
+ int months = 0;
+ int days = 0;
+ Duration duration = Duration.ZERO;
+ for (TemporalUnit unit : amount.getUnits()) {
+ long value = amount.get(unit);
+ if (value != 0) {
+ // ignore unless non-zero
+ if (unit.isDurationEstimated()) {
+ if (unit == ChronoUnit.DAYS) {
+ days = Math.addExact(days, Math.toIntExact(value));
+ } else if (unit == ChronoUnit.WEEKS) {
+ days = Math.addExact(days, Math.toIntExact(Math.multiplyExact(value, 7)));
+ } else if (unit == ChronoUnit.MONTHS) {
+ months = Math.addExact(months, Math.toIntExact(value));
+ } else if (unit == IsoFields.QUARTER_YEARS) {
+ months = Math.addExact(months, Math.toIntExact(Math.multiplyExact(value, 3)));
+ } else if (unit == ChronoUnit.YEARS) {
+ years = Math.addExact(years, Math.toIntExact(value));
+ } else if (unit == ChronoUnit.DECADES) {
+ years = Math.addExact(years, Math.toIntExact(Math.multiplyExact(value, 10)));
+ } else if (unit == ChronoUnit.CENTURIES) {
+ years = Math.addExact(years, Math.toIntExact(Math.multiplyExact(value, 100)));
+ } else if (unit == ChronoUnit.MILLENNIA) {
+ years = Math.addExact(years, Math.toIntExact(Math.multiplyExact(value, 1000)));
+ } else {
+ throw new DateTimeException("Unknown unit: " + unit);
+ }
+ } else {
+ // total of exact durations
+ duration = duration.plus(amount.get(unit), unit);
+ }
+ }
+ }
+ return PeriodDuration.of(Period.of(years, months, days), duration);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains an instance from a text string such as {@code PnYnMnDTnHnMnS}.
+ *
+ * This will parse the string produced by {@code toString()} which is
+ * based on the ISO-8601 period formats {@code PnYnMnDTnHnMnS} and {@code PnW}.
+ *
+ * The string starts with an optional sign, denoted by the ASCII negative
+ * or positive symbol. If negative, the whole amount is negated.
+ * The ASCII letter "P" is next in upper or lower case.
+ * There are then a number of sections, each consisting of a number and a suffix.
+ * At least one of the sections must be present.
+ * The sections have suffixes in ASCII of "Y" for years, "M" for months,
+ * "W" for weeks, "D" for days, "H" for hours, "M" for minutes, "S" for seconds,
+ * accepted in upper or lower case. Note that the ASCII letter "T" separates
+ * the date and time parts and must be present if any time part is present.
+ * The suffixes must occur in order.
+ * The number part of each section must consist of ASCII digits.
+ * The number may be prefixed by the ASCII negative or positive symbol.
+ * The number must parse to an {@code int}.
+ * Any week-based input is multiplied by 7 and treated as a number of days.
+ *
+ * The leading plus/minus sign, and negative values for weeks and days are
+ * not part of the ISO-8601 standard.
+ *
+ * Note that the date style format {@code PYYYY-MM-DDTHH:MM:SS} is not supported.
+ *
+ * For example, the following are valid inputs:
+ *
+ *
+ * @param text the text to parse, not null
+ * @return the parsed period, not null
+ * @throws DateTimeParseException if the text cannot be parsed to a period
+ */
+ @FromString
+ public static PeriodDuration parse(CharSequence text) {
+ Objects.requireNonNull(text, "text");
+ String upper = text.toString().toUpperCase(Locale.ENGLISH);
+ String negate = "";
+ if (upper.startsWith("+")) {
+ upper = upper.substring(1);
+ } else if (upper.startsWith("-")) {
+ upper = upper.substring(1);
+ negate = "-";
+ }
+ // duration only, parse original text so it does negation
+ if (upper.startsWith("PT")) {
+ return PeriodDuration.of(Duration.parse(text));
+ }
+ // period only, parse original text so it does negation
+ int tpos = upper.indexOf('T');
+ if (tpos < 0) {
+ return PeriodDuration.of(Period.parse(text));
+ }
+ // period and duration
+ Period period = Period.parse(negate + upper.substring(0, tpos));
+ Duration duration = Duration.parse(negate + "P" + upper.substring(tpos));
+ return PeriodDuration.of(period, duration);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains an instance consisting of the amount of time between two temporals.
+ *
+ * The start is included, but the end is not.
+ * The result of this method can be negative if the end is before the start.
+ *
+ * The calculation examines the temporals and extracts {@link LocalDate} and {@link LocalTime}.
+ * If the time is missing, it will be defaulted to midnight.
+ * If one date is missing, it will be defaulted to the other date.
+ * It then finds the amount of time between the two dates and between the two times.
+ *
+ * @param startInclusive the start, inclusive, not null
+ * @param endExclusive the end, exclusive, not null
+ * @return the number of days between this date and the end date, not null
+ */
+ public static PeriodDuration between(Temporal startInclusive, Temporal endExclusive) {
+ LocalDate startDate = startInclusive.query(TemporalQueries.localDate());
+ LocalDate endDate = endExclusive.query(TemporalQueries.localDate());
+ Period period = Period.ZERO;
+ if (startDate != null && endDate != null) {
+ period = Period.between(startDate, endDate);
+ }
+ LocalTime startTime = startInclusive.query(TemporalQueries.localTime());
+ LocalTime endTime = endExclusive.query(TemporalQueries.localTime());
+ startTime = startTime != null ? startTime : LocalTime.MIDNIGHT;
+ endTime = endTime != null ? endTime : LocalTime.MIDNIGHT;
+ Duration duration = Duration.between(startTime, endTime);
+ return PeriodDuration.of(period, duration);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Constructs an instance.
+ *
+ * @param period the period
+ * @param duration the duration
+ */
+ private PeriodDuration(Period period, Duration duration) {
+ this.period = period;
+ this.duration = duration;
+ }
+
+ /**
+ * Resolves singletons.
+ *
+ * @return the singleton instance
+ */
+ private Object readResolve() {
+ return PeriodDuration.of(period, duration);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the value of the requested unit.
+ *
+ * This returns a value for the supported units - {@link ChronoUnit#YEARS},
+ * {@link ChronoUnit#MONTHS}, {@link ChronoUnit#DAYS}, {@link ChronoUnit#SECONDS}
+ * and {@link ChronoUnit#NANOS}.
+ * All other units throw an exception.
+ * Note that hours and minutes throw an exception.
+ *
+ * @param unit the {@code TemporalUnit} for which to return the value
+ * @return the long value of the unit
+ * @throws UnsupportedTemporalTypeException if the unit is not supported
+ */
+ @Override
+ public long get(TemporalUnit unit) {
+ if (unit instanceof ChronoUnit) {
+ switch ((ChronoUnit) unit) {
+ case YEARS:
+ return period.getYears();
+ case MONTHS:
+ return period.getMonths();
+ case DAYS:
+ return period.getDays();
+ case SECONDS:
+ return duration.getSeconds();
+ case NANOS:
+ return duration.getNano();
+ default:
+ break;
+ }
+ }
+ throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
+ }
+
+ /**
+ * Gets the set of units supported by this amount.
+ *
+ * This returns the list {@link ChronoUnit#YEARS}, {@link ChronoUnit#MONTHS},
+ * {@link ChronoUnit#DAYS}, {@link ChronoUnit#SECONDS} and {@link ChronoUnit#NANOS}.
+ *
+ * This set can be used in conjunction with {@link #get(TemporalUnit)}
+ * to access the entire state of the amount.
+ *
+ * @return a list containing the days unit, not null
+ */
+ @Override
+ public List getUnits() {
+ return SUPPORTED_UNITS;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the period part.
+ *
+ * @return the period part
+ */
+ public Period getPeriod() {
+ return period;
+ }
+
+ /**
+ * Returns a copy of this period-duration with a different period.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param period the new period
+ * @return the updated period-duration
+ */
+ public PeriodDuration withPeriod(Period period) {
+ return PeriodDuration.of(period, duration);
+ }
+
+ /**
+ * Gets the duration part.
+ *
+ * @return the duration part
+ */
+ public Duration getDuration() {
+ return duration;
+ }
+
+ /**
+ * Returns a copy of this period-duration with a different duration.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param duration the new duration
+ * @return the updated period-duration
+ */
+ public PeriodDuration withDuration(Duration duration) {
+ return PeriodDuration.of(period, duration);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Checks if all parts of this amount are zero.
+ *
+ * This returns true if both {@link Period#isZero()} and {@link Duration#isZero()}
+ * return true.
+ *
+ * @return true if this period is zero-length
+ */
+ public boolean isZero() {
+ return period.isZero() && duration.isZero();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this amount with the specified amount added.
+ *
+ * The parameter is converted using {@link PeriodDuration#from(TemporalAmount)}.
+ * The period and duration are combined separately.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToAdd the amount to add, not null
+ * @return a {@code Days} based on this instance with the requested amount added, not null
+ * @throws DateTimeException if the specified amount contains an invalid unit
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ public PeriodDuration plus(TemporalAmount amountToAdd) {
+ PeriodDuration other = PeriodDuration.from(amountToAdd);
+ return of(period.plus(other.period), duration.plus(other.duration));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this amount with the specified amount subtracted.
+ *
+ * The parameter is converted using {@link PeriodDuration#from(TemporalAmount)}.
+ * The period and duration are combined separately.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToAdd the amount to add, not null
+ * @return a {@code Days} based on this instance with the requested amount subtracted, not null
+ * @throws DateTimeException if the specified amount contains an invalid unit
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ public PeriodDuration minus(TemporalAmount amountToAdd) {
+ PeriodDuration other = PeriodDuration.from(amountToAdd);
+ return of(period.minus(other.period), duration.minus(other.duration));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns an instance with the amount multiplied by the specified scalar.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param scalar the scalar to multiply by, not null
+ * @return the amount multiplied by the specified scalar, not null
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ public PeriodDuration multipliedBy(int scalar) {
+ if (scalar == 1) {
+ return this;
+ }
+ return of(period.multipliedBy(scalar), duration.multipliedBy(scalar));
+ }
+
+ /**
+ * Returns an instance with the amount negated.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @return the negated amount, not null
+ * @throws ArithmeticException if numeric overflow occurs, which only happens if
+ * the amount is {@code Long.MIN_VALUE}
+ */
+ public PeriodDuration negated() {
+ return multipliedBy(-1);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this instance with the years and months exactly normalized.
+ *
+ * This normalizes the years and months units, leaving the days unit unchanged.
+ * The result is exact, always representing the same amount of time.
+ *
+ * The months unit is adjusted to have an absolute value less than 11,
+ * with the years unit being adjusted to compensate. For example, a period of
+ * "1 year and 15 months" will be normalized to "2 years and 3 months".
+ *
+ * The sign of the years and months units will be the same after normalization.
+ * For example, a period of "1 year and -25 months" will be normalized to
+ * "-1 year and -1 month".
+ *
+ * Note that no normalization is performed on the days or duration.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @return a {@code PeriodDuration} based on this one with excess months normalized to years, not null
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ public PeriodDuration normalizedYears() {
+ return withPeriod(period.normalized());
+ }
+
+ /**
+ * Returns a copy of this instance with the days and duration normalized using the standard day of 24 hours.
+ *
+ * This normalizes the days and duration, leaving the years and months unchanged.
+ * The result uses a standard day length of 24 hours.
+ *
+ * This combines the duration seconds with the number of days and shares the total
+ * seconds between the two fields. For example, a period of
+ * "2 days and 86401 seconds" will be normalized to "3 days and 1 second".
+ *
+ * The sign of the days and duration will be the same after normalization.
+ * For example, a period of "1 day and -172801 seconds" will be normalized to
+ * "-1 day and -1 second".
+ *
+ * Note that no normalization is performed on the years or months.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @return a {@code PeriodDuration} based on this one with excess duration normalized to days, not null
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ public PeriodDuration normalizedStandardDays() {
+ long totalSecs = period.getDays() * SECONDS_PER_DAY + duration.getSeconds();
+ int splitDays = Math.toIntExact(totalSecs / SECONDS_PER_DAY);
+ long splitSecs = totalSecs % SECONDS_PER_DAY;
+ if (splitDays == period.getDays() && splitSecs == duration.getSeconds()) {
+ return this;
+ }
+ return PeriodDuration.of(period.withDays(splitDays), duration.withSeconds(splitSecs));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds this amount to the specified temporal object.
+ *
+ * This returns a temporal object of the same observable type as the input
+ * with this amount added. This simply adds the period and duration to the temporal.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param temporal the temporal object to adjust, not null
+ * @return an object of the same type with the adjustment made, not null
+ * @throws DateTimeException if unable to add
+ * @throws UnsupportedTemporalTypeException if the DAYS unit is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public Temporal addTo(Temporal temporal) {
+ return temporal.plus(period).plus(duration);
+ }
+
+ /**
+ * Subtracts this amount from the specified temporal object.
+ *
+ * This returns a temporal object of the same observable type as the input
+ * with this amount subtracted. This simply subtracts the period and duration from the temporal.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param temporal the temporal object to adjust, not null
+ * @return an object of the same type with the adjustment made, not null
+ * @throws DateTimeException if unable to subtract
+ * @throws UnsupportedTemporalTypeException if the DAYS unit is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public Temporal subtractFrom(Temporal temporal) {
+ return temporal.minus(period).minus(duration);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Checks if this amount is equal to the specified {@code PeriodDuration}.
+ *
+ * The comparison is based on the underlying period and duration.
+ *
+ * @param otherAmount the other amount, null returns false
+ * @return true if the other amount is equal to this one
+ */
+ @Override
+ public boolean equals(Object otherAmount) {
+ if (this == otherAmount) {
+ return true;
+ }
+ if (otherAmount instanceof PeriodDuration) {
+ PeriodDuration other = (PeriodDuration) otherAmount;
+ return this.period.equals(other.period) && this.duration.equals(other.duration);
+ }
+ return false;
+ }
+
+ /**
+ * A hash code for this amount.
+ *
+ * @return a suitable hash code
+ */
+ @Override
+ public int hashCode() {
+ return period.hashCode() ^ duration.hashCode();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a string representation of the amount.
+ * This will be in the format 'PnYnMnDTnHnMnS', with sections omitted as necessary.
+ * An empty amount will return "PT0S".
+ *
+ * @return the period in ISO-8601 string format
+ */
+ @Override
+ @ToString
+ public String toString() {
+ if (period.isZero()) {
+ return duration.toString();
+ }
+ if (duration.isZero()) {
+ return period.toString();
+ }
+ return period.toString() + duration.toString().substring(1);
+ }
+
+}
diff --git a/src/main/java/org/threeten/extra/Quarter.java b/src/main/java/org/threeten/extra/Quarter.java
index 5f29118b..04265ce8 100644
--- a/src/main/java/org/threeten/extra/Quarter.java
+++ b/src/main/java/org/threeten/extra/Quarter.java
@@ -57,9 +57,8 @@
/**
* A quarter-of-year, such as 'Q2'.
*
- * {@code Quarter} is an enum representing the 4 quarters of the year -
- * Q1, Q2, Q3 and Q4. These are defined as January to March, April to June,
- * July to September and October to December.
+ * {@code Quarter} is an enum representing the 4 quarters of the year - Q1, Q2, Q3 and Q4.
+ * These are defined as January to March, April to June, July to September and October to December.
*
* The {@code int} value follows the quarter, from 1 (Q1) to 4 (Q4).
* It is recommended that applications use the enum rather than the {@code int} value
@@ -170,10 +169,11 @@ public static Quarter from(TemporalAccessor temporal) {
return of(month.ordinal() / 3 + 1);
}
try {
- if (IsoChronology.INSTANCE.equals(Chronology.from(temporal)) == false) {
- temporal = LocalDate.from(temporal);
- }
- return of(temporal.get(QUARTER_OF_YEAR));
+ TemporalAccessor adjusted =
+ !IsoChronology.INSTANCE.equals(Chronology.from(temporal)) ? LocalDate.from(temporal) : temporal;
+ // need to use getLong() as JDK Parsed class get() doesn't work properly
+ int qoy = Math.toIntExact(adjusted.getLong(QUARTER_OF_YEAR));
+ return of(qoy);
} catch (DateTimeException ex) {
throw new DateTimeException("Unable to obtain Quarter from TemporalAccessor: " +
temporal + " of type " + temporal.getClass().getName(), ex);
diff --git a/src/main/java/org/threeten/extra/Seconds.java b/src/main/java/org/threeten/extra/Seconds.java
new file mode 100644
index 00000000..d3cb18a3
--- /dev/null
+++ b/src/main/java/org/threeten/extra/Seconds.java
@@ -0,0 +1,664 @@
+/*
+ * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of JSR-310 nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.threeten.extra;
+
+import static java.time.temporal.ChronoUnit.SECONDS;
+
+import java.io.Serializable;
+import java.time.DateTimeException;
+import java.time.Duration;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAmount;
+import java.time.temporal.TemporalUnit;
+import java.time.temporal.UnsupportedTemporalTypeException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.joda.convert.FromString;
+import org.joda.convert.ToString;
+
+/**
+ * A second-based amount of time, such as '8 seconds'.
+ *
+ * This class models a quantity or amount of time in terms of seconds.
+ * It is a type-safe way of representing a number of seconds in an application.
+ * Note that {@link Duration} also models time in terms of seconds, but that
+ * class allows nanoseconds, which this class does not.
+ *
+ * The model is of a directed amount, meaning that the amount may be negative.
+ *
+ *
Implementation Requirements:
+ * This class is immutable and thread-safe.
+ *
+ * This class must be treated as a value type. Do not synchronize, rely on the
+ * identity hash code or use the distinction between equals() and ==.
+ */
+public final class Seconds
+ implements TemporalAmount, Comparable, Serializable {
+
+ /**
+ * A constant for zero seconds.
+ */
+ public static final Seconds ZERO = new Seconds(0);
+
+ /**
+ * A serialization identifier for this class.
+ */
+ private static final long serialVersionUID = 2602801843170589407L;
+
+ /**
+ * The number of seconds per day.
+ */
+ private static final int SECONDS_PER_DAY = 86400;
+ /**
+ * The number of seconds per hour.
+ */
+ private static final int SECONDS_PER_HOUR = 3600;
+ /**
+ * The number of seconds per minute.
+ */
+ private static final int SECONDS_PER_MINUTE = 60;
+
+ /**
+ * The pattern for parsing.
+ */
+ private static final Pattern PATTERN =
+ Pattern.compile("([-+]?)P"
+ + "(?:([-+]?[0-9]+)D)?"
+ + "(?:T"
+ + "(?:([-+]?[0-9]+)H)?"
+ + "(?:([-+]?[0-9]+)M)?"
+ + "(?:([-+]?[0-9]+)S)?)?", Pattern.CASE_INSENSITIVE);
+
+ /**
+ * The number of seconds.
+ */
+ private final int seconds;
+
+ /**
+ * Obtains a {@code Seconds} representing a number of seconds.
+ *
+ * The resulting amount will have the specified seconds.
+ *
+ * @param seconds the number of seconds, positive or negative
+ * @return the number of seconds, not null
+ */
+ public static Seconds of(int seconds) {
+ if (seconds == 0) {
+ return ZERO;
+ }
+ return new Seconds(seconds);
+ }
+
+ /**
+ * Obtains a {@code Seconds} representing the number of seconds
+ * equivalent to a number of hours.
+ *
+ * The resulting amount will be second-based, with the number of seconds
+ * equal to the number of hours multiplied by 3600.
+ *
+ * @param hours the number of hours, positive or negative
+ * @return the amount with the input hours converted to seconds, not null
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ public static Seconds ofHours(int hours) {
+ if (hours == 0) {
+ return ZERO;
+ }
+ return new Seconds(Math.multiplyExact(hours, SECONDS_PER_HOUR));
+ }
+
+ /**
+ * Obtains a {@code Seconds} representing the number of seconds
+ * equivalent to a number of hours.
+ *
+ * The resulting amount will be second-based, with the number of seconds
+ * equal to the number of minutes multiplied by 60.
+ *
+ * @param minutes the number of minutes, positive or negative
+ * @return the amount with the input minutes converted to seconds, not null
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ public static Seconds ofMinutes(int minutes) {
+ if (minutes == 0) {
+ return ZERO;
+ }
+ return new Seconds(Math.multiplyExact(minutes, SECONDS_PER_MINUTE));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains an instance of {@code Seconds} from a temporal amount.
+ *
+ * This obtains an instance based on the specified amount.
+ * A {@code TemporalAmount} represents an amount of time, which may be
+ * date-based or time-based, which this factory extracts to a {@code Seconds}.
+ *
+ * The result is calculated by looping around each unit in the specified amount.
+ * Each amount is converted to seconds using {@link Temporals#convertAmount}.
+ * If the conversion yields a remainder, an exception is thrown.
+ * If the amount is zero, the unit is ignored.
+ *
+ * @param amount the temporal amount to convert, not null
+ * @return the equivalent amount, not null
+ * @throws DateTimeException if unable to convert to a {@code Seconds}
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ public static Seconds from(TemporalAmount amount) {
+ if (amount instanceof Seconds) {
+ return (Seconds) amount;
+ }
+ Objects.requireNonNull(amount, "amount");
+ int seconds = 0;
+ for (TemporalUnit unit : amount.getUnits()) {
+ long value = amount.get(unit);
+ if (value != 0) {
+ long[] converted = Temporals.convertAmount(value, unit, SECONDS);
+ if (converted[1] != 0) {
+ throw new DateTimeException(
+ "Amount could not be converted to a whole number of seconds: " + value + " " + unit);
+ }
+ seconds = Math.addExact(seconds, Math.toIntExact(converted[0]));
+ }
+ }
+ return of(seconds);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains a {@code Seconds} from a text string such as {@code PTnS}.
+ *
+ * This will parse the string produced by {@code toString()} and other
+ * related formats based on ISO-8601 {@code PnDTnHnMnS}.
+ *
+ * The string starts with an optional sign, denoted by the ASCII negative
+ * or positive symbol. If negative, the whole amount is negated.
+ * The ASCII letter "P" is next in upper or lower case.
+ * There are four sections consisting of a number and a suffix.
+ * There is one section for days suffixed by "D",
+ * followed by one section for hours suffixed by "H",
+ * followed by one section for minutes suffixed by "M",
+ * followed by one section for seconds suffixed by "S".
+ * At least one section must be present.
+ * If the hours, minutes or seconds section is present it must be prefixed by "T".
+ * If the hours, minutes or seconds section is omitted the "T" must be omitted.
+ * Letters must be in ASCII upper or lower case.
+ * The number part of each section must consist of ASCII digits.
+ * The number may be prefixed by the ASCII negative or positive symbol.
+ * The number must parse to an {@code int}.
+ *
+ * The leading plus/minus sign, and negative values for days, hours, minutes
+ * and seconds are not part of the ISO-8601 standard.
+ *
+ * For example, the following are valid inputs:
+ *
+ *
+ * @param text the text to parse, not null
+ * @return the parsed period, not null
+ * @throws DateTimeParseException if the text cannot be parsed to a period
+ */
+ @FromString
+ public static Seconds parse(CharSequence text) {
+ Objects.requireNonNull(text, "text");
+ Matcher matcher = PATTERN.matcher(text);
+ if (matcher.matches()) {
+ int negate = "-".equals(matcher.group(1)) ? -1 : 1;
+ String daysStr = matcher.group(2);
+ String hoursStr = matcher.group(3);
+ String minutesStr = matcher.group(4);
+ String secondsStr = matcher.group(5);
+ if (daysStr != null || hoursStr != null || minutesStr != null || secondsStr != null) {
+ int seconds = 0;
+ if (secondsStr != null) {
+ try {
+ seconds = Integer.parseInt(secondsStr);
+ } catch (NumberFormatException ex) {
+ throw new DateTimeParseException("Text cannot be parsed to Seconds, non-numeric seconds", text, 0, ex);
+ }
+ }
+ if (minutesStr != null) {
+ try {
+ int minutesAsSecs = Math.multiplyExact(Integer.parseInt(minutesStr), SECONDS_PER_MINUTE);
+ seconds = Math.addExact(seconds, minutesAsSecs);
+ } catch (NumberFormatException ex) {
+ throw new DateTimeParseException("Text cannot be parsed to Seconds, non-numeric minutes", text, 0, ex);
+ }
+ }
+ if (hoursStr != null) {
+ try {
+ int hoursAsSecs = Math.multiplyExact(Integer.parseInt(hoursStr), SECONDS_PER_HOUR);
+ seconds = Math.addExact(seconds, hoursAsSecs);
+ } catch (NumberFormatException ex) {
+ throw new DateTimeParseException("Text cannot be parsed to Seconds, non-numeric hours", text, 0, ex);
+ }
+ }
+ if (daysStr != null) {
+ try {
+ int daysAsSecs = Math.multiplyExact(Integer.parseInt(daysStr), SECONDS_PER_DAY);
+ seconds = Math.addExact(seconds, daysAsSecs);
+ } catch (NumberFormatException ex) {
+ throw new DateTimeParseException("Text cannot be parsed to Seconds, non-numeric days", text, 0, ex);
+ }
+ }
+ return of(Math.multiplyExact(seconds, negate));
+ }
+ }
+ throw new DateTimeParseException("Text cannot be parsed to Seconds", text, 0);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains a {@code Seconds} consisting of the number of seconds between two temporals.
+ *
+ * The start temporal is included, but the end temporal is not.
+ * The result of this method can be negative if the end is before the start.
+ *
+ * @param startInclusive the start temporal, inclusive, not null
+ * @param endExclusive the end temporal, exclusive, not null
+ * @return the number of seconds between the start and end temporals, not null
+ */
+ public static Seconds between(Temporal startInclusive, Temporal endExclusive) {
+ return of(Math.toIntExact(SECONDS.between(startInclusive, endExclusive)));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Constructs an instance using a specific number of seconds.
+ *
+ * @param seconds the amount of seconds
+ */
+ private Seconds(int seconds) {
+ this.seconds = seconds;
+ }
+
+ /**
+ * Resolves singletons.
+ *
+ * @return the singleton instance
+ */
+ private Object readResolve() {
+ return Seconds.of(seconds);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the value of the requested unit.
+ *
+ * This returns a value for the supported unit - {@link ChronoUnit#SECONDS SECONDS}.
+ * All other units throw an exception.
+ *
+ * @param unit the {@code TemporalUnit} for which to return the value
+ * @return the long value of the unit
+ * @throws UnsupportedTemporalTypeException if the unit is not supported
+ */
+ @Override
+ public long get(TemporalUnit unit) {
+ if (unit == SECONDS) {
+ return seconds;
+ }
+ throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
+ }
+
+ /**
+ * Gets the set of units supported by this amount.
+ *
+ * The single supported unit is {@link ChronoUnit#SECONDS SECONDS}.
+ *
+ * This set can be used in conjunction with {@link #get(TemporalUnit)} to
+ * access the entire state of the amount.
+ *
+ * @return a list containing the seconds unit, not null
+ */
+ @Override
+ public List getUnits() {
+ return Collections.singletonList(SECONDS);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the number of seconds in this amount.
+ *
+ * @return the number of seconds
+ */
+ public int getAmount() {
+ return seconds;
+ }
+
+ /**
+ * Checks if the amount is negative.
+ *
+ * @return true if the amount is negative, false if the amount is zero or positive
+ */
+ public boolean isNegative() {
+ return getAmount() < 0;
+ }
+
+ /**
+ * Checks if the amount is zero.
+ *
+ * @return true if the amount is zero, false if not
+ */
+ public boolean isZero() {
+ return getAmount() == 0;
+ }
+
+ /**
+ * Checks if the amount is positive.
+ *
+ * @return true if the amount is positive, false if the amount is zero or negative
+ */
+ public boolean isPositive() {
+ return getAmount() > 0;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this amount with the specified amount added.
+ *
+ * The parameter is converted using {@link Seconds#from(TemporalAmount)}.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToAdd the amount to add, not null
+ * @return a {@code Seconds} based on this instance with the requested amount added, not null
+ * @throws DateTimeException if the specified amount contains an invalid unit
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ public Seconds plus(TemporalAmount amountToAdd) {
+ return plus(Seconds.from(amountToAdd).getAmount());
+ }
+
+ /**
+ * Returns a copy of this amount with the specified number of seconds added.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param seconds the amount of seconds to add, may be negative
+ * @return a {@code Seconds} based on this instance with the requested amount added, not null
+ * @throws ArithmeticException if the result overflows an int
+ */
+ public Seconds plus(int seconds) {
+ if (seconds == 0) {
+ return this;
+ }
+ return of(Math.addExact(this.seconds, seconds));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this amount with the specified amount subtracted.
+ *
+ * The parameter is converted using {@link Seconds#from(TemporalAmount)}.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToSubtract the amount to subtract, not null
+ * @return a {@code Seconds} based on this instance with the requested amount subtracted, not null
+ * @throws DateTimeException if the specified amount contains an invalid unit
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ public Seconds minus(TemporalAmount amountToSubtract) {
+ return minus(Seconds.from(amountToSubtract).getAmount());
+ }
+
+ /**
+ * Returns a copy of this amount with the specified number of seconds subtracted.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param seconds the amount of seconds to add, may be negative
+ * @return a {@code Seconds} based on this instance with the requested amount subtracted, not null
+ * @throws ArithmeticException if the result overflows an int
+ */
+ public Seconds minus(int seconds) {
+ if (seconds == 0) {
+ return this;
+ }
+ return of(Math.subtractExact(this.seconds, seconds));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns an instance with the amount multiplied by the specified scalar.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param scalar the scalar to multiply by, not null
+ * @return the amount multiplied by the specified scalar, not null
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ public Seconds multipliedBy(int scalar) {
+ if (scalar == 1) {
+ return this;
+ }
+ return of(Math.multiplyExact(seconds, scalar));
+ }
+
+ /**
+ * Returns an instance with the amount divided by the specified divisor.
+ *
+ * The calculation uses integer division, thus 3 divided by 2 is 1.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param divisor the amount to divide by, may be negative
+ * @return the amount divided by the specified divisor, not null
+ * @throws ArithmeticException if the divisor is zero
+ */
+ public Seconds dividedBy(int divisor) {
+ if (divisor == 1) {
+ return this;
+ }
+ return of(seconds / divisor);
+ }
+
+ /**
+ * Returns an instance with the amount negated.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @return the negated amount, not null
+ * @throws ArithmeticException if numeric overflow occurs, which only happens if
+ * the amount is {@code Long.MIN_VALUE}
+ */
+ public Seconds negated() {
+ return multipliedBy(-1);
+ }
+
+ /**
+ * Returns a copy of this duration with a positive length.
+ *
+ * This method returns a positive duration by effectively removing the sign from any negative total length.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @return the absolute amount, not null
+ * @throws ArithmeticException if numeric overflow occurs, which only happens if
+ * the amount is {@code Long.MIN_VALUE}
+ */
+ public Seconds abs() {
+ return seconds < 0 ? negated() : this;
+ }
+
+ //-------------------------------------------------------------------------
+ /**
+ * Gets the number of seconds as a {@code Duration}.
+ *
+ * This returns a duration with the same number of seconds.
+ *
+ * @return the equivalent duration, not null
+ */
+ public Duration toDuration() {
+ return Duration.ofSeconds(seconds);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Adds this amount to the specified temporal object.
+ *
+ * This returns a temporal object of the same observable type as the input
+ * with this amount added.
+ *
+ * In most cases, it is clearer to reverse the calling pattern by using
+ * {@link Temporal#plus(TemporalAmount)}.
+ *
+ * // these two lines are equivalent, but the second approach is recommended
+ * dateTime = thisAmount.addTo(dateTime);
+ * dateTime = dateTime.plus(thisAmount);
+ *
+ *
+ * Only non-zero amounts will be added.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param temporal the temporal object to adjust, not null
+ * @return an object of the same type with the adjustment made, not null
+ * @throws DateTimeException if unable to add
+ * @throws UnsupportedTemporalTypeException if the SECONDS unit is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public Temporal addTo(Temporal temporal) {
+ if (seconds != 0) {
+ temporal = temporal.plus(seconds, SECONDS);
+ }
+ return temporal;
+ }
+
+ /**
+ * Subtracts this amount from the specified temporal object.
+ *
+ * This returns a temporal object of the same observable type as the input
+ * with this amount subtracted.
+ *
+ * In most cases, it is clearer to reverse the calling pattern by using
+ * {@link Temporal#minus(TemporalAmount)}.
+ *
+ * // these two lines are equivalent, but the second approach is recommended
+ * dateTime = thisAmount.subtractFrom(dateTime);
+ * dateTime = dateTime.minus(thisAmount);
+ *
+ *
+ * Only non-zero amounts will be subtracted.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param temporal the temporal object to adjust, not null
+ * @return an object of the same type with the adjustment made, not null
+ * @throws DateTimeException if unable to subtract
+ * @throws UnsupportedTemporalTypeException if the SECONDS unit is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public Temporal subtractFrom(Temporal temporal) {
+ if (seconds != 0) {
+ temporal = temporal.minus(seconds, SECONDS);
+ }
+ return temporal;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this amount to the specified {@code Seconds}.
+ *
+ * The comparison is based on the total length of the amounts.
+ * It is "consistent with equals", as defined by {@link Comparable}.
+ *
+ * @param otherAmount the other amount, not null
+ * @return the comparator value, negative if less, positive if greater
+ */
+ @Override
+ public int compareTo(Seconds otherAmount) {
+ int thisValue = this.seconds;
+ int otherValue = otherAmount.seconds;
+ return Integer.compare(thisValue, otherValue);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Checks if this amount is equal to the specified {@code Seconds}.
+ *
+ * The comparison is based on the total length of the durations.
+ *
+ * @param otherAmount the other amount, null returns false
+ * @return true if the other amount is equal to this one
+ */
+ @Override
+ public boolean equals(Object otherAmount) {
+ if (this == otherAmount) {
+ return true;
+ }
+ if (otherAmount instanceof Seconds) {
+ Seconds other = (Seconds) otherAmount;
+ return this.seconds == other.seconds;
+ }
+ return false;
+ }
+
+ /**
+ * A hash code for this amount.
+ *
+ * @return a suitable hash code
+ */
+ @Override
+ public int hashCode() {
+ return seconds;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a string representation of the number of seconds.
+ * This will be in the format 'PTnS' where n is the number of seconds.
+ *
+ * @return the number of seconds in ISO-8601 string format
+ */
+ @Override
+ @ToString
+ public String toString() {
+ return "PT" + seconds + "S";
+ }
+
+}
diff --git a/src/main/java/org/threeten/extra/TemporalFields.java b/src/main/java/org/threeten/extra/TemporalFields.java
new file mode 100644
index 00000000..d2d207ef
--- /dev/null
+++ b/src/main/java/org/threeten/extra/TemporalFields.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of JSR-310 nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.threeten.extra;
+
+import static java.time.temporal.ChronoField.DAY_OF_YEAR;
+import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
+import static java.time.temporal.ChronoField.YEAR;
+import static java.time.temporal.ChronoUnit.DAYS;
+import static java.time.temporal.ChronoUnit.YEARS;
+import static java.time.temporal.IsoFields.QUARTER_OF_YEAR;
+import static java.time.temporal.IsoFields.QUARTER_YEARS;
+
+import java.time.DateTimeException;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.chrono.ChronoLocalDate;
+import java.time.chrono.IsoChronology;
+import java.time.format.ResolverStyle;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalField;
+import java.time.temporal.TemporalUnit;
+import java.time.temporal.UnsupportedTemporalTypeException;
+import java.time.temporal.ValueRange;
+import java.util.Map;
+
+/**
+ * Additional Temporal fields.
+ *
+ * This provides additional fields and units not in the JDK.
+ */
+public final class TemporalFields {
+
+ /**
+ * The field that represents the day-of-half.
+ *
+ * This field allows the day-of-half value to be queried and set.
+ * The day-of-half has values from 1 to 181 in H1 of a standard year, from 1 to 182
+ * in H1 of a leap year and from 1 to 184 in H2.
+ *
+ * The day-of-half can only be calculated if the day-of-year, month-of-year and year
+ * are available.
+ *
+ * When setting this field, the value is allowed to be partially lenient, taking any
+ * value from 1 to 184. If the half has less than 184 days, then the day will end up
+ * in the following half-year.
+ *
+ * In the resolving phase of parsing, a date can be created from a year,
+ * half-of-year and day-of-half.
+ *
+ * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are
+ * validated against their range of valid values. The day-of-half field
+ * is validated from 1 to 181, 182 or 184 depending on the year and half.
+ *
+ * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are
+ * validated against their range of valid values. The day-of-half field is
+ * validated between 1 and 184, ignoring the actual range based on the year and half.
+ * If the day-of-half exceeds the actual range, then the resulting date is in the next half-year.
+ *
+ * In {@linkplain ResolverStyle#LENIENT lenient mode}, only the year is validated
+ * against the range of valid values. The resulting date is calculated equivalent to
+ * the following three stage approach. First, create a date on the first of January
+ * in the requested year. Then take the half-of-year, subtract one, and add the
+ * amount in halves to the date. Finally, take the day-of-half, subtract one,
+ * and add the amount in days to the date.
+ *
+ * This unit is an immutable and thread-safe singleton.
+ */
+ public static final TemporalField DAY_OF_HALF = DayOfHalfField.INSTANCE;
+ /**
+ * The field that represents the half-of-year.
+ *
+ * This field allows the half-of-year value to be queried and set.
+ * The half-of-year has values from 1 to 2.
+ *
+ * The half-of-year can only be calculated if the month-of-year is available.
+ *
+ * In the resolving phase of parsing, a date can be created from a year,
+ * half-of-year and day-of-half.
+ * See {@link #DAY_OF_HALF} for details.
+ *
+ * This unit is an immutable and thread-safe singleton.
+ */
+ public static final TemporalField HALF_OF_YEAR = HalfOfYearField.INSTANCE;
+ /**
+ * Unit that represents the concept of a half-year.
+ * For the ISO calendar system, it is equal to 6 months.
+ * The estimated duration of a half-year is one half of {@code 365.2425 Days}.
+ *
+ * This unit is an immutable and thread-safe singleton.
+ */
+ public static final TemporalUnit HALF_YEARS = HalfUnit.INSTANCE;
+
+ /**
+ * Restricted constructor.
+ */
+ private TemporalFields() {
+ }
+
+ //-------------------------------------------------------------------------
+ /**
+ * Implementation of day-of-half.
+ */
+ private static enum DayOfHalfField implements TemporalField {
+ INSTANCE;
+
+ private static final ValueRange RANGE = ValueRange.of(1, 181, 184);
+ private static final long serialVersionUID = 262362728L;
+
+ //-----------------------------------------------------------------------
+ @Override
+ public TemporalUnit getBaseUnit() {
+ return DAYS;
+ }
+
+ @Override
+ public TemporalUnit getRangeUnit() {
+ return HALF_YEARS;
+ }
+
+ @Override
+ public boolean isDateBased() {
+ return true;
+ }
+
+ @Override
+ public boolean isTimeBased() {
+ return false;
+ }
+
+ @Override
+ public ValueRange range() {
+ return RANGE;
+ }
+
+ //-----------------------------------------------------------------------
+ @Override
+ public boolean isSupportedBy(TemporalAccessor temporal) {
+ return temporal.isSupported(DAY_OF_YEAR) &&
+ temporal.isSupported(MONTH_OF_YEAR) &&
+ temporal.isSupported(YEAR);
+ }
+
+ @Override
+ public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
+ if (!temporal.isSupported(this)) {
+ throw new DateTimeException("Unsupported field: DayOfHalf");
+ }
+ long hoy = temporal.getLong(HALF_OF_YEAR);
+ if (hoy == 1) {
+ long year = temporal.getLong(YEAR);
+ return (IsoChronology.INSTANCE.isLeapYear(year) ? ValueRange.of(1, 182) : ValueRange.of(1, 181));
+ } else if (hoy == 2) {
+ return ValueRange.of(1, 184);
+ } // else value not from 1 to 2, so drop through
+ return range();
+ }
+
+ @Override
+ public long getFrom(TemporalAccessor temporal) {
+ if (isSupportedBy(temporal) == false) {
+ throw new UnsupportedTemporalTypeException("Unsupported field: DayOfHalf");
+ }
+ int doy = temporal.get(DAY_OF_YEAR);
+ int moy = temporal.get(MONTH_OF_YEAR);
+ long year = temporal.getLong(YEAR);
+ return moy <= 6 ? doy : doy - 181 - (IsoChronology.INSTANCE.isLeapYear(year) ? 1 : 0);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public R adjustInto(R temporal, long newValue) {
+ // calls getFrom() to check if supported
+ long curValue = getFrom(temporal);
+ range().checkValidValue(newValue, this); // leniently check from 1 to 184
+ return (R) temporal.with(DAY_OF_YEAR, temporal.getLong(DAY_OF_YEAR) + (newValue - curValue));
+ }
+
+ //-----------------------------------------------------------------------
+ @Override
+ public ChronoLocalDate resolve(
+ Map fieldValues,
+ TemporalAccessor partialTemporal,
+ ResolverStyle resolverStyle) {
+
+ Long yearLong = fieldValues.get(YEAR);
+ Long hoyLong = fieldValues.get(HALF_OF_YEAR);
+ if (yearLong == null || hoyLong == null) {
+ return null;
+ }
+ int y = YEAR.checkValidIntValue(yearLong); // always validate
+ long doh = fieldValues.get(DAY_OF_HALF);
+ LocalDate date;
+ if (resolverStyle == ResolverStyle.LENIENT) {
+ date = LocalDate.of(y, 1, 1).plusMonths(Math.multiplyExact(Math.subtractExact(hoyLong, 1), 6));
+ doh = Math.subtractExact(doh, 1);
+ } else {
+ int qoy = HALF_OF_YEAR.range().checkValidIntValue(hoyLong, HALF_OF_YEAR); // validated
+ date = LocalDate.of(y, ((qoy - 1) * 6) + 1, 1);
+ if (doh < 1 || doh > 181) {
+ if (resolverStyle == ResolverStyle.STRICT) {
+ rangeRefinedBy(date).checkValidValue(doh, this); // only allow exact range
+ } else { // SMART
+ range().checkValidValue(doh, this); // allow 1-184 rolling into next quarter
+ }
+ }
+ doh--;
+ }
+ fieldValues.remove(this);
+ fieldValues.remove(YEAR);
+ fieldValues.remove(HALF_OF_YEAR);
+ return date.plusDays(doh);
+ }
+
+ //-----------------------------------------------------------------------
+ @Override
+ public String toString() {
+ return "DayOfHalf";
+ }
+ }
+
+ //-------------------------------------------------------------------------
+ /**
+ * Implementation of half-of-year.
+ */
+ private static enum HalfOfYearField implements TemporalField {
+ INSTANCE;
+
+ private static final long serialVersionUID = -29115701L;
+
+ //-----------------------------------------------------------------------
+ @Override
+ public TemporalUnit getBaseUnit() {
+ return HALF_YEARS;
+ }
+
+ @Override
+ public TemporalUnit getRangeUnit() {
+ return YEARS;
+ }
+
+ @Override
+ public boolean isDateBased() {
+ return true;
+ }
+
+ @Override
+ public boolean isTimeBased() {
+ return false;
+ }
+
+ @Override
+ public ValueRange range() {
+ return ValueRange.of(1, 2);
+ }
+
+ @Override
+ public boolean isSupportedBy(TemporalAccessor temporal) {
+ return temporal.isSupported(QUARTER_OF_YEAR);
+ }
+
+ @Override
+ public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
+ return range();
+ }
+
+ @Override
+ public long getFrom(TemporalAccessor temporal) {
+ if (isSupportedBy(temporal) == false) {
+ throw new UnsupportedTemporalTypeException("Unsupported field: HalfOfYear");
+ }
+ long qoy = temporal.get(QUARTER_OF_YEAR);
+ return qoy <= 2 ? 1 : 2;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public R adjustInto(R temporal, long newValue) {
+ // calls getFrom() to check if supported
+ long curValue = getFrom(temporal);
+ range().checkValidValue(newValue, this); // strictly check from 1 to 2
+ return (R) temporal.with(MONTH_OF_YEAR, temporal.getLong(MONTH_OF_YEAR) + (newValue - curValue) * 6);
+ }
+
+ @Override
+ public String toString() {
+ return "HalfOfYear";
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Implementation of the half-year unit.
+ */
+ private static enum HalfUnit implements TemporalUnit {
+
+ /**
+ * Unit that represents the concept of a week-based-year.
+ */
+ INSTANCE;
+
+ @Override
+ public Duration getDuration() {
+ return Duration.ofSeconds(31556952L / 2);
+ }
+
+ @Override
+ public boolean isDurationEstimated() {
+ return true;
+ }
+
+ @Override
+ public boolean isDateBased() {
+ return true;
+ }
+
+ @Override
+ public boolean isTimeBased() {
+ return false;
+ }
+
+ @Override
+ public boolean isSupportedBy(Temporal temporal) {
+ return temporal.isSupported(QUARTER_OF_YEAR);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public R addTo(R temporal, long amount) {
+ return (R) temporal.plus(Math.multiplyExact(amount, 2), QUARTER_YEARS);
+ }
+
+ @Override
+ public long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive) {
+ if (temporal1Inclusive.getClass() != temporal2Exclusive.getClass()) {
+ return temporal1Inclusive.until(temporal2Exclusive, this);
+ }
+ return temporal1Inclusive.until(temporal2Exclusive, QUARTER_YEARS) / 2;
+ }
+
+ @Override
+ public String toString() {
+ return "HalfYears";
+ }
+ }
+
+}
diff --git a/src/main/java/org/threeten/extra/Temporals.java b/src/main/java/org/threeten/extra/Temporals.java
index 797ea8c2..0b856bc5 100644
--- a/src/main/java/org/threeten/extra/Temporals.java
+++ b/src/main/java/org/threeten/extra/Temporals.java
@@ -37,8 +37,12 @@
import static java.time.temporal.ChronoUnit.FOREVER;
import static java.time.temporal.ChronoUnit.WEEKS;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.RoundingMode;
import java.text.ParsePosition;
import java.time.DateTimeException;
+import java.time.Duration;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
@@ -87,6 +91,19 @@ public static TemporalAdjuster nextWorkingDay() {
return Adjuster.NEXT_WORKING;
}
+ /**
+ * Returns an adjuster that returns the next working day or same day if already working day, ignoring Saturday and Sunday.
+ *
+ * Some territories have weekends that do not consist of Saturday and Sunday.
+ * No implementation is supplied to support this, however an adjuster
+ * can be easily written to do so.
+ *
+ * @return the next working day or same adjuster, not null
+ */
+ public static TemporalAdjuster nextWorkingDayOrSame() {
+ return Adjuster.NEXT_WORKING_OR_SAME;
+ }
+
/**
* Returns an adjuster that returns the previous working day, ignoring Saturday and Sunday.
*
@@ -100,6 +117,19 @@ public static TemporalAdjuster previousWorkingDay() {
return Adjuster.PREVIOUS_WORKING;
}
+ /**
+ * Returns an adjuster that returns the previous working day or same day if already working day, ignoring Saturday and Sunday.
+ *
+ * Some territories have weekends that do not consist of Saturday and Sunday.
+ * No implementation is supplied to support this, however an adjuster
+ * can be easily written to do so.
+ *
+ * @return the previous working day or same adjuster, not null
+ */
+ public static TemporalAdjuster previousWorkingDayOrSame() {
+ return Adjuster.PREVIOUS_WORKING_OR_SAME;
+ }
+
//-----------------------------------------------------------------------
/**
* Enum implementing the adjusters.
@@ -135,6 +165,36 @@ public Temporal adjustInto(Temporal temporal) {
}
}
},
+ /** Next working day or same adjuster. */
+ NEXT_WORKING_OR_SAME {
+ @Override
+ public Temporal adjustInto(Temporal temporal) {
+ int dow = temporal.get(DAY_OF_WEEK);
+ switch (dow) {
+ case 6: // Saturday
+ return temporal.plus(2, DAYS);
+ case 7: // Sunday
+ return temporal.plus(1, DAYS);
+ default:
+ return temporal;
+ }
+ }
+ },
+ /** Previous working day or same adjuster. */
+ PREVIOUS_WORKING_OR_SAME {
+ @Override
+ public Temporal adjustInto(Temporal temporal) {
+ int dow = temporal.get(DAY_OF_WEEK);
+ switch (dow) {
+ case 6: //Saturday
+ return temporal.minus(1, DAYS);
+ case 7: // Sunday
+ return temporal.minus(2, DAYS);
+ default:
+ return temporal;
+ }
+ }
+ }
}
//-------------------------------------------------------------------------
@@ -330,4 +390,91 @@ private static int monthMonthFactor(TemporalUnit unit, TemporalUnit fromUnit, Te
return 3; // quarters
}
+ //-------------------------------------------------------------------------
+ /**
+ * Converts a duration to a {@code BigDecimal} with a scale of 9.
+ *
+ * @param duration the duration to convert, not null
+ * @return the {@code BigDecimal} equivalent of the duration, in seconds with a scale of 9
+ */
+ public static BigDecimal durationToBigDecimalSeconds(Duration duration) {
+ return BigDecimal.valueOf(duration.getSeconds()).add(BigDecimal.valueOf(duration.getNano(), 9));
+ }
+
+ /**
+ * Converts a {@code BigDecimal} representing seconds to a duration, saturating if necessary.
+ *
+ * No exception is thrown by this method.
+ * Numbers are rounded up to the nearest nanosecond (away from zero).
+ * The duration will saturate at the biggest positive or negative {@code Duration}.
+ *
+ * @param seconds the number of seconds to convert, positive or negative
+ * @return a {@code Duration}, not null
+ */
+ public static Duration durationFromBigDecimalSeconds(BigDecimal seconds) {
+ BigInteger nanos = seconds.setScale(9, RoundingMode.UP).max(BigDecimalSeconds.MIN).min(BigDecimalSeconds.MAX).unscaledValue();
+ BigInteger[] secondsNanos = nanos.divideAndRemainder(BigInteger.valueOf(1_000_000_000));
+ return Duration.ofSeconds(secondsNanos[0].longValue(), secondsNanos[1].intValue());
+ }
+
+ /**
+ * Converts a duration to a {@code double}.
+ *
+ * @param duration the duration to convert, not null
+ * @return the {@code double} equivalent of the duration, in seconds
+ */
+ public static double durationToDoubleSeconds(Duration duration) {
+ if (duration.getSeconds() < 1_000_000_000) {
+ return duration.toNanos() / 1_000_000_000d;
+ }
+ return durationToBigDecimalSeconds(duration).doubleValue();
+ }
+
+ /**
+ * Converts a {@code double} representing seconds to a duration, saturating if necessary.
+ *
+ * No exception is thrown by this method.
+ * Numbers are rounded up to the nearest nanosecond (away from zero).
+ * The duration will saturate at the biggest positive or negative {@code Duration}.
+ *
+ * @param seconds the number of seconds to convert, positive or negative
+ * @return a {@code Duration}, not null
+ */
+ public static Duration durationFromDoubleSeconds(double seconds) {
+ return durationFromBigDecimalSeconds(BigDecimal.valueOf(seconds));
+ }
+
+ /**
+ * Multiplies a duration by a {@code double}.
+ *
+ * The amount is rounded away from zero, thus the result is only zero if zero is passed in.
+ * See {@link #durationToBigDecimalSeconds(Duration)} and {@link #durationFromBigDecimalSeconds(BigDecimal)}.
+ * Note that due to the rounding up, 1 nanosecond multiplied by any number smaller than 1 will still be 1 nanosecond.
+ *
+ * @param duration the duration to multiply, not null
+ * @param multiplicand the multiplication factor
+ * @return the multiplied duration, not null
+ */
+ public static Duration multiply(Duration duration, double multiplicand) {
+ if (multiplicand == 0d || duration.isZero()) {
+ return Duration.ZERO;
+ }
+ if (multiplicand == 1d) {
+ return duration;
+ }
+ BigDecimal amount = durationToBigDecimalSeconds(duration);
+ amount = amount.multiply(BigDecimal.valueOf(multiplicand));
+ return durationFromBigDecimalSeconds(amount);
+ }
+
+ /**
+ * Useful Duration constants expressed as BigDecimal seconds with a scale of 9.
+ */
+ private static final class BigDecimalSeconds {
+ public static final BigDecimal MIN = BigDecimal.valueOf(Long.MIN_VALUE).add(BigDecimal.valueOf(0, 9));
+ public static final BigDecimal MAX = BigDecimal.valueOf(Long.MAX_VALUE).add(BigDecimal.valueOf(999_999_999, 9));
+
+ private BigDecimalSeconds() {
+ }
+ }
}
diff --git a/src/main/java/org/threeten/extra/Weeks.java b/src/main/java/org/threeten/extra/Weeks.java
index 261a93c7..659abadf 100644
--- a/src/main/java/org/threeten/extra/Weeks.java
+++ b/src/main/java/org/threeten/extra/Weeks.java
@@ -48,6 +48,9 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.joda.convert.FromString;
+import org.joda.convert.ToString;
+
/**
* A week-based amount of time, such as '12 weeks'.
*
@@ -173,11 +176,12 @@ public static Weeks from(TemporalAmount amount) {
* @return the parsed period, not null
* @throws DateTimeParseException if the text cannot be parsed to a period
*/
+ @FromString
public static Weeks parse(CharSequence text) {
Objects.requireNonNull(text, "text");
Matcher matcher = PATTERN.matcher(text);
if (matcher.matches()) {
- int negate = ("-".equals(matcher.group(1)) ? -1 : 1);
+ int negate = "-".equals(matcher.group(1)) ? -1 : 1;
String str = matcher.group(2);
try {
int val = Integer.parseInt(str);
@@ -237,7 +241,7 @@ private Object readResolve() {
*/
@Override
public long get(TemporalUnit unit) {
- if (unit == ChronoUnit.WEEKS) {
+ if (unit == WEEKS) {
return weeks;
}
throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
@@ -268,6 +272,33 @@ public int getAmount() {
return weeks;
}
+ /**
+ * Checks if the amount is negative.
+ *
+ * @return true if the amount is negative, false if the amount is zero or positive
+ */
+ public boolean isNegative() {
+ return getAmount() < 0;
+ }
+
+ /**
+ * Checks if the amount is zero.
+ *
+ * @return true if the amount is zero, false if not
+ */
+ public boolean isZero() {
+ return getAmount() == 0;
+ }
+
+ /**
+ * Checks if the amount is positive.
+ *
+ * @return true if the amount is positive, false if the amount is zero or negative
+ */
+ public boolean isPositive() {
+ return getAmount() > 0;
+ }
+
//-----------------------------------------------------------------------
/**
* Returns a copy of this amount with the specified amount added.
@@ -309,13 +340,13 @@ public Weeks plus(int weeks) {
*
* This instance is immutable and unaffected by this method call.
*
- * @param amountToAdd the amount to add, not null
+ * @param amountToSubtract the amount to subtract, not null
* @return a {@code Weeks} based on this instance with the requested amount subtracted, not null
* @throws DateTimeException if the specified amount contains an invalid unit
* @throws ArithmeticException if numeric overflow occurs
*/
- public Weeks minus(TemporalAmount amountToAdd) {
- return minus(Weeks.from(amountToAdd).getAmount());
+ public Weeks minus(TemporalAmount amountToSubtract) {
+ return minus(Weeks.from(amountToSubtract).getAmount());
}
/**
@@ -530,6 +561,7 @@ public int hashCode() {
* @return the number of weeks in ISO-8601 string format
*/
@Override
+ @ToString
public String toString() {
return "P" + weeks + "W";
}
diff --git a/src/main/java/org/threeten/extra/YearHalf.java b/src/main/java/org/threeten/extra/YearHalf.java
new file mode 100644
index 00000000..49436413
--- /dev/null
+++ b/src/main/java/org/threeten/extra/YearHalf.java
@@ -0,0 +1,1288 @@
+/*
+ * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of JSR-310 nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.threeten.extra;
+
+import static java.time.temporal.ChronoField.ERA;
+import static java.time.temporal.ChronoField.YEAR;
+import static java.time.temporal.ChronoField.YEAR_OF_ERA;
+import static java.time.temporal.ChronoUnit.CENTURIES;
+import static java.time.temporal.ChronoUnit.DECADES;
+import static java.time.temporal.ChronoUnit.ERAS;
+import static java.time.temporal.ChronoUnit.MILLENNIA;
+import static java.time.temporal.ChronoUnit.YEARS;
+import static org.threeten.extra.TemporalFields.DAY_OF_HALF;
+import static org.threeten.extra.TemporalFields.HALF_OF_YEAR;
+import static org.threeten.extra.TemporalFields.HALF_YEARS;
+
+import java.io.Serializable;
+import java.time.Clock;
+import java.time.DateTimeException;
+import java.time.LocalDate;
+import java.time.Month;
+import java.time.Period;
+import java.time.Year;
+import java.time.ZoneId;
+import java.time.chrono.Chronology;
+import java.time.chrono.IsoChronology;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.DateTimeParseException;
+import java.time.format.SignStyle;
+import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalAdjuster;
+import java.time.temporal.TemporalAmount;
+import java.time.temporal.TemporalField;
+import java.time.temporal.TemporalQueries;
+import java.time.temporal.TemporalQuery;
+import java.time.temporal.TemporalUnit;
+import java.time.temporal.UnsupportedTemporalTypeException;
+import java.time.temporal.ValueRange;
+import java.util.Objects;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+
+import org.joda.convert.FromString;
+import org.joda.convert.ToString;
+
+/**
+ * A year-half in the ISO-8601 calendar system, such as {@code 2007-H2}.
+ *
+ * {@code YearHalf} is an immutable date-time object that represents the combination
+ * of a year and a half-year. Any field that can be derived from a year and a half-year can be obtained.
+ * A half is defined by {@link Half} - H1 and H2.
+ * H1 is January to June, H2 is July to December.
+ *
+ * This class does not store or represent a day, time or time-zone.
+ * For example, the value "2nd half 2007" can be stored in a {@code YearHalf}.
+ *
+ * The ISO-8601 calendar system is the modern civil calendar system used today
+ * in most of the world. It is equivalent to the proleptic Gregorian calendar
+ * system, in which today's rules for leap years are applied for all time.
+ * For most applications written today, the ISO-8601 rules are entirely suitable.
+ * However, any application that makes use of historical dates, and requires them
+ * to be accurate will find the ISO-8601 approach unsuitable.
+ * Note that the ISO-8601 standard does not define or refer to halves.
+ *
+ *
Implementation Requirements:
+ * This class is immutable and thread-safe.
+ *
+ * This class must be treated as a value type. Do not synchronize, rely on the
+ * identity hash code or use the distinction between equals() and ==.
+ */
+public final class YearHalf
+ implements Temporal, TemporalAdjuster, Comparable, Serializable {
+
+ /**
+ * Serialization version.
+ */
+ private static final long serialVersionUID = 782467825761518L;
+ /**
+ * Parser.
+ */
+ private static final DateTimeFormatter PARSER = new DateTimeFormatterBuilder()
+ .parseCaseInsensitive()
+ .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
+ .appendLiteral('-')
+ .appendLiteral('H')
+ .appendValue(HALF_OF_YEAR, 1)
+ .toFormatter();
+
+ /**
+ * The year.
+ */
+ private final int year;
+ /**
+ * The half-of-year, not null.
+ */
+ private final Half half;
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains the current year-half from the system clock in the default time-zone.
+ *
+ * This will query the {@link java.time.Clock#systemDefaultZone() system clock} in the default
+ * time-zone to obtain the current year-half.
+ * The zone and offset will be set based on the time-zone in the clock.
+ *
+ * Using this method will prevent the ability to use an alternate clock for testing
+ * because the clock is hard-coded.
+ *
+ * @return the current year-half using the system clock and default time-zone, not null
+ */
+ public static YearHalf now() {
+ return now(Clock.systemDefaultZone());
+ }
+
+ /**
+ * Obtains the current year-half from the system clock in the specified time-zone.
+ *
+ * This will query the {@link Clock#system(java.time.ZoneId) system clock} to obtain the current year-half.
+ * Specifying the time-zone avoids dependence on the default time-zone.
+ *
+ * Using this method will prevent the ability to use an alternate clock for testing
+ * because the clock is hard-coded.
+ *
+ * @param zone the zone ID to use, not null
+ * @return the current year-half using the system clock, not null
+ */
+ public static YearHalf now(ZoneId zone) {
+ return now(Clock.system(zone));
+ }
+
+ /**
+ * Obtains the current year-half from the specified clock.
+ *
+ * This will query the specified clock to obtain the current year-half.
+ * Using this method allows the use of an alternate clock for testing.
+ * The alternate clock may be introduced using {@link Clock dependency injection}.
+ *
+ * @param clock the clock to use, not null
+ * @return the current year-half, not null
+ */
+ public static YearHalf now(Clock clock) {
+ final LocalDate now = LocalDate.now(clock); // called once
+ return YearHalf.of(now.getYear(), Half.from(now.getMonth()));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains an instance of {@code YearHalf} from a year and half.
+ *
+ * @param year the year to represent, not null
+ * @param half the half-of-year to represent, not null
+ * @return the year-half, not null
+ */
+ public static YearHalf of(Year year, Half half) {
+ return of(year.getValue(), half);
+ }
+
+ /**
+ * Obtains an instance of {@code YearHalf} from a year and half.
+ *
+ * @param year the year to represent, not null
+ * @param half the half-of-year to represent, from 1 to 2
+ * @return the year-half, not null
+ * @throws DateTimeException if the half value is invalid
+ */
+ public static YearHalf of(Year year, int half) {
+ return of(year.getValue(), Half.of(half));
+ }
+
+ /**
+ * Obtains an instance of {@code YearHalf} from a year and half.
+ *
+ * @param year the year to represent, from MIN_YEAR to MAX_YEAR
+ * @param half the half-of-year to represent, not null
+ * @return the year-half, not null
+ * @throws DateTimeException if the year value is invalid
+ */
+ public static YearHalf of(int year, Half half) {
+ YEAR.checkValidValue(year);
+ Objects.requireNonNull(half, "half");
+ return new YearHalf(year, half);
+ }
+
+ /**
+ * Obtains an instance of {@code YearHalf} from a year and half.
+ *
+ * @param year the year to represent, from MIN_YEAR to MAX_YEAR
+ * @param half the half-of-year to represent, from 1 to 2
+ * @return the year-half, not null
+ * @throws DateTimeException if either field value is invalid
+ */
+ public static YearHalf of(int year, int half) {
+ YEAR.checkValidValue(year);
+ return new YearHalf(year, Half.of(half));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains an instance of {@code YearHalf} from a temporal object.
+ *
+ * This obtains a year-half based on the specified temporal.
+ * A {@code TemporalAccessor} represents an arbitrary set of date and time information,
+ * which this factory converts to an instance of {@code YearHalf}.
+ *
+ * The conversion extracts the {@link ChronoField#YEAR YEAR} and
+ * {@link TemporalFields#HALF_OF_YEAR HALF_OF_YEAR} fields.
+ * The extraction is only permitted if the temporal object has an ISO
+ * chronology, or can be converted to a {@code LocalDate}.
+ *
+ * This method matches the signature of the functional interface {@link TemporalQuery}
+ * allowing it to be used in queries via method reference, {@code YearHalf::from}.
+ *
+ * @param temporal the temporal object to convert, not null
+ * @return the year-half, not null
+ * @throws DateTimeException if unable to convert to a {@code YearHalf}
+ */
+ public static YearHalf from(TemporalAccessor temporal) {
+ if (temporal instanceof YearHalf) {
+ return (YearHalf) temporal;
+ }
+ Objects.requireNonNull(temporal, "temporal");
+ try {
+ TemporalAccessor adjusted =
+ !IsoChronology.INSTANCE.equals(Chronology.from(temporal)) ? LocalDate.from(temporal) : temporal;
+ // need to use getLong() as JDK Parsed class get() doesn't work properly
+ int year = Math.toIntExact(adjusted.getLong(YEAR));
+ int hoy = Math.toIntExact(adjusted.getLong(HALF_OF_YEAR));
+ return of(year, hoy);
+ } catch (DateTimeException ex) {
+ throw new DateTimeException("Unable to obtain YearHalf from TemporalAccessor: " +
+ temporal + " of type " + temporal.getClass().getName(), ex);
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Obtains an instance of {@code YearHalf} from a text string such as {@code 2007-H2}.
+ *
+ * The string must represent a valid year-half.
+ * The format must be {@code uuuu-'Q'Q} where the 'Q' is case insensitive.
+ * Years outside the range 0000 to 9999 must be prefixed by the plus or minus symbol.
+ *
+ * @param text the text to parse such as "2007-H2", not null
+ * @return the parsed year-half, not null
+ * @throws DateTimeParseException if the text cannot be parsed
+ */
+ @FromString
+ public static YearHalf parse(CharSequence text) {
+ return parse(text, PARSER);
+ }
+
+ /**
+ * Obtains an instance of {@code YearHalf} from a text string using a specific formatter.
+ *
+ * The text is parsed using the formatter, returning a year-half.
+ *
+ * @param text the text to parse, not null
+ * @param formatter the formatter to use, not null
+ * @return the parsed year-half, not null
+ * @throws DateTimeParseException if the text cannot be parsed
+ */
+ public static YearHalf parse(CharSequence text, DateTimeFormatter formatter) {
+ Objects.requireNonNull(formatter, "formatter");
+ return formatter.parse(text, YearHalf::from);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Constructor.
+ *
+ * @param year the year to represent, validated from MIN_YEAR to MAX_YEAR
+ * @param half the half-of-year to represent, validated not null
+ */
+ private YearHalf(int year, Half half) {
+ this.year = year;
+ this.half = half;
+ }
+
+ /**
+ * Validates the input.
+ *
+ * @return the valid object, not null
+ */
+ private Object readResolve() {
+ return of(year, half);
+ }
+
+ /**
+ * Returns a copy of this year-half with the new year and half, checking
+ * to see if a new object is in fact required.
+ *
+ * @param newYear the year to represent, validated from MIN_YEAR to MAX_YEAR
+ * @param newHalf the half-of-year to represent, validated not null
+ * @return the year-half, not null
+ */
+ private YearHalf with(int newYear, Half newHalf) {
+ if (year == newYear && half == newHalf) {
+ return this;
+ }
+ return new YearHalf(newYear, newHalf);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Checks if the specified field is supported.
+ *
+ * This checks if this year-half can be queried for the specified field.
+ * If false, then calling the {@link #range(TemporalField) range},
+ * {@link #get(TemporalField) get} and {@link #with(TemporalField, long)}
+ * methods will throw an exception.
+ *
+ * If the field is a {@link ChronoField} then the query is implemented here.
+ * The supported fields are:
+ *
+ *
{@code HALF_OF_YEAR}
+ *
{@code YEAR_OF_ERA}
+ *
{@code YEAR}
+ *
{@code ERA}
+ *
+ * All other {@code ChronoField} instances will return false.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.isSupportedBy(TemporalAccessor)}
+ * passing {@code this} as the argument.
+ * Whether the field is supported is determined by the field.
+ *
+ * @param field the field to check, null returns false
+ * @return true if the field is supported on this year-half, false if not
+ */
+ @Override
+ public boolean isSupported(TemporalField field) {
+ if (field == HALF_OF_YEAR) {
+ return true;
+ } else if (field instanceof ChronoField) {
+ return field == YEAR || field == YEAR_OF_ERA || field == ERA;
+ }
+ return field != null && field.isSupportedBy(this);
+ }
+
+ /**
+ * Checks if the specified unit is supported.
+ *
+ * This checks if the specified unit can be added to, or subtracted from, this year-half.
+ * If false, then calling the {@link #plus(long, TemporalUnit)} and
+ * {@link #minus(long, TemporalUnit) minus} methods will throw an exception.
+ *
+ * If the unit is a {@link ChronoUnit} then the query is implemented here.
+ * The supported units are:
+ *
+ *
{@code HALF_YEARS}
+ *
{@code YEARS}
+ *
{@code DECADES}
+ *
{@code CENTURIES}
+ *
{@code MILLENNIA}
+ *
{@code ERAS}
+ *
+ * All other {@code ChronoUnit} instances will return false.
+ *
+ * If the unit is not a {@code ChronoUnit}, then the result of this method
+ * is obtained by invoking {@code TemporalUnit.isSupportedBy(Temporal)}
+ * passing {@code this} as the argument.
+ * Whether the unit is supported is determined by the unit.
+ *
+ * @param unit the unit to check, null returns false
+ * @return true if the unit can be added/subtracted, false if not
+ */
+ @Override
+ public boolean isSupported(TemporalUnit unit) {
+ if (unit == HALF_YEARS) {
+ return true;
+ } else if (unit instanceof ChronoUnit) {
+ return unit == YEARS || unit == DECADES || unit == CENTURIES || unit == MILLENNIA || unit == ERAS;
+ }
+ return unit != null && unit.isSupportedBy(this);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the range of valid values for the specified field.
+ *
+ * The range object expresses the minimum and maximum valid values for a field.
+ * This year-half is used to enhance the accuracy of the returned range.
+ * If it is not possible to return the range, because the field is not supported
+ * or for some other reason, an exception is thrown.
+ *
+ * If the field is a {@link ChronoField} then the query is implemented here.
+ * The {@link #isSupported(TemporalField) supported fields} will return
+ * appropriate range instances.
+ * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)}
+ * passing {@code this} as the argument.
+ * Whether the range can be obtained is determined by the field.
+ *
+ * @param field the field to query the range for, not null
+ * @return the range of valid values for the field, not null
+ * @throws DateTimeException if the range for the field cannot be obtained
+ * @throws UnsupportedTemporalTypeException if the field is not supported
+ */
+ @Override
+ public ValueRange range(TemporalField field) {
+ if (field == HALF_OF_YEAR) {
+ return HALF_OF_YEAR.range();
+ }
+ if (field == YEAR_OF_ERA) {
+ return (getYear() <= 0 ? ValueRange.of(1, Year.MAX_VALUE + 1) : ValueRange.of(1, Year.MAX_VALUE));
+ }
+ return Temporal.super.range(field);
+ }
+
+ /**
+ * Gets the value of the specified field from this year-half as an {@code int}.
+ *
+ * This queries this year-half for the value for the specified field.
+ * The returned value will always be within the valid range of values for the field.
+ * If it is not possible to return the value, because the field is not supported
+ * or for some other reason, an exception is thrown.
+ *
+ * If the field is a {@link ChronoField} then the query is implemented here.
+ * The {@link #isSupported(TemporalField) supported fields} will return valid
+ * values based on this year-half,.
+ * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}
+ * passing {@code this} as the argument. Whether the value can be obtained,
+ * and what the value represents, is determined by the field.
+ *
+ * @param field the field to get, not null
+ * @return the value for the field
+ * @throws DateTimeException if a value for the field cannot be obtained or
+ * the value is outside the range of valid values for the field
+ * @throws UnsupportedTemporalTypeException if the field is not supported or
+ * the range of values exceeds an {@code int}
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public int get(TemporalField field) {
+ if (field == HALF_OF_YEAR) {
+ return half.getValue();
+ } else if (field instanceof ChronoField) {
+ switch ((ChronoField) field) {
+ case YEAR_OF_ERA:
+ return (year < 1 ? 1 - year : year);
+ case YEAR:
+ return year;
+ case ERA:
+ return (year < 1 ? 0 : 1);
+ default:
+ throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
+ }
+ }
+ return Temporal.super.get(field);
+ }
+
+ /**
+ * Gets the value of the specified field from this year-half as a {@code long}.
+ *
+ * This queries this year-half for the value for the specified field.
+ * If it is not possible to return the value, because the field is not supported
+ * or for some other reason, an exception is thrown.
+ *
+ * If the field is a {@link ChronoField} then the query is implemented here.
+ * The {@link #isSupported(TemporalField) supported fields} will return valid
+ * values based on this year-half.
+ * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}
+ * passing {@code this} as the argument. Whether the value can be obtained,
+ * and what the value represents, is determined by the field.
+ *
+ * @param field the field to get, not null
+ * @return the value for the field
+ * @throws DateTimeException if a value for the field cannot be obtained
+ * @throws UnsupportedTemporalTypeException if the field is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public long getLong(TemporalField field) {
+ if (field == HALF_OF_YEAR) {
+ return half.getValue();
+ } else if (field instanceof ChronoField) {
+ switch ((ChronoField) field) {
+ case YEAR_OF_ERA:
+ return (year < 1 ? 1 - year : year);
+ case YEAR:
+ return year;
+ case ERA:
+ return (year < 1 ? 0 : 1);
+ default:
+ throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
+ }
+ }
+ return field.getFrom(this);
+ }
+
+ private long getProlepticHalf() {
+ return year * 2L + (half.getValue() - 1);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the year field.
+ *
+ * This method returns the primitive {@code int} value for the year.
+ *
+ * The year returned by this method is proleptic as per {@code get(YEAR)}.
+ *
+ * @return the year, from MIN_YEAR to MAX_YEAR
+ */
+ public int getYear() {
+ return year;
+ }
+
+ /**
+ * Gets the half-of-year field from 1 to 2.
+ *
+ * This method returns the half as an {@code int} from 1 to 2.
+ * Application code is frequently clearer if the enum {@link Half}
+ * is used by calling {@link #getHalf()}.
+ *
+ * @return the half-of-year, from 1 to 2
+ * @see #getHalf()
+ */
+ public int getHalfValue() {
+ return half.getValue();
+ }
+
+ /**
+ * Gets the half-of-year field using the {@code Half} enum.
+ *
+ * This method returns the enum {@link Half} for the half.
+ * This avoids confusion as to what {@code int} values mean.
+ * If you need access to the primitive {@code int} value then the enum
+ * provides the {@link Half#getValue() int value}.
+ *
+ * @return the half-of-year, not null
+ * @see #getHalfValue()
+ */
+ public Half getHalf() {
+ return half;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Checks if the year is a leap year, according to the ISO proleptic
+ * calendar system rules.
+ *
+ * This method applies the current rules for leap years across the whole time-line.
+ * In general, a year is a leap year if it is divisible by four without
+ * remainder. However, years divisible by 100, are not leap years, with
+ * the exception of years divisible by 400 which are.
+ *
+ * For example, 1904 is a leap year it is divisible by 4.
+ * 1900 was not a leap year as it is divisible by 100, however 2000 was a
+ * leap year as it is divisible by 400.
+ *
+ * The calculation is proleptic - applying the same rules into the far future and far past.
+ * This is historically inaccurate, but is correct for the ISO-8601 standard.
+ *
+ * @return true if the year is leap, false otherwise
+ */
+ public boolean isLeapYear() {
+ return IsoChronology.INSTANCE.isLeapYear(year);
+ }
+
+ /**
+ * Checks if the day-of-half is valid for this year-half.
+ *
+ * This method checks whether this year and half and the input day form
+ * a valid date.
+ *
+ * @param dayOfHalf the day-of-half to validate, from 1 to 181, 182 or 184, invalid value returns false
+ * @return true if the day is valid for this year-half
+ */
+ public boolean isValidDay(int dayOfHalf) {
+ return dayOfHalf >= 1 && dayOfHalf <= lengthOfHalf();
+ }
+
+ /**
+ * Returns the length of the half, taking account of the year.
+ *
+ * This returns the length of the half in days.
+ *
+ * @return the length of the half in days, 181, 182 or 184
+ */
+ public int lengthOfHalf() {
+ return half.length(isLeapYear());
+ }
+
+ /**
+ * Returns the length of the year.
+ *
+ * This returns the length of the year in days, either 365 or 366.
+ *
+ * @return 366 if the year is leap, 365 otherwise
+ */
+ public int lengthOfYear() {
+ return (isLeapYear() ? 366 : 365);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns an adjusted copy of this year-half.
+ *
+ * This returns a {@code YearHalf} based on this one, with the year-half adjusted.
+ * The adjustment takes place using the specified adjuster strategy object.
+ * Read the documentation of the adjuster to understand what adjustment will be made.
+ *
+ * A simple adjuster might simply set the one of the fields, such as the year field.
+ * A more complex adjuster might set the year-half to the next half that
+ * Halley's comet will pass the Earth.
+ *
+ * The result of this method is obtained by invoking the
+ * {@link TemporalAdjuster#adjustInto(Temporal)} method on the
+ * specified adjuster passing {@code this} as the argument.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param adjuster the adjuster to use, not null
+ * @return a {@code YearHalf} based on {@code this} with the adjustment made, not null
+ * @throws DateTimeException if the adjustment cannot be made
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public YearHalf with(TemporalAdjuster adjuster) {
+ return (YearHalf) adjuster.adjustInto(this);
+ }
+
+ /**
+ * Returns a copy of this year-half with the specified field set to a new value.
+ *
+ * This returns a {@code YearHalf} based on this one, with the value
+ * for the specified field changed.
+ * This can be used to change any supported field, such as the year or half.
+ * If it is not possible to set the value, because the field is not supported or for
+ * some other reason, an exception is thrown.
+ *
+ * If the field is a {@link ChronoField} then the adjustment is implemented here.
+ * The supported fields behave as follows:
+ *
+ *
{@code HALF_OF_YEAR} -
+ * Returns a {@code YearHalf} with the specified half-of-year.
+ * The year will be unchanged.
+ *
{@code YEAR_OF_ERA} -
+ * Returns a {@code YearHalf} with the specified year-of-era
+ * The half and era will be unchanged.
+ *
{@code YEAR} -
+ * Returns a {@code YearHalf} with the specified year.
+ * The half will be unchanged.
+ *
{@code ERA} -
+ * Returns a {@code YearHalf} with the specified era.
+ * The half and year-of-era will be unchanged.
+ *
+ *
+ * In all cases, if the new value is outside the valid range of values for the field
+ * then a {@code DateTimeException} will be thrown.
+ *
+ * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)}
+ * passing {@code this} as the argument. In this case, the field determines
+ * whether and how to adjust the instant.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param field the field to set in the result, not null
+ * @param newValue the new value of the field in the result
+ * @return a {@code YearHalf} based on {@code this} with the specified field set, not null
+ * @throws DateTimeException if the field cannot be set
+ * @throws UnsupportedTemporalTypeException if the field is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public YearHalf with(TemporalField field, long newValue) {
+ if (field == HALF_OF_YEAR) {
+ return withHalf(HALF_OF_YEAR.range().checkValidIntValue(newValue, HALF_OF_YEAR));
+ } else if (field instanceof ChronoField) {
+ ChronoField f = (ChronoField) field;
+ f.checkValidValue(newValue);
+ switch (f) {
+ case YEAR_OF_ERA:
+ return withYear((int) (year < 1 ? 1 - newValue : newValue));
+ case YEAR:
+ return withYear((int) newValue);
+ case ERA:
+ return (getLong(ERA) == newValue ? this : withYear(1 - year));
+ default:
+ throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
+ }
+ }
+ return field.adjustInto(this, newValue);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this {@code YearHalf} with the year altered.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param year the year to set in the returned year-half, from MIN_YEAR to MAX_YEAR
+ * @return a {@code YearHalf} based on this year-half with the requested year, not null
+ * @throws DateTimeException if the year value is invalid
+ */
+ public YearHalf withYear(int year) {
+ YEAR.checkValidValue(year);
+ return with(year, half);
+ }
+
+ /**
+ * Returns a copy of this {@code YearHalf} with the half-of-year altered.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param half the half-of-year to set in the returned year-half, from 1 to 2
+ * @return a {@code YearHalf} based on this year-half with the requested half, not null
+ * @throws DateTimeException if the half-of-year value is invalid
+ */
+ public YearHalf withHalf(int half) {
+ HALF_OF_YEAR.range().checkValidValue(half, HALF_OF_YEAR);
+ return with(year, Half.of(half));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this year-half with the specified amount added.
+ *
+ * This returns a {@code YearHalf} based on this one, with the specified amount added.
+ * The amount is typically {@link Period} but may be any other type implementing
+ * the {@link TemporalAmount} interface.
+ *
+ * The calculation is delegated to the amount object by calling
+ * {@link TemporalAmount#addTo(Temporal)}. The amount implementation is free
+ * to implement the addition in any way it wishes, however it typically
+ * calls back to {@link #plus(long, TemporalUnit)}. Consult the documentation
+ * of the amount implementation to determine if it can be successfully added.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToAdd the amount to add, not null
+ * @return a {@code YearHalf} based on this year-half with the addition made, not null
+ * @throws DateTimeException if the addition cannot be made
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public YearHalf plus(TemporalAmount amountToAdd) {
+ return (YearHalf) amountToAdd.addTo(this);
+ }
+
+ /**
+ * Returns a copy of this year-half with the specified amount added.
+ *
+ * This returns a {@code YearHalf} based on this one, with the amount
+ * in terms of the unit added. If it is not possible to add the amount, because the
+ * unit is not supported or for some other reason, an exception is thrown.
+ *
+ * If the field is a {@link ChronoUnit} then the addition is implemented here.
+ * The supported fields behave as follows:
+ *
+ *
{@code HALF_YEARS} -
+ * Returns a {@code YearHalf} with the specified number of halves added.
+ * This is equivalent to {@link #plusHalves(long)}.
+ *
{@code YEARS} -
+ * Returns a {@code YearHalf} with the specified number of years added.
+ * This is equivalent to {@link #plusYears(long)}.
+ *
{@code DECADES} -
+ * Returns a {@code YearHalf} with the specified number of decades added.
+ * This is equivalent to calling {@link #plusYears(long)} with the amount
+ * multiplied by 10.
+ *
{@code CENTURIES} -
+ * Returns a {@code YearHalf} with the specified number of centuries added.
+ * This is equivalent to calling {@link #plusYears(long)} with the amount
+ * multiplied by 100.
+ *
{@code MILLENNIA} -
+ * Returns a {@code YearHalf} with the specified number of millennia added.
+ * This is equivalent to calling {@link #plusYears(long)} with the amount
+ * multiplied by 1,000.
+ *
{@code ERAS} -
+ * Returns a {@code YearHalf} with the specified number of eras added.
+ * Only two eras are supported so the amount must be one, zero or minus one.
+ * If the amount is non-zero then the year is changed such that the year-of-era
+ * is unchanged.
+ *
+ *
+ * All other {@code ChronoUnit} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoUnit}, then the result of this method
+ * is obtained by invoking {@code TemporalUnit.addTo(Temporal, long)}
+ * passing {@code this} as the argument. In this case, the unit determines
+ * whether and how to perform the addition.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToAdd the amount of the unit to add to the result, may be negative
+ * @param unit the unit of the amount to add, not null
+ * @return a {@code YearHalf} based on this year-half with the specified amount added, not null
+ * @throws DateTimeException if the addition cannot be made
+ * @throws UnsupportedTemporalTypeException if the unit is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public YearHalf plus(long amountToAdd, TemporalUnit unit) {
+ if (unit == HALF_YEARS) {
+ return plusHalves(amountToAdd);
+ } else if (unit instanceof ChronoUnit) {
+ switch ((ChronoUnit) unit) {
+ case YEARS:
+ return plusYears(amountToAdd);
+ case DECADES:
+ return plusYears(Math.multiplyExact(amountToAdd, 10));
+ case CENTURIES:
+ return plusYears(Math.multiplyExact(amountToAdd, 100));
+ case MILLENNIA:
+ return plusYears(Math.multiplyExact(amountToAdd, 1000));
+ case ERAS:
+ return with(ERA, Math.addExact(getLong(ERA), amountToAdd));
+ default:
+ throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
+ }
+ }
+ return unit.addTo(this, amountToAdd);
+ }
+
+ /**
+ * Returns a copy of this year-half with the specified period in years added.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param yearsToAdd the years to add, may be negative
+ * @return a {@code YearHalf} based on this year-half with the years added, not null
+ * @throws DateTimeException if the result exceeds the supported range
+ */
+ public YearHalf plusYears(long yearsToAdd) {
+ if (yearsToAdd == 0) {
+ return this;
+ }
+ int newYear = YEAR.checkValidIntValue(year + yearsToAdd); // safe overflow
+ return with(newYear, half);
+ }
+
+ /**
+ * Returns a copy of this year-half with the specified period in halves added.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param halvesToAdd the halves to add, may be negative
+ * @return a {@code YearHalf} based on this year-half with the halves added, not null
+ * @throws DateTimeException if the result exceeds the supported range
+ */
+ public YearHalf plusHalves(long halvesToAdd) {
+ if (halvesToAdd == 0) {
+ return this;
+ }
+ long halfCount = year * 2L + (half.getValue() - 1);
+ long calcHalves = halfCount + halvesToAdd; // safe overflow
+ int newYear = YEAR.checkValidIntValue(Math.floorDiv(calcHalves, 2));
+ int newHalf = (int) Math.floorMod(calcHalves, 2L) + 1;
+ return with(newYear, Half.of(newHalf));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this year-half with the specified amount subtracted.
+ *
+ * This returns a {@code YearHalf} based on this one, with the specified amount subtracted.
+ * The amount is typically {@link Period} but may be any other type implementing
+ * the {@link TemporalAmount} interface.
+ *
+ * The calculation is delegated to the amount object by calling
+ * {@link TemporalAmount#subtractFrom(Temporal)}. The amount implementation is free
+ * to implement the subtraction in any way it wishes, however it typically
+ * calls back to {@link #minus(long, TemporalUnit)}. Consult the documentation
+ * of the amount implementation to determine if it can be successfully subtracted.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToSubtract the amount to subtract, not null
+ * @return a {@code YearHalf} based on this year-half with the subtraction made, not null
+ * @throws DateTimeException if the subtraction cannot be made
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public YearHalf minus(TemporalAmount amountToSubtract) {
+ return (YearHalf) amountToSubtract.subtractFrom(this);
+ }
+
+ /**
+ * Returns a copy of this year-half with the specified amount subtracted.
+ *
+ * This returns a {@code YearHalf} based on this one, with the amount
+ * in terms of the unit subtracted. If it is not possible to subtract the amount,
+ * because the unit is not supported or for some other reason, an exception is thrown.
+ *
+ * This method is equivalent to {@link #plus(long, TemporalUnit)} with the amount negated.
+ * See that method for a full description of how addition, and thus subtraction, works.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToSubtract the amount of the unit to subtract from the result, may be negative
+ * @param unit the unit of the amount to subtract, not null
+ * @return a {@code YearHalf} based on this year-half with the specified amount subtracted, not null
+ * @throws DateTimeException if the subtraction cannot be made
+ * @throws UnsupportedTemporalTypeException if the unit is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public YearHalf minus(long amountToSubtract, TemporalUnit unit) {
+ return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit));
+ }
+
+ /**
+ * Returns a copy of this year-half with the specified period in years subtracted.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param yearsToSubtract the years to subtract, may be negative
+ * @return a {@code YearHalf} based on this year-half with the years subtracted, not null
+ * @throws DateTimeException if the result exceeds the supported range
+ */
+ public YearHalf minusYears(long yearsToSubtract) {
+ return (yearsToSubtract == Long.MIN_VALUE ? plusYears(Long.MAX_VALUE).plusYears(1) : plusYears(-yearsToSubtract));
+ }
+
+ /**
+ * Returns a copy of this year-half with the specified period in halves subtracted.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param halvesToSubtract the halves to subtract, may be negative
+ * @return a {@code YearHalf} based on this year-half with the halves subtracted, not null
+ * @throws DateTimeException if the result exceeds the supported range
+ */
+ public YearHalf minusHalves(long halvesToSubtract) {
+ return (halvesToSubtract == Long.MIN_VALUE ? plusHalves(Long.MAX_VALUE).plusHalves(1) : plusHalves(-halvesToSubtract));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Queries this year-half using the specified query.
+ *
+ * This queries this year-half using the specified query strategy object.
+ * The {@code TemporalQuery} object defines the logic to be used to
+ * obtain the result. Read the documentation of the query to understand
+ * what the result of this method will be.
+ *
+ * The result of this method is obtained by invoking the
+ * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the
+ * specified query passing {@code this} as the argument.
+ *
+ * @param the type of the result
+ * @param query the query to invoke, not null
+ * @return the query result, null may be returned (defined by the query)
+ * @throws DateTimeException if unable to query (defined by the query)
+ * @throws ArithmeticException if numeric overflow occurs (defined by the query)
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public R query(TemporalQuery query) {
+ if (query == TemporalQueries.chronology()) {
+ return (R) IsoChronology.INSTANCE;
+ } else if (query == TemporalQueries.precision()) {
+ return (R) HALF_YEARS;
+ }
+ return Temporal.super.query(query);
+ }
+
+ /**
+ * Adjusts the specified temporal object to have this year-half.
+ *
+ * This returns a temporal object of the same observable type as the input
+ * with the year and half changed to be the same as this.
+ *
+ * The adjustment is equivalent to using {@link Temporal#plus(long, TemporalUnit)}
+ * passing the number of halves to adjust by.
+ * If the specified temporal object does not use the ISO calendar system then
+ * a {@code DateTimeException} is thrown.
+ *
+ * In most cases, it is clearer to reverse the calling pattern by using
+ * {@link Temporal#with(TemporalAdjuster)}:
+ *
+ * // these two lines are equivalent, but the second approach is recommended
+ * temporal = thisYearHalf.adjustInto(temporal);
+ * temporal = temporal.with(thisYearHalf);
+ *
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param temporal the target object to be adjusted, not null
+ * @return the adjusted object, not null
+ * @throws DateTimeException if unable to make the adjustment
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public Temporal adjustInto(Temporal temporal) {
+ if (Chronology.from(temporal).equals(IsoChronology.INSTANCE) == false) {
+ throw new DateTimeException("Adjustment only supported on ISO date-time");
+ }
+ long newProlepticHalf = getProlepticHalf();
+ long oldProlepticHalf = temporal.get(YEAR) * 2L + (temporal.get(HALF_OF_YEAR) - 1);
+ return temporal.plus(newProlepticHalf - oldProlepticHalf, HALF_YEARS);
+ }
+
+ /**
+ * Calculates the amount of time until another year-half in terms of the specified unit.
+ *
+ * This calculates the amount of time between two {@code YearHalf}
+ * objects in terms of a single {@code TemporalUnit}.
+ * The start and end points are {@code this} and the specified year-half.
+ * The result will be negative if the end is before the start.
+ * The {@code Temporal} passed to this method is converted to a
+ * {@code YearHalf} using {@link #from(TemporalAccessor)}.
+ * For example, the period in years between two year-halves can be calculated
+ * using {@code startYearHalf.until(endYearHalf, YEARS)}.
+ *
+ * The calculation returns a whole number, representing the number of
+ * complete units between the two year-halves.
+ * For example, the period in decades between 2012-H2 and 2032-H1
+ * will only be one decade as it is one half short of two decades.
+ *
+ * There are two equivalent ways of using this method.
+ * The first is to invoke this method.
+ * The second is to use {@link TemporalUnit#between(Temporal, Temporal)}:
+ *
+ * // these two lines are equivalent
+ * amount = start.until(end, HALF_YEARS);
+ * amount = HALF_YEARS.between(start, end);
+ *
+ * The choice should be made based on which makes the code more readable.
+ *
+ * The calculation is implemented in this method for {@link ChronoUnit}.
+ * The units {@code HALF_YEARS}, {@code YEARS}, {@code DECADES},
+ * {@code CENTURIES}, {@code MILLENNIA} and {@code ERAS} are supported.
+ * Other {@code ChronoUnit} values will throw an exception.
+ *
+ * If the unit is not a {@code ChronoUnit}, then the result of this method
+ * is obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)}
+ * passing {@code this} as the first argument and the converted input temporal
+ * as the second argument.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param endExclusive the end date, exclusive, which is converted to a {@code YearHalf}, not null
+ * @param unit the unit to measure the amount in, not null
+ * @return the amount of time between this year-half and the end year-half
+ * @throws DateTimeException if the amount cannot be calculated, or the end
+ * temporal cannot be converted to a {@code YearHalf}
+ * @throws UnsupportedTemporalTypeException if the unit is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public long until(Temporal endExclusive, TemporalUnit unit) {
+ YearHalf end = YearHalf.from(endExclusive);
+ long halvesUntil = end.getProlepticHalf() - getProlepticHalf(); // no overflow
+ if (unit == HALF_YEARS) {
+ return halvesUntil;
+ } else if (unit instanceof ChronoUnit) {
+ switch ((ChronoUnit) unit) {
+ case YEARS:
+ return halvesUntil / 2;
+ case DECADES:
+ return halvesUntil / 20;
+ case CENTURIES:
+ return halvesUntil / 200;
+ case MILLENNIA:
+ return halvesUntil / 2000;
+ case ERAS:
+ return end.getLong(ERA) - getLong(ERA);
+ default:
+ throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
+ }
+ }
+ return unit.between(this, end);
+ }
+
+ /**
+ * Returns a sequential ordered stream of year-half. The returned stream starts from this year-half
+ * (inclusive) and goes to {@code endExclusive} (exclusive) by an incremental step of 1 {@code HALF_YEARS}.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param endExclusive the end year-half, exclusive, not null
+ * @return a sequential {@code Stream} for the range of {@code YearHalf} values
+ * @throws IllegalArgumentException if end year-half is before this year-half
+ */
+ public Stream halvesUntil(YearHalf endExclusive) {
+ if (endExclusive.isBefore(this)) {
+ throw new IllegalArgumentException(endExclusive + " < " + this);
+ }
+ long intervalLength = until(endExclusive, HALF_YEARS);
+ return LongStream.range(0, intervalLength).mapToObj(n -> plusHalves(n));
+ }
+
+ /**
+ * Formats this year-half using the specified formatter.
+ *
+ * This year-half will be passed to the formatter to produce a string.
+ *
+ * @param formatter the formatter to use, not null
+ * @return the formatted year-half string, not null
+ * @throws DateTimeException if an error occurs during printing
+ */
+ public String format(DateTimeFormatter formatter) {
+ Objects.requireNonNull(formatter, "formatter");
+ return formatter.format(this);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Combines this year-half with a day-of-half to create a {@code LocalDate}.
+ *
+ * This returns a {@code LocalDate} formed from this year-half and the specified day-of-half.
+ *
+ * The day-of-half value must be valid for the year-half.
+ *
+ * This method can be used as part of a chain to produce a date:
+ *
+ * LocalDate date = yearHalf.atDay(day);
+ *
+ *
+ * @param dayOfHalf the day-of-half to use, from 1 to 184
+ * @return the date formed from this year-half and the specified day, not null
+ * @throws DateTimeException if the day is invalid for the year-half
+ * @see #isValidDay(int)
+ */
+ public LocalDate atDay(int dayOfHalf) {
+ ValueRange.of(1, lengthOfHalf()).checkValidValue(dayOfHalf, DAY_OF_HALF);
+ boolean leap = Year.isLeap(year);
+ Month month = half.firstMonth();
+ int dom = dayOfHalf;
+ while (dom > month.length(leap)) {
+ dom -= month.length(leap);
+ month = month.plus(1);
+ }
+ return LocalDate.of(year, month, dom);
+ }
+
+ /**
+ * Returns a {@code LocalDate} at the end of the half.
+ *
+ * This returns a {@code LocalDate} based on this year-half.
+ * The day-of-half is set to the last valid day of the half, taking
+ * into account leap years.
+ *
+ * This method can be used as part of a chain to produce a date:
+ *
+ * LocalDate date = year.atHalf(half).atEndOfHalf();
+ *
+ *
+ * @return the last valid date of this year-half, not null
+ */
+ public LocalDate atEndOfHalf() {
+ Month month = half.firstMonth().plus(5);
+ return LocalDate.of(year, month, month.maxLength());
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Compares this year-half to another
+ *
+ * The comparison is based first on the value of the year, then on the value of the half.
+ * It is "consistent with equals", as defined by {@link Comparable}.
+ *
+ * @param other the other year-half to compare to, not null
+ * @return the comparator value, negative if less, positive if greater
+ */
+ @Override
+ public int compareTo(YearHalf other) {
+ int cmp = (year - other.year);
+ if (cmp == 0) {
+ cmp = half.compareTo(other.half);
+ }
+ return cmp;
+ }
+
+ /**
+ * Is this year-half after the specified year-half.
+ *
+ * @param other the other year-half to compare to, not null
+ * @return true if this is after the specified year-half
+ */
+ public boolean isAfter(YearHalf other) {
+ return compareTo(other) > 0;
+ }
+
+ /**
+ * Is this year-half before the specified year-half.
+ *
+ * @param other the other year-half to compare to, not null
+ * @return true if this point is before the specified year-half
+ */
+ public boolean isBefore(YearHalf other) {
+ return compareTo(other) < 0;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Checks if this year-half is equal to another year-half.
+ *
+ * The comparison is based on the time-line position of the year-halves.
+ *
+ * @param obj the object to check, null returns false
+ * @return true if this is equal to the other year-half
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof YearHalf) {
+ YearHalf other = (YearHalf) obj;
+ return year == other.year && half == other.half;
+ }
+ return false;
+ }
+
+ /**
+ * A hash code for this year-half.
+ *
+ * @return a suitable hash code
+ */
+ @Override
+ public int hashCode() {
+ return year ^ (half.getValue() << 28);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Outputs this year-half as a {@code String}, such as {@code 2007-H2}.
+ *
+ * @return a string representation of this year-half, not null
+ */
+ @Override
+ @ToString
+ public String toString() {
+ int absYear = Math.abs(year);
+ StringBuilder buf = new StringBuilder(10);
+ if (absYear < 1000) {
+ if (year < 0) {
+ buf.append(year - 10000).deleteCharAt(1);
+ } else {
+ buf.append(year + 10000).deleteCharAt(0);
+ }
+ } else {
+ if (year > 9999) {
+ buf.append('+');
+ }
+ buf.append(year);
+ }
+ return buf.append('-').append(half).toString();
+ }
+
+}
diff --git a/src/main/java/org/threeten/extra/YearQuarter.java b/src/main/java/org/threeten/extra/YearQuarter.java
index d7ff44ba..5d455abc 100644
--- a/src/main/java/org/threeten/extra/YearQuarter.java
+++ b/src/main/java/org/threeten/extra/YearQuarter.java
@@ -71,6 +71,11 @@
import java.time.temporal.UnsupportedTemporalTypeException;
import java.time.temporal.ValueRange;
import java.util.Objects;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+
+import org.joda.convert.FromString;
+import org.joda.convert.ToString;
/**
* A year-quarter in the ISO-8601 calendar system, such as {@code 2007-Q2}.
@@ -173,6 +178,29 @@ public static YearQuarter now(Clock clock) {
}
//-----------------------------------------------------------------------
+ /**
+ * Obtains an instance of {@code YearQuarter} from a year and quarter.
+ *
+ * @param year the year to represent, not null
+ * @param quarter the quarter-of-year to represent, not null
+ * @return the year-quarter, not null
+ */
+ public static YearQuarter of(Year year, Quarter quarter) {
+ return of(year.getValue(), quarter);
+ }
+
+ /**
+ * Obtains an instance of {@code YearQuarter} from a year and quarter.
+ *
+ * @param year the year to represent, not null
+ * @param quarter the quarter-of-year to represent, from 1 to 4
+ * @return the year-quarter, not null
+ * @throws DateTimeException if the quarter value is invalid
+ */
+ public static YearQuarter of(Year year, int quarter) {
+ return of(year.getValue(), Quarter.of(quarter));
+ }
+
/**
* Obtains an instance of {@code YearQuarter} from a year and quarter.
*
@@ -226,10 +254,12 @@ public static YearQuarter from(TemporalAccessor temporal) {
}
Objects.requireNonNull(temporal, "temporal");
try {
- if (IsoChronology.INSTANCE.equals(Chronology.from(temporal)) == false) {
- temporal = LocalDate.from(temporal);
- }
- return of(temporal.get(YEAR), temporal.get(QUARTER_OF_YEAR));
+ TemporalAccessor adjusted =
+ !IsoChronology.INSTANCE.equals(Chronology.from(temporal)) ? LocalDate.from(temporal) : temporal;
+ // need to use getLong() as JDK Parsed class get() doesn't work properly
+ int year = Math.toIntExact(adjusted.getLong(YEAR));
+ int qoy = Math.toIntExact(adjusted.getLong(QUARTER_OF_YEAR));
+ return of(year, qoy);
} catch (DateTimeException ex) {
throw new DateTimeException("Unable to obtain YearQuarter from TemporalAccessor: " +
temporal + " of type " + temporal.getClass().getName(), ex);
@@ -248,6 +278,7 @@ public static YearQuarter from(TemporalAccessor temporal) {
* @return the parsed year-quarter, not null
* @throws DateTimeParseException if the text cannot be parsed
*/
+ @FromString
public static YearQuarter parse(CharSequence text) {
return parse(text, PARSER);
}
@@ -343,7 +374,7 @@ public boolean isSupported(TemporalField field) {
/**
* Checks if the specified unit is supported.
*
- * This checks if the specified unit can be added to, or subtracted from, this date-time.
+ * This checks if the specified unit can be added to, or subtracted from, this year-quarter.
* If false, then calling the {@link #plus(long, TemporalUnit)} and
* {@link #minus(long, TemporalUnit) minus} methods will throw an exception.
*
@@ -403,6 +434,9 @@ public boolean isSupported(TemporalUnit unit) {
*/
@Override
public ValueRange range(TemporalField field) {
+ if (field == QUARTER_OF_YEAR) {
+ return QUARTER_OF_YEAR.range();
+ }
if (field == YEAR_OF_ERA) {
return (getYear() <= 0 ? ValueRange.of(1, Year.MAX_VALUE + 1) : ValueRange.of(1, Year.MAX_VALUE));
}
@@ -437,6 +471,20 @@ public ValueRange range(TemporalField field) {
*/
@Override
public int get(TemporalField field) {
+ if (field == QUARTER_OF_YEAR) {
+ return quarter.getValue();
+ } else if (field instanceof ChronoField) {
+ switch ((ChronoField) field) {
+ case YEAR_OF_ERA:
+ return (year < 1 ? 1 - year : year);
+ case YEAR:
+ return year;
+ case ERA:
+ return (year < 1 ? 0 : 1);
+ default:
+ throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
+ }
+ }
return Temporal.super.get(field);
}
@@ -591,7 +639,7 @@ public int lengthOfYear() {
/**
* Returns an adjusted copy of this year-quarter.
*
- * This returns a {@code YearQuarter}, based on this one, with the year-quarter adjusted.
+ * This returns a {@code YearQuarter} based on this one, with the year-quarter adjusted.
* The adjustment takes place using the specified adjuster strategy object.
* Read the documentation of the adjuster to understand what adjustment will be made.
*
@@ -618,7 +666,7 @@ public YearQuarter with(TemporalAdjuster adjuster) {
/**
* Returns a copy of this year-quarter with the specified field set to a new value.
*
- * This returns a {@code YearQuarter}, based on this one, with the value
+ * This returns a {@code YearQuarter} based on this one, with the value
* for the specified field changed.
* This can be used to change any supported field, such as the year or quarter.
* If it is not possible to set the value, because the field is not supported or for
@@ -714,7 +762,7 @@ public YearQuarter withQuarter(int quarter) {
/**
* Returns a copy of this year-quarter with the specified amount added.
*
- * This returns a {@code YearQuarter}, based on this one, with the specified amount added.
+ * This returns a {@code YearQuarter} based on this one, with the specified amount added.
* The amount is typically {@link Period} but may be any other type implementing
* the {@link TemporalAmount} interface.
*
@@ -739,7 +787,7 @@ public YearQuarter plus(TemporalAmount amountToAdd) {
/**
* Returns a copy of this year-quarter with the specified amount added.
*
- * This returns a {@code YearQuarter}, based on this one, with the amount
+ * This returns a {@code YearQuarter} based on this one, with the amount
* in terms of the unit added. If it is not possible to add the amount, because the
* unit is not supported or for some other reason, an exception is thrown.
*
@@ -843,7 +891,7 @@ public YearQuarter plusQuarters(long quartersToAdd) {
long quarterCount = year * 4L + (quarter.getValue() - 1);
long calcQuarters = quarterCount + quartersToAdd; // safe overflow
int newYear = YEAR.checkValidIntValue(Math.floorDiv(calcQuarters, 4));
- int newQuarter = (int) Math.floorMod(calcQuarters, 4) + 1;
+ int newQuarter = (int) Math.floorMod(calcQuarters, 4L) + 1;
return with(newYear, Quarter.of(newQuarter));
}
@@ -851,7 +899,7 @@ public YearQuarter plusQuarters(long quartersToAdd) {
/**
* Returns a copy of this year-quarter with the specified amount subtracted.
*
- * This returns a {@code YearQuarter}, based on this one, with the specified amount subtracted.
+ * This returns a {@code YearQuarter} based on this one, with the specified amount subtracted.
* The amount is typically {@link Period} but may be any other type implementing
* the {@link TemporalAmount} interface.
*
@@ -876,7 +924,7 @@ public YearQuarter minus(TemporalAmount amountToSubtract) {
/**
* Returns a copy of this year-quarter with the specified amount subtracted.
*
- * This returns a {@code YearQuarter}, based on this one, with the amount
+ * This returns a {@code YearQuarter} based on this one, with the amount
* in terms of the unit subtracted. If it is not possible to subtract the amount,
* because the unit is not supported or for some other reason, an exception is thrown.
*
@@ -1061,6 +1109,24 @@ public long until(Temporal endExclusive, TemporalUnit unit) {
return unit.between(this, end);
}
+ /**
+ * Returns a sequential ordered stream of year-quarter. The returned stream starts from this year-quarter
+ * (inclusive) and goes to {@code endExclusive} (exclusive) by an incremental step of 1 {@code QUARTER_YEARS}.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param endExclusive the end year-quarter, exclusive, not null
+ * @return a sequential {@code Stream} for the range of {@code YearQuarter} values
+ * @throws IllegalArgumentException if end year-quarter is before this year-quarter
+ */
+ public Stream quartersUntil(YearQuarter endExclusive) {
+ if (endExclusive.isBefore(this)) {
+ throw new IllegalArgumentException(endExclusive + " < " + this);
+ }
+ long intervalLength = until(endExclusive, QUARTER_YEARS);
+ return LongStream.range(0, intervalLength).mapToObj(n -> plusQuarters(n));
+ }
+
/**
* Formats this year-quarter using the specified formatter.
*
@@ -1097,11 +1163,12 @@ public LocalDate atDay(int dayOfQuarter) {
ValueRange.of(1, lengthOfQuarter()).checkValidValue(dayOfQuarter, DAY_OF_QUARTER);
boolean leap = Year.isLeap(year);
Month month = quarter.firstMonth();
- while (dayOfQuarter > month.length(leap)) {
- dayOfQuarter -= month.length(leap);
+ int dom = dayOfQuarter;
+ while (dom > month.length(leap)) {
+ dom -= month.length(leap);
month = month.plus(1);
}
- return LocalDate.of(year, month, dayOfQuarter);
+ return LocalDate.of(year, month, dom);
}
/**
@@ -1202,6 +1269,7 @@ public int hashCode() {
* @return a string representation of this year-quarter, not null
*/
@Override
+ @ToString
public String toString() {
int absYear = Math.abs(year);
StringBuilder buf = new StringBuilder(10);
diff --git a/src/main/java/org/threeten/extra/YearWeek.java b/src/main/java/org/threeten/extra/YearWeek.java
index 17cbae23..148212ae 100644
--- a/src/main/java/org/threeten/extra/YearWeek.java
+++ b/src/main/java/org/threeten/extra/YearWeek.java
@@ -31,9 +31,12 @@
*/
package org.threeten.extra;
+import static java.time.DayOfWeek.MONDAY;
import static java.time.DayOfWeek.THURSDAY;
import static java.time.DayOfWeek.WEDNESDAY;
+import static java.time.temporal.ChronoUnit.WEEKS;
import static java.time.temporal.IsoFields.WEEK_BASED_YEAR;
+import static java.time.temporal.IsoFields.WEEK_BASED_YEARS;
import static java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR;
import java.io.Serializable;
@@ -41,6 +44,7 @@
import java.time.DateTimeException;
import java.time.DayOfWeek;
import java.time.LocalDate;
+import java.time.Period;
import java.time.Year;
import java.time.ZoneId;
import java.time.chrono.Chronology;
@@ -50,17 +54,23 @@
import java.time.format.DateTimeParseException;
import java.time.format.SignStyle;
import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
import java.time.temporal.IsoFields;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAdjuster;
+import java.time.temporal.TemporalAmount;
import java.time.temporal.TemporalField;
import java.time.temporal.TemporalQueries;
import java.time.temporal.TemporalQuery;
+import java.time.temporal.TemporalUnit;
import java.time.temporal.UnsupportedTemporalTypeException;
import java.time.temporal.ValueRange;
import java.util.Objects;
+import org.joda.convert.FromString;
+import org.joda.convert.ToString;
+
/**
* A year-week in the ISO week date system such as {@code 2015-W13}
*
@@ -89,7 +99,7 @@
* identity hash code or use the distinction between equals() and ==.
*/
public final class YearWeek
- implements TemporalAccessor, TemporalAdjuster, Comparable, Serializable {
+ implements Temporal, TemporalAdjuster, Comparable, Serializable {
/**
* Serialization version.
@@ -164,6 +174,28 @@ public static YearWeek now(Clock clock) {
}
//-----------------------------------------------------------------------
+ /**
+ * Obtains an instance of {@code YearWeek} from a year and week.
+ *
+ * If the week is 53 and the year does not have 53 weeks, week one of the following
+ * year is selected.
+ *
+ * Note that this class is based on the week-based-year which aligns to Monday to Sunday weeks,
+ * whereas {@code Year} is intended to represent standard years aligned from January to December.
+ * This difference may be seen at the start and/or end of the year.
+ * This method treats the standard year as though it is the week-based-year.
+ * Thus, {@code YearWeek.of(Year.of(2020), 1)} creates an object where Monday and Tuesday of the week
+ * are actually the last two days of 2019.
+ *
+ * @param year the year to represent, not null
+ * @param week the week-of-week-based-year to represent, from 1 to 53
+ * @return the year-week, not null
+ * @throws DateTimeException if the week value is invalid
+ */
+ public static YearWeek of(Year year, int week) {
+ return of(year.getValue(), week);
+ }
+
/**
* Obtains an instance of {@code YearWeek} from a week-based-year and week.
*
@@ -189,7 +221,7 @@ public static YearWeek of(int weekBasedYear, int week) {
// from IsoFields in ThreeTen-Backport
private static int weekRange(int weekBasedYear) {
LocalDate date = LocalDate.of(weekBasedYear, 1, 1);
- // 53 weeks if standard year starts on Thursday, or Wed in a leap year
+ // 53 weeks if year starts on Thursday, or Wed in a leap year
if (date.getDayOfWeek() == THURSDAY || (date.getDayOfWeek() == WEDNESDAY && date.isLeapYear())) {
return 53;
}
@@ -222,10 +254,13 @@ public static YearWeek from(TemporalAccessor temporal) {
}
Objects.requireNonNull(temporal, "temporal");
try {
- if (IsoChronology.INSTANCE.equals(Chronology.from(temporal)) == false) {
+ if (!IsoChronology.INSTANCE.equals(Chronology.from(temporal))) {
temporal = LocalDate.from(temporal);
}
- return of(temporal.get(WEEK_BASED_YEAR), (int) temporal.getLong(WEEK_OF_WEEK_BASED_YEAR));
+ // need to use getLong() as JDK Parsed class get() doesn't work properly
+ int year = Math.toIntExact(temporal.getLong(WEEK_BASED_YEAR));
+ int week = Math.toIntExact(temporal.getLong(WEEK_OF_WEEK_BASED_YEAR));
+ return of(year, week);
} catch (DateTimeException ex) {
throw new DateTimeException("Unable to obtain YearWeek from TemporalAccessor: " +
temporal + " of type " + temporal.getClass().getName(), ex);
@@ -245,6 +280,7 @@ public static YearWeek from(TemporalAccessor temporal) {
* @return the parsed year-week, not null
* @throws DateTimeParseException if the text cannot be parsed
*/
+ @FromString
public static YearWeek parse(CharSequence text) {
return parse(text, PARSER);
}
@@ -308,13 +344,12 @@ private YearWeek with(int newYear, int newWeek) {
* If false, then calling the {@link #range(TemporalField) range} and
* {@link #get(TemporalField) get} methods will throw an exception.
*
- * If the field is a {@link ChronoField} then the query is implemented here.
* The supported fields are:
*
*
{@code WEEK_OF_WEEK_BASED_YEAR}
*
{@code WEEK_BASED_YEAR}
*
- * All {@code ChronoField} instances will return false.
+ * All other {@code ChronoField} instances will return false.
*
* If the field is not a {@code ChronoField}, then the result of this method
* is obtained by invoking {@code TemporalField.isSupportedBy(TemporalAccessor)}
@@ -334,6 +369,38 @@ public boolean isSupported(TemporalField field) {
return field != null && field.isSupportedBy(this);
}
+ /**
+ * Checks if the specified unit is supported.
+ *
+ * This checks if the specified unit can be added to, or subtracted from, this date-time.
+ * If false, then calling the {@link #plus(long, TemporalUnit)} and
+ * {@link #minus(long, TemporalUnit) minus} methods will throw an exception.
+ *
+ * The supported units are:
+ *
+ *
{@code WEEKS}
+ *
{@code WEEK_BASED_YEARS}
+ *
+ * All other {@code ChronoUnit} instances will return false.
+ *
+ * If the unit is not a {@code ChronoUnit}, then the result of this method
+ * is obtained by invoking {@code TemporalUnit.isSupportedBy(Temporal)}
+ * passing {@code this} as the argument.
+ * Whether the unit is supported is determined by the unit.
+ *
+ * @param unit the unit to check, null returns false
+ * @return true if the unit can be added/subtracted, false if not
+ */
+ @Override
+ public boolean isSupported(TemporalUnit unit) {
+ if (unit == WEEKS || unit == WEEK_BASED_YEARS) {
+ return true;
+ } else if (unit instanceof ChronoUnit) {
+ return false;
+ }
+ return unit != null && unit.isSupportedBy(this);
+ }
+
//-----------------------------------------------------------------------
/**
* Gets the range of valid values for the specified field.
@@ -360,7 +427,7 @@ public ValueRange range(TemporalField field) {
if (field == WEEK_OF_WEEK_BASED_YEAR) {
return ValueRange.of(1, weekRange(year));
}
- return TemporalAccessor.super.range(field);
+ return Temporal.super.range(field);
}
/**
@@ -391,7 +458,7 @@ public int get(TemporalField field) {
if (field == WEEK_OF_WEEK_BASED_YEAR) {
return week;
}
- return TemporalAccessor.super.get(field);
+ return Temporal.super.get(field);
}
/**
@@ -475,6 +542,75 @@ public int lengthOfYear() {
}
//-----------------------------------------------------------------------
+ /**
+ * Returns an adjusted copy of this year-week.
+ *
+ * This returns a {@code YearWeek}, based on this one, with the year-week adjusted.
+ * The adjustment takes place using the specified adjuster strategy object.
+ * Read the documentation of the adjuster to understand what adjustment will be made.
+ *
+ * The result of this method is obtained by invoking the
+ * {@link TemporalAdjuster#adjustInto(Temporal)} method on the
+ * specified adjuster passing {@code this} as the argument.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param adjuster the adjuster to use, not null
+ * @return a {@code YearWeek} based on {@code this} with the adjustment made, not null
+ * @throws DateTimeException if the adjustment cannot be made
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public YearWeek with(TemporalAdjuster adjuster) {
+ return (YearWeek) adjuster.adjustInto(this);
+ }
+
+ /**
+ * Returns a copy of this year-week with the specified field set to a new value.
+ *
+ * This returns a {@code YearWeek}, based on this one, with the value
+ * for the specified field changed.
+ * This can be used to change any supported field, such as the year or week.
+ * If it is not possible to set the value, because the field is not supported or for
+ * some other reason, an exception is thrown.
+ *
+ * If the field is a {@link ChronoField} then the adjustment is implemented here.
+ * The supported fields behave as follows:
+ *
+ *
{@code WEEK_OF_WEEK_BASED_YEAR} -
+ * Returns a {@code YearWeek} with the specified week-of-year set as per {@link #withWeek(int)}.
+ *
{@code WEEK_BASED_YEAR} -
+ * Returns a {@code YearWeek} with the specified year set as per {@link #withYear(int)}.
+ *
+ *
+ * All {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoField}, then the result of this method
+ * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)}
+ * passing {@code this} as the argument. In this case, the field determines
+ * whether and how to adjust the instant.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param field the field to set in the result, not null
+ * @param newValue the new value of the field in the result
+ * @return a {@code YearWeek} based on {@code this} with the specified field set, not null
+ * @throws DateTimeException if the field cannot be set
+ * @throws UnsupportedTemporalTypeException if the field is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public YearWeek with(TemporalField field, long newValue) {
+ if (field == WEEK_OF_WEEK_BASED_YEAR) {
+ return withWeek(WEEK_OF_WEEK_BASED_YEAR.range().checkValidIntValue(newValue, WEEK_OF_WEEK_BASED_YEAR));
+ } else if (field == WEEK_BASED_YEAR) {
+ return withYear(WEEK_BASED_YEAR.range().checkValidIntValue(newValue, WEEK_BASED_YEAR));
+ } else if (field instanceof ChronoField) {
+ throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
+ }
+ return field.adjustInto(this, newValue);
+ }
+
/**
* Returns a copy of this {@code YearWeek} with the week-based-year altered.
*
@@ -512,6 +648,196 @@ public YearWeek withWeek(int week) {
return with(year, week);
}
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this year-week with the specified amount added.
+ *
+ * This returns a {@code YearWeek}, based on this one, with the specified amount added.
+ * The amount is typically {@link Period} but may be any other type implementing
+ * the {@link TemporalAmount} interface.
+ *
+ * The calculation is delegated to the amount object by calling
+ * {@link TemporalAmount#addTo(Temporal)}. The amount implementation is free
+ * to implement the addition in any way it wishes, however it typically
+ * calls back to {@link #plus(long, TemporalUnit)}. Consult the documentation
+ * of the amount implementation to determine if it can be successfully added.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToAdd the amount to add, not null
+ * @return a {@code YearWeek} based on this year-week with the addition made, not null
+ * @throws DateTimeException if the addition cannot be made
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public YearWeek plus(TemporalAmount amountToAdd) {
+ return (YearWeek) amountToAdd.addTo(this);
+ }
+
+ /**
+ * Returns a copy of this year-week with the specified amount added.
+ *
+ * This returns a {@code YearWeek}, based on this one, with the amount
+ * in terms of the unit added. If it is not possible to add the amount, because the
+ * unit is not supported or for some other reason, an exception is thrown.
+ *
+ * If the field is a {@link ChronoUnit} then the addition is implemented here.
+ * The supported fields behave as follows:
+ *
+ *
{@code WEEKS} -
+ * Returns a {@code YearWeek} with the weeks added as per {@link #plusWeeks(long)}.
+ *
{@code WEEK_BASED_YEARS} -
+ * Returns a {@code YearWeek} with the years added as per {@link #plusYears(long)}.
+ *
+ *
+ * All {@code ChronoUnit} instances will throw an {@code UnsupportedTemporalTypeException}.
+ *
+ * If the field is not a {@code ChronoUnit}, then the result of this method
+ * is obtained by invoking {@code TemporalUnit.addTo(Temporal, long)}
+ * passing {@code this} as the argument. In this case, the unit determines
+ * whether and how to perform the addition.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToAdd the amount of the unit to add to the result, may be negative
+ * @param unit the unit of the amount to add, not null
+ * @return a {@code YearWeek} based on this year-week with the specified amount added, not null
+ * @throws DateTimeException if the addition cannot be made
+ * @throws UnsupportedTemporalTypeException if the unit is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public YearWeek plus(long amountToAdd, TemporalUnit unit) {
+ if (unit == WEEKS) {
+ return plusWeeks(amountToAdd);
+ } else if (unit == WEEK_BASED_YEARS) {
+ return plusYears(amountToAdd);
+ } else if (unit instanceof ChronoUnit) {
+ throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
+ }
+ return unit.addTo(this, amountToAdd);
+ }
+
+ /**
+ * Returns a copy of this year-week with the specified number of years added.
+ *
+ * If the week of this instance is 53 and the new year does not have 53 weeks,
+ * the week will be adjusted to be 52.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param yearsToAdd the years to add, may be negative
+ * @return the year-week with the years added, not null
+ */
+ public YearWeek plusYears(long yearsToAdd) {
+ if (yearsToAdd == 0) {
+ return this;
+ }
+ int newYear = Math.toIntExact(Math.addExact(year, yearsToAdd));
+ return withYear(newYear);
+ }
+
+ /**
+ * Returns a copy of this year-week with the specified number of weeks added.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param weeksToAdd the weeks to add, may be negative
+ * @return the year-week with the weeks added, not null
+ */
+ public YearWeek plusWeeks(long weeksToAdd) {
+ if (weeksToAdd == 0) {
+ return this;
+ }
+ LocalDate mondayOfWeek = atDay(MONDAY).plusWeeks(weeksToAdd);
+ return YearWeek.from(mondayOfWeek);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a copy of this year-week with the specified amount subtracted.
+ *
+ * This returns a {@code YearWeek}, based on this one, with the specified amount subtracted.
+ * The amount is typically {@link Period} but may be any other type implementing
+ * the {@link TemporalAmount} interface.
+ *
+ * The calculation is delegated to the amount object by calling
+ * {@link TemporalAmount#subtractFrom(Temporal)}. The amount implementation is free
+ * to implement the subtraction in any way it wishes, however it typically
+ * calls back to {@link #minus(long, TemporalUnit)}. Consult the documentation
+ * of the amount implementation to determine if it can be successfully subtracted.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToSubtract the amount to subtract, not null
+ * @return a {@code YearWeek} based on this year-week with the subtraction made, not null
+ * @throws DateTimeException if the subtraction cannot be made
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public YearWeek minus(TemporalAmount amountToSubtract) {
+ return (YearWeek) amountToSubtract.subtractFrom(this);
+ }
+
+ /**
+ * Returns a copy of this year-week with the specified amount subtracted.
+ *
+ * This returns a {@code YearWeek}, based on this one, with the amount
+ * in terms of the unit subtracted. If it is not possible to subtract the amount,
+ * because the unit is not supported or for some other reason, an exception is thrown.
+ *
+ * This method is equivalent to {@link #plus(long, TemporalUnit)} with the amount negated.
+ * See that method for a full description of how addition, and thus subtraction, works.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param amountToSubtract the amount of the unit to subtract from the result, may be negative
+ * @param unit the unit of the amount to subtract, not null
+ * @return a {@code YearWeek} based on this year-week with the specified amount subtracted, not null
+ * @throws DateTimeException if the subtraction cannot be made
+ * @throws UnsupportedTemporalTypeException if the unit is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public YearWeek minus(long amountToSubtract, TemporalUnit unit) {
+ return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit));
+ }
+
+ /**
+ * Returns a copy of this year-week with the specified number of years subtracted.
+ *
+ * If the week of this instance is 53 and the new year does not have 53 weeks,
+ * the week will be adjusted to be 52.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param yearsToSubtract the years to subtract, may be negative
+ * @return the year-week with the years subtracted, not null
+ */
+ public YearWeek minusYears(long yearsToSubtract) {
+ if (yearsToSubtract == 0) {
+ return this;
+ }
+ int newYear = Math.toIntExact(Math.subtractExact(year, yearsToSubtract));
+ return withYear(newYear);
+ }
+
+ /**
+ * Returns a copy of this year-week with the specified number of weeks subtracted.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param weeksToSubtract the weeks to subtract, may be negative
+ * @return the year-week with the weeks subtracted, not null
+ */
+ public YearWeek minusWeeks(long weeksToSubtract) {
+ if (weeksToSubtract == 0) {
+ return this;
+ }
+ LocalDate mondayOfWeek = atDay(MONDAY).minusWeeks(weeksToSubtract);
+ return YearWeek.from(mondayOfWeek);
+ }
+
//-----------------------------------------------------------------------
/**
* Queries this year-week using the specified query.
@@ -537,7 +863,7 @@ public R query(TemporalQuery query) {
if (query == TemporalQueries.chronology()) {
return (R) IsoChronology.INSTANCE;
}
- return TemporalAccessor.super.query(query);
+ return Temporal.super.query(query);
}
/**
@@ -569,12 +895,89 @@ public R query(TemporalQuery query) {
*/
@Override
public Temporal adjustInto(Temporal temporal) {
- if (Chronology.from(temporal).equals(IsoChronology.INSTANCE) == false) {
+ if (!Chronology.from(temporal).equals(IsoChronology.INSTANCE)) {
throw new DateTimeException("Adjustment only supported on ISO date-time");
}
return temporal.with(WEEK_BASED_YEAR, year).with(WEEK_OF_WEEK_BASED_YEAR, week);
}
+ /**
+ * Calculates the amount of time until another year-week in terms of the specified unit.
+ *
+ * This calculates the amount of time between two {@code YearWeek}
+ * objects in terms of a single {@code TemporalUnit}.
+ * The start and end points are {@code this} and the specified year-week.
+ * The result will be negative if the end is before the start.
+ * The {@code Temporal} passed to this method is converted to a
+ * {@code YearWeek} using {@link #from(TemporalAccessor)}.
+ * For example, the period in years between two year-weeks can be calculated
+ * using {@code startYearWeek.until(endYearWeek, YEARS)}.
+ *
+ * The calculation returns a whole number, representing the number of
+ * complete units between the two year-weeks.
+ * For example, the period in years between 2012-W23 and 2032-W22
+ * will only be 9 years as it is one week short of 10 years.
+ *
+ * There are two equivalent ways of using this method.
+ * The first is to invoke this method.
+ * The second is to use {@link TemporalUnit#between(Temporal, Temporal)}:
+ *
+ * // these two lines are equivalent
+ * amount = start.until(end, WEEKS);
+ * amount = WEEKS.between(start, end);
+ *
+ * The choice should be made based on which makes the code more readable.
+ *
+ * The calculation is implemented in this method for units {@code WEEKS}
+ * and {@code WEEK_BASED_YEARS}.
+ * Other {@code ChronoUnit} values will throw an exception.
+ *
+ * If the unit is not a {@code ChronoUnit}, then the result of this method
+ * is obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)}
+ * passing {@code this} as the first argument and the converted input temporal
+ * as the second argument.
+ *
+ * This instance is immutable and unaffected by this method call.
+ *
+ * @param endExclusive the end date, exclusive, which is converted to a {@code YearWeek}, not null
+ * @param unit the unit to measure the amount in, not null
+ * @return the amount of time between this year-week and the end year-week
+ * @throws DateTimeException if the amount cannot be calculated, or the end
+ * temporal cannot be converted to a {@code YearWeek}
+ * @throws UnsupportedTemporalTypeException if the unit is not supported
+ * @throws ArithmeticException if numeric overflow occurs
+ */
+ @Override
+ public long until(Temporal endExclusive, TemporalUnit unit) {
+ YearWeek end = YearWeek.from(endExclusive);
+ if (unit == WEEKS) {
+ return daysUntil(end);
+ } else if (unit == WEEK_BASED_YEARS) {
+ return yearsUntil(end);
+ } else if (unit instanceof ChronoUnit) {
+ throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
+ }
+ return unit.between(this, end);
+ }
+
+ private long daysUntil(YearWeek end) {
+ LocalDate startDate = this.atDay(MONDAY);
+ LocalDate endDate = end.atDay(MONDAY);
+ long days = endDate.toEpochDay() - startDate.toEpochDay();
+ return days / 7;
+ }
+
+ private long yearsUntil(YearWeek end) {
+ long yearsDiff = end.year - this.year;
+ if (yearsDiff > 0 && end.week < this.week) {
+ return yearsDiff - 1;
+ }
+ if (yearsDiff < 0 && end.week > this.week) {
+ return yearsDiff + 1;
+ }
+ return yearsDiff;
+ }
+
/**
* Formats this year-week using the specified formatter.
*
@@ -607,6 +1010,10 @@ public LocalDate atDay(DayOfWeek dayOfWeek) {
Objects.requireNonNull(dayOfWeek, "dayOfWeek");
int correction = LocalDate.of(year, 1, 4).getDayOfWeek().getValue() + 3;
int dayOfYear = week * 7 + dayOfWeek.getValue() - correction;
+ int maxDaysOfYear = Year.isLeap(year) ? 366 : 365;
+ if (dayOfYear > maxDaysOfYear) {
+ return LocalDate.ofYearDay(year + 1, dayOfYear - maxDaysOfYear);
+ }
if (dayOfYear > 0) {
return LocalDate.ofYearDay(year, dayOfYear);
} else {
@@ -692,6 +1099,7 @@ public int hashCode() {
* @return a string representation of this year-week, not null
*/
@Override
+ @ToString
public String toString() {
int absYear = Math.abs(year);
StringBuilder buf = new StringBuilder(10);
diff --git a/src/main/java/org/threeten/extra/Years.java b/src/main/java/org/threeten/extra/Years.java
index 22b7a20d..780aab54 100644
--- a/src/main/java/org/threeten/extra/Years.java
+++ b/src/main/java/org/threeten/extra/Years.java
@@ -48,6 +48,9 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.joda.convert.FromString;
+import org.joda.convert.ToString;
+
/**
* A year-based amount of time, such as '12 years'.
*
@@ -173,11 +176,12 @@ public static Years from(TemporalAmount amount) {
* @return the parsed period, not null
* @throws DateTimeParseException if the text cannot be parsed to a period
*/
+ @FromString
public static Years parse(CharSequence text) {
Objects.requireNonNull(text, "text");
Matcher matcher = PATTERN.matcher(text);
if (matcher.matches()) {
- int negate = ("-".equals(matcher.group(1)) ? -1 : 1);
+ int negate = "-".equals(matcher.group(1)) ? -1 : 1;
String str = matcher.group(2);
try {
int val = Integer.parseInt(str);
@@ -237,7 +241,7 @@ private Object readResolve() {
*/
@Override
public long get(TemporalUnit unit) {
- if (unit == ChronoUnit.YEARS) {
+ if (unit == YEARS) {
return years;
}
throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
@@ -268,6 +272,33 @@ public int getAmount() {
return years;
}
+ /**
+ * Checks if the amount is negative.
+ *
+ * @return true if the amount is negative, false if the amount is zero or positive
+ */
+ public boolean isNegative() {
+ return getAmount() < 0;
+ }
+
+ /**
+ * Checks if the amount is zero.
+ *
+ * @return true if the amount is zero, false if not
+ */
+ public boolean isZero() {
+ return getAmount() == 0;
+ }
+
+ /**
+ * Checks if the amount is positive.
+ *
+ * @return true if the amount is positive, false if the amount is zero or negative
+ */
+ public boolean isPositive() {
+ return getAmount() > 0;
+ }
+
//-----------------------------------------------------------------------
/**
* Returns a copy of this amount with the specified amount added.
@@ -309,13 +340,13 @@ public Years plus(int years) {
*
* This instance is immutable and unaffected by this method call.
*
- * @param amountToAdd the amount to add, not null
+ * @param amountToSubtract the amount to subtract, not null
* @return a {@code Years} based on this instance with the requested amount subtracted, not null
* @throws DateTimeException if the specified amount contains an invalid unit
* @throws ArithmeticException if numeric overflow occurs
*/
- public Years minus(TemporalAmount amountToAdd) {
- return minus(Years.from(amountToAdd).getAmount());
+ public Years minus(TemporalAmount amountToSubtract) {
+ return minus(Years.from(amountToSubtract).getAmount());
}
/**
@@ -530,6 +561,7 @@ public int hashCode() {
* @return the number of years in ISO-8601 string format
*/
@Override
+ @ToString
public String toString() {
return "P" + years + "Y";
}
diff --git a/src/main/java/org/threeten/extra/chrono/AccountingChronology.java b/src/main/java/org/threeten/extra/chrono/AccountingChronology.java
index 3def5e1a..fafcda3d 100644
--- a/src/main/java/org/threeten/extra/chrono/AccountingChronology.java
+++ b/src/main/java/org/threeten/extra/chrono/AccountingChronology.java
@@ -44,6 +44,7 @@
import java.time.chrono.AbstractChronology;
import java.time.chrono.ChronoLocalDateTime;
import java.time.chrono.ChronoZonedDateTime;
+import java.time.chrono.Chronology;
import java.time.chrono.Era;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
@@ -57,8 +58,8 @@
* An Accounting calendar system.
*
* This chronology defines the rules of a proleptic 52/53-week Accounting calendar system.
- * This calendar system follows the rules as laid down in IRS Publication 538
- * and the International Financial Reporting Standards.
+ * This calendar system follows the rules as laid down in IRS Publication 538
+ * and the International Financial Reporting Standards.
* The start of the Accounting calendar will vary against the ISO calendar.
* Depending on options chosen, it can start as early as {@code 0000-01-26 (ISO)} or as late as {@code 0001-01-04 (ISO)}.
*
@@ -112,7 +113,7 @@ public final class AccountingChronology extends AbstractChronology implements Se
*/
private final DayOfWeek endsOn;
/**
- * Whether the calendar ends in the last week of a given Gregorian/ISO month,
+ * Whether the calendar ends in the last week of a given Gregorian/ISO month,
* or nearest to the last day of the month (will sometimes be in the next month).
*/
private final boolean inLastWeek;
@@ -128,12 +129,16 @@ public final class AccountingChronology extends AbstractChronology implements Se
* The month which will have the leap-week added.
*/
private final int leapWeekInMonth;
+ /**
+ * The year offset.
+ */
+ private final int yearOffset;
/**
* Difference in days between accounting year end and ISO month end, in ISO year 0.
*/
private final transient int yearZeroDifference;
- /**
+ /**
* Number of weeks in a month range.
*/
private final transient ValueRange alignedWeekOfMonthRange;
@@ -150,7 +155,7 @@ public final class AccountingChronology extends AbstractChronology implements Se
/**
* Creates an {@code AccountingChronology} validating the input.
* Package private as only meant to be called from the builder.
- *
+ *
* @param endsOn The day-of-week a given year ends on.
* @param end The month-end the year is based on.
* @param inLastWeek Whether the year ends in the last week of the month, or nearest the end-of-month.
@@ -159,7 +164,8 @@ public final class AccountingChronology extends AbstractChronology implements Se
* @return The created Chronology, not null.
* @throws DateTimeException if the chronology cannot be built.
*/
- static AccountingChronology create(DayOfWeek endsOn, Month end, boolean inLastWeek, AccountingYearDivision division, int leapWeekInMonth) {
+ static AccountingChronology create(DayOfWeek endsOn, Month end, boolean inLastWeek, AccountingYearDivision division,
+ int leapWeekInMonth, int yearOffset) {
if (endsOn == null || end == null || division == null || leapWeekInMonth == 0) {
throw new IllegalStateException("AccountingCronology cannot be built: "
+ (endsOn == null ? "| ending day-of-week |" : "")
@@ -169,13 +175,13 @@ static AccountingChronology create(DayOfWeek endsOn, Month end, boolean inLastWe
+ " not set.");
}
if (!division.getMonthsInYearRange().isValidValue(leapWeekInMonth)) {
- throw new IllegalStateException("Leap week cannot not be placed in non-existant month " + leapWeekInMonth
+ throw new IllegalStateException("Leap week cannot not be placed in non-existent month " + leapWeekInMonth
+ ", range is [" + division.getMonthsInYearRange() + "].");
}
// Derive cached information.
- LocalDate endingLimit = inLastWeek ? LocalDate.of(0, end, 1).with(TemporalAdjusters.lastDayOfMonth()) :
- LocalDate.of(0, end, 1).with(TemporalAdjusters.lastDayOfMonth()).plusDays(3);
+ LocalDate endingLimit = inLastWeek ? LocalDate.of(0 + yearOffset, end, 1).with(TemporalAdjusters.lastDayOfMonth()) :
+ LocalDate.of(0 + yearOffset, end, 1).with(TemporalAdjusters.lastDayOfMonth()).plusDays(3);
LocalDate yearZeroEnd = endingLimit.with(TemporalAdjusters.previousOrSame(endsOn));
int yearZeroDifference = (int) yearZeroEnd.until(endingLimit, ChronoUnit.DAYS);
// Longest/shortest month lengths and related
@@ -190,13 +196,14 @@ static AccountingChronology create(DayOfWeek endsOn, Month end, boolean inLastWe
ValueRange dayOfMonthRange = ValueRange.of(1, shortestMonthLength * 7, longestMonthLength * 7);
int daysToEpoch = Math.toIntExact(0 - yearZeroEnd.plusDays(1).toEpochDay());
- return new AccountingChronology(endsOn, end, inLastWeek, division, leapWeekInMonth, yearZeroDifference, alignedWeekOfMonthRange, dayOfMonthRange, daysToEpoch);
+ return new AccountingChronology(endsOn, end, inLastWeek, division, leapWeekInMonth, yearZeroDifference,
+ alignedWeekOfMonthRange, dayOfMonthRange, daysToEpoch, yearOffset);
}
//-----------------------------------------------------------------------
/**
* Creates an instance from validated data, and cached data.
- *
+ *
* @param endsOn The day-of-week a given year ends on.
* @param end The month-end the year is based on.
* @param inLastWeek Whether the year ends in the last week of the month, or nearest the end-of-month.
@@ -208,7 +215,7 @@ static AccountingChronology create(DayOfWeek endsOn, Month end, boolean inLastWe
* @param daysToEpoch The number of days between the start of Accounting 1 and ISO 1970.
*/
private AccountingChronology(DayOfWeek endsOn, Month end, boolean inLastWeek, AccountingYearDivision division, int leapWeekInMonth, int yearZeroDifference, ValueRange alignedWeekOfMonthRange,
- ValueRange dayOfMonthRange, int daysToEpoch) {
+ ValueRange dayOfMonthRange, int daysToEpoch, int yearOffset) {
this.endsOn = endsOn;
this.end = end;
this.inLastWeek = inLastWeek;
@@ -218,6 +225,7 @@ private AccountingChronology(DayOfWeek endsOn, Month end, boolean inLastWeek, Ac
this.alignedWeekOfMonthRange = alignedWeekOfMonthRange;
this.dayOfMonthRange = dayOfMonthRange;
this.days0001ToIso1970 = daysToEpoch;
+ this.yearOffset = yearOffset;
}
/**
@@ -226,7 +234,7 @@ private AccountingChronology(DayOfWeek endsOn, Month end, boolean inLastWeek, Ac
* @return a built, validated instance.
*/
private Object readResolve() {
- return AccountingChronology.create(endsOn, end, inLastWeek, getDivision(), leapWeekInMonth);
+ return AccountingChronology.create(endsOn, end, inLastWeek, getDivision(), leapWeekInMonth, yearOffset);
}
//-----------------------------------------------------------------------
@@ -246,9 +254,9 @@ int getDays0001ToIso1970() {
/**
* Gets the ID of the chronology - 'Accounting'.
*
- * The ID uniquely identifies the {@code Chronology},
+ * The ID uniquely identifies the {@code Chronology},
* but does not differentiate between instances of {@code AccountingChronology}.
- * It cannot be used to lookup the {@code Chronology} using {@link #of(String)},
+ * It cannot be used to lookup the {@code Chronology} using {@link Chronology#of(String)},
* because each instance requires setup.
*
* @return the chronology ID - 'Accounting'
@@ -264,7 +272,7 @@ public String getId() {
*
* The Unicode Locale Data Markup Language (LDML) specification
* does not define an identifier for 52/53 week calendars used for accounting purposes,
- * and given that setup required is unlikely to do so.
+ * and given that setup required is unlikely to do so.
* For this reason, the calendar type is null.
*
* @return null, as the calendar is unlikely to be specified in LDML
@@ -459,7 +467,7 @@ public ChronoZonedDateTime zonedDateTime(Instant instant, ZoneId
/**
* Checks if the specified year is a leap year.
*
- * An Accounting proleptic-year is leap if the time between the end of the previous year
+ * An Accounting proleptic-year is leap if the time between the end of the previous year
* and the end of the current year is 371 days.
* This method does not validate the year passed in, and only has a
* well-defined result for years in the supported range.
@@ -477,26 +485,26 @@ public boolean isLeapYear(long prolepticYear) {
* Return the number of ISO Leap Years since Accounting Year 1.
*
* This method calculates how many ISO leap years have passed since year 1.
- * The count returned will be negative for years before 1.
+ * The count returned may be negative for years before 1.
* This method does not validate the year passed in, and only has a
* well-defined result for years in the supported range.
- *
+ *
* @param prolepticYear the proleptic-year to check, not validated for range
* @return the count of leap years since year 1.
*/
private long getISOLeapYearCount(long prolepticYear) {
- long offsetYear = prolepticYear - (end == Month.JANUARY ? 1 : 0) - 1;
- return Math.floorDiv(offsetYear, 4) - Math.floorDiv(offsetYear, 100) + Math.floorDiv(offsetYear, 400) + (end == Month.JANUARY ? 1 : 0);
+ long offsetYear = prolepticYear - (end == Month.JANUARY? 1 : 0) - 1 + yearOffset;
+ return Math.floorDiv(offsetYear, 4) - Math.floorDiv(offsetYear, 100) + Math.floorDiv(offsetYear, 400) + (end == Month.JANUARY && yearOffset == 0 ? 1 : 0);
}
/**
* Returns the count of leap years since year 1.
*
* This method calculates how many Accounting leap years have passed since year 1.
- * The count returned will be negative for years before 1.
+ * The count returned may be negative for years before 1.
* This method does not validate the year passed in, and only has a
* well-defined result for years in the supported range.
- *
+ *
* @param prolepticYear the proleptic-year to check, not validated for range
* @return the count of leap years since year 1.
*/
@@ -556,7 +564,8 @@ public boolean equals(Object obj) {
this.inLastWeek == other.inLastWeek &&
this.end == other.end &&
this.getDivision() == other.getDivision() &&
- this.leapWeekInMonth == other.leapWeekInMonth;
+ this.leapWeekInMonth == other.leapWeekInMonth &&
+ this.yearOffset == other.yearOffset;
}
return false;
}
@@ -570,6 +579,7 @@ public int hashCode() {
result = prime * result + end.hashCode();
result = prime * result + leapWeekInMonth;
result = prime * result + getDivision().hashCode();
+ result = prime * result + yearOffset;
return result;
}
@@ -584,7 +594,8 @@ public String toString() {
.append(", year divided in ")
.append(getDivision())
.append(" with leap-week in month ")
- .append(leapWeekInMonth);
+ .append(leapWeekInMonth)
+ .append(yearOffset == 0 ? " ending in the given ISO year" : " starting in the given ISO year");
return bld.toString();
}
diff --git a/src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.java b/src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.java
index 9016e8de..28ecd608 100644
--- a/src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.java
+++ b/src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.java
@@ -45,13 +45,15 @@
*
last-in-month vs. nearest-end-of-month - Whether the ending day-of-week is the last in the month,
* or the nearest to the end of the month (will sometimes be in the next month.
*
month end - Which Gregorian/ISO end-of-month the year ends in/is nearest to.
- *
year division - How many 'months' (periods) to divide the accounting year into,
+ *
year division - How many 'months' (periods) to divide the accounting year into,
* and how many weeks are in each.
- *
leap-week month - Which month will have the leap 'week' added to it.
+ *
leap-week month - Which month will have the leap 'week' added to it.
* In practice this is probably the last one, but this does not seem to be required.
+ *
year start/end offset - Whether the fiscal year starts or ends in the similarly numbered ISO year.
+ * If nearest-end-of-month is set and the ending month is December, the effective offset will shift over time.
*
*
- * There are approximately 7 x 2 x 12 x 4 x 12/13 = 4032 combinations.
+ * There are approximately 7 x 2 x 12 x 4 x 12/13 x 2 = 8064 combinations.
*
*
Implementation Requirements
* This class is a mutable builder suitable for use from a single thread.
@@ -63,7 +65,7 @@ public final class AccountingChronologyBuilder {
*/
private DayOfWeek endsOn;
/**
- * Whether the calendar ends in the last week of a given Gregorian/ISO month,
+ * Whether the calendar ends in the last week of a given Gregorian/ISO month,
* or nearest to the last day of the month (will sometimes be in the next month).
*/
private boolean inLastWeek;
@@ -80,6 +82,11 @@ public final class AccountingChronologyBuilder {
*/
private int leapWeekInMonth;
+ /**
+ * The offset to apply to the year.
+ */
+ private int yearOffset;
+
/**
* Constructs a new instance of the builder.
*/
@@ -89,9 +96,9 @@ public AccountingChronologyBuilder() {
/**
* Sets the day-of-week on which a given Accounting calendar year ends.
- *
+ *
* @param endsOn The day-of-week on which a given Accounting calendar year ends.
- *
+ *
* @return this, for chaining, not null.
*/
public AccountingChronologyBuilder endsOn(DayOfWeek endsOn) {
@@ -103,9 +110,9 @@ public AccountingChronologyBuilder endsOn(DayOfWeek endsOn) {
* Sets the Gregorian/ISO month-end which a given Accounting calendar year ends nearest to.
* Calendars setup this way will occasionally end in the start of the next month.
* For example, for July, the month ends on any day from July 28th to August 3rd.
- *
+ *
* @param end The Gregorian/ISO month-end a given Accounting calendar year ends nearest to.
- *
+ *
* @return this, for chaining, not null.
*/
public AccountingChronologyBuilder nearestEndOf(Month end) {
@@ -118,9 +125,9 @@ public AccountingChronologyBuilder nearestEndOf(Month end) {
* Sets the Gregorian/ISO month-end in which a given Accounting calendar year ends.
* Calendars setup this way will always end in the last week of the given month.
* For example, for July, the month ends on any day from July 25th to July 31st.
- *
+ *
* @param end The Gregorian/ISO month-end a given Accounting calendar year ends in the last week of.
- *
+ *
* @return this, for chaining, not null.
*/
public AccountingChronologyBuilder inLastWeekOf(Month end) {
@@ -131,9 +138,9 @@ public AccountingChronologyBuilder inLastWeekOf(Month end) {
/**
* Sets how a given Accounting year will be divided.
- *
+ *
* @param division How to divide a given calendar year.
- *
+ *
* @return this, for chaining, not null.
*/
public AccountingChronologyBuilder withDivision(AccountingYearDivision division) {
@@ -143,9 +150,9 @@ public AccountingChronologyBuilder withDivision(AccountingYearDivision division)
/**
* Sets the month in which the leap-week occurs.
- *
+ *
* @param leapWeekInMonth The month in which the leap-week occurs.
- *
+ *
* @return this, for chaining, not null.
*/
public AccountingChronologyBuilder leapWeekInMonth(int leapWeekInMonth) {
@@ -153,14 +160,35 @@ public AccountingChronologyBuilder leapWeekInMonth(int leapWeekInMonth) {
return this;
}
+ /**
+ * Sets the proleptic accounting year to end in the matching Iso year.
+ *
+ * @return this, for chaining, not null.
+ */
+ public AccountingChronologyBuilder accountingYearEndsInIsoYear() {
+ this.yearOffset = 0;
+ return this;
+ }
+
+
+ /**
+ * Sets the proleptic accounting year to start in the matching Iso year.
+ *
+ * @return this, for chaining, not null.
+ */
+ public AccountingChronologyBuilder accountingYearStartsInIsoYear() {
+ this.yearOffset = 1;
+ return this;
+ }
+
/**
* Completes this builder by creating the {@code AccountingChronology}.
- *
+ *
* @return the created chronology, not null.
* @throws DateTimeException if the chronology cannot be built.
*/
public AccountingChronology toChronology() {
- return AccountingChronology.create(endsOn, end, inLastWeek, division, leapWeekInMonth);
+ return AccountingChronology.create(endsOn, end, inLastWeek, division, leapWeekInMonth, yearOffset);
}
}
diff --git a/src/main/java/org/threeten/extra/chrono/AccountingDate.java b/src/main/java/org/threeten/extra/chrono/AccountingDate.java
index cf14c1df..409ff8a7 100644
--- a/src/main/java/org/threeten/extra/chrono/AccountingDate.java
+++ b/src/main/java/org/threeten/extra/chrono/AccountingDate.java
@@ -407,7 +407,7 @@ public int lengthOfMonth() {
@Override
public int lengthOfYear() {
return (WEEKS_IN_YEAR + (isLeapYear() ? 1 : 0)) * DAYS_IN_WEEK;
- };
+ }
//-------------------------------------------------------------------------
@Override
@@ -445,7 +445,7 @@ public AccountingDate minus(long amountToSubtract, TemporalUnit unit) {
@Override // for covariant return type
@SuppressWarnings("unchecked")
public ChronoLocalDateTime atTime(LocalTime localTime) {
- return (ChronoLocalDateTime) ChronoLocalDate.super.atTime(localTime);
+ return (ChronoLocalDateTime) super.atTime(localTime);
}
@Override
diff --git a/src/main/java/org/threeten/extra/chrono/AccountingYearDivision.java b/src/main/java/org/threeten/extra/chrono/AccountingYearDivision.java
index 4688966d..f4155e06 100644
--- a/src/main/java/org/threeten/extra/chrono/AccountingYearDivision.java
+++ b/src/main/java/org/threeten/extra/chrono/AccountingYearDivision.java
@@ -200,7 +200,7 @@ int getMonthFromElapsedWeeks(int weeksElapsed) {
*/
int getMonthFromElapsedWeeks(int weeksElapsed, int leapWeekInMonth) {
if (weeksElapsed < 0 || weeksElapsed >= (leapWeekInMonth == 0 ? 52 : 53)) {
- throw new DateTimeException("Count of '" + elapsedWeeks + "' elapsed weeks not valid,"
+ throw new DateTimeException("Count of '" + elapsedWeeks.length + "' elapsed weeks not valid,"
+ " should be in the range [0, " + (leapWeekInMonth == 0 ? 52 : 53) + ")");
}
leapWeekInMonth = (leapWeekInMonth == 0 ? 0 : monthsInYearRange.checkValidIntValue(leapWeekInMonth, ChronoField.MONTH_OF_YEAR));
diff --git a/src/main/java/org/threeten/extra/chrono/BritishCutoverChronology.java b/src/main/java/org/threeten/extra/chrono/BritishCutoverChronology.java
index 5cd61052..ee8f936a 100644
--- a/src/main/java/org/threeten/extra/chrono/BritishCutoverChronology.java
+++ b/src/main/java/org/threeten/extra/chrono/BritishCutoverChronology.java
@@ -40,6 +40,7 @@
import java.time.chrono.AbstractChronology;
import java.time.chrono.ChronoLocalDateTime;
import java.time.chrono.ChronoZonedDateTime;
+import java.time.chrono.Chronology;
import java.time.chrono.Era;
import java.time.chrono.IsoChronology;
import java.time.format.ResolverStyle;
@@ -183,7 +184,7 @@ public LocalDate getCutover() {
* Gets the ID of the chronology - 'BritishCutover'.
*
* The ID uniquely identifies the {@code Chronology}.
- * It can be used to lookup the {@code Chronology} using {@link #of(String)}.
+ * It can be used to lookup the {@code Chronology} using {@link Chronology#of(String)}.
*
* @return the chronology ID - 'BritishCutover'
* @see #getCalendarType()
diff --git a/src/main/java/org/threeten/extra/chrono/BritishCutoverDate.java b/src/main/java/org/threeten/extra/chrono/BritishCutoverDate.java
index 17d69f55..fb4eb5a1 100644
--- a/src/main/java/org/threeten/extra/chrono/BritishCutoverDate.java
+++ b/src/main/java/org/threeten/extra/chrono/BritishCutoverDate.java
@@ -370,6 +370,8 @@ BritishCutoverDate resolvePrevious(int year, int month, int dayOfMonth) {
case 11:
dayOfMonth = Math.min(dayOfMonth, 30);
break;
+ default:
+ break;
}
return create(year, month, dayOfMonth);
}
@@ -469,7 +471,7 @@ public BritishCutoverDate minus(long amountToSubtract, TemporalUnit unit) {
@Override // for covariant return type
@SuppressWarnings("unchecked")
public ChronoLocalDateTime atTime(LocalTime localTime) {
- return (ChronoLocalDateTime) ChronoLocalDate.super.atTime(localTime);
+ return (ChronoLocalDateTime) super.atTime(localTime);
}
@Override
@@ -520,7 +522,7 @@ public R query(TemporalQuery query) {
if (query == TemporalQueries.localDate()) {
return (R) isoDate;
}
- return ChronoLocalDate.super.query(query);
+ return super.query(query);
}
//-------------------------------------------------------------------------
diff --git a/src/main/java/org/threeten/extra/chrono/CopticChronology.java b/src/main/java/org/threeten/extra/chrono/CopticChronology.java
index 46479e74..4cb39ce7 100644
--- a/src/main/java/org/threeten/extra/chrono/CopticChronology.java
+++ b/src/main/java/org/threeten/extra/chrono/CopticChronology.java
@@ -38,6 +38,7 @@
import java.time.ZoneId;
import java.time.chrono.ChronoLocalDateTime;
import java.time.chrono.ChronoZonedDateTime;
+import java.time.chrono.Chronology;
import java.time.chrono.Era;
import java.time.format.ResolverStyle;
import java.time.temporal.TemporalAccessor;
@@ -108,7 +109,7 @@ private Object readResolve() {
* Gets the ID of the chronology - 'Coptic'.
*
* The ID uniquely identifies the {@code Chronology}.
- * It can be used to lookup the {@code Chronology} using {@link #of(String)}.
+ * It can be used to lookup the {@code Chronology} using {@link Chronology#of(String)}.
*
* @return the chronology ID - 'Coptic'
* @see #getCalendarType()
@@ -123,7 +124,7 @@ public String getId() {
*
* The calendar type is an identifier defined by the
* Unicode Locale Data Markup Language (LDML) specification.
- * It can be used to lookup the {@code Chronology} using {@link #of(String)}.
+ * It can be used to lookup the {@code Chronology} using {@link Chronology#of(String)}.
* It can also be used as part of a locale, accessible via
* {@link Locale#getUnicodeLocaleType(String)} with the key 'ca'.
*
diff --git a/src/main/java/org/threeten/extra/chrono/CopticDate.java b/src/main/java/org/threeten/extra/chrono/CopticDate.java
index 003352ad..1698811b 100644
--- a/src/main/java/org/threeten/extra/chrono/CopticDate.java
+++ b/src/main/java/org/threeten/extra/chrono/CopticDate.java
@@ -380,7 +380,7 @@ public CopticDate minus(long amountToSubtract, TemporalUnit unit) {
@Override // for covariant return type
@SuppressWarnings("unchecked")
public ChronoLocalDateTime atTime(LocalTime localTime) {
- return (ChronoLocalDateTime) ChronoLocalDate.super.atTime(localTime);
+ return (ChronoLocalDateTime) super.atTime(localTime);
}
@Override
diff --git a/src/main/java/org/threeten/extra/chrono/DiscordianChronology.java b/src/main/java/org/threeten/extra/chrono/DiscordianChronology.java
index 6b0152cb..13d23323 100644
--- a/src/main/java/org/threeten/extra/chrono/DiscordianChronology.java
+++ b/src/main/java/org/threeten/extra/chrono/DiscordianChronology.java
@@ -39,6 +39,7 @@
import java.time.chrono.AbstractChronology;
import java.time.chrono.ChronoLocalDateTime;
import java.time.chrono.ChronoZonedDateTime;
+import java.time.chrono.Chronology;
import java.time.chrono.Era;
import java.time.format.ResolverStyle;
import java.time.temporal.ChronoField;
@@ -172,7 +173,7 @@ private Object readResolve() {
* Gets the ID of the chronology - 'Discordian'.
*
* The ID uniquely identifies the {@code Chronology}.
- * It can be used to lookup the {@code Chronology} using {@link #of(String)}.
+ * It can be used to lookup the {@code Chronology} using {@link Chronology#of(String)}.
*
* @return the chronology ID - 'Discordian'
* @see #getCalendarType()
diff --git a/src/main/java/org/threeten/extra/chrono/DiscordianDate.java b/src/main/java/org/threeten/extra/chrono/DiscordianDate.java
index a6372678..4023fc35 100644
--- a/src/main/java/org/threeten/extra/chrono/DiscordianDate.java
+++ b/src/main/java/org/threeten/extra/chrono/DiscordianDate.java
@@ -326,7 +326,7 @@ static DiscordianDate create(int prolepticYear, int month, int dayOfMonth) {
if (month == 0 || dayOfMonth == 0) {
if (month != 0 || dayOfMonth != 0) {
- throw new DateTimeException("Invalid date '" + month + " " + dayOfMonth + "' as St. Tib's Day is the only special day inserted in a nonexistant month.");
+ throw new DateTimeException("Invalid date '" + month + " " + dayOfMonth + "' as St. Tib's Day is the only special day inserted in a non-existent month.");
} else if (!DiscordianChronology.INSTANCE.isLeapYear(prolepticYear)) {
throw new DateTimeException("Invalid date 'St. Tibs Day' as '" + prolepticYear + "' is not a leap year");
}
@@ -665,7 +665,7 @@ public DiscordianDate minus(long amountToSubtract, TemporalUnit unit) {
@Override // for covariant return type
@SuppressWarnings("unchecked")
public ChronoLocalDateTime atTime(LocalTime localTime) {
- return (ChronoLocalDateTime) ChronoLocalDate.super.atTime(localTime);
+ return (ChronoLocalDateTime) super.atTime(localTime);
}
@Override
diff --git a/src/main/java/org/threeten/extra/chrono/EthiopicChronology.java b/src/main/java/org/threeten/extra/chrono/EthiopicChronology.java
index 3442006b..7f33b7d1 100644
--- a/src/main/java/org/threeten/extra/chrono/EthiopicChronology.java
+++ b/src/main/java/org/threeten/extra/chrono/EthiopicChronology.java
@@ -38,6 +38,7 @@
import java.time.ZoneId;
import java.time.chrono.ChronoLocalDateTime;
import java.time.chrono.ChronoZonedDateTime;
+import java.time.chrono.Chronology;
import java.time.chrono.Era;
import java.time.format.ResolverStyle;
import java.time.temporal.TemporalAccessor;
@@ -108,7 +109,7 @@ private Object readResolve() {
* Gets the ID of the chronology - 'Ethiopic'.
*
* The ID uniquely identifies the {@code Chronology}.
- * It can be used to lookup the {@code Chronology} using {@link #of(String)}.
+ * It can be used to lookup the {@code Chronology} using {@link Chronology#of(String)}.
*
* @return the chronology ID - 'Ethiopic'
* @see #getCalendarType()
@@ -123,7 +124,7 @@ public String getId() {
*
* The calendar type is an identifier defined by the
* Unicode Locale Data Markup Language (LDML) specification.
- * It can be used to lookup the {@code Chronology} using {@link #of(String)}.
+ * It can be used to lookup the {@code Chronology} using {@link Chronology#of(String)}.
* It can also be used as part of a locale, accessible via
* {@link Locale#getUnicodeLocaleType(String)} with the key 'ca'.
*
diff --git a/src/main/java/org/threeten/extra/chrono/EthiopicDate.java b/src/main/java/org/threeten/extra/chrono/EthiopicDate.java
index 78aacb78..b9bb1318 100644
--- a/src/main/java/org/threeten/extra/chrono/EthiopicDate.java
+++ b/src/main/java/org/threeten/extra/chrono/EthiopicDate.java
@@ -382,7 +382,7 @@ public EthiopicDate minus(long amountToSubtract, TemporalUnit unit) {
@Override // for covariant return type
@SuppressWarnings("unchecked")
public ChronoLocalDateTime atTime(LocalTime localTime) {
- return (ChronoLocalDateTime) ChronoLocalDate.super.atTime(localTime);
+ return (ChronoLocalDateTime) super.atTime(localTime);
}
@Override
diff --git a/src/main/java/org/threeten/extra/chrono/InternationalFixedChronology.java b/src/main/java/org/threeten/extra/chrono/InternationalFixedChronology.java
index b585be4e..fdef876a 100644
--- a/src/main/java/org/threeten/extra/chrono/InternationalFixedChronology.java
+++ b/src/main/java/org/threeten/extra/chrono/InternationalFixedChronology.java
@@ -39,6 +39,7 @@
import java.time.chrono.AbstractChronology;
import java.time.chrono.ChronoLocalDateTime;
import java.time.chrono.ChronoZonedDateTime;
+import java.time.chrono.Chronology;
import java.time.chrono.Era;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
@@ -183,7 +184,7 @@ private Object readResolve() {
* Gets the ID of the chronology - 'Ifc'.
*
* The ID uniquely identifies the {@code Chronology}.
- * It can be used to lookup the {@code Chronology} using {@link #of(String)}.
+ * It can be used to lookup the {@code Chronology} using {@link Chronology#of(String)}.
*
* @return the chronology ID - 'Ifc'
* @see #getCalendarType()
@@ -466,8 +467,7 @@ public int prolepticYear(Era era, int yearOfEra) {
if (!(era instanceof InternationalFixedEra)) {
throw new ClassCastException("Invalid era: " + era);
}
- YEAR_RANGE.checkValidIntValue(yearOfEra, ChronoField.YEAR_OF_ERA);
- return yearOfEra;
+ return YEAR_RANGE.checkValidIntValue(yearOfEra, ChronoField.YEAR_OF_ERA);
}
/**
diff --git a/src/main/java/org/threeten/extra/chrono/InternationalFixedDate.java b/src/main/java/org/threeten/extra/chrono/InternationalFixedDate.java
index 5fd9f597..e42659e8 100644
--- a/src/main/java/org/threeten/extra/chrono/InternationalFixedDate.java
+++ b/src/main/java/org/threeten/extra/chrono/InternationalFixedDate.java
@@ -634,7 +634,6 @@ InternationalFixedDate plusWeeks(long weeks) {
int newYear = Math.toIntExact(Math.floorDiv(calcEm, WEEKS_IN_YEAR));
int newWeek = Math.toIntExact(Math.floorMod(calcEm, WEEKS_IN_YEAR));
int newMonth = 1 + Math.floorDiv(newWeek, WEEKS_IN_MONTH);
- //int newDay = 1 + ((newWeek * DAYS_IN_WEEK + ((day - 1) % DAYS_IN_WEEK)) % DAYS_IN_MONTH);
int newDay = 1 + ((newWeek * DAYS_IN_WEEK + 8 +
(isLeapDay ? 0 : isYearDay ? -1 : (day - 1) % DAYS_IN_WEEK) - 1) % DAYS_IN_MONTH);
return create(newYear, newMonth, newDay);
@@ -677,7 +676,7 @@ public InternationalFixedDate minus(long amountToSubtract, TemporalUnit unit) {
@Override // for covariant return type
@SuppressWarnings("unchecked")
public ChronoLocalDateTime atTime(LocalTime localTime) {
- return (ChronoLocalDateTime) ChronoLocalDate.super.atTime(localTime);
+ return (ChronoLocalDateTime) super.atTime(localTime);
}
@Override
diff --git a/src/main/java/org/threeten/extra/chrono/InternationalFixedEra.java b/src/main/java/org/threeten/extra/chrono/InternationalFixedEra.java
index b851c056..a8accea1 100644
--- a/src/main/java/org/threeten/extra/chrono/InternationalFixedEra.java
+++ b/src/main/java/org/threeten/extra/chrono/InternationalFixedEra.java
@@ -37,7 +37,7 @@
/**
* An era in the International Fixed calendar system.
*
- * The International Fixed calendar system only has one era.
+ * The International Fixed calendar system officially only has one era.
* The current era, for years from 1 onwards, is known as 'Current Era'.
* All previous years are invalid.
*
@@ -85,7 +85,7 @@ public static InternationalFixedEra of(final int era) {
*/
@Override
public int getValue() {
- return ordinal();
+ return 1;
}
}
diff --git a/src/main/java/org/threeten/extra/chrono/JulianChronology.java b/src/main/java/org/threeten/extra/chrono/JulianChronology.java
index d9bca63d..ca73e03f 100644
--- a/src/main/java/org/threeten/extra/chrono/JulianChronology.java
+++ b/src/main/java/org/threeten/extra/chrono/JulianChronology.java
@@ -39,6 +39,7 @@
import java.time.chrono.AbstractChronology;
import java.time.chrono.ChronoLocalDateTime;
import java.time.chrono.ChronoZonedDateTime;
+import java.time.chrono.Chronology;
import java.time.chrono.Era;
import java.time.format.ResolverStyle;
import java.time.temporal.ChronoField;
@@ -128,7 +129,7 @@ private Object readResolve() {
* Gets the ID of the chronology - 'Julian'.
*
* The ID uniquely identifies the {@code Chronology}.
- * It can be used to lookup the {@code Chronology} using {@link #of(String)}.
+ * It can be used to lookup the {@code Chronology} using {@link Chronology#of(String)}.
*
* @return the chronology ID - 'Julian'
* @see #getCalendarType()
diff --git a/src/main/java/org/threeten/extra/chrono/JulianDate.java b/src/main/java/org/threeten/extra/chrono/JulianDate.java
index aaf287f1..9f01ee0a 100644
--- a/src/main/java/org/threeten/extra/chrono/JulianDate.java
+++ b/src/main/java/org/threeten/extra/chrono/JulianDate.java
@@ -255,6 +255,8 @@ private static JulianDate resolvePreviousValid(int prolepticYear, int month, int
case 11:
day = Math.min(day, 30);
break;
+ default:
+ break;
}
return new JulianDate(prolepticYear, month, day);
}
@@ -285,6 +287,8 @@ static JulianDate create(int prolepticYear, int month, int dayOfMonth) {
case 11:
dom = 30;
break;
+ default:
+ break;
}
if (dayOfMonth > dom) {
if (dayOfMonth == 29) {
@@ -437,7 +441,7 @@ public JulianDate minus(long amountToSubtract, TemporalUnit unit) {
@Override // for covariant return type
@SuppressWarnings("unchecked")
public ChronoLocalDateTime atTime(LocalTime localTime) {
- return (ChronoLocalDateTime) ChronoLocalDate.super.atTime(localTime);
+ return (ChronoLocalDateTime) super.atTime(localTime);
}
@Override
diff --git a/src/main/java/org/threeten/extra/chrono/PaxChronology.java b/src/main/java/org/threeten/extra/chrono/PaxChronology.java
index c7b4c48e..ea66e8d8 100644
--- a/src/main/java/org/threeten/extra/chrono/PaxChronology.java
+++ b/src/main/java/org/threeten/extra/chrono/PaxChronology.java
@@ -39,6 +39,7 @@
import java.time.chrono.AbstractChronology;
import java.time.chrono.ChronoLocalDateTime;
import java.time.chrono.ChronoZonedDateTime;
+import java.time.chrono.Chronology;
import java.time.chrono.Era;
import java.time.format.ResolverStyle;
import java.time.temporal.ChronoField;
@@ -74,7 +75,7 @@
*
leap-year - Leap years occur in every year whose last two digits are divisible by {@code 6}, are {@code 99}, or are {@code 00} and the year is not divisible by 400.
*
*
- * For more information, please read the Pax Calendar Wikipedia article.
+ * For more information, please read the Pax Calendar Wikipedia article.
*
*
Implementation Requirements
* This class is immutable and thread-safe.
@@ -164,7 +165,7 @@ private Object readResolve() {
* Gets the ID of the chronology - 'Pax'.
*
* The ID uniquely identifies the {@code Chronology}.
- * It can be used to lookup the {@code Chronology} using {@link #of(String)}.
+ * It can be used to lookup the {@code Chronology} using {@link Chronology#of(String)}.
*
* @return the chronology ID - 'Pax'
* @see #getCalendarType()
diff --git a/src/main/java/org/threeten/extra/chrono/PaxDate.java b/src/main/java/org/threeten/extra/chrono/PaxDate.java
index c0c95c23..f5e31f1d 100644
--- a/src/main/java/org/threeten/extra/chrono/PaxDate.java
+++ b/src/main/java/org/threeten/extra/chrono/PaxDate.java
@@ -72,7 +72,7 @@
* The Pax differs from the Gregorian in terms of month count and length, and the leap year rule.
* Dates are aligned such that {@code 0001-01-01 (Pax)} is {@code 0000-12-31 (ISO)}.
*
- * More information is available in the Pax Calendar Wikipedia article.
+ * More information is available in the Pax Calendar Wikipedia article.
*
*
day-of-year - There are 364 days in a standard Symmetry010 year and 371 days in a leap year.
* The days are numbered accordingly.
*
leap-year - Leap years occur every 5 or 6 years, evenly spread over 293 years according the formula:
- * (52 > ((52 * year + 146) % 293)).
+ * (52 > ((52 * year + 146) % 293)).
*
Week day - every year and every quarter starts on a Monday.
* In each quarter, the first month starts on a Monday, the second month on a Wednesday and the 3rd on a Saturday.
* There are no days outside of the week or month.
@@ -151,8 +152,6 @@ public final class Symmetry010Chronology
* There are 6 full 293-year cycles from CE 1 to 1758, with 6 * 52 leap years, i.e. 312.
* There are 37 leap years from CE 1758 to 1970.
*/
- //public static final long DAYS_0001_TO_1970 = (DAYS_PER_CYCLE * 6L) + 211L * DAYS_IN_YEAR + 37 * DAYS_IN_WEEK;
- //public static final long DAYS_0001_TO_1970_ISO = IsoChronology.INSTANCE.date(1,1,1).toEpochDay() * -1;
public static final long DAYS_0001_TO_1970 = (146097 * 5L) - (31L * 365L + 7L) - 1;
/**
* Highest year in the range.
@@ -215,7 +214,7 @@ private Object readResolve() {
* Gets the ID of the chronology - 'Sym010'.
*
* The ID uniquely identifies the {@code Chronology}.
- * It can be used to lookup the {@code Chronology} using {@link #of(String)}.
+ * It can be used to lookup the {@code Chronology} using {@link Chronology#of(String)}.
*
* @return the chronology ID - 'Sym010'
* @see #getCalendarType()
@@ -498,10 +497,7 @@ public int prolepticYear(Era era, int yearOfEra) {
if (!(era instanceof IsoEra)) {
throw new ClassCastException("Invalid era: " + era);
}
-
- YEAR_RANGE.checkValidIntValue(yearOfEra, ChronoField.YEAR_OF_ERA);
-
- return yearOfEra;
+ return YEAR_RANGE.checkValidIntValue(yearOfEra, ChronoField.YEAR_OF_ERA);
}
/**
diff --git a/src/main/java/org/threeten/extra/chrono/Symmetry010Date.java b/src/main/java/org/threeten/extra/chrono/Symmetry010Date.java
index 5777b443..16a85e25 100644
--- a/src/main/java/org/threeten/extra/chrono/Symmetry010Date.java
+++ b/src/main/java/org/threeten/extra/chrono/Symmetry010Date.java
@@ -92,8 +92,8 @@
* The 13th day of a month is always a Saturday.
*
day-of-year - There are 364 days in a standard Symmetry454 year and 371 days in a leap year.
* The days are numbered accordingly.
*
leap-year - Leap years occur every 5 or 6 years, evenly spread over 293 years according the formula:
- * (52 > ((52 * year + 146) % 293)).
+ * (52 > ((52 * year + 146) % 293)).
*
Week day - every month starts on a Monday. There are no days outside of the week or month.
*
*
@@ -144,8 +145,6 @@ public final class Symmetry454Chronology
* There are 6 full 293-year cycles from CE 1 to 1758, with 6 * 52 leap years, i.e. 312.
* There are 37 leap years from CE 1758 to 1970.
*/
- //public static final long DAYS_0001_TO_1970 = (DAYS_PER_CYCLE * 6L) + 211L * DAYS_IN_YEAR + 37 * DAYS_IN_WEEK;
- //public static final long DAYS_0001_TO_1970_ISO = IsoChronology.INSTANCE.date(1,1,1).toEpochDay() * -1;
public static final long DAYS_0001_TO_1970 = (146097 * 5L) - (31L * 365L + 7L) - 1;
/**
* Highest year in the range.
@@ -208,7 +207,7 @@ private Object readResolve() {
* Gets the ID of the chronology - 'Sym454'.
*
* The ID uniquely identifies the {@code Chronology}.
- * It can be used to lookup the {@code Chronology} using {@link #of(String)}.
+ * It can be used to lookup the {@code Chronology} using {@link Chronology#of(String)}.
*
* @return the chronology ID - 'Sym454'
* @see #getCalendarType()
@@ -491,10 +490,7 @@ public int prolepticYear(Era era, int yearOfEra) {
if (!(era instanceof IsoEra)) {
throw new ClassCastException("Invalid era: " + era);
}
-
- YEAR_RANGE.checkValidIntValue(yearOfEra, ChronoField.YEAR_OF_ERA);
-
- return yearOfEra;
+ return YEAR_RANGE.checkValidIntValue(yearOfEra, ChronoField.YEAR_OF_ERA);
}
/**
diff --git a/src/main/java/org/threeten/extra/chrono/Symmetry454Date.java b/src/main/java/org/threeten/extra/chrono/Symmetry454Date.java
index 517c1e07..e4fa1a99 100644
--- a/src/main/java/org/threeten/extra/chrono/Symmetry454Date.java
+++ b/src/main/java/org/threeten/extra/chrono/Symmetry454Date.java
@@ -91,8 +91,8 @@
* The 13th day of a month is always a Saturday.
*
@@ -593,7 +593,7 @@ public Symmetry454Date minus(long amountToSubtract, TemporalUnit unit) {
@Override // for covariant return type
@SuppressWarnings("unchecked")
public ChronoLocalDateTime atTime(LocalTime localTime) {
- return (ChronoLocalDateTime) ChronoLocalDate.super.atTime(localTime);
+ return (ChronoLocalDateTime) super.atTime(localTime);
}
@Override
diff --git a/src/main/java/org/threeten/extra/chrono/package-info.java b/src/main/java/org/threeten/extra/chrono/package-info.java
new file mode 100644
index 00000000..46c815a7
--- /dev/null
+++ b/src/main/java/org/threeten/extra/chrono/package-info.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of JSR-310 nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * Additional chronologies (calendar systems) that extend {@code java.time.*}.
+ */
+package org.threeten.extra.chrono;
diff --git a/src/main/java/org/threeten/extra/package-info.java b/src/main/java/org/threeten/extra/package-info.java
new file mode 100644
index 00000000..bb7d000e
--- /dev/null
+++ b/src/main/java/org/threeten/extra/package-info.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of JSR-310 nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * Value types and utilities that extend {@code java.time.*}.
+ */
+package org.threeten.extra;
diff --git a/src/main/java/org/threeten/extra/scale/SystemUtcRules.java b/src/main/java/org/threeten/extra/scale/SystemUtcRules.java
index 1c9f65f9..f1ac8dbe 100644
--- a/src/main/java/org/threeten/extra/scale/SystemUtcRules.java
+++ b/src/main/java/org/threeten/extra/scale/SystemUtcRules.java
@@ -58,6 +58,10 @@
*/
final class SystemUtcRules extends UtcRules implements Serializable {
+ /**
+ * The leap seconds config file.
+ */
+ private static final String LEAP_SECONDS_TXT = "org/threeten/extra/scale/LeapSeconds.txt";
/**
* Leap second file format.
*/
@@ -213,7 +217,17 @@ private static Data loadLeapSeconds() {
Data bestData = null;
URL url = null;
try {
- Enumeration en = Thread.currentThread().getContextClassLoader().getResources("org/threeten/extra/scale/LeapSeconds.txt");
+ // this is the new location of the file, working on Java 8, Java 9 class path and Java 9 module path
+ Enumeration en = Thread.currentThread().getContextClassLoader().getResources("META-INF/" + LEAP_SECONDS_TXT);
+ while (en.hasMoreElements()) {
+ url = en.nextElement();
+ Data candidate = loadLeapSeconds(url);
+ if (bestData == null || candidate.getNewestDate() > bestData.getNewestDate()) {
+ bestData = candidate;
+ }
+ }
+ // this location does not work on Java 9 module path because the resource is encapsulated
+ en = Thread.currentThread().getContextClassLoader().getResources(LEAP_SECONDS_TXT);
while (en.hasMoreElements()) {
url = en.nextElement();
Data candidate = loadLeapSeconds(url);
@@ -221,6 +235,14 @@ private static Data loadLeapSeconds() {
bestData = candidate;
}
}
+ // this location is the canonical one, and class-based loading works on Java 9 module path
+ url = SystemUtcRules.class.getResource("/" + LEAP_SECONDS_TXT);
+ if (url != null) {
+ Data candidate = loadLeapSeconds(url);
+ if (bestData == null || candidate.getNewestDate() > bestData.getNewestDate()) {
+ bestData = candidate;
+ }
+ }
} catch (Exception ex) {
throw new RuntimeException("Unable to load time-zone rule data: " + url, ex);
}
@@ -239,7 +261,7 @@ private static Data loadLeapSeconds() {
* @throws Exception if an error occurs
*/
private static Data loadLeapSeconds(URL url) throws ClassNotFoundException, IOException {
- List lines = new ArrayList<>();
+ List lines;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
lines = reader.lines().collect(Collectors.toList());
}
diff --git a/src/main/java/org/threeten/extra/scale/TaiInstant.java b/src/main/java/org/threeten/extra/scale/TaiInstant.java
index e4bb7e5a..125d43d7 100644
--- a/src/main/java/org/threeten/extra/scale/TaiInstant.java
+++ b/src/main/java/org/threeten/extra/scale/TaiInstant.java
@@ -40,6 +40,9 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.joda.convert.FromString;
+import org.joda.convert.ToString;
+
/**
* An instantaneous point on the time-line measured in the TAI time-scale.
*
@@ -144,7 +147,7 @@ public static TaiInstant ofTaiSeconds(long taiSeconds, long nanoAdjustment) {
* This method uses the latest available system rules.
* The conversion first maps from UTC-SLS to UTC, then converts to TAI.
*
- * Conversion from an {@code Instant} will not be completely accurate near
+ * Conversion from an {@link Instant} will not be completely accurate near
* a leap second in accordance with UTC-SLS.
*
* @param instant the instant to convert, not null
@@ -189,12 +192,13 @@ public static TaiInstant of(UtcInstant instant) {
* The seconds part must contain only numbers and a possible leading negative sign.
* The nanoseconds part must contain exactly nine digits.
* The trailing literal must be exactly specified.
- * This format parses the {@code toString} format.
+ * This format parses the {@link #toString()} format.
*
* @param text the text to parse such as "12345.123456789s(TAI)", not null
* @return the parsed instant, not null
* @throws DateTimeParseException if the text cannot be parsed
*/
+ @FromString
public static TaiInstant parse(CharSequence text) {
Objects.requireNonNull(text, "text");
Matcher matcher = PARSER.matcher(text);
@@ -229,7 +233,7 @@ private TaiInstant(long taiSeconds, int nanoOfSecond) {
*
* The TAI second count is a simple incrementing count of seconds where
* second 0 is 1958-01-01T00:00:00(TAI).
- * The nanosecond part of the day is returned by {@code getNanosOfSecond}.
+ * The nanosecond part of the second is returned by {@link #getNano()}.
*
* @return the seconds from the epoch of 1958-01-01T00:00:00(TAI)
*/
@@ -243,7 +247,7 @@ public long getTaiSeconds() {
*
* The TAI second count is a simple incrementing count of seconds where
* second 0 is 1958-01-01T00:00:00(TAI).
- * The nanosecond part of the day is returned by {@code getNanosOfSecond}.
+ * The nanosecond offset of the second is returned by {@code getNano}.
*
* This instance is immutable and unaffected by this method call.
*
@@ -259,7 +263,7 @@ public TaiInstant withTaiSeconds(long taiSeconds) {
* of the second.
*
* The nanosecond-of-second value measures the total number of nanoseconds from
- * the second returned by {@code getTaiSeconds()}.
+ * the second returned by {@link #getTaiSeconds()}.
*
* @return the nanoseconds within the second, from 0 to 999,999,999
*/
@@ -271,7 +275,7 @@ public int getNano() {
* Returns a copy of this {@code TaiInstant} with the nano-of-second value changed.
*
* The nanosecond-of-second value measures the total number of nanoseconds from
- * the second returned by {@code getTaiSeconds()}.
+ * the second returned by {@link #getTaiSeconds()}.
*
* This instance is immutable and unaffected by this method call.
*
@@ -364,7 +368,7 @@ public Duration durationUntil(TaiInstant otherInstant) {
* This method uses the latest available system rules.
* The conversion first maps from TAI to UTC, then converts to UTC-SLS.
*
- * Conversion to an {@code Instant} will not be completely accurate near
+ * Conversion to an {@link Instant} will not be completely accurate near
* a leap second in accordance with UTC-SLS.
*
* @return an {@code Instant} representing the best approximation of this instant, not null
@@ -381,7 +385,7 @@ public Instant toInstant() {
* Converting a TAI instant to UTC requires leap second rules.
* This method uses the latest available system rules.
*
- * The {@code UtcInstant} will represent exactly the same point on the
+ * The {@link UtcInstant} will represent exactly the same point on the
* time-line as per the available leap-second rules.
* If the leap-second rules change then conversion back to TAI may
* result in a different instant.
@@ -410,6 +414,32 @@ public int compareTo(TaiInstant otherInstant) {
return nanos - otherInstant.nanos;
}
+ /**
+ * Checks if this instant is after the specified instant.
+ *
+ * The comparison is based on the time-line position of the instants.
+ *
+ * @param otherInstant the other instant to compare to, not null
+ * @return true if this instant is after the specified instant
+ * @throws NullPointerException if otherInstant is null
+ */
+ public boolean isAfter(TaiInstant otherInstant) {
+ return compareTo(otherInstant) > 0;
+ }
+
+ /**
+ * Checks if this instant is before the specified instant.
+ *
+ * The comparison is based on the time-line position of the instants.
+ *
+ * @param otherInstant the other instant to compare to, not null
+ * @return true if this instant is before the specified instant
+ * @throws NullPointerException if otherInstant is null
+ */
+ public boolean isBefore(TaiInstant otherInstant) {
+ return compareTo(otherInstant) < 0;
+ }
+
//-----------------------------------------------------------------------
/**
* Checks if this instant is equal to the specified {@code TaiInstant}.
@@ -452,6 +482,7 @@ public int hashCode() {
* @return a representation of this instant, not null
*/
@Override
+ @ToString
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(seconds);
diff --git a/src/main/java/org/threeten/extra/scale/UtcInstant.java b/src/main/java/org/threeten/extra/scale/UtcInstant.java
index 0607e60d..db7f28ce 100644
--- a/src/main/java/org/threeten/extra/scale/UtcInstant.java
+++ b/src/main/java/org/threeten/extra/scale/UtcInstant.java
@@ -47,6 +47,9 @@
import java.time.temporal.JulianFields;
import java.time.temporal.TemporalAccessor;
+import org.joda.convert.FromString;
+import org.joda.convert.ToString;
+
/**
* An instantaneous point on the time-line measured in the UTC time-scale
* with leap seconds.
@@ -123,6 +126,10 @@ public final class UtcInstant
* This is always positive and includes leap seconds.
*/
private final long nanoOfDay;
+ /**
+ * A cache of the result from {@link #toString()}
+ */
+ private transient String toString;
//-----------------------------------------------------------------------
/**
@@ -192,17 +199,18 @@ public static UtcInstant of(TaiInstant instant) {
//-------------------------------------------------------------------------
/**
- * Obtains an instance of {@code UtcInstant} from a text string
- * {@code 2007-12-03T10:15:30.00Z}.
+ * Obtains an instance of {@code UtcInstant} from a text string,
+ * such as {@code 2007-12-03T10:15:30.00Z}.
*
* The string must represent a valid instant in UTC and is parsed using
* {@link DateTimeFormatter#ISO_INSTANT} with leap seconds handled.
*
- * @param text the text to parse such as "12345.123456789s(TAI)", not null
+ * @param text the text to parse such as "2007-12-03T10:15:30.00Z", not null
* @return the parsed instant, not null
* @throws DateTimeParseException if the text cannot be parsed
* @throws DateTimeException if parsed text represents an invalid leap second
*/
+ @FromString
public static UtcInstant parse(CharSequence text) {
TemporalAccessor parsed = DateTimeFormatter.ISO_INSTANT.parse(text);
long epochSecond = parsed.getLong(INSTANT_SECONDS);
@@ -302,7 +310,7 @@ public UtcInstant withNanoOfDay(long nanoOfDay) {
* @return true if this instant is within a leap second
*/
public boolean isLeapSecond() {
- return nanoOfDay > SECS_PER_DAY * NANOS_PER_SECOND;
+ return nanoOfDay >= SECS_PER_DAY * NANOS_PER_SECOND;
}
//-----------------------------------------------------------------------
@@ -418,6 +426,32 @@ public int compareTo(UtcInstant otherInstant) {
return Long.compare(nanoOfDay, otherInstant.nanoOfDay);
}
+ /**
+ * Checks if this instant is after the specified instant.
+ *
+ * The comparison is based on the time-line position of the instants.
+ *
+ * @param otherInstant the other instant to compare to, not null
+ * @return true if this instant is after the specified instant
+ * @throws NullPointerException if otherInstant is null
+ */
+ public boolean isAfter(UtcInstant otherInstant) {
+ return compareTo(otherInstant) > 0;
+ }
+
+ /**
+ * Checks if this instant is before the specified instant.
+ *
+ * The comparison is based on the time-line position of the instants.
+ *
+ * @param otherInstant the other instant to compare to, not null
+ * @return true if this instant is before the specified instant
+ * @throws NullPointerException if otherInstant is null
+ */
+ public boolean isBefore(UtcInstant otherInstant) {
+ return compareTo(otherInstant) < 0;
+ }
+
//-----------------------------------------------------------------------
/**
* Checks if this instant is equal to the specified {@code UtcInstant}.
@@ -461,8 +495,20 @@ public int hashCode() {
* @return a representation of this instant, not null
*/
@Override
+ @ToString
public String toString() {
- LocalDate date = LocalDate.MAX.with(JulianFields.MODIFIED_JULIAN_DAY, mjDay); // TODO: capacity/import issues
+ // racy single-check idiom
+ String currentStringValue = toString;
+ if (currentStringValue == null) {
+ currentStringValue = buildToString();
+ toString = currentStringValue;
+ }
+ return currentStringValue;
+ }
+
+ // produces the string representation of this instant
+ private String buildToString() {
+ LocalDate date = LocalDate.MAX.with(JulianFields.MODIFIED_JULIAN_DAY, mjDay); // TODO: capacity/import issues
StringBuilder buf = new StringBuilder(30);
int sod = (int) (nanoOfDay / NANOS_PER_SECOND);
int hourValue = sod / (60 * 60);
diff --git a/src/main/java/org/threeten/extra/scale/UtcRules.java b/src/main/java/org/threeten/extra/scale/UtcRules.java
index a977fb71..212ffc2a 100644
--- a/src/main/java/org/threeten/extra/scale/UtcRules.java
+++ b/src/main/java/org/threeten/extra/scale/UtcRules.java
@@ -45,9 +45,11 @@
* These are used by default in {@code UtcInstant} and {@code TaiInstant}.
* Using other rules requires manual use of this class.
*
- * The system rules can be updated using the file {@code org/threeten/extra/scale/LeapSeconds.txt}.
- * You can create your own version of this file and place it on the classpath
- * and it will be used.
+ * The system rules can be updated using a {@code LeapSeconds.txt}} file.
+ * You can create your own version of this file and place it in on the classpath
+ * and it will be used. Due to Java 9 module restrictions, the file is located
+ * under META-INF to avoid module encapsulation problems -
+ * {@code META-INF/org/threeten/extra/scale/LeapSeconds.txt}.
*
*
Implementation Requirements:
* This is an abstract class and must be implemented with care
diff --git a/src/main/java/org/threeten/extra/scale/package-info.java b/src/main/java/org/threeten/extra/scale/package-info.java
new file mode 100644
index 00000000..525b80bc
--- /dev/null
+++ b/src/main/java/org/threeten/extra/scale/package-info.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of JSR-310 nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * Support for time scales that extend {@code java.time.*}.
+ */
+package org.threeten.extra.scale;
diff --git a/src/main/resources/org/threeten/extra/wordbased.properties b/src/main/resources/org/threeten/extra/wordbased.properties
new file mode 100644
index 00000000..bc4624c1
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,and
+WordBased.commaspaceand=, and
+WordBased.commaspace=,
+WordBased.spaceandspace=\ and
+WordBased.year=\ year
+WordBased.years=\ years
+WordBased.month=\ month
+WordBased.months=\ months
+WordBased.week=\ week
+WordBased.weeks=\ weeks
+WordBased.day=\ day
+WordBased.days=\ days
+WordBased.hour=\ hour
+WordBased.hours=\ hours
+WordBased.minute=\ minute
+WordBased.minutes=\ minutes
+WordBased.second=\ second
+WordBased.seconds=\ seconds
+WordBased.millisecond=\ millisecond
+WordBased.milliseconds=\ milliseconds
diff --git a/src/main/resources/org/threeten/extra/wordbased_bg.properties b/src/main/resources/org/threeten/extra/wordbased_bg.properties
new file mode 100644
index 00000000..c6b94a05
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_bg.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,и
+WordBased.commaspaceand=, и
+WordBased.commaspace=,
+WordBased.spaceandspace=\ и
+WordBased.year=\ година
+WordBased.years=\ години
+WordBased.month=\ месец
+WordBased.months=\ месеца
+WordBased.week=\ седмица
+WordBased.weeks=\ седмици
+WordBased.day=\ ден
+WordBased.days=\ дни
+WordBased.hour=\ час
+WordBased.hours=\ часа
+WordBased.minute=\ минута
+WordBased.minutes=\ минути
+WordBased.second=\ секунда
+WordBased.seconds=\ секунди
+WordBased.millisecond=\ милисекунда
+WordBased.milliseconds=\ милисекунди
diff --git a/src/main/resources/org/threeten/extra/wordbased_ca.properties b/src/main/resources/org/threeten/extra/wordbased_ca.properties
new file mode 100644
index 00000000..381b7475
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_ca.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,i
+WordBased.commaspaceand=, i
+WordBased.commaspace=,
+WordBased.spaceandspace=\ i
+WordBased.year=\ any
+WordBased.years=\ anys
+WordBased.month=\ mes
+WordBased.months=\ mesos
+WordBased.week=\ setmana
+WordBased.weeks=\ setmanes
+WordBased.day=\ dia
+WordBased.days=\ dies
+WordBased.hour=\ hora
+WordBased.hours=\ hores
+WordBased.minute=\ minut
+WordBased.minutes=\ minuts
+WordBased.second=\ segon
+WordBased.seconds=\ segons
+WordBased.millisecond=\ mil·lisegon
+WordBased.milliseconds=\ mil·lisegons
diff --git a/src/main/resources/org/threeten/extra/wordbased_cs.properties b/src/main/resources/org/threeten/extra/wordbased_cs.properties
new file mode 100644
index 00000000..67bb462d
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_cs.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,a
+WordBased.commaspaceand=, a
+WordBased.commaspace=,
+WordBased.spaceandspace=\ a
+WordBased.years.predicates=One|||End234NotTeens
+WordBased.years.list=\ rok||| roky||| let
+WordBased.months.predicates=One|||End234NotTeens
+WordBased.months.list=\ měsíc||| měsíce||| měsíců
+WordBased.weeks.predicates=One|||End234NotTeens
+WordBased.weeks.list=\ týden||| týdny||| týdnů
+WordBased.days.predicates=One|||End234NotTeens
+WordBased.days.list=\ den||| dny||| dnů
+WordBased.hours.predicates=One|||End234NotTeens
+WordBased.hours.list=\ hodina||| hodiny||| hodin
+WordBased.minutes.predicates=One|||End234NotTeens
+WordBased.minutes.list=\ minuta||| minuty||| minut
+WordBased.seconds.predicates=One|||End234NotTeens
+WordBased.seconds.list=\ sekunda||| sekundy||| sekund
+WordBased.milliseconds.predicates=One|||End234NotTeens
+WordBased.milliseconds.list=\ milisekunda||| milisekundy||| milisekund
diff --git a/src/main/resources/org/threeten/extra/wordbased_da.properties b/src/main/resources/org/threeten/extra/wordbased_da.properties
new file mode 100644
index 00000000..11d0fca8
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_da.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,og
+WordBased.commaspaceand=, og
+WordBased.commaspace=,\
+WordBased.spaceandspace=\ og\
+WordBased.year=\ \u00e5r
+WordBased.years=\ \u00e5r
+WordBased.month=\ m\u00e5ned
+WordBased.months=\ m\u00e5neder
+WordBased.week=\ uge
+WordBased.weeks=\ uger
+WordBased.day=\ dag
+WordBased.days=\ dage
+WordBased.hour=\ time
+WordBased.hours=\ timer
+WordBased.minute=\ minut
+WordBased.minutes=\ minutter
+WordBased.second=\ sekund
+WordBased.seconds=\ sekunder
+WordBased.millisecond=\ millisekund
+WordBased.milliseconds=\ millisekunder
diff --git a/src/main/resources/org/threeten/extra/wordbased_de.properties b/src/main/resources/org/threeten/extra/wordbased_de.properties
new file mode 100644
index 00000000..02b40167
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_de.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,und
+WordBased.commaspaceand=, und
+WordBased.commaspace=,
+WordBased.spaceandspace=\ und
+WordBased.year=\ Jahr
+WordBased.years=\ Jahre
+WordBased.month=\ Monat
+WordBased.months=\ Monate
+WordBased.week=\ Woche
+WordBased.weeks=\ Wochen
+WordBased.day=\ Tag
+WordBased.days=\ Tage
+WordBased.hour=\ Stunde
+WordBased.hours=\ Stunden
+WordBased.minute=\ Minute
+WordBased.minutes=\ Minuten
+WordBased.second=\ Sekunde
+WordBased.seconds=\ Sekunden
+WordBased.millisecond=\ Millisekunde
+WordBased.milliseconds=\ Millisekunden
diff --git a/src/main/resources/org/threeten/extra/wordbased_en.properties b/src/main/resources/org/threeten/extra/wordbased_en.properties
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_en.properties
@@ -0,0 +1 @@
+
diff --git a/src/main/resources/org/threeten/extra/wordbased_es.properties b/src/main/resources/org/threeten/extra/wordbased_es.properties
new file mode 100644
index 00000000..1f901e2d
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_es.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,y
+WordBased.commaspaceand=, y
+WordBased.commaspace=,
+WordBased.spaceandspace=\ y
+WordBased.year=\ a\u00f1o
+WordBased.years=\ a\u00f1os
+WordBased.month=\ mes
+WordBased.months=\ meses
+WordBased.week=\ semana
+WordBased.weeks=\ semanas
+WordBased.day=\ d\u00eda
+WordBased.days=\ d\u00edas
+WordBased.hour=\ hora
+WordBased.hours=\ horas
+WordBased.minute=\ minuto
+WordBased.minutes=\ minutos
+WordBased.second=\ segundo
+WordBased.seconds=\ segundos
+WordBased.millisecond=\ milisegundo
+WordBased.milliseconds=\ milisegundos
diff --git a/src/main/resources/org/threeten/extra/wordbased_fa.properties b/src/main/resources/org/threeten/extra/wordbased_fa.properties
new file mode 100644
index 00000000..4edf3b06
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_fa.properties
@@ -0,0 +1,23 @@
+WordBased.space=\u0020
+WordBased.comma=,
+WordBased.commandand=,\u0648
+WordBased.commaspaceand=, \u0648
+WordBased.commaspace=,
+WordBased.spaceandspace=\ \u0648
+#In Farsi, to denote a time period or time duration is always in singular form, never plural.
+WordBased.year=\ \u0633\u0627\u0644
+WordBased.years=\ \u0633\u0627\u0644
+WordBased.month=\ \u0645\u0627\u0647
+WordBased.months=\ \u0645\u0627\u0647
+WordBased.week=\ \u0647\u0641\u062A\u0647
+WordBased.weeks=\ \u0647\u0641\u062A\u0647
+WordBased.day=\ \u0631\u0648\u0632
+WordBased.days=\ \u0631\u0648\u0632
+WordBased.hour=\ \u0633\u0627\u0639\u062A
+WordBased.hours=\ \u0633\u0627\u0639\u062A
+WordBased.minute=\ \u062f\u0642\u06cc\u0642\u0647
+WordBased.minutes=\ \u062f\u0642\u06cc\u0642\u0647
+WordBased.second=\ \u062b\u0627\u0646\u06cc\u0647
+WordBased.seconds=\ \u062b\u0627\u0646\u06cc\u0647
+WordBased.millisecond=\ \u0645\u06cc\u0644\u06cc\u0020\u062b\u0627\u0646\u06cc\u0647
+WordBased.milliseconds=\ \u0645\u06cc\u0644\u06cc\u0020\u062b\u0627\u0646\u06cc\u0647
diff --git a/src/main/resources/org/threeten/extra/wordbased_fi.properties b/src/main/resources/org/threeten/extra/wordbased_fi.properties
new file mode 100644
index 00000000..16fd346c
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_fi.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,ja
+WordBased.commaspaceand=, ja
+WordBased.commaspace=,
+WordBased.spaceandspace=\ ja
+WordBased.year=\ vuosi
+WordBased.years=\ vuotta
+WordBased.month=\ kuukausi
+WordBased.months=\ kuukautta
+WordBased.week=\ viikko
+WordBased.weeks=\ viikkoa
+WordBased.day=\ päivä
+WordBased.days=\ päivää
+WordBased.hour=\ tunti
+WordBased.hours=\ tuntia
+WordBased.minute=\ minuutti
+WordBased.minutes=\ minuttia
+WordBased.second=\ sekunti
+WordBased.seconds=\ sekuntia
+WordBased.millisecond=\ millisekunti
+WordBased.milliseconds=\ millisekuntia
diff --git a/src/main/resources/org/threeten/extra/wordbased_fr.properties b/src/main/resources/org/threeten/extra/wordbased_fr.properties
new file mode 100644
index 00000000..63b89d2f
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_fr.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,et
+WordBased.commaspaceand=, et
+WordBased.commaspace=,
+WordBased.spaceandspace=\ et
+WordBased.year=\ ann\u00e9e
+WordBased.years=\ ann\u00e9es
+WordBased.month=\ mois
+WordBased.months=\ mois
+WordBased.week=\ semaine
+WordBased.weeks=\ semaines
+WordBased.day=\ jour
+WordBased.days=\ jours
+WordBased.hour=\ heure
+WordBased.hours=\ heures
+WordBased.minute=\ minute
+WordBased.minutes=\ minutes
+WordBased.second=\ seconde
+WordBased.seconds=\ secondes
+WordBased.millisecond=\ milliseconde
+WordBased.milliseconds=\ millisecondes
diff --git a/src/main/resources/org/threeten/extra/wordbased_it.properties b/src/main/resources/org/threeten/extra/wordbased_it.properties
new file mode 100644
index 00000000..c6df8b36
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_it.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,e
+WordBased.commaspaceand=, e
+WordBased.commaspace=,
+WordBased.spaceandspace=\ e
+WordBased.year=\ anno
+WordBased.years=\ anni
+WordBased.month=\ mese
+WordBased.months=\ mesi
+WordBased.week=\ settimana
+WordBased.weeks=\ settimane
+WordBased.day=\ giorno
+WordBased.days=\ giorni
+WordBased.hour=\ ora
+WordBased.hours=\ ore
+WordBased.minute=\ minuto
+WordBased.minutes=\ minuti
+WordBased.second=\ secondo
+WordBased.seconds=\ secondi
+WordBased.millisecond=\ millisecondo
+WordBased.milliseconds=\ millisecondi
diff --git a/src/main/resources/org/threeten/extra/wordbased_ja.properties b/src/main/resources/org/threeten/extra/wordbased_ja.properties
new file mode 100644
index 00000000..4f322799
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_ja.properties
@@ -0,0 +1,22 @@
+WordBased.space=
+WordBased.comma=
+WordBased.commandand=
+WordBased.commaspaceand=
+WordBased.commaspace=
+WordBased.spaceandspace=
+WordBased.year=\u5E74
+WordBased.years=\u5E74
+WordBased.month=\u304B\u6708
+WordBased.months=\u304B\u6708
+WordBased.week=\u9031\u9593
+WordBased.weeks=\u9031\u9593
+WordBased.day=\u65E5
+WordBased.days=\u65E5
+WordBased.hour=\u6642\u9593
+WordBased.hours=\u6642\u9593
+WordBased.minute=\u5206
+WordBased.minutes=\u5206
+WordBased.second=\u79D2
+WordBased.seconds=\u79D2
+WordBased.millisecond=\u30DF\u30EA\u79D2
+WordBased.milliseconds=\u30DF\u30EA\u79D2
diff --git a/src/main/resources/org/threeten/extra/wordbased_nb.properties b/src/main/resources/org/threeten/extra/wordbased_nb.properties
new file mode 100644
index 00000000..93c587e9
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_nb.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,og
+WordBased.commaspaceand=, og
+WordBased.commaspace=,
+WordBased.spaceandspace=\ og
+WordBased.year=\ år
+WordBased.years=\ år
+WordBased.month=\ måned
+WordBased.months=\ måneder
+WordBased.week=\ uke
+WordBased.weeks=\ uker
+WordBased.day=\ dag
+WordBased.days=\ dager
+WordBased.hour=\ time
+WordBased.hours=\ timer
+WordBased.minute=\ minutt
+WordBased.minutes=\ minutt
+WordBased.second=\ sekund
+WordBased.seconds=\ sekund
+WordBased.millisecond=\ millisekund
+WordBased.milliseconds=\ millisekund
diff --git a/src/main/resources/org/threeten/extra/wordbased_nl.properties b/src/main/resources/org/threeten/extra/wordbased_nl.properties
new file mode 100644
index 00000000..2561c7f6
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_nl.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,en
+WordBased.commaspaceand=, en
+WordBased.commaspace=,
+WordBased.spaceandspace=\ en
+WordBased.year=\ jaar
+WordBased.years=\ jaar
+WordBased.month=\ maand
+WordBased.months=\ maanden
+WordBased.week=\ week
+WordBased.weeks=\ weken
+WordBased.day=\ dag
+WordBased.days=\ dagen
+WordBased.hour=\ uur
+WordBased.hours=\ uur
+WordBased.minute=\ minuut
+WordBased.minutes=\ minuten
+WordBased.second=\ seconde
+WordBased.seconds=\ seconden
+WordBased.millisecond=\ milliseconde
+WordBased.milliseconds=\ milliseconden
diff --git a/src/main/resources/org/threeten/extra/wordbased_nn.properties b/src/main/resources/org/threeten/extra/wordbased_nn.properties
new file mode 100644
index 00000000..12dc8f24
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_nn.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,og
+WordBased.commaspaceand=, og
+WordBased.commaspace=,
+WordBased.spaceandspace=\ og
+WordBased.year=\ år
+WordBased.years=\ år
+WordBased.month=\ månad
+WordBased.months=\ månader
+WordBased.week=\ veke
+WordBased.weeks=\ veker
+WordBased.day=\ dag
+WordBased.days=\ dagar
+WordBased.hour=\ time
+WordBased.hours=\ timar
+WordBased.minute=\ minutt
+WordBased.minutes=\ minutt
+WordBased.second=\ sekund
+WordBased.seconds=\ sekund
+WordBased.millisecond=\ millisekund
+WordBased.milliseconds=\ millisekund
diff --git a/src/main/resources/org/threeten/extra/wordbased_pl.properties b/src/main/resources/org/threeten/extra/wordbased_pl.properties
new file mode 100644
index 00000000..02f717f4
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_pl.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,i
+WordBased.commaspaceand=, i
+WordBased.commaspace=,
+WordBased.spaceandspace=\ i
+WordBased.years.predicates=One|||End234NotTeens
+WordBased.years.list=\ rok||| lata||| lat
+WordBased.months.predicates=One|||End234NotTeens
+WordBased.months.list=\ miesi\u0105c||| miesi\u0105ce||| miesi\u0119cy
+WordBased.weeks.predicates=One|||End234NotTeens
+WordBased.weeks.list=\ tydzie\u0144||| tygodnie||| tygodni
+WordBased.day=\ dzie\u0144
+WordBased.days=\ dni
+WordBased.hours.predicates=One|||End234NotTeens
+WordBased.hours.list=\ godzina||| godziny||| godzin
+WordBased.minutes.predicates=One|||End234NotTeens
+WordBased.minutes.list=\ minuta||| minuty||| minut
+WordBased.seconds.predicates=One|||End234NotTeens
+WordBased.seconds.list=\ sekunda||| sekundy||| sekund
+WordBased.milliseconds.predicates=One|||End234NotTeens
+WordBased.milliseconds.list=\ milisekunda||| milisekundy||| milisekund
diff --git a/src/main/resources/org/threeten/extra/wordbased_pt.properties b/src/main/resources/org/threeten/extra/wordbased_pt.properties
new file mode 100644
index 00000000..95db4d60
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_pt.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,e
+WordBased.commaspaceand=, e
+WordBased.commaspace=,
+WordBased.spaceandspace=\ e
+WordBased.year=\ ano
+WordBased.years=\ anos
+WordBased.month=\ m\u00eas
+WordBased.months=\ meses
+WordBased.week=\ semana
+WordBased.weeks=\ semanas
+WordBased.day=\ dia
+WordBased.days=\ dias
+WordBased.hour=\ hora
+WordBased.hours=\ horas
+WordBased.minute=\ minuto
+WordBased.minutes=\ minutos
+WordBased.second=\ segundo
+WordBased.seconds=\ segundos
+WordBased.millisecond=\ milissegundo
+WordBased.milliseconds=\ milissegundos
diff --git a/src/main/resources/org/threeten/extra/wordbased_ro.properties b/src/main/resources/org/threeten/extra/wordbased_ro.properties
new file mode 100644
index 00000000..e9d96576
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_ro.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,\u0219i
+WordBased.commaspaceand=, \u0219i
+WordBased.commaspace=,
+WordBased.spaceandspace=\ \u0219i
+WordBased.year=\ an
+WordBased.years=\ ani
+WordBased.month=\ lun\u0103
+WordBased.months=\ luni
+WordBased.week=\ s\u0103pt\u0103m\u00E2n\u0103
+WordBased.weeks=\ s\u0103pt\u0103m\u00E2ni
+WordBased.day=\ zi
+WordBased.days=\ zile
+WordBased.hour=\ or\u0103
+WordBased.hours=\ ore
+WordBased.minute=\ minut
+WordBased.minutes=\ minute
+WordBased.second=\ secund\u0103
+WordBased.seconds=\ secunde
+WordBased.millisecond=\ milisecund\u0103
+WordBased.milliseconds=\ milisecunde
diff --git a/src/main/resources/org/threeten/extra/wordbased_ru.properties b/src/main/resources/org/threeten/extra/wordbased_ru.properties
new file mode 100644
index 00000000..cea63ec6
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_ru.properties
@@ -0,0 +1,22 @@
+WordBased.space=\u0020
+WordBased.comma=,
+WordBased.commandand=,\u0438
+WordBased.commaspaceand=, \u0438
+WordBased.commaspace=,\u0020
+WordBased.spaceandspace=\ \u0438\u0020
+WordBased.years.predicates=End1Not11|||End234NotTeens
+WordBased.years.list=\ \u0433\u043E\u0434||| \u0433\u043E\u0434\u0430||| \u043B\u0435\u0442
+WordBased.months.predicates=End1Not11|||End234NotTeens
+WordBased.months.list=\ \u043C\u0435\u0441\u044F\u0446||| \u043C\u0435\u0441\u044F\u0446\u0430||| \u043C\u0435\u0441\u044F\u0446\u0435\u0432
+WordBased.weeks.predicates=End1Not11|||End234NotTeens
+WordBased.weeks.list=\ \u043D\u0435\u0434\u0435\u043B\u044F||| \u043D\u0435\u0434\u0435\u043B\u0438||| \u043D\u0435\u0434\u0435\u043B\u044C
+WordBased.days.predicates=End1Not11|||End234NotTeens
+WordBased.days.list=\ \u0434\u0435\u043D\u044C||| \u0434\u043D\u044F||| \u0434\u043D\u0435\u0439
+WordBased.hours.predicates=End1Not11|||End234NotTeens
+WordBased.hours.list=\ \u0447\u0430\u0441||| \u0447\u0430\u0441\u0430||| \u0447\u0430\u0441\u043E\u0432
+WordBased.minutes.predicates=End1Not11|||End234NotTeens
+WordBased.minutes.list=\ \u043C\u0438\u043D\u0443\u0442\u0430||| \u043C\u0438\u043D\u0443\u0442\u044B||| \u043C\u0438\u043D\u0443\u0442
+WordBased.seconds.predicates=End1Not11|||End234NotTeens
+WordBased.seconds.list=\ \u0441\u0435\u043A\u0443\u043D\u0434\u0430||| \u0441\u0435\u043A\u0443\u043D\u0434\u044B||| \u0441\u0435\u043A\u0443\u043D\u0434
+WordBased.milliseconds.predicates=End1Not11|||End234NotTeens
+WordBased.milliseconds.list=\ \u043C\u0438\u043B\u043B\u0438\u0441\u0435\u043A\u0443\u043D\u0434\u0430||| \u043C\u0438\u043B\u043B\u0438\u0441\u0435\u043A\u0443\u043D\u0434\u044B||| \u043C\u0438\u043B\u043B\u0438\u0441\u0435\u043A\u0443\u043D\u0434
diff --git a/src/main/resources/org/threeten/extra/wordbased_sv.properties b/src/main/resources/org/threeten/extra/wordbased_sv.properties
new file mode 100644
index 00000000..cd0c10ee
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_sv.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,och
+WordBased.commaspaceand=, och
+WordBased.commaspace=,
+WordBased.spaceandspace=\ och
+WordBased.year=\ år
+WordBased.years=\ år
+WordBased.month=\ månad
+WordBased.months=\ månader
+WordBased.week=\ vecka
+WordBased.weeks=\ veckor
+WordBased.day=\ dag
+WordBased.days=\ dagar
+WordBased.hour=\ timme
+WordBased.hours=\ timmar
+WordBased.minute=\ minut
+WordBased.minutes=\ minuter
+WordBased.second=\ sekund
+WordBased.seconds=\ sekunder
+WordBased.millisecond=\ millisekund
+WordBased.milliseconds=\ millisekunder
diff --git a/src/main/resources/org/threeten/extra/wordbased_tr.properties b/src/main/resources/org/threeten/extra/wordbased_tr.properties
new file mode 100644
index 00000000..25629c42
--- /dev/null
+++ b/src/main/resources/org/threeten/extra/wordbased_tr.properties
@@ -0,0 +1,22 @@
+WordBased.space=\
+WordBased.comma=,
+WordBased.commandand=,ve
+WordBased.commaspaceand=, ve
+WordBased.commaspace=,
+WordBased.spaceandspace=\ ve
+WordBased.year=\ y\u0131l
+WordBased.years=\ y\u0131l
+WordBased.month=\ ay
+WordBased.months=\ ay
+WordBased.week=\ hafta
+WordBased.weeks=\ hafta
+WordBased.day=\ g\u00fcn
+WordBased.days=\ g\u00fcn
+WordBased.hour=\ saat
+WordBased.hours=\ saat
+WordBased.minute=\ dakika
+WordBased.minutes=\ dakika
+WordBased.second=\ saniye
+WordBased.seconds=\ saniye
+WordBased.millisecond=\ milisaniye
+WordBased.milliseconds=\ milisaniye
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
index 371153b3..f77158c0 100644
--- a/src/site/markdown/index.md
+++ b/src/site/markdown/index.md
@@ -6,31 +6,38 @@
Not every piece of date/time logic is destined for the JDK.
Some concepts are too specialized or too bulky to make it in.
This project provides some of those additional classes as a well-tested and reliable jar.
-It is curated by the primary author of the Java 8 date and time library, [Stephen Colebourne](http://www.joda.org/).
+It is curated by the primary author of the Java 8 date and time library, [Stephen Colebourne](https://www.joda.org/).
-ThreeTen-Extra is licensed under the business-friendly [BSD 3-clause license](license.html).
+ThreeTen-Extra is licensed under the business-friendly [BSD 3-clause license](licenses.html).
## Features
The following features are included:
-* [`DayOfMonth`](apidocs/org/threeten/extra/DayOfMonth.html) - a day-of-month without month or year
-* [`DayOfYear`](apidocs/org/threeten/extra/DayOfYear.html) - a day-of-year without year
-* [`AmPm`](apidocs/org/threeten/extra/AmPm.html) - before or after midday
-* [`Quarter`](apidocs/org/threeten/extra/Quarter.html) - the four quarters, Q1, Q2, Q3 and Q4
-* [`YearQuarter`](apidocs/org/threeten/extra/YearQuarter.html) - combines a year and quarter, 2014-Q4
-* [`YearWeek`](apidocs/org/threeten/extra/YearWeek.html) - combines a week-based-year and a week, 2014-W06
-* [`Days`](apidocs/org/threeten/extra/Days.html),
-[`Weeks`](apidocs/org/threeten/extra/Weeks.html),
-[`Months`](apidocs/org/threeten/extra/Months.html) and
-[`Years`](apidocs/org/threeten/extra/Years.html) - amounts of time
-* [`Interval`](apidocs/org/threeten/extra/Interval.html) - an interval between two instants
-* Weekend adjusters
-* [Coptic](apidocs/org/threeten/extra/chrono/CopticChronology.html) calendar system
-* [Ethiopic](apidocs/org/threeten/extra/chrono/EthiopicChronology.html) calendar system
-* [Julian](apidocs/org/threeten/extra/chrono/JulianChronology.html) calendar system
-* Support for the TAI and UTC [time-scales](apidocs/org/threeten/extra/scale/package-summary.html)
+* [`DayOfMonth`](apidocs/org.threeten.extra/org/threeten/extra/DayOfMonth.html) - a day-of-month without month or year
+* [`DayOfYear`](apidocs/org.threeten.extra/org/threeten/extra/DayOfYear.html) - a day-of-year without year
+* [`AmPm`](apidocs/org.threeten.extra/org/threeten/extra/AmPm.html) - before or after midday
+* [`Quarter`](apidocs/org.threeten.extra/org/threeten/extra/Quarter.html) - the four quarters, Q1, Q2, Q3 and Q4
+* [`YearQuarter`](apidocs/org.threeten.extra/org/threeten/extra/YearQuarter.html) - combines year and quarter, 2014-Q4
+* [`YearWeek`](apidocs/org.threeten.extra/org/threeten/extra/YearWeek.html) - a week in a week-based-year, 2014-W06
+* [`YearHalf`](apidocs/org.threeten.extra/org/threeten/extra/YearHalf.html) - a half-year, 2014-H1
+* [`OffsetDate`](apidocs/org.threeten.extra/org/threeten/extra/OffsetDate.html) - combines `LocalDate` and `ZoneOffset`
+* [`HourMinute`](apidocs/org.threeten.extra/org/threeten/extra/HourMinute.html) - time to minute precision, 10:24
+* [`Seconds`](apidocs/org.threeten.extra/org/threeten/extra/Seconds.html),
+[`Minutes`](apidocs/org.threeten.extra/org/threeten/extra/Minutes.html),
+[`Hours`](apidocs/org.threeten.extra/org/threeten/extra/Hours.html),
+[`Days`](apidocs/org.threeten.extra/org/threeten/extra/Days.html),
+[`Weeks`](apidocs/org.threeten.extra/org/threeten/extra/Weeks.html),
+[`Months`](apidocs/org.threeten.extra/org/threeten/extra/Months.html) and
+[`Years`](apidocs/org.threeten.extra/org/threeten/extra/Years.html) - amounts of time
+* [`Interval`](apidocs/org.threeten.extra/org/threeten/extra/Interval.html) - an interval between two instants
+* [`LocalDateRange`](apidocs/org.threeten.extra/org/threeten/extra/LocalDateRange.html) - a range between two dates
+* [`PeriodDuration`](apidocs/org.threeten.extra/org/threeten/extra/PeriodDuration.html) - combines `Period` and `Duration`
+* More [utilities](apidocs/org.threeten.extra/org/threeten/extra/Temporals.html), such as weekend adjusters
+* Extended [formatting](apidocs/org.threeten.extra/org/threeten/extra/AmountFormats.html) of periods and durations, including word-based formatting
+* Additional [calendar systems](apidocs/org.threeten.extra/org/threeten/extra/chrono/package-summary.html)
+* Support for the TAI and UTC [time-scales](apidocs/org.threeten.extra/org/threeten/extra/scale/package-summary.html)
## Documentation
@@ -38,7 +45,8 @@ The following features are included:
Various documentation is available:
* The helpful [user guide](userguide.html)
-* The [Javadoc](apidocs/index.html)
+* The list of [related projects](related.html)
+* The [Javadoc](apidocs/org.threeten.extra/module-summary.html)
* The [change notes](changes-report.html) for each release
* The [GitHub](https://github.com/ThreeTen/threeten-extra) source repository
@@ -46,18 +54,18 @@ Various documentation is available:
## Releases
-Release 1.0 is the current release.
-This release is considered stable and worthy of the 1.x tag.
+Release 1.8.0 is the current release.
+This release is considered stable and worthy of the 1.x tag as per [SemVer](https://semver.org/spec/v2.0.0.html).
ThreeTen-Extra requires Java SE 8 or later and has no [dependencies](dependencies.html).
-Available in [Maven Central](http://search.maven.org/#artifactdetails%7Corg.threeten%7Cthreeten-extra%7C1.0%7Cjar).
+Available in [Maven Central](https://search.maven.org/search?q=g:org.threeten%20AND%20a:threeten-extra&core=gav).
```xml
org.threetenthreeten-extra
- 1.0
+ 1.8.0
```
@@ -65,10 +73,11 @@ Available in [Maven Central](http://search.maven.org/#artifactdetails%7Corg.thre
### Support
-Support on bugs, library usage or enhancement requests is available on a best efforts basis.
-
-To suggest enhancements or contribute, please [fork the source code](https://github.com/ThreeTen/threeten-extra)
-on GitHub and send a Pull Request.
-
-Alternatively, use GitHub [issues](https://github.com/ThreeTen/threeten-extra/issues).
+Please use [Stack Overflow](https://stackoverflow.com/search?q=threeten-extra) for general usage questions.
+GitHub [issues](https://github.com/ThreeTen/threeten-extra/issues) and [pull requests](https://github.com/ThreeTen/threeten-extra/pulls)
+should be used when you want to help advance the project.
+Commercial support is available via the
+[Tidelift subscription](https://tidelift.com/subscription/pkg/maven-org-threeten-threeten-extra?utm_source=maven-org-threeten-threeten-extra&utm_medium=referral&utm_campaign=website).
+To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security).
+Tidelift will coordinate the fix and disclosure.
diff --git a/src/site/markdown/related.md b/src/site/markdown/related.md
new file mode 100644
index 00000000..dead271e
--- /dev/null
+++ b/src/site/markdown/related.md
@@ -0,0 +1,12 @@
+## Related projects
+
+Here are links to a number of projects related to **JSR-310** or **ThreeTen-Extra**.
+These projects are independent - no guarantees of quality are given!
+
+* [JPA adapters](https://github.com/marschall/threeten-jpa) - Integration with JPA
+* [JAXB adapters](https://github.com/threeten-jaxb/threeten-jaxb) - Integration with JAXB
+* [ObjectLab Holiday calculation](http://objectlabkit.sourceforge.net/) - Support for working and non-working days, plus how to calculate and resolve holidays, from a finance industry perspective.
+* [MyBatis Type Handlers](https://github.com/mybatis/typehandlers-threeten-extra) - The MyBatis type handlers supporting types of ThreeTen-Extra.
+
+If your open source library provides a value added feature on top of JSR-310 or ThreeTen-Extra,
+then let us know! Just send a pull request to edit this page.
diff --git a/src/site/markdown/userguide.md b/src/site/markdown/userguide.md
index 040561c6..7a1a65ca 100644
--- a/src/site/markdown/userguide.md
+++ b/src/site/markdown/userguide.md
@@ -9,17 +9,38 @@ ThreeTen-Extra is a small library that builds on the Java SE 8
The additional value types operate exactly as per similar classes in Java SE 8.
These include:
-* [`DayOfMonth`](apidocs/org/threeten/extra/DayOfMonth.html) - a day-of-month without month or year
-* [`DayOfYear`](apidocs/org/threeten/extra/DayOfYear.html) - a day-of-year without year
-* [`AmPm`](apidocs/org/threeten/extra/AmPm.html) - before or after midday
-* [`Quarter`](apidocs/org/threeten/extra/Quarter.html) - the four quarters, Q1, Q2, Q3 and Q4
-* [`YearQuarter`](apidocs/org/threeten/extra/YearQuarter.html) - combines a year and quarter, 2014-Q4
-* [`YearWeek`](apidocs/org/threeten/extra/YearWeek.html) - combines a week-based-year and a week, 2014-W06
-* [`Days`](apidocs/org/threeten/extra/Days.html) - an amount of time measured in days
-* [`Weeks`](apidocs/org/threeten/extra/Weeks.html) - an amount of time measured in weeks
-* [`Months`](apidocs/org/threeten/extra/Months.html) - an amount of time measured in months
-* [`Years`](apidocs/org/threeten/extra/Years.html) - an amount of time measured in years
-* [`Interval`](apidocs/org/threeten/extra/Interval.html) - an interval between two instants
+* [`DayOfMonth`](apidocs/org.threeten.extra/org/threeten/extra/DayOfMonth.html) - a day-of-month without month or year
+* [`DayOfYear`](apidocs/org.threeten.extra/org/threeten/extra/DayOfYear.html) - a day-of-year without year
+* [`AmPm`](apidocs/org.threeten.extra/org/threeten/extra/AmPm.html) - before or after midday
+* [`Quarter`](apidocs/org.threeten.extra/org/threeten/extra/Quarter.html) - the four quarters, Q1, Q2, Q3 and Q4
+* [`YearQuarter`](apidocs/org.threeten.extra/org/threeten/extra/YearQuarter.html) - combines a year and quarter, 2014-Q4
+* [`YearWeek`](apidocs/org.threeten.extra/org/threeten/extra/YearWeek.html) - combines a week-based-year and a week, 2014-W06
+* [`OffsetDate`](apidocs/org.threeten.extra/org/threeten/extra/OffsetDate.html) - combines `LocalDate` and `ZoneOffset`
+* [`Seconds`](apidocs/org.threeten.extra/org/threeten/extra/Seconds.html) - an amount of time measured in seconds
+* [`Minutes`](apidocs/org.threeten.extra/org/threeten/extra/Minutes.html) - an amount of time measured in minutes
+* [`Hours`](apidocs/org.threeten.extra/org/threeten/extra/Hours.html) - an amount of time measured in hours
+* [`Days`](apidocs/org.threeten.extra/org/threeten/extra/Days.html) - an amount of time measured in days
+* [`Weeks`](apidocs/org.threeten.extra/org/threeten/extra/Weeks.html) - an amount of time measured in weeks
+* [`Months`](apidocs/org.threeten.extra/org/threeten/extra/Months.html) - an amount of time measured in months
+* [`Years`](apidocs/org.threeten.extra/org/threeten/extra/Years.html) - an amount of time measured in years
+* [`Interval`](apidocs/org.threeten.extra/org/threeten/extra/Interval.html) - an interval between two instants
+* [`LocalDateRange`](apidocs/org.threeten.extra/org/threeten/extra/LocalDateRange.html) - a range between two dates
+* [`PeriodDuration`](apidocs/org.threeten.extra/org/threeten/extra/PeriodDuration.html) - combines `Period` and `Duration`
+
+
+## Period/Duration formatting
+
+The JDK does not provide a mechanism to format periods or durations beyond ISO-8601.
+A simple mechanism is provided here in [`AmountFormats`](apidocs/org.threeten.extra/org/threeten/extra/AmountFormats.html):
+
+```
+ Period period = Period.of(1, 6, 5);
+ String str = AmountFormats.wordBased(period, Locale.ENGLISH);
+ // output: "1 year, 6 months and 5 days"
+```
+
+Translations are provided for bg, ca, cs, da, de, en, es, fa, fi, fr, it, ja, nb, nl, nn, pl, pt, ro, ru, sv, tr.
+Feel free to raise PRs for other languages.
## Calendar systems
@@ -27,16 +48,16 @@ These include:
The additional calendar systems operate exactly as per similar classes in Java SE 8.
These include:
-* [Accounting](apidocs/org/threeten/extra/chrono/AccountingChronology.html) calendar system
-* [British Cutover](apidocs/org/threeten/extra/chrono/BritishCutoverChronology.html) calendar system
-* [Coptic](apidocs/org/threeten/extra/chrono/CopticChronology.html) calendar system
-* [Discordian](apidocs/org/threeten/extra/chrono/DiscordianChronology.html) calendar system
-* [Ethiopic](apidocs/org/threeten/extra/chrono/EthiopicChronology.html) calendar system
-* [International Fixed](apidocs/org/threeten/extra/chrono/InternationalFixedChronology.html) calendar system
-* [Julian](apidocs/org/threeten/extra/chrono/JulianChronology.html) calendar system
-* [Pax](apidocs/org/threeten/extra/chrono/PaxChronology.html) calendar system
-* [Symmetry010](apidocs/org/threeten/extra/chrono/Symmetry010Chronology.html) calendar system
-* [Symmetry454](apidocs/org/threeten/extra/chrono/Symmetry454Chronology.html) calendar system
+* [Accounting](apidocs/org.threeten.extra/org/threeten/extra/chrono/AccountingChronology.html) calendar system
+* [British Cutover](apidocs/org.threeten.extra/org/threeten/extra/chrono/BritishCutoverChronology.html) calendar system
+* [Coptic](apidocs/org.threeten.extra/org/threeten/extra/chrono/CopticChronology.html) calendar system
+* [Discordian](apidocs/org.threeten.extra/org/threeten/extra/chrono/DiscordianChronology.html) calendar system
+* [Ethiopic](apidocs/org.threeten.extra/org/threeten/extra/chrono/EthiopicChronology.html) calendar system
+* [International Fixed](apidocs/org.threeten.extra/org/threeten/extra/chrono/InternationalFixedChronology.html) calendar system
+* [Julian](apidocs/org.threeten.extra/org/threeten/extra/chrono/JulianChronology.html) calendar system
+* [Pax](apidocs/org.threeten.extra/org/threeten/extra/chrono/PaxChronology.html) calendar system
+* [Symmetry010](apidocs/org.threeten.extra/org/threeten/extra/chrono/Symmetry010Chronology.html) calendar system
+* [Symmetry454](apidocs/org.threeten.extra/org/threeten/extra/chrono/Symmetry454Chronology.html) calendar system
## Time scales
@@ -52,5 +73,13 @@ They were not included as they were deemed to be too specialized for the JDK.
Use `TaiInstant` if you need an instant using the TAI time-scale.
Use `UtcInstant` if you need an instant using the UTC time-scale.
-The leap second data is provided in a text file loaded via the `ServiceLoader`.
+
+The leap second data is provided in a text file loaded from the classpath.
Only whole leap seconds are handled, and data starts from 1972 by default.
+To replace the built in leap seconds file, create a file `META-INF/org/threeten/extra/scale/LeapSeconds.txt`.
+The content should have two columns as per [this format](https://github.com/ThreeTen/threeten-extra/blob/0cf61e35fc165062eb70a66b026c54c261dce46d/src/main/resources/org/threeten/extra/scale/LeapSeconds.txt).
+
+
+## Related projects
+
+See also the list of [related projects](related.html).
diff --git a/src/site/site.xml b/src/site/site.xml
index 1cb60d32..fe8438a7 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -5,9 +5,9 @@
UA-1425975-2
- lt.velykis.maven.skins
+ org.joda.externalreflow-maven-skin
- 1.1.1
+ 1.2
@@ -34,6 +34,7 @@
falsefalsefalse
+ true
@@ -59,21 +60,22 @@
-
-
+
+ ]]>
diff --git a/src/test/java/org/threeten/extra/AbstractDateTimeTest.java b/src/test/java/org/threeten/extra/AbstractDateTimeTest.java
new file mode 100644
index 00000000..f6265a1b
--- /dev/null
+++ b/src/test/java/org/threeten/extra/AbstractDateTimeTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of JSR-310 nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.threeten.extra;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.time.DateTimeException;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalField;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Base test class for {@code DateTime}.
+ */
+public abstract class AbstractDateTimeTest {
+
+ /**
+ * Sample {@code DateTime} objects.
+ * @return the objects, not null
+ */
+ protected abstract List samples();
+
+ /**
+ * List of valid supported fields.
+ * @return the fields, not null
+ */
+ protected abstract List validFields();
+
+ /**
+ * List of invalid unsupported fields.
+ * @return the fields, not null
+ */
+ protected abstract List invalidFields();
+
+ //-----------------------------------------------------------------------
+ // isSupported(TemporalField)
+ //-----------------------------------------------------------------------
+ @Test
+ public void basicTest_isSupported_TemporalField_supported() {
+ for (TemporalAccessor sample : samples()) {
+ for (TemporalField field : validFields()) {
+ assertTrue(sample.isSupported(field), "Failed on " + sample + " " + field);
+ }
+ }
+ }
+
+ @Test
+ public void basicTest_isSupported_TemporalField_unsupported() {
+ for (TemporalAccessor sample : samples()) {
+ for (TemporalField field : invalidFields()) {
+ assertFalse(sample.isSupported(field), "Failed on " + sample + " " + field);
+ }
+ }
+ }
+
+ @Test
+ public void basicTest_isSupported_TemporalField_null() {
+ for (TemporalAccessor sample : samples()) {
+ assertFalse(sample.isSupported(null), "Failed on " + sample);
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ // range(TemporalField)
+ //-----------------------------------------------------------------------
+ @Test
+ public void basicTest_range_TemporalField_supported() {
+ for (TemporalAccessor sample : samples()) {
+ for (TemporalField field : validFields()) {
+ assertDoesNotThrow(() -> sample.range(field));
+ }
+ }
+ }
+
+ @Test
+ public void basicTest_range_TemporalField_unsupported() {
+ for (TemporalAccessor sample : samples()) {
+ for (TemporalField field : invalidFields()) {
+ assertThrows(DateTimeException.class, () -> sample.range(field), "Failed on " + sample + " " + field);
+ }
+ }
+ }
+
+ @Test
+ public void basicTest_range_TemporalField_null() {
+ for (TemporalAccessor sample : samples()) {
+ assertThrows(NullPointerException.class, () -> sample.range(null), "Failed on " + sample);
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ // get(TemporalField)
+ //-----------------------------------------------------------------------
+ @Test
+ public void basicTest_get_TemporalField_supported() {
+ for (TemporalAccessor sample : samples()) {
+ for (TemporalField field : validFields()) {
+ if (sample.range(field).isIntValue()) {
+ assertDoesNotThrow(() -> sample.get(field));
+ } else {
+ assertThrows(DateTimeException.class, () -> sample.get(field), "Failed on " + sample + " " + field);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void basicTest_get_TemporalField_unsupported() {
+ for (TemporalAccessor sample : samples()) {
+ for (TemporalField field : invalidFields()) {
+ assertThrows(DateTimeException.class, () -> sample.get(field), "Failed on " + sample + " " + field);
+ }
+ }
+ }
+
+ @Test
+ public void basicTest_get_TemporalField_null() {
+ for (TemporalAccessor sample : samples()) {
+ assertThrows(NullPointerException.class, () -> sample.get(null), "Failed on " + sample);
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ // getLong(TemporalField)
+ //-----------------------------------------------------------------------
+ @Test
+ public void basicTest_getLong_TemporalField_supported() {
+ for (TemporalAccessor sample : samples()) {
+ for (TemporalField field : validFields()) {
+ assertDoesNotThrow(() -> sample.getLong(field));
+ }
+ }
+ }
+
+ @Test
+ public void basicTest_getLong_TemporalField_unsupported() {
+ for (TemporalAccessor sample : samples()) {
+ for (TemporalField field : invalidFields()) {
+ assertThrows(DateTimeException.class, () -> sample.getLong(field), "Failed on " + sample + " " + field);
+ }
+ }
+ }
+
+ @Test
+ public void basicTest_getLong_TemporalField_null() {
+ for (TemporalAccessor sample : samples()) {
+ assertThrows(NullPointerException.class, () -> sample.getLong(null), "Failed on " + sample);
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void basicTest_query() {
+ for (TemporalAccessor sample : samples()) {
+ assertEquals("foo", sample.query(dateTime -> "foo"));
+ }
+ }
+
+}
+
diff --git a/src/test/java/org/threeten/extra/MockSimplePeriod.java b/src/test/java/org/threeten/extra/MockSimplePeriod.java
new file mode 100644
index 00000000..00ccb1dc
--- /dev/null
+++ b/src/test/java/org/threeten/extra/MockSimplePeriod.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of JSR-310 nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.threeten.extra;
+
+import static java.time.temporal.ChronoUnit.DAYS;
+import static java.time.temporal.ChronoUnit.FOREVER;
+import static java.time.temporal.ChronoUnit.SECONDS;
+
+import java.time.DateTimeException;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAmount;
+import java.time.temporal.TemporalUnit;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Mock period of time measured using a single unit, such as {@code 3 Days}.
+ */
+public final class MockSimplePeriod
+ implements TemporalAmount, Comparable {
+
+ /**
+ * A constant for a period of zero, measured in days.
+ */
+ public static final MockSimplePeriod ZERO_DAYS = new MockSimplePeriod(0, DAYS);
+ /**
+ * A constant for a period of zero, measured in seconds.
+ */
+ public static final MockSimplePeriod ZERO_SECONDS = new MockSimplePeriod(0, SECONDS);
+
+ /**
+ * The amount of the period.
+ */
+ private final long amount;
+ /**
+ * The unit the period is measured in.
+ */
+ private final TemporalUnit unit;
+
+ /**
+ * Obtains a {@code MockSimplePeriod} from an amount and unit.
+ *