Full Code of shred/commons-suncalc for AI

master a8778731b030 cached
59 files
272.2 KB
74.1k tokens
393 symbols
1 requests
Download .txt
Showing preview only (292K chars total). Download the full file or copy to clipboard to get everything.
Repository: shred/commons-suncalc
Branch: master
Commit: a8778731b030
Files: 59
Total size: 272.2 KB

Directory structure:
gitextract_52r2ik9m/

├── .gitignore
├── .gitlab-ci.yml
├── .project
├── LICENSE-APL.txt
├── README.md
├── pom.xml
└── src/
    ├── doc/
    │   ├── docs/
    │   │   ├── examples.md
    │   │   ├── faq.md
    │   │   ├── index.md
    │   │   ├── migration.md
    │   │   └── usage.md
    │   ├── mkdocs.yml
    │   └── theme/
    │       ├── breadcrumbs.html
    │       ├── css/
    │       │   ├── font.css
    │       │   ├── theme_custom.css
    │       │   └── theme_pygments.css
    │       ├── footer.html
    │       └── main.html
    ├── main/
    │   ├── java/
    │   │   ├── module-info.java
    │   │   └── org/
    │   │       └── shredzone/
    │   │           └── commons/
    │   │               └── suncalc/
    │   │                   ├── MoonIllumination.java
    │   │                   ├── MoonPhase.java
    │   │                   ├── MoonPosition.java
    │   │                   ├── MoonTimes.java
    │   │                   ├── SunPosition.java
    │   │                   ├── SunTimes.java
    │   │                   ├── package-info.java
    │   │                   ├── param/
    │   │                   │   ├── Builder.java
    │   │                   │   ├── GenericParameter.java
    │   │                   │   ├── LocationParameter.java
    │   │                   │   ├── TimeParameter.java
    │   │                   │   ├── WindowParameter.java
    │   │                   │   └── package-info.java
    │   │                   └── util/
    │   │                       ├── BaseBuilder.java
    │   │                       ├── ExtendedMath.java
    │   │                       ├── JulianDate.java
    │   │                       ├── Matrix.java
    │   │                       ├── Moon.java
    │   │                       ├── Pegasus.java
    │   │                       ├── QuadraticInterpolation.java
    │   │                       ├── Sun.java
    │   │                       ├── Vector.java
    │   │                       └── package-info.java
    │   └── resources/
    │       └── .gitignore
    └── test/
        ├── java/
        │   └── org/
        │       └── shredzone/
        │           └── commons/
        │               └── suncalc/
        │                   ├── ExamplesTest.java
        │                   ├── Locations.java
        │                   ├── MoonIlluminationTest.java
        │                   ├── MoonPhaseTest.java
        │                   ├── MoonPositionTest.java
        │                   ├── MoonTimesTest.java
        │                   ├── SunPositionTest.java
        │                   ├── SunTimesTest.java
        │                   └── util/
        │                       ├── BaseBuilderTest.java
        │                       ├── ExtendedMathTest.java
        │                       ├── JulianDateTest.java
        │                       ├── MatrixTest.java
        │                       ├── PegasusTest.java
        │                       ├── QuadraticInterpolationTest.java
        │                       └── VectorTest.java
        └── resources/
            └── .gitignore

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.classpath
.settings
target
/bin/


================================================
FILE: .gitlab-ci.yml
================================================
build:
  tags:
    - maven
  script:
    - mvn clean compile

deploy:
  tags:
    - maven
  script:
    - mvn -B install javadoc:javadoc


================================================
FILE: .project
================================================
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
	<name>commons-suncalc</name>
	<comment></comment>
	<projects>
	</projects>
	<buildSpec>
		<buildCommand>
			<name>org.eclipse.jdt.core.javabuilder</name>
			<arguments>
			</arguments>
		</buildCommand>
		<buildCommand>
			<name>org.eclipse.m2e.core.maven2Builder</name>
			<arguments>
			</arguments>
		</buildCommand>
	</buildSpec>
	<natures>
		<nature>org.eclipse.jdt.core.javanature</nature>
		<nature>org.eclipse.m2e.core.maven2Nature</nature>
	</natures>
</projectDescription>


================================================
FILE: LICENSE-APL.txt
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
# commons-suncalc ![build status](https://shredzone.org/badge/commons-suncalc.svg) ![maven central](https://shredzone.org/maven-central/org.shredzone.commons/commons-suncalc/badge.svg)

A Java library for calculation of sun and moon positions and phases.

## Features

* Lightweight, only requires Java 8 or higher, no other dependencies
* Android compatible, requires API level 26 (Android 8.0 "Oreo") or higher. For older Android versions, use [commons-suncalc v2](https://codeberg.org/shred/commons-suncalc/tree/v2), which is similar to this version, but does not use the Java Date/Time API.
* Available at [Maven Central](http://search.maven.org/#search|ga|1|a%3A%22commons-suncalc%22)
* Extensive unit tests

## Accuracy

Astronomical calculations are far more complex than throwing a few numbers into an obscure formula and then getting a fully accurate result. There is always a tradeoff between accuracy and computing time.

This library has its focus on getting _acceptable_ results at low cost, so it can also run on mobile devices, or devices with a low computing power. The results have an accuracy of about a minute, which should be good enough for common applications (like sunrise/sunset timers), but is probably not sufficient for astronomical purposes.

If you are looking for the highest possible accuracy, you are looking for a different library.

## Quick Start

This library consists of a few models, all of them are invoked the same way:

```java
ZonedDateTime dateTime = // date, time and timezone of calculation
double lat, lng = // geolocation
SunTimes times = SunTimes.compute()
        .on(dateTime)   // set a date
        .at(lat, lng)   // set a location
        .execute();     // get the results
System.out.println("Sunrise: " + times.getRise());
System.out.println("Sunset: " + times.getSet());
```

Read the [online documentation](https://shredzone.org/maven/commons-suncalc/) for examples and API details.

See the [migration guide](https://shredzone.org/maven/commons-suncalc/migration.html) for how to migrate from a previous version.

## References

This library bases on:

* "Astronomy on the Personal Computer", 4th edition, by Oliver Montenbruck and Thomas Pfleger
* "Astronomical Algorithms" by Jean Meeus

## Contribute

* Fork the [Source code at Codeberg](https://codeberg.org/shred/commons-suncalc). Feel free to send pull requests.
* Found a bug? Please [file a bug report](https://codeberg.org/shred/commons-suncalc/issues).

## License

_commons-suncalc_ is open source software. The source code is distributed under the terms of [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0).


================================================
FILE: pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.shredzone.commons</groupId>
    <artifactId>commons-suncalc</artifactId>
    <version>3.12-SNAPSHOT</version>
    <name>Commons: Suncalc</name>
    <description>Compute sun and moon phases</description>
    <url>http://commons.shredzone.org</url>
    <inceptionYear>2016</inceptionYear>
    <licenses>
        <license>
            <name>Apache License Version 2.0</name>
            <url>LICENSE-APL.txt</url>
        </license>
    </licenses>
    <scm>
        <url>https://codeberg.org/shred/commons-suncalc/</url>
        <connection>scm:git:git@codeberg.org:shred/commons-suncalc.git</connection>
        <developerConnection>scm:git:git@codeberg.org:shred/commons-suncalc.git</developerConnection>
      <tag>HEAD</tag>
  </scm>
    <issueManagement>
        <system>Codeberg Issues</system>
        <url>https://codeberg.org/shred/commons-suncalc/issues</url>
    </issueManagement>
    <developers>
        <developer>
            <id>shred</id>
            <name>Richard Körber</name>
        </developer>
    </developers>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <release>8</release>
                </configuration>
                <executions>
                    <execution>
                        <id>default-compile</id>
                        <configuration>
                            <release>9</release>
                        </configuration>
                    </execution>
                    <execution>
                        <id>base-compile</id>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                        <configuration>
                            <excludes>
                                <exclude>module-info.java</exclude>
                            </excludes>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <excludes>
                        <exclude>**/.gitignore</exclude>
                    </excludes>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.github.spotbugs</groupId>
                <artifactId>spotbugs-maven-plugin</artifactId>
                <version>4.7.3.5</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>check</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>3.2.0</version>
                <configuration>
                    <doclint>syntax,reference</doclint>
                    <linksource>true</linksource>
                    <locale>en</locale>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-release-plugin</artifactId>
                <version>2.5.3</version>
                <configuration>
                    <autoVersionSubmodules>true</autoVersionSubmodules>
                    <tagNameFormat>v@{project.version}</tagNameFormat>
                    <pushChanges>false</pushChanges>
                    <localCheckout>true</localCheckout>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>3.2.1</version>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.shredzone.maven</groupId>
                <artifactId>mkdocs-maven-plugin</artifactId>
                <version>1.1</version>
                <configuration>
                    <outputDirectory>${project.build.directory}/site</outputDirectory>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>com.github.spotbugs</groupId>
            <artifactId>spotbugs-annotations</artifactId>
            <version>4.8.5</version>
            <optional>true</optional>
            <exclusions>
                <exclusion>
                    <groupId>com.google.code.findbugs</groupId>
                    <artifactId>jsr305</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.26.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <!-- Workaround: Java 9's javadoc search is broken if no module is defined -->
    <profiles>
        <profile>
            <id>java-9</id>
            <activation>
                <jdk>[9,10]</jdk>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-javadoc-plugin</artifactId>
                        <configuration>
                            <additionalJOption combine.self="override">--no-module-directories</additionalJOption>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
            <reporting>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-javadoc-plugin</artifactId>
                        <configuration>
                            <additionalJOption combine.self="override">--no-module-directories</additionalJOption>
                        </configuration>
                    </plugin>
                </plugins>
            </reporting>
        </profile>
        <profile>
            <id>java-11</id>
            <activation>
                <jdk>[11,)</jdk>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-javadoc-plugin</artifactId>
                        <configuration>
                            <source>8</source>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
            <reporting>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-javadoc-plugin</artifactId>
                        <configuration>
                            <source>8</source>
                        </configuration>
                    </plugin>
                </plugins>
            </reporting>
        </profile>
    </profiles>
</project>


================================================
FILE: src/doc/docs/examples.md
================================================
# Examples

In this chapter, you will find code examples that demonstrate the use and the possibilities of the API. You can also find (and run) these examples in the [ExamplesTest](https://codeberg.org/shred/commons-suncalc/blob/master/src/test/java/org/shredzone/commons/suncalc/ExamplesTest.java) unit test.

I know this is a long chapter. It is because _suncalc_ offers a lot of functionality. I still recommend to read it, or at least skim it, to get an idea of what is possible or best practice.

## Time Zone

All calculations use your own system's local time and time zone, unless you give other parameters. This can give surprising and confusing results.

Let's assume we're living in Paris, and we want to compute our sunrise and sunset time for May 1st, 2020.

```java
SunTimes paris = SunTimes.compute()
        .on(2020, 5, 1)             // May 1st, 2020, starting midnight
        .latitude(48, 51, 24.0)     // Latitude of Paris: 48°51'24" N
        .longitude(2, 21, 6.0)      // Longitude:          2°21'06" E
        .execute();
System.out.println("Sunrise in Paris: " + paris.getRise());
System.out.println("Sunset in Paris:  " + paris.getSet());
```

The result is not very surprising:

```text
Sunrise in Paris: 2020-05-01T06:29:47+02:00[Europe/Paris]
Sunset in Paris:  2020-05-01T21:06:45+02:00[Europe/Paris]
```

Now we want to compute the sunrise and sunset times of New York.

```java
SunTimes newYork = SunTimes.compute()
        .on(2020, 5, 1)             // May 1st, 2020, starting midnight
        .at(40.712778, -74.005833)  // Coordinates of New York
        .execute();
System.out.println("Sunrise in New York: " + newYork.getRise());
System.out.println("Sunset in New York:  " + newYork.getSet());
```

The result is:

```text
Sunrise in New York: 2020-05-01T11:54:05+02:00[Europe/Paris]
Sunset in New York:  2020-05-01T01:51:51+02:00[Europe/Paris]
```

Huh? The sun rises at noon and sets past midnight? The sun also sets before it is rising that day?

The reason is that we're still using the Paris timezone. On May 1st, midnight **Paris time**, the sun is still up in New York. It sets in New York when it's 1:52 in Paris, and raises again when it's 11:54 in Paris.

We can pass a `timezone()` parameter to tell _suncalc_ that we actually want to use a different timezone.

```java
SunTimes newYorkTz = SunTimes.compute()
        .on(2020, 5, 1)             // May 1st, 2020, starting midnight
        .timezone("America/New_York") // ...New York timezone
        .at(40.712778, -74.005833)  // Coordinates of New York
        .execute();
System.out.println("Sunrise in New York: " + newYorkTz.getRise());
System.out.println("Sunset in New York:  " + newYorkTz.getSet());
```

Now, we finally see the actual sunrise and sunset time in New York:

```text
Sunrise in New York: 2020-05-01T05:54:05-04:00[America/New_York]
Sunset in New York:  2020-05-01T19:52:53-04:00[America/New_York]
```

## Time Window

[Alert, Nunavut, Canada](https://en.wikipedia.org/wiki/Alert,_Nunavut) is the northernmost place in the world with a permanent population. Let's find out when the sun rises and sets there on March 15th, 2020:

```java
final double[] ALERT_CANADA = new double[] { 82.5, -62.316667 };
final ZoneId ALERT_TZ = ZoneId.of("Canada/Eastern");

SunTimes march = SunTimes.compute()
        .on(2020, 3, 15)            // March 15th, 2020, starting midnight
        .at(ALERT_CANADA)           // Coordinates are stored in an array
        .timezone(ALERT_TZ)
        .execute();
System.out.println("Sunrise: " + march.getRise());
System.out.println("Sunset:  " + march.getSet());
```

The result is looking fine so far:

```text
Sunrise: 2020-03-15T06:49:03-04:00[Canada/Eastern]
Sunset:  2020-03-15T17:52:53-04:00[Canada/Eastern]
```

What about June 15th?

```java
SunTimes june = SunTimes.compute()
        .on(2020, 6, 15)            // June 15th, 2020, starting midnight
        .at(ALERT_CANADA)
        .timezone(ALERT_TZ)
        .execute();
System.out.println("Sunrise: " + june.getRise());
System.out.println("Sunset:  " + june.getSet());
```

The result:

```text
Sunrise: 2020-09-05T00:24:03-04:00[Canada/Eastern]
Sunset:  2020-09-04T23:55:46-04:00[Canada/Eastern]
```

The sun will set on September 4th, and will rise again about 30 minutes later. This is technically correct, because Alert is above the Arctic Circle, where the sun never sets all summer.

So the sun will set about three months later, that's interesting! When did it raise for the last time before Northern summer? We can use `.reverse()` for that. It changes the search direction, so the given date is the end date of our search window.

```java
SunTimes juneReverse = SunTimes.compute()
        .on(2020, 6, 15)            // June 15th, 2020, starting midnight
        .at(ALERT_CANADA)
        .timezone(ALERT_TZ)
        .reverse()                  // reverse the search direction!
        .execute();
System.out.println("Sunrise: " + juneReverse.getRise());
System.out.println("Sunset:  " + juneReverse.getSet());
```

The result:

```text
Sunrise: 2020-04-06T00:35:04-04:00[Canada/Eastern]
Sunset:  2020-04-05T23:44:05-04:00[Canada/Eastern]
```

Now we know that the sun was rising for the last time on April 6th, and will set again on September 4th. However, we initially wanted to get a result for June 15th, so we limit the window to 24 hours:

```java
SunTimes june15OnlyCycle = SunTimes.compute()
        .on(2020, 6, 15)            // June 15th, 2020, starting midnight
        .at(ALERT_CANADA)
        .timezone(ALERT_TZ)
        .limit(Duration.ofHours(24))
        .execute();
System.out.println("Sunset:  " + june15OnlyCycle.getSet());
System.out.println("Sunrise: " + june15OnlyCycle.getRise());
```

Instead of `limit(Duration.ofHours(24))`, we could also use `oneDay()`.

Now we get a different result. There is no sunrise or sunset on June 15th:

```text
Sunrise: null
Sunset:  null
```

But is the sun up or down all that day?

```java
System.out.println("Sun is up all day:   " + june15OnlyCycle.isAlwaysUp());
System.out.println("Sun is down all day: " + june15OnlyCycle.isAlwaysDown());
```

The result confirms that the sun is up all day:

```text
Sun is up all day:   true
Sun is down all day: false
```

## Parameter Reuse

As soon as `execute()` is invoked, _suncalc_ performs the calculations according to the given parameters, and creates a result object which is immutable. The parameters can be reused after that:

```java
final double[] COLOGNE = new double[] { 50.938056, 6.956944 };

MoonTimes.Parameters parameters = MoonTimes.compute()
        .at(COLOGNE)
        .midnight();

MoonTimes today = parameters.execute();
System.out.println("Today, the moon rises in Cologne at " + today.getRise());

parameters.tomorrow();
MoonTimes tomorrow = parameters.execute();
System.out.println("Tomorrow, the moon will rise in Cologne at " + tomorrow.getRise());
System.out.println("But today, the moon still rises at " + today.getRise());
```

The result is (at the time of writing):

```text
Today, the moon rises in Cologne at 2020-05-24T06:40:45+02:00[Europe/Berlin]
Tomorrow, the moon will rise in Cologne at 2020-05-25T07:23:06+02:00[Europe/Berlin]
But today, the moon still rises at 2020-05-24T06:40:45+02:00[Europe/Berlin]
```

As you can see in the last line, the invocation of `tomorrow()` did not affect the `today` result.

This can be useful for loops. Let's find out how much of the visible moon surface is lit by the sun on each day of January 2020.

```java
MoonIllumination.Parameters parameters = MoonIllumination.compute()
        .on(2020, 1, 1);

for (int i = 1; i <= 31; i++) {
    long percent = Math.round(parameters.execute().getFraction() * 100.0);
    System.out.println("On January " + i + " the moon was " + percent + "% lit.");
    parameters.plusDays(1);
}
```

The result (excerpt):

```text
On January 1 the moon was 29% lit.
On January 2 the moon was 38% lit.
On January 3 the moon was 48% lit.
 [...]
On January 29 the moon was 15% lit.
On January 30 the moon was 22% lit.
On January 31 the moon was 30% lit.
```

## Twilight

By default `SunTimes` computes the sunrise and sunset times as we would expect it. The sun rises when the upper part of the sun disc just appears on the horizon, and it sets when the upper part just vanishes. Because of our atmosphere, the sun is actually deeper on the horizon as it appears to be. This effect is called [atmospheric refraction](https://en.wikipedia.org/wiki/Atmospheric_refraction), and it is factored into the calculation.

There are other [twilights](https://en.wikipedia.org/wiki/Twilight) that may be interesting. Photographers are especially interested in the [Golden hour](https://en.wikipedia.org/wiki/Golden_hour_(photography)), which gives a warm and soft sunlight. In the morning, Golden hour starts at an angle of -4° (which is the end of the Blue hour), and ends when the sun reaches an angle of 6°. In the evening, the golden hour starts when the sun reaches an angle of 6°, and ends at an angle of -4°.

To learn more about the individual twilight transitions, see the [illustration of twilights](usage.md#twilight).

Let's calculate the golden hour in Singapore for the next four Mondays starting June 1st, 2020:

```java
SunTimes.Parameters base = SunTimes.compute()
        .at(1.283333, 103.833333)            // Singapore
        .on(2020, 6, 1)
        .timezone("Asia/Singapore");

for (int i = 0; i < 4; i++) {
    SunTimes blue = base
            .copy()                          // Use a copy of base
            .plusDays(i * 7)
            .twilight(SunTimes.Twilight.BLUE_HOUR)      // Blue Hour, -4°
            .execute();
    SunTimes golden = base
            .copy()                          // Use a copy of base
            .plusDays(i * 7)
            .twilight(SunTimes.Twilight.GOLDEN_HOUR)    // Golden Hour, 6°
            .execute();

    System.out.println("Morning golden hour starts at " + blue.getRise());
    System.out.println("Morning golden hour ends at   " + golden.getRise());
    System.out.println("Evening golden hour starts at " + golden.getSet());
    System.out.println("Evening golden hour ends at   " + blue.getSet());
}
```

Note the `copy()` method! It copies the current set of parameters into a new parameter object. Both objects can then be changed independently of each other. This is very useful when you need to have different parameters in loops.

This is the result:

```text
Morning golden hour starts at 2020-06-01T06:43:13+08:00[Asia/Singapore]
Morning golden hour ends at   2020-06-01T07:26:24+08:00[Asia/Singapore]
Evening golden hour starts at 2020-06-01T18:38:50+08:00[Asia/Singapore]
Evening golden hour ends at   2020-06-01T19:22:02+08:00[Asia/Singapore]
Morning golden hour starts at 2020-06-08T06:44:16+08:00[Asia/Singapore]
Morning golden hour ends at   2020-06-08T07:27:41+08:00[Asia/Singapore]
Evening golden hour starts at 2020-06-08T18:40:01+08:00[Asia/Singapore]
Evening golden hour ends at   2020-06-08T19:23:27+08:00[Asia/Singapore]
Morning golden hour starts at 2020-06-15T06:45:35+08:00[Asia/Singapore]
Morning golden hour ends at   2020-06-15T07:29:10+08:00[Asia/Singapore]
Evening golden hour starts at 2020-06-15T18:41:25+08:00[Asia/Singapore]
Evening golden hour ends at   2020-06-15T19:25:00+08:00[Asia/Singapore]
Morning golden hour starts at 2020-06-22T06:47:04+08:00[Asia/Singapore]
Morning golden hour ends at   2020-06-22T07:30:41+08:00[Asia/Singapore]
Evening golden hour starts at 2020-06-22T18:42:56+08:00[Asia/Singapore]
Evening golden hour ends at   2020-06-22T19:26:32+08:00[Asia/Singapore]
```

## Moon Phase

I'd like to print a calendar of 2023, and mark all the days having a full moon. As I print a calendar, I'm only interested in the day of full moon, but I won't care for the concrete time. Besides that, I also want to know if a full moon qualifies as [supermoon](https://en.wikipedia.org/wiki/Supermoon) or micromoon.

As the visible moon phase is identical on every place on earth, we won't have to set a location here.

But we have to be careful! Since there are about 29.5 days between two full moons, a month might actually have two full moons. For this reason, we cannot simply iterate over the months. Instead we take the previous full moon, add one day so we won't find the same full moon again, and use this date as a base for the next iteration.

```java
LocalDate date = LocalDate.of(2023, 1, 1);

MoonPhase.Parameters parameters = MoonPhase.compute()
        .phase(MoonPhase.Phase.FULL_MOON);

while (true) {
    MoonPhase moonPhase = parameters
            .on(date)
            .execute();
    LocalDate nextFullMoon = moonPhase
            .getTime()
            .toLocalDate();
    if (nextFullMoon.getYear() == 2024) {
        break;      // we've reached the next year
    }

    System.out.print(nextFullMoon);
    if (moonPhase.isMicroMoon()) {
        System.out.print(" (micromoon)");
    }
    if (moonPhase.isSuperMoon()) {
        System.out.print(" (supermoon)");
    }
    System.out.println();

    date = nextFullMoon.plusDays(1);
}
```

The result is:

```text
2023-01-07 (micromoon)
2023-02-05 (micromoon)
2023-03-07
2023-04-06
2023-05-05
2023-06-04
2023-07-03
2023-08-01 (supermoon)
2023-08-31 (supermoon)
2023-09-29
2023-10-28
2023-11-27
2023-12-27
```

As you can see, there are two full moons in August 2023.

## Sun and Moon Positions

I'm in Tokyo. It's November 13th 2018, 10:03:24. In what direction do I have to look in order to see the sun and the moon?

```java
SunPosition.Parameters sunParam = SunPosition.compute()
        .at(35.689722, 139.692222)      // Tokyo
        .timezone("Asia/Tokyo")         // local time
        .on(2018, 11, 13, 10, 3, 24);   // 2018-11-13 10:03:24

MoonPosition.Parameters moonParam = MoonPosition.compute()
        .sameLocationAs(sunParam)
        .sameTimeAs(sunParam);

SunPosition sun = sunParam.execute();
System.out.println(String.format(
        "The sun can be seen %.1f° clockwise from the North and "
        + "%.1f° above the horizon.\nIt is about %.0f km away right now.",
        sun.getAzimuth(),
        sun.getAltitude(),
        sun.getDistance()
));

MoonPosition moon = moonParam.execute();
System.out.println(String.format(
        "The moon can be seen %.1f° clockwise from the North and "
        + "%.1f° above the horizon.\nIt is about %.0f km away right now.",
        moon.getAzimuth(),
        moon.getAltitude(),
        moon.getDistance()
));
```

Note the invocations of `sameLocationAs()` and `sameTimeAs()`. Both methods are useful to copy the location and time parameter from other pararameter objects. The other parameter object won't need to be of the same type, so the `MoonPosition` can just "steal" the location and time from the `SunPosition`.

The result is:

```text
The sun can be seen 156,6° clockwise from the North and 33,0° above the horizon.
It is about 148075152 km away right now.
The moon can be seen 109,0° clockwise from the North and -9,5° above the horizon.
It is about 404629 km away right now.
```

The sun is in the southeast and about 33° above the horizon. The moon is to the east, but below the horizon, so it is not visible right now.



================================================
FILE: src/doc/docs/faq.md
================================================
# FAQ

## There is a different result on another website/app. Which one is right?

Probably both. Calculating sun and moon positions is rather complex, there is no universal formula that can be used. Other tools might also include the topology or other factors. If the difference is less than two minutes, it is within the acceptable tolerance.

There is no official definition of supermoon and micromoon, so the results may differ from other sources. _suncalc_ assumes a supermoon if the moon is closer than 360,000 km from Earth, and a micromoon if the moon is farther than 405,000 km from Earth.

## Can you enhance the precision?

The positions of the sun and moon are approximated by a rather simple set of formulae. The results have an accuracy of about a minute, which should be good enough for common applications (like sunrise/sunset timers). This library is targeted for mobile devices, or devices with low computing power, and the precision is acceptable for that target. A higher precision would involve perturbation tables of all planets, and would multiply the necessary computing load.

## Can you add other planets or stars?

No. It would add much more complexity to this library. It is not meant to be used for astronomical purposes.

## What about sea tide levels?

As the tide directly depends on the position of the sun and moon, there could be some kind of `Tide` calculator class. But it's not as easy as that. The tide at a location also depends on geological conditions and currents. This library could only give a very rough estimation. At best it would have no practical use, at worst it would even be life threatening if someone relied on the results. There are sophisitcated tools that have been made just for this purpose, e.g. [JTides](https://arachnoid.com/JTides/).


================================================
FILE: src/doc/docs/index.md
================================================
# commons-suncalc

A Java library for calculation of sun and moon positions and phases.

The source code can be found at [Codeberg](https://codeberg.org/shred/commons-suncalc) and is distributed under the terms of [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0).

## Accuracy

Astronomical calculations are far more complex than throwing a few numbers into an obscure formula and then getting a fully accurate result. There is always a tradeoff between accuracy and computing time.

This library has its focus on getting _acceptable_ results at low cost, so it can also run on mobile devices, or devices with a low computing power. The results have an accuracy of about a minute, which should be good enough for common applications (like sunrise/sunset timers), but is probably not sufficient for astronomical purposes.

## Dependencies and Requirements

_commons-suncalc_ requires at least Java 8, but has no other runtime dependencies. It can also be used on Android, API level 26 (Android 8.0 "Oreo") or higher.

!!! NOTE
    **For Android versions before API level 26**, please use [commons-suncalc v2](https://codeberg.org/shred/commons-suncalc/tree/v2). It is similar to this version, but does not use the Java Date/Time API. Also see the [commons-suncalc v2 documentation](https://shredzone.org/maven/commons-suncalc-v2/index.html).

## Installation

_commons-suncalc_ is available at Maven Central. Just add this snippet to your `pom.xml`:

```xml
<dependency>
  <groupId>org.shredzone.commons</groupId>
  <artifactId>commons-suncalc</artifactId>
  <version>$version</version>
</dependency>
```

Or use this snippet in your `build.gradle` (e.g. in Android Studio):

```groovy
dependencies {
    compile('org.shredzone.commons:commons-suncalc:$version')
}
```

Replace `$version` with your desired version. The latest version is: ![maven central](https://shredzone.org/maven-central/org.shredzone.commons/commons-suncalc/badge.svg)

## Java Module

Add this line to your module descriptor:

```
requires org.shredzone.commons.suncalc;
```

## References

This library bases on:

* "Astronomy on the Personal Computer", 4th edition, by Oliver Montenbruck and Thomas Pfleger
* "Astronomical Algorithms" by Jean Meeus

## Contribute

* Fork the [Source code at Codeberg](https://codeberg.org/shred/commons-suncalc). Feel free to send pull requests.
* Found a bug? Please [file a bug report](https://codeberg.org/shred/commons-suncalc/issues).

## License

_commons-suncalc_ is open source software. The source code is distributed under the terms of [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0).


================================================
FILE: src/doc/docs/migration.md
================================================
# Migration Guide

This document will help you migrate your code to the latest _suncalc_ version.

## Version 3.9
* `MoonIllumination` now permits to set an optional geolocation. If set, the results are topocentric. If not set, the result is geocentric, which was the default behavior. For this reason, no migration is necessary here.
* In previous versions, the geolocation was assumed to be 0°N 0°E ([Null Island](https://en.wikipedia.org/wiki/Null_Island)) if not set. This was not very useful in practice. Starting now, `execute()` will throw an exception if latitude or longitude is not set. To emulate the old behavior, use `at(0.0, 0.0)` as parameter. However, if you get an exception now, it rather means that you have used suncalc wrong.
* For the spectator's altitude above sea level, `elevation()` and `elevationFt()` is now used instead of `height()` and `heightFt()`. The old methods are marked as deprecated, but are still functional. Please change to the new methods, they are drop-in replacements.

## Version 3.6
* Due to a very old bug, `MoonPosition.getParallacticAngle()` gave completely wrong results. If you used that method in your code or tests, prepare to get totally different values now.

## Version 3.3
* Due to a very old bug, setting the `height()` had almost no impact on the result, which is obviously wrong (on sunset, a plane in the sky is still illuminated while the sun has just gone at the viewer's position). This bug has been fixed. If you are using `height()`, you will get correct results now. They may deviate by several minutes from the results of earlier versions.

## Version 3.1
* As it was an eternal source of confusion, `SunTime` and `MoonTime` now default to `fullCycle()`, so they always compute until all times have been found. To revert to the previous behavior, use the `oneDay()` parameter. There is also a new `limit()` parameter which limits the window to any end date.
* `isAlwaysUp()`, `isAlwaysDown()`, `getNoon()` and `getNadir()` now also use the given time window instead of the next 24 hours. Use `oneDay()` to revert to the previous behavior.

## Version 3.0
* _suncalc_ now requires at least Java 8 or Android 8.0 "Oreo" (API level 26). You can still use _suncalc_ v2 for Java 7 or Android API level 19 compatibility. The v2 branch is not discontinued and will still receive bugfixes.
* The outdated `Date` and `Calendar` classes have been replaced by the Java Date/Time API. All results are now returned as `ZonedDateTime` instances, and now also carry the timezone that was used for calculation. It is now much easier to use the result.
* Result rounding has been removed from this library, as it can easily be done by `ZoneDateTime.truncateTo()`. Note that even though the results now contain milliseconds, the precision is still only up to about a minute.
* The JSR305 null-safe annotations have been replaced by SpotBugs annotations. This _should_ have no impact on your code, as the method signatures themselves are unchanged. However, the compiler could now complain about some `null` dereferences that have been undetected before. Reason is that JSR305 uses the `javax.annotations` package, which leads to split packages in a Java 9 modular environment.


================================================
FILE: src/doc/docs/usage.md
================================================
# Usage

`commons-suncalc` offers six astronomical calculations:

* [SunTimes](./apidocs/org/shredzone/commons/suncalc/SunTimes.html): Sunrise, sunset, noon and nadir times.
* [MoonTimes](./apidocs/org/shredzone/commons/suncalc/MoonTimes.html): Moonrise and moonset times.
* [MoonPhase](./apidocs/org/shredzone/commons/suncalc/MoonPhase.html): Date and time of new moon, full moon and half moons.
* [SunPosition](./apidocs/org/shredzone/commons/suncalc/SunPosition.html): Position of the sun.
* [MoonPosition](./apidocs/org/shredzone/commons/suncalc/MoonPosition.html): Position of the moon.
* [MoonIllumination](./apidocs/org/shredzone/commons/suncalc/MoonIllumination.html): Moon phase and angle.

## Quick Start

All of the calculations mentioned above are invoked in the same way:

```java
ZonedDateTime dateTime =    // date, time and timezone of calculation
double lat, lng =           // geolocation
SunTimes times = SunTimes.compute()
            .on(dateTime)   // set a date
            .at(lat, lng)   // set a location
            .execute();     // get the results
System.out.println("Sunrise: " + times.getRise());
System.out.println("Sunset: " + times.getSet());
```

You invoke `compute()`, set your parameters, invoke `execute()`, and then get the result of the calculation as an object.

All parameters are passed in by a fluent builder-style interface. After retrieving the builder from `compute()`, you can chain the parameter setters, and finally call `execute()` to perform the computation.

```java
SunPosition pos = SunPosition.compute().today().at(12.3, 45.6).execute();
```

It is also possible to collect the parameters first, and execute the computation in a separate step:

```java
SunPosition.Parameters param = SunPosition.compute();
param.today();
param.at(12.3, 45.6);

SunPosition pos = param.execute();
```

The instance that is returned by `execute()` is immutable and only holds the calculation result of the current set of parameters. You can modify the parameters without changing the first result, then call `execute()` again for a second result.

```java
param.on(2016, 12, 24); // modify the param from above

SunPosition posAtChristmas = param.execute();
// pos from above is unchanged
```

## Time-based Parameters

All calculations need a date and time parameter. Some examples:

```java
// Now (the default)
SunPosition.compute().now();

// The same: Current time, local time zone
ZonedDateTime now = ZonedDateTime.now();
SunPosition.compute().on(now);

// August 21st, 2017, local midnight
SunPosition.compute().on(2017, 8, 21);

// Today (midnight), Berlin time zone
SunPosition.compute().today().timezone("Europe/Berlin");
```

The available time-based parameters are:

* `on(int year, int month, int date)`: Midnight of the given date. Note that `month` is counted from 1 (1 = January, 2 = February, …).
* `on(int year, int month, int date, int hour, int minute, int second)`: Given date and time.
* `on(ZonedDateTime dateTime)`: Given date, time, and timezone.
* `on(LocalDateTime dateTime)`: Given local date and time, without a timezone.
* `on(LocalDate date)`: Midnight of the given local date, without a timezone.
* `on(Instant instant)`: An instant without a timezone.
* `on(Calendar cal)`: Date, time and timezone as given in the old-fashioned `Calendar`. The `Calender` is copied and can safely be modified after that.
* `on(Date date)`: Date and time as given in the old-fashioned `Date`. The `Date` is copied and can safely be modified after that.
* `plusDays(int days)`: Adds the given number of days to the current date. `days` can also be negative, of course.
* `now()`: The current system date and time. This is the default.
* `midnight()`: Past midnight of the current date. It just truncates the time.
* `today()`: Identical to `.now().midnight()`.
* `tomorrow()`: Identical to `today().plusDays(1)`.
* `timezone(ZoneId tz)`: Use the given `ZoneId` as timezone. The current local time is unchanged (this is, it is not converted to the new timezone), so the order of parameters is not important.
* `timezone(String id)`: Same as above, but accepts a `String` for your convenience.
* `timezone(TimeZone tz)`: Same as above, but accepts an old-fashioned `TimeZone` object.
* `localTime()`: The system's timezone. This is the default.
* `utc()`: UTC timezone. Identical to `timezone("UTC")`.
* `sameTimeAs(TimeParameter<?> t)`: Copies the current date, time, and timezone from any other parameter object. Note that subsequent changes to the other object are not adopted.

If no time-based parameter is given, the current date and time, and the system's time zone is used.

!!! NOTE
    The accuracy of the results is decreasing for dates that are far in the future, or far in the past.

## Location-based Parameters

The geolocation is required, and `execute()` will throw an exception if the latitude or longitude is missing. The elevation is optional, and is 0 meters (sea level) if not set.

```java
// At 20.5°N, 18.3°E
SunPosition.compute().at(20.5, 18.3);

// The same, but more verbose
SunPosition.compute().latitude(20.5).longitude(18.3);

// Use arrays for coordinate constants
final double[] COLOGNE = new double[] { 50.938056, 6.956944 };
SunPosition.compute().at(COLOGNE);
```

There are two exceptions:

* [`MoonIllumination`](./apidocs/org/shredzone/commons/suncalc/MoonIllumination.Parameters.html):  If the geolocation is set, the result is topocentric. If the geolocation is unset, the result is geocentric.
* [`MoonPhase`](./apidocs/org/shredzone/commons/suncalc/MoonPhase.Parameters.html): The geolocation is not used here.

The available location-based parameters are:

* `at(double lat, double lng)`: Latitude and longitude to be used for computation.
* `at(double[] coords)`: Accepts an array of 2 values (latitude, longitude) or 3 values (latitude, longitude, elevation).
* `latitude(double lat)`: Verbose way to set the latitude only.
* `longitude(double lng)`: Verbose way to set the longitude only.
* `latitude(int d, int m, double s)`: Set the latitude in degrees, minutes, seconds and fraction of seconds.
* `longitude(int d, int m, double s)`: Set the longitude in degrees, minutes, seconds and fraction of seconds.
* `elevation(double h)`: Elevation above sea level, in meters. Sea level is used by default.
* `elevationFt(double h)`: Elevation above sea level, in foot. Sea level is used by default.
* `sameLocationAs(LocationParameter<?> l)`: Copies the current location and elevation from any other parameter object. Note that subsequent changes to the other object are not adoped.

!!! NOTE
    `elevation` cannot be negative. If you pass in a negative elevation, it is silently changed to the accepted minimum of 0 meters. For this reason, it is safe to pass coordinates from satellite-based navigation systems without range checking.

## Window-based Parameters

By default, [`SunTimes`](./apidocs/org/shredzone/commons/suncalc/SunTimes.Parameters.html) and [`MoonTimes`](./apidocs/org/shredzone/commons/suncalc/MoonTimes.Parameters.html) will calculate a full cycle of the sun or moon. This means that rise, set, noon, and nadir times may be more than 24 hours ahead. You can limit this time window.

The available window-based parameters are:

* `limit(Duration duration)`: Limits the time window to the given duration. A reverse time window is implicitly set if this value is negative.
* `oneDay()`: Limits the time window to 24 hours.
* `fullCycle()`: No limit. Calculation will find all rise, set, noon, and nadir times.
* `reverse()`: Sets a reverse time window. It will end at the given time. The rise, set, noon, and nadir times will be in the past. You can also pass a negative duration as `limit()`.
* `forward()`: Sets a forward time window. It will start at the given time. The rise, set, noon, and nadir times will be in the future. This is the default.
* `sameWindowAs(WindowParameter<?> w)`: Copies the current time window from any other parameter object. Note that subsequent changes to the other object are not adoped.

If the sun or moon does not rise or set within the given window, the appropriate getters return `null`. You can check if the sun or moon is always above or below the horizon, by checking `isAlwaysUp()` and `isAlwaysDown()`.

## Twilight

<img src="twilights.svg" alt="Twilight Zones" align="right" width="550px"/>

By default, [`SunTimes`](./apidocs/org/shredzone/commons/suncalc/SunTimes.Parameters.html) calculates the time of the visual sunrise and sunset. This means that `getRise()` returns the instant when the sun just starts to rise above the horizon, and `getSet()` returns the instant when the sun just disappeared from the horizon. [Atmospheric refraction](https://en.wikipedia.org/wiki/Atmospheric_refraction) is taken into account.

There are other interesting [twilight](https://en.wikipedia.org/wiki/Twilight) angles available. You can set them via the `twilight()` parameter, by using one of the [`SunTimes.Twilight`](./apidocs/org/shredzone/commons/suncalc/SunTimes.Twilight.html) constants:

| Constant       | Description | Angle of the Sun | Topocentric |
| -------------- | ----------- | ----------------:|:-----------:|
| `VISUAL`       | The moment when the visual upper edge of the sun crosses the horizon. This is the default. | | yes |
| `VISUAL_LOWER` | The moment when the visual lower edge of the sun crosses the horizon. | | yes |
| `ASTRONOMICAL` | [Astronomical twilight](https://en.wikipedia.org/wiki/Twilight#Astronomical_twilight) | -18° | no |
| `NAUTICAL`     | [Nautical twilight](https://en.wikipedia.org/wiki/Twilight#Nautical_twilight) | -12° | no |
| `CIVIL`        | [Civil twilight](https://en.wikipedia.org/wiki/Twilight#Civil_twilight) | -6° | no |
| `HORIZON`      | The moment when the center of the sun crosses the horizon. | 0° | no |
| `GOLDEN_HOUR`  | Transition from daylight to [Golden Hour](https://en.wikipedia.org/wiki/Golden_hour_%28photography%29) | 6° | no |
| `BLUE_HOUR`    | Transition from [Golden Hour](https://en.wikipedia.org/wiki/Golden_hour_%28photography%29) to [Blue Hour](https://en.wikipedia.org/wiki/Blue_hour) | -4° | no |
| `NIGHT_HOUR`   | Transition from [Blue Hour](https://en.wikipedia.org/wiki/Blue_hour) to night | -8° | no |

The illustration shows the transitions of each twilight constant. If you want to get the duration of a twilight, you need to calculate the times of both transitions of the twilight. For example, to get the beginning and ending of the civil twilight, you need to calculate both the `VISUAL` and the `CIVIL` twilight transition times.

Alternatively you can also pass any other angle (in degrees) to `twilight()`.

!!! NOTE
    Only `VISUAL` and `VISUAL_LOWER` are topocentric. They refer to the visual edge of the sun, take account of the `elevation` parameter, and compensate atmospheric refraction.
    
    All other twilights are geocentric and heliocentric. The `elevation` parameter is then ignored, and atmospheric refraction is not compensated.

Example:

```java
SunTimes.compute().twilight(SunTimes.Twilight.GOLDEN_HOUR);
```

## Phase

By default, [`MoonPhase`](./apidocs/org/shredzone/commons/suncalc/MoonPhase.Parameters.html) calculates the date of the next new moon. If you want to compute the date of another phase, you can set it via the `phase()` parameter, by using one of the [`MoonPhase.Phase`](./apidocs/org/shredzone/commons/suncalc/MoonPhase.Phase.html) constants:

| Constant          | Description | Angle |
| ----------------- | ----------- | -----:|
| `NEW_MOON`        | Moon is not illuminated (new moon). This is the default. | 0° |
| `WAXING_CRESCENT` | Waxing crescent moon. | 45° |
| `FIRST_QUARTER`   | Half of the waxing moon is illuminated. | 90° |
| `WAXING_GIBBOUS`  | Waxing gibbous moon. | 135° |
| `FULL_MOON`       | Moon is fully illuminated. | 180° |
| `WANING_GIBBOUS`  | Waning gibbous moon. | 225° |
| `LAST_QUARTER`    | Half of the waning moon is illuminated. | 270° |
| `WANING_CRESCENT` | Waning crescent moon. | 315° |

Alternatively you can also pass any other angle (in degrees) to `phase()`.

Example:

```java
MoonPhase.compute().phase(MoonPhase.Phase.FULL_MOON);
```


================================================
FILE: src/doc/mkdocs.yml
================================================
site_name: commons-suncalc
site_author: Richard Körber
site_url: https://commons.shredzone.org/suncalc
site_dir: target/site/
repo_url: https://codeberg.org/shred/commons-suncalc
edit_uri: ''
use_directory_urls: false
theme:
    name: readthedocs
    custom_dir: theme/
    highlightjs: false
markdown_extensions:
  - admonition
  - codehilite
  - toc:
      permalink: true
nav:
  - 'index.md'
  - 'usage.md'
  - 'examples.md'
  - 'faq.md'
  - 'JavaDocs': apidocs/index.html
  - 'migration.md'


================================================
FILE: src/doc/theme/breadcrumbs.html
================================================


================================================
FILE: src/doc/theme/css/font.css
================================================
/*
 * Lato
 * Copyright 2010-2011 tyPoland Lukasz Dziedzic
 * SIL Open Font License, 1.1
 */
@font-face {
  font-family: 'Lato';
  font-style: normal;
  font-weight: 400;
  src: local('Lato Regular'), local('Lato-Regular'), url('../fonts/lato-v15-latin-regular.woff') format('woff');
}

/*
 * Roboto Slab
 * Copyright 2013 Google
 * Apache License, version 2.0
 */
@font-face {
  font-family: 'Roboto Slab';
  font-style: normal;
  font-weight: 400;
  src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'), url('../fonts/roboto-slab-v8-latin-regular.woff') format('woff');
}

/*
 * Inconsolata
 * Copyright 2006 The Inconsolata Project Authors
 * SIL Open Font License, 1.1
 */
@font-face {
  font-family: 'Inconsolata';
  font-style: normal;
  font-weight: 400;
  src: local('Inconsolata Regular'), local('Inconsolata-Regular'), url('../fonts/inconsolata-v17-latin-regular.woff') format('woff');
}


================================================
FILE: src/doc/theme/css/theme_custom.css
================================================

.wy-nav-content {
    max-width: none;
}

.codehilite pre code {
    font-size: 15px;
}

table {
    margin-bottom: 2rem;
}

footer {
    margin-top: 2rem;
}


================================================
FILE: src/doc/theme/css/theme_pygments.css
================================================

/* github pygments theme by @jwarby, https://github.com/jwarby/jekyll-pygments-themes */
.codehilite .hll { background-color: #ffffcc }
.codehilite .c { color: #999988; font-style: italic }
.codehilite .err { color: #a61717; background-color: #e3d2d2 }
.codehilite .k { color: #000000; font-weight: bold }
.codehilite .o { color: #000000; font-weight: bold }
.codehilite .cm { color: #999988; font-style: italic }
.codehilite .cp { color: #999999; font-weight: bold; font-style: italic }
.codehilite .c1 { color: #999988; font-style: italic }
.codehilite .cs { color: #999999; font-weight: bold; font-style: italic }
.codehilite .gd { color: #000000; background-color: #ffdddd }
.codehilite .ge { color: #000000; font-style: italic }
.codehilite .gr { color: #aa0000 }
.codehilite .gh { color: #999999 }
.codehilite .gi { color: #000000; background-color: #ddffdd }
.codehilite .go { color: #888888 }
.codehilite .gp { color: #555555 }
.codehilite .gs { font-weight: bold }
.codehilite .gu { color: #aaaaaa }
.codehilite .gt { color: #aa0000 }
.codehilite .kc { color: #000000; font-weight: bold }
.codehilite .kd { color: #000000; font-weight: bold }
.codehilite .kn { color: #000000; font-weight: bold }
.codehilite .kp { color: #000000; font-weight: bold }
.codehilite .kr { color: #000000; font-weight: bold }
.codehilite .kt { color: #445588; font-weight: bold }
.codehilite .m { color: #009999 }
.codehilite .s { color: #d01040 }
.codehilite .na { color: #008080 }
.codehilite .nb { color: #0086B3 }
.codehilite .nc { color: #445588; font-weight: bold }
.codehilite .no { color: #008080 }
.codehilite .nd { color: #3c5d5d; font-weight: bold }
.codehilite .ni { color: #800080 }
.codehilite .ne { color: #990000; font-weight: bold }
.codehilite .nf { color: #990000; font-weight: bold }
.codehilite .nl { color: #990000; font-weight: bold }
.codehilite .nn { color: #555555 }
.codehilite .nt { color: #000080 }
.codehilite .nv { color: #008080 }
.codehilite .ow { color: #000000; font-weight: bold }
.codehilite .w { color: #bbbbbb }
.codehilite .mf { color: #009999 }
.codehilite .mh { color: #009999 }
.codehilite .mi { color: #009999 }
.codehilite .mo { color: #009999 }
.codehilite .sb { color: #d01040 }
.codehilite .sc { color: #d01040 }
.codehilite .sd { color: #d01040 }
.codehilite .s2 { color: #d01040 }
.codehilite .se { color: #d01040 }
.codehilite .sh { color: #d01040 }
.codehilite .si { color: #d01040 }
.codehilite .sx { color: #d01040 }
.codehilite .sr { color: #009926 }
.codehilite .s1 { color: #d01040 }
.codehilite .ss { color: #990073 }
.codehilite .bp { color: #999999 }
.codehilite .vc { color: #008080 }
.codehilite .vg { color: #008080 }
.codehilite .vi { color: #008080 }
.codehilite .il { color: #009999 }


================================================
FILE: src/doc/theme/footer.html
================================================
<footer>
  <div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
    {% if page.next_page %}
      <a href="{{ page.next_page.url|url }}" class="btn btn-neutral float-right" title="{{ page.next_page.title }}">Next <span class="icon icon-circle-arrow-right"></span></a>
    {% endif %}
    {% if page.previous_page %}
      <a href="{{ page.previous_page.url|url }}" class="btn btn-neutral" title="{{ page.previous_page.title }}"><span class="icon icon-circle-arrow-left"></span> Previous</a>
    {% endif %}
  </div>
</footer>


================================================
FILE: src/doc/theme/main.html
================================================
{% extends "base.html" %}

{% block styles %}
<link rel="stylesheet" href="{{ 'css/font.css'|url }}" />
<link rel="stylesheet" href="{{ 'css/theme.css'|url }}" />
<link rel="stylesheet" href="{{ 'css/theme_extra.css'|url }}" />
<link rel="stylesheet" href="{{ 'css/theme_custom.css'|url }}" />
<link rel="stylesheet" href="{{ 'css/theme_pygments.css'|url }}" />
{%- for path in config['extra_css'] %}
<link href="{{ path|url }}" rel="stylesheet" />
{%- endfor %}
{% endblock %}

{% block repo %}
<a href="https://shredzone.org/maven/commons-suncalc/apidocs/index.html">API javadoc</a>s
{% endblock %}


================================================
FILE: src/main/java/module-info.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2020 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

/**
 * Module definition for commons-suncalc.
 */
module org.shredzone.commons.suncalc {
    requires static com.github.spotbugs.annotations;

    exports org.shredzone.commons.suncalc;
    exports org.shredzone.commons.suncalc.param;
}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/MoonIllumination.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc;

import static java.lang.Math.*;

import org.shredzone.commons.suncalc.param.Builder;
import org.shredzone.commons.suncalc.param.GenericParameter;
import org.shredzone.commons.suncalc.param.LocationParameter;
import org.shredzone.commons.suncalc.param.TimeParameter;
import org.shredzone.commons.suncalc.util.BaseBuilder;
import org.shredzone.commons.suncalc.util.JulianDate;
import org.shredzone.commons.suncalc.util.Moon;
import org.shredzone.commons.suncalc.util.Sun;
import org.shredzone.commons.suncalc.util.Vector;

/**
 * Calculates the illumination of the moon.
 * <p>
 * Starting with v3.9, a geolocation can be set optionally. If set, the results will be
 * topocentric, relative to the given location. If not set, the result is geocentric,
 * which was the standard behavior before v3.9.
 */
public class MoonIllumination {

    private final double fraction;
    private final double phase;
    private final double angle;
    private final double elongation;
    private final double radius;
    private final double crescentWidth;

    private MoonIllumination(double fraction, double phase, double angle,
                             double elongation, double radius, double crescentWidth) {
        this.fraction = fraction;
        this.phase = phase;
        this.angle = angle;
        this.elongation = elongation;
        this.radius = radius;
        this.crescentWidth = crescentWidth;
    }

    /**
     * Starts the computation of {@link MoonIllumination}.
     *
     * @return {@link Parameters} to set.
     */
    public static Parameters compute() {
        return new MoonIlluminationBuilder();
    }

    /**
     * Collects all parameters for {@link MoonIllumination}.
     */
    public interface Parameters extends
            GenericParameter<Parameters>,
            TimeParameter<Parameters>,
            LocationParameter<Parameters>,
            Builder<MoonIllumination> {

        /**
         * Clears the geolocation, so the result will be geocentric.
         *
         * @return itself
         * @since 3.9
         */
        Parameters geocentric();

    }

    /**
     * Builder for {@link MoonIllumination}. Performs the computations based on the
     * parameters, and creates a {@link MoonIllumination} object that holds the result.
     */
    private static class MoonIlluminationBuilder extends BaseBuilder<Parameters> implements Parameters {
        @Override
        public Parameters geocentric() {
            clearLocation();
            return this;
        }

        @Override
        public MoonIllumination execute() {
            JulianDate t = getJulianDate();
            Vector s = Sun.position(t);
            Vector m = Moon.position(t);

            double phi = PI - acos(m.dot(s) / (m.getR() * s.getR()));
            Vector sunMoon = m.cross(s);
            double angle = atan2(
                    cos(s.getTheta()) * sin(s.getPhi() - m.getPhi()),
                    sin(s.getTheta()) * cos(m.getTheta()) - cos(s.getTheta()) * sin(m.getTheta()) * cos(s.getPhi() - m.getPhi())
            );

            Vector sTopo, mTopo;
            if (hasLocation()) {
                sTopo = Sun.positionTopocentric(t, getLatitudeRad(), getLongitudeRad(), getElevation());
                mTopo = Moon.positionTopocentric(t, getLatitudeRad(), getLongitudeRad(), getElevation());
            } else {
                sTopo = s;
                mTopo = m;
            }

            double r = mTopo.subtract(sTopo).norm();
            double re = sTopo.norm();
            double d = mTopo.norm();
            double elongation = acos((d*d + re*re - r*r) / (2.0*d*re));
            double moonRadius = Moon.angularRadius(mTopo.getR());
            double crescentWidth = moonRadius * (1 - cos(elongation));

            return new MoonIllumination(
                            (1 + cos(phi)) / 2,
                            toDegrees(phi * signum(sunMoon.getTheta())),
                            toDegrees(angle),
                            toDegrees(elongation),
                            toDegrees(moonRadius),
                            toDegrees(crescentWidth));
        }
    }

    /**
     * Illuminated fraction. {@code 0.0} indicates new moon, {@code 1.0} indicates full
     * moon.
     */
    public double getFraction() {
        return fraction;
    }

    /**
     * Moon phase. Starts at {@code -180.0} (new moon, waxing), passes {@code 0.0} (full
     * moon) and moves toward {@code 180.0} (waning, new moon).
     * <p>
     * Note that for historical reasons, the range of this phase is different to the
     * moon phase angle used in {@link MoonPhase}.
     */
    public double getPhase() {
        return phase;
    }

    /**
     * The angle of the moon illumination relative to earth. The moon is waxing if the
     * angle is negative, and waning if positive.
     * <p>
     * By subtracting {@link MoonPosition#getParallacticAngle()} from {@link #getAngle()},
     * one can get the zenith angle of the moons bright limb (anticlockwise). The zenith
     * angle can be used do draw the moon shape from the observer's perspective (e.g. the
     * moon lying on its back).
     */
    public double getAngle() {
        return angle;
    }

    /**
     * The closest {@link MoonPhase.Phase} that is matching the moon's angle.
     *
     * @return Closest {@link MoonPhase.Phase}
     * @since 3.5
     */
    public MoonPhase.Phase getClosestPhase() {
        return MoonPhase.Phase.toPhase(phase + 180.0);
    }

    /**
     * The elongation, which is the angular distance between the moon and the sun as
     * observed from a specific location on earth.
     *
     * @return Elongation between moon and sun, in degrees.
     * @since 3.9
     */
    public double getElongation() {
        return elongation;
    }

    /**
     * The radius of the moon disk, as observed from a specific location on earth.
     *
     * @return Moon radius, in degrees.
     * @since 3.9
     */
    public double getRadius() {
        return radius;
    }

    /**
     * The width of the moon crescent, as observed from a specific location on earth.
     *
     * @return Crescent width, in degrees.
     * @since 3.9
     */
    public double getCrescentWidth() {
        return crescentWidth;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("MoonIllumination[fraction=").append(fraction);
        sb.append(", phase=").append(phase);
        sb.append("°, angle=").append(angle);
        sb.append("°, elongation=").append(elongation);
        sb.append("°, radius=").append(radius);
        sb.append("°, crescentWidth=").append(crescentWidth);
        sb.append("°]");
        return sb.toString();
    }

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/MoonPhase.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2018 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc;

import static java.lang.Math.PI;
import static java.lang.Math.toRadians;
import static org.shredzone.commons.suncalc.util.ExtendedMath.PI2;

import java.time.ZonedDateTime;

import org.shredzone.commons.suncalc.param.Builder;
import org.shredzone.commons.suncalc.param.GenericParameter;
import org.shredzone.commons.suncalc.param.TimeParameter;
import org.shredzone.commons.suncalc.util.BaseBuilder;
import org.shredzone.commons.suncalc.util.JulianDate;
import org.shredzone.commons.suncalc.util.Moon;
import org.shredzone.commons.suncalc.util.Pegasus;
import org.shredzone.commons.suncalc.util.Sun;
import org.shredzone.commons.suncalc.util.Vector;

/**
 * Calculates the date and time when the moon reaches the desired phase.
 * <p>
 * Note: Due to the simplified formulas used in suncalc, the returned time can have an
 * error of several minutes.
 */
public class MoonPhase {

    private final ZonedDateTime time;
    private final double distance;

    private MoonPhase(ZonedDateTime time, double distance) {
        this.time = time;
        this.distance = distance;
    }

    /**
     * Starts the computation of {@link MoonPhase}.
     *
     * @return {@link Parameters} to set.
     */
    public static Parameters compute() {
        return new MoonPhaseBuilder();
    }

    /**
     * Collects all parameters for {@link MoonPhase}.
     */
    public interface Parameters extends
            GenericParameter<Parameters>,
            TimeParameter<Parameters>,
            Builder<MoonPhase> {

        /**
         * Sets the desired {@link Phase}.
         * <p>
         * Defaults to {@link Phase#NEW_MOON}.
         *
         * @param phase
         *            {@link Phase} to be used.
         * @return itself
         */
        Parameters phase(Phase phase);

        /**
         * Sets a free phase to be used.
         *
         * @param phase
         *            Desired phase, in degrees. 0 = new moon, 90 = first quarter, 180 =
         *            full moon, 270 = third quarter.
         * @return itself
         */
        Parameters phase(double phase);
    }

    /**
     * Enumeration of moon phases.
     */
    public enum Phase {

        /**
         * New moon.
         */
        NEW_MOON(0.0),

        /**
         * Waxing crescent moon.
         *
         * @since 3.5
         */
        WAXING_CRESCENT(45.0),

        /**
         * Waxing half moon.
         */
        FIRST_QUARTER(90.0),

        /**
         * Waxing gibbous moon.
         *
         * @since 3.5
         */
        WAXING_GIBBOUS(135.0),

        /**
         * Full moon.
         */
        FULL_MOON(180.0),

        /**
         * Waning gibbous moon.
         *
         * @since 3.5
         */
        WANING_GIBBOUS(225.0),

        /**
         * Waning half moon.
         */
        LAST_QUARTER(270.0),

        /**
         * Waning crescent moon.
         *
         * @since 3.5
         */
        WANING_CRESCENT(315.0);

        /**
         * Converts an angle to the closest matching moon phase.
         *
         * @param angle
         *         Moon phase angle, in degrees. 0 = New Moon, 180 = Full Moon. Angles
         *         outside the [0,360) range are normalized into that range.
         * @return Closest Phase that is matching that angle.
         * @since 3.5
         */
        public static Phase toPhase(double angle) {
            // bring into range 0.0 .. 360.0
            double normalized = angle % 360.0;
            if (normalized < 0.0) {
                normalized += 360.0;
            }

            if (normalized < 22.5) {
                return NEW_MOON;
            }
            if (normalized < 67.5) {
                return WAXING_CRESCENT;
            }
            if (normalized < 112.5) {
                return FIRST_QUARTER;
            }
            if (normalized < 157.5) {
                return WAXING_GIBBOUS;
            }
            if (normalized < 202.5) {
                return FULL_MOON;
            }
            if (normalized < 247.5) {
                return WANING_GIBBOUS;
            }
            if (normalized < 292.5) {
                return LAST_QUARTER;
            }
            if (normalized < 337.5) {
                return WANING_CRESCENT;
            }
            return NEW_MOON;
        }

        private final double angle;
        private final double angleRad;

        Phase(double angle) {
            this.angle = angle;
            this.angleRad = toRadians(angle);
        }

        /**
         * Returns the moons's angle in reference to the sun, in degrees.
         */
        public double getAngle() {
            return angle;
        }

        /**
         * Returns the moons's angle in reference to the sun, in radians.
         */
        public double getAngleRad() {
            return angleRad;
        }
    }

    /**
     * Builder for {@link MoonPhase}. Performs the computations based on the parameters,
     * and creates a {@link MoonPhase} object that holds the result.
     */
    private static class MoonPhaseBuilder extends BaseBuilder<Parameters> implements Parameters {
        private static final double SUN_LIGHT_TIME_TAU = 8.32 / (1440.0 * 36525.0);

        private double phase = Phase.NEW_MOON.getAngleRad();

        @Override
        public Parameters phase(Phase phase) {
            this.phase = phase.getAngleRad();
            return this;
        }

        @Override
        public Parameters phase(double phase) {
            this.phase = toRadians(phase);
            return this;
        }

        @Override
        public MoonPhase execute() {
            final JulianDate jd = getJulianDate();

            double dT = 7.0 / 36525.0;                      // step rate: 1 week
            double accuracy = (0.5 / 1440.0) / 36525.0;     // accuracy: 30 seconds

            double t0 = jd.getJulianCentury();
            double t1 = t0 + dT;

            double d0 = moonphase(jd, t0);
            double d1 = moonphase(jd, t1);

            while (d0 * d1 > 0.0 || d1 < d0) {
                t0 = t1;
                d0 = d1;
                t1 += dT;
                d1 = moonphase(jd, t1);
            }

            double tphase = Pegasus.calculate(t0, t1, accuracy, x -> moonphase(jd, x));
            JulianDate tjd = jd.atJulianCentury(tphase);
            return new MoonPhase(tjd.getDateTime(), Moon.positionEquatorial(tjd).getR());
        }

        /**
         * Calculates the position of the moon at the given phase.
         *
         * @param jd
         *            Base Julian date
         * @param t
         *            Ephemeris time
         * @return difference angle of the sun's and moon's position
         */
        private double moonphase(JulianDate jd, double t) {
            Vector sun = Sun.positionEquatorial(jd.atJulianCentury(t - SUN_LIGHT_TIME_TAU));
            Vector moon = Moon.positionEquatorial(jd.atJulianCentury(t));
            double diff = moon.getPhi() - sun.getPhi() - phase; //NOSONAR: false positive
            while (diff < 0.0) {
                diff += PI2;
            }
            return ((diff + PI) % PI2) - PI;
        }

    }

    /**
     * Date and time of the desired moon phase. The time is rounded to full minutes.
     */
    public ZonedDateTime getTime() {
        return time;
    }

    /**
     * Geocentric distance of the moon at the given phase, in kilometers.
     *
     * @since 3.4
     */
    public double getDistance() { return distance; }

    /**
     * Checks if the moon is in a SuperMoon position.
     * <p>
     * Note that there is no official definition of supermoon. Suncalc will assume a
     * supermoon if the center of the moon is closer than 360,000 km to the center of
     * Earth. Usually only full moons or new moons are candidates for supermoons.
     *
     * @since 3.4
     */
    public boolean isSuperMoon() {
        return distance < 360000.0;
    }

    /**
     * Checks if the moon is in a MicroMoon position.
     * <p>
     * Note that there is no official definition of micromoon. Suncalc will assume a
     * micromoon if the center of the moon is farther than 405,000 km from the center of
     * Earth. Usually only full moons or new moons are candidates for micromoons.
     *
     * @since 3.4
     */
    public boolean isMicroMoon() {
        return distance > 405000.0;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("MoonPhase[time=").append(time);
        sb.append(", distance=").append(distance);
        sb.append(" km]");
        return sb.toString();
    }

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/MoonPosition.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc;

import static java.lang.Math.*;
import static org.shredzone.commons.suncalc.util.ExtendedMath.equatorialToHorizontal;
import static org.shredzone.commons.suncalc.util.ExtendedMath.refraction;

import org.shredzone.commons.suncalc.param.Builder;
import org.shredzone.commons.suncalc.param.GenericParameter;
import org.shredzone.commons.suncalc.param.LocationParameter;
import org.shredzone.commons.suncalc.param.TimeParameter;
import org.shredzone.commons.suncalc.util.BaseBuilder;
import org.shredzone.commons.suncalc.util.JulianDate;
import org.shredzone.commons.suncalc.util.Moon;
import org.shredzone.commons.suncalc.util.Vector;

/**
 * Calculates the position of the moon.
 */
public class MoonPosition {

    private final double azimuth;
    private final double altitude;
    private final double trueAltitude;
    private final double distance;
    private final double parallacticAngle;

    private MoonPosition(double azimuth, double altitude, double trueAltitude, double distance, double parallacticAngle) {
        this.azimuth = (toDegrees(azimuth) + 180.0) % 360.0;
        this.altitude = toDegrees(altitude);
        this.trueAltitude = toDegrees(trueAltitude);
        this.distance = distance;
        this.parallacticAngle = toDegrees(parallacticAngle);
    }

    /**
     * Starts the computation of {@link MoonPosition}.
     *
     * @return {@link Parameters} to set.
     */
    public static Parameters compute() {
        return new MoonPositionBuilder();
    }

    /**
     * Collects all parameters for {@link MoonPosition}.
     */
    public interface Parameters extends
            GenericParameter<Parameters>,
            LocationParameter<Parameters>,
            TimeParameter<Parameters>,
            Builder<MoonPosition> {
    }

    /**
     * Builder for {@link MoonPosition}. Performs the computations based on the
     * parameters, and creates a {@link MoonPosition} object that holds the result.
     */
    private static class MoonPositionBuilder extends BaseBuilder<Parameters> implements Parameters {
        @Override
        public MoonPosition execute() {
            if (!hasLocation()) {
                throw new IllegalArgumentException("Geolocation is missing.");
            }

            JulianDate t = getJulianDate();

            double phi = getLatitudeRad();
            double lambda = getLongitudeRad();

            Vector mc = Moon.position(t);
            double h = t.getGreenwichMeanSiderealTime() + lambda - mc.getPhi();

            Vector horizontal = equatorialToHorizontal(h, mc.getTheta(), mc.getR(), phi);

            double hRef = refraction(horizontal.getTheta());

            double pa = atan2(sin(h), tan(phi) * cos(mc.getTheta()) - sin(mc.getTheta()) * cos(h));

            return new MoonPosition(
                    horizontal.getPhi(),
                    horizontal.getTheta() + hRef,
                    horizontal.getTheta(),
                    mc.getR(),
                    pa);
        }
    }

    /**
     * Moon altitude above the horizon, in degrees.
     * <p>
     * {@code 0.0} means the moon's center is at the horizon, {@code 90.0} at the zenith
     * (straight over your head). Atmospheric refraction is taken into account.
     *
     * @see #getTrueAltitude()
     */
    public double getAltitude() {
        return altitude;
    }

    /**
     * The true moon altitude above the horizon, in degrees.
     * <p>
     * {@code 0.0} means the moon's center is at the horizon, {@code 90.0} at the zenith
     * (straight over your head).
     *
     * @see #getAltitude()
     * @since 3.8
     */
    public double getTrueAltitude() {
        return trueAltitude;
    }

    /**
     * Moon azimuth, in degrees, north-based.
     * <p>
     * This is the direction along the horizon, measured from north to east. For example,
     * {@code 0.0} means north, {@code 135.0} means southeast, {@code 270.0} means west.
     */
    public double getAzimuth() {
        return azimuth;
    }

    /**
     * Distance to the moon in kilometers.
     */
    public double getDistance() {
        return distance;
    }

    /**
     * Parallactic angle of the moon, in degrees.
     */
    public double getParallacticAngle() {
        return parallacticAngle;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("MoonPosition[azimuth=").append(azimuth);
        sb.append("°, altitude=").append(altitude);
        sb.append("°, true altitude=").append(trueAltitude);
        sb.append("°, distance=").append(distance);
        sb.append(" km, parallacticAngle=").append(parallacticAngle);
        sb.append("°]");
        return sb.toString();
    }

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/MoonTimes.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc;

import static java.lang.Math.ceil;
import static java.lang.Math.floor;
import static org.shredzone.commons.suncalc.util.ExtendedMath.apparentRefraction;
import static org.shredzone.commons.suncalc.util.ExtendedMath.parallax;

import java.time.Duration;
import java.time.ZonedDateTime;

import edu.umd.cs.findbugs.annotations.Nullable;
import org.shredzone.commons.suncalc.param.Builder;
import org.shredzone.commons.suncalc.param.GenericParameter;
import org.shredzone.commons.suncalc.param.LocationParameter;
import org.shredzone.commons.suncalc.param.TimeParameter;
import org.shredzone.commons.suncalc.param.WindowParameter;
import org.shredzone.commons.suncalc.util.BaseBuilder;
import org.shredzone.commons.suncalc.util.JulianDate;
import org.shredzone.commons.suncalc.util.Moon;
import org.shredzone.commons.suncalc.util.QuadraticInterpolation;
import org.shredzone.commons.suncalc.util.Vector;

/**
 * Calculates the times of the moon.
 */
public final class MoonTimes {

    private final @Nullable ZonedDateTime rise;
    private final @Nullable ZonedDateTime set;
    private final boolean alwaysUp;
    private final boolean alwaysDown;

    private MoonTimes(@Nullable ZonedDateTime rise, @Nullable ZonedDateTime set,
              boolean alwaysUp, boolean alwaysDown) {
        this.rise = rise;
        this.set = set;
        this.alwaysUp = alwaysUp;
        this.alwaysDown = alwaysDown;
    }

    /**
     * Starts the computation of {@link MoonTimes}.
     *
     * @return {@link Parameters} to set.
     */
    public static Parameters compute() {
        return new MoonTimesBuilder();
    }

    /**
     * Collects all parameters for {@link MoonTimes}.
     */
    public static interface Parameters extends
            GenericParameter<Parameters>,
            LocationParameter<Parameters>,
            TimeParameter<Parameters>,
            WindowParameter<Parameters>,
            Builder<MoonTimes> {
    }

    /**
     * Builder for {@link MoonTimes}. Performs the computations based on the parameters,
     * and creates a {@link MoonTimes} object that holds the result.
     */
    private static class MoonTimesBuilder extends BaseBuilder<Parameters> implements Parameters {
        private double refraction = apparentRefraction(0.0);

        @Override
        public MoonTimes execute() {
            if (!hasLocation()) {
                throw new IllegalArgumentException("Geolocation is missing.");
            }

            JulianDate jd = getJulianDate();

            Double rise = null;
            Double set = null;
            boolean alwaysUp = false;
            boolean alwaysDown = false;
            double ye;

            int hourStep;
            double lowerLimitHours, upperLimitHours;
            if (getDuration().isNegative()) {
                hourStep = -1;
                lowerLimitHours = getDuration().toMillis() / (60 * 60 * 1000.0);
                upperLimitHours = 0.0;
            } else {
                hourStep = 1;
                lowerLimitHours = 0.0;
                upperLimitHours = getDuration().toMillis() / (60 * 60 * 1000.0);;
            }

            int hour = 0;
            int minHours = (int) floor(lowerLimitHours);
            int maxHours = (int) ceil(upperLimitHours);

            double y_minus = correctedMoonHeight(jd.atHour(hour - 1.0));
            double y_0 = correctedMoonHeight(jd.atHour(hour));
            double y_plus = correctedMoonHeight(jd.atHour(hour + 1.0));

            if (y_0 > 0.0) {
                alwaysUp = true;
            } else {
                alwaysDown = true;
            }

            while (hour <= maxHours && hour >= minHours) {
                QuadraticInterpolation qi = new QuadraticInterpolation(y_minus, y_0, y_plus);
                ye = qi.getYe();

                if (qi.getNumberOfRoots() == 1) {
                    double rt = qi.getRoot1() + hour;
                    if (y_minus < 0.0) {
                        if (rise == null && rt >= lowerLimitHours && rt < upperLimitHours) {
                            rise = rt;
                            alwaysDown = false;
                        }
                    } else {
                        if (set == null && rt >= lowerLimitHours && rt < upperLimitHours) {
                            set = rt;
                            alwaysUp = false;
                        }
                    }
                } else if (qi.getNumberOfRoots() == 2) {
                    if (rise == null) {
                        double rt = hour + (ye < 0.0 ? qi.getRoot2() : qi.getRoot1());
                        if (rt >= lowerLimitHours && rt < upperLimitHours) {
                            rise = rt;
                            alwaysDown = false;
                        }
                    }
                    if (set == null) {
                        double rt = hour + (ye < 0.0 ? qi.getRoot1() : qi.getRoot2());
                        if (rt >= lowerLimitHours && rt < upperLimitHours) {
                            set = rt;
                            alwaysUp = false;
                        }
                    }
                }

                if (rise != null && set != null) {
                    break;
                }

                hour += hourStep;
                if (hourStep > 0) {
                    y_minus = y_0;
                    y_0 = y_plus;
                    y_plus = correctedMoonHeight(jd.atHour(hour + 1.0));
                } else {
                    y_plus = y_0;
                    y_0 = y_minus;
                    y_minus = correctedMoonHeight(jd.atHour(hour - 1.0));
                }
            }

            return new MoonTimes(
                    rise != null ? jd.atHour(rise).getDateTime() : null,
                    set != null ? jd.atHour(set).getDateTime() : null,
                    alwaysUp,
                    alwaysDown);
        }

        /**
         * Computes the moon height at the given date and position.
         *
         * @param jd {@link JulianDate} to use
         * @return height, in radians
         */
        private double correctedMoonHeight(JulianDate jd) {
            Vector pos = Moon.positionHorizontal(jd, getLatitudeRad(), getLongitudeRad());
            double hc = parallax(getElevation(), pos.getR())
                            - refraction
                            - Moon.angularRadius(pos.getR());
            return pos.getTheta() - hc;
        }
    }

    /**
     * Moonrise time. {@code null} if the moon does not rise that day.
     */
    @Nullable
    public ZonedDateTime getRise() {
        return rise;
    }

    /**
     * Moonset time. {@code null} if the moon does not set that day.
     */
    @Nullable
    public ZonedDateTime getSet() {
        return set;
    }

    /**
     * {@code true} if the moon never rises/sets, but is always above the horizon.
     */
    public boolean isAlwaysUp() {
        return alwaysUp;
    }

    /**
     * {@code true} if the moon never rises/sets, but is always below the horizon.
     */
    public boolean isAlwaysDown() {
        return alwaysDown;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("MoonTimes[rise=").append(rise);
        sb.append(", set=").append(set);
        sb.append(", alwaysUp=").append(alwaysUp);
        sb.append(", alwaysDown=").append(alwaysDown);
        sb.append(']');
        return sb.toString();
    }

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/SunPosition.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc;

import static java.lang.Math.toDegrees;
import static org.shredzone.commons.suncalc.util.ExtendedMath.refraction;

import org.shredzone.commons.suncalc.param.Builder;
import org.shredzone.commons.suncalc.param.GenericParameter;
import org.shredzone.commons.suncalc.param.LocationParameter;
import org.shredzone.commons.suncalc.param.TimeParameter;
import org.shredzone.commons.suncalc.util.BaseBuilder;
import org.shredzone.commons.suncalc.util.JulianDate;
import org.shredzone.commons.suncalc.util.Sun;
import org.shredzone.commons.suncalc.util.Vector;

/**
 * Calculates the position of the sun.
 */
public class SunPosition {

    private final double azimuth;
    private final double altitude;
    private final double trueAltitude;
    private final double distance;

    private SunPosition(double azimuth, double altitude, double trueAltitude, double distance) {
        this.azimuth = (toDegrees(azimuth) + 180.0) % 360.0;
        this.altitude = toDegrees(altitude);
        this.trueAltitude = toDegrees(trueAltitude);
        this.distance = distance;
    }

    /**
     * Starts the computation of {@link SunPosition}.
     *
     * @return {@link Parameters} to set.
     */
    public static Parameters compute() {
        return new SunPositionBuilder();
    }

    /**
     * Collects all parameters for {@link SunPosition}.
     */
    public interface Parameters extends
            GenericParameter<Parameters>,
            LocationParameter<Parameters>,
            TimeParameter<Parameters>,
            Builder<SunPosition> {
    }

    /**
     * Builder for {@link SunPosition}. Performs the computations based on the parameters,
     * and creates a {@link SunPosition} object that holds the result.
     */
    private static class SunPositionBuilder extends BaseBuilder<Parameters> implements Parameters {
        @Override
        public SunPosition execute() {
            if (!hasLocation()) {
                throw new IllegalArgumentException("Geolocation is missing.");
            }

            JulianDate t = getJulianDate();

            Vector horizontal = Sun.positionHorizontal(t, getLatitudeRad(), getLongitudeRad());
            double hRef = refraction(horizontal.getTheta());

            return new SunPosition(horizontal.getPhi(),
                            horizontal.getTheta() + hRef,
                            horizontal.getTheta(),
                            horizontal.getR());
        }
    }

    /**
     * The visible sun altitude above the horizon, in degrees.
     * <p>
     * {@code 0.0} means the sun's center is at the horizon, {@code 90.0} at the zenith
     * (straight over your head). Atmospheric refraction is taken into account.
     *
     * @see #getTrueAltitude()
     */
    public double getAltitude() {
        return altitude;
    }

    /**
     * The true sun altitude above the horizon, in degrees.
     * <p>
     * {@code 0.0} means the sun's center is at the horizon, {@code 90.0} at the zenith
     * (straight over your head).
     *
     * @see #getAltitude()
     */
    public double getTrueAltitude() {
        return trueAltitude;
    }

    /**
     * Sun azimuth, in degrees, north-based.
     * <p>
     * This is the direction along the horizon, measured from north to east. For example,
     * {@code 0.0} means north, {@code 135.0} means southeast, {@code 270.0} means west.
     */
    public double getAzimuth() {
        return azimuth;
    }

    /**
     * Sun's distance, in kilometers.
     */
    public double getDistance() {
        return distance;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("SunPosition[azimuth=").append(azimuth);
        sb.append("°, altitude=").append(altitude);
        sb.append("°, true altitude=").append(trueAltitude);
        sb.append("°, distance=").append(distance).append(" km]");
        return sb.toString();
    }

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/SunTimes.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc;

import static java.lang.Math.*;
import static org.shredzone.commons.suncalc.util.ExtendedMath.*;

import java.time.Duration;
import java.time.ZonedDateTime;

import edu.umd.cs.findbugs.annotations.Nullable;
import org.shredzone.commons.suncalc.param.Builder;
import org.shredzone.commons.suncalc.param.GenericParameter;
import org.shredzone.commons.suncalc.param.LocationParameter;
import org.shredzone.commons.suncalc.param.TimeParameter;
import org.shredzone.commons.suncalc.param.WindowParameter;
import org.shredzone.commons.suncalc.util.BaseBuilder;
import org.shredzone.commons.suncalc.util.JulianDate;
import org.shredzone.commons.suncalc.util.QuadraticInterpolation;
import org.shredzone.commons.suncalc.util.Sun;
import org.shredzone.commons.suncalc.util.Vector;

/**
 * Calculates the rise and set times of the sun.
 */
public class SunTimes {

    private final @Nullable ZonedDateTime rise;
    private final @Nullable ZonedDateTime set;
    private final @Nullable ZonedDateTime noon;
    private final @Nullable ZonedDateTime nadir;
    private final boolean alwaysUp;
    private final boolean alwaysDown;

    private SunTimes(@Nullable ZonedDateTime rise, @Nullable ZonedDateTime set,
                     @Nullable ZonedDateTime noon, @Nullable ZonedDateTime nadir,
                     boolean alwaysUp, boolean alwaysDown) {
        this.rise = rise;
        this.set = set;
        this.noon = noon;
        this.nadir = nadir;
        this.alwaysUp = alwaysUp;
        this.alwaysDown = alwaysDown;
    }

    /**
     * Starts the computation of {@link SunTimes}.
     *
     * @return {@link Parameters} to set.
     */
    public static Parameters compute() {
        return new SunTimesBuilder();
    }

    /**
     * Collects all parameters for {@link SunTimes}.
     */
    public interface Parameters extends
            GenericParameter<Parameters>,
            LocationParameter<Parameters>,
            TimeParameter<Parameters>,
            WindowParameter<Parameters>,
            Builder<SunTimes> {

        /**
         * Sets the {@link Twilight} mode.
         * <p>
         * Defaults to {@link Twilight#VISUAL}.
         *
         * @param twilight
         *            {@link Twilight} mode to be used.
         * @return itself
         */
        Parameters twilight(Twilight twilight);

        /**
         * Sets the desired elevation angle of the sun. The sunrise and sunset times are
         * referring to the moment when the center of the sun passes this angle.
         *
         * @param angle
         *            Geocentric elevation angle, in degrees.
         * @return itself
         */
        Parameters twilight(double angle);
    }

    /**
     * Enumeration of predefined twilights.
     * <p>
     * The twilight angles use a geocentric reference, by definition. However,
     * {@link #VISUAL} and {@link #VISUAL_LOWER} are topocentric, and take the spectator's
     * elevation and the atmospheric refraction into account.
     *
     * @see <a href="https://en.wikipedia.org/wiki/Twilight">Wikipedia: Twilight</a>
     */
    public enum Twilight {

        /**
         * The moment when the visual upper edge of the sun crosses the horizon. This is
         * commonly referred to as "sunrise" and "sunset". Atmospheric refraction is taken
         * into account.
         * <p>
         * This is the default.
         */
        VISUAL(0.0, 1.0),

        /**
         * The moment when the visual lower edge of the sun crosses the horizon. This is
         * the ending of the sunrise and the starting of the sunset. Atmospheric
         * refraction is taken into account.
         */
        VISUAL_LOWER(0.0, -1.0),

        /**
         * The moment when the center of the sun crosses the horizon (0°).
         */
        HORIZON(0.0),

        /**
         * Civil twilight (-6°).
         */
        CIVIL(-6.0),

        /**
         * Nautical twilight (-12°).
         */
        NAUTICAL(-12.0),

        /**
         * Astronomical twilight (-18°).
         */
        ASTRONOMICAL(-18.0),

        /**
         * Golden hour (6°). The Golden hour is between {@link #GOLDEN_HOUR} and
         * {@link #BLUE_HOUR}. The Magic hour is between {@link #GOLDEN_HOUR} and
         * {@link #CIVIL}.
         *
         * @see <a href=
         *      "https://en.wikipedia.org/wiki/Golden_hour_(photography)">Wikipedia:
         *      Golden hour</a>
         */
        GOLDEN_HOUR(6.0),

        /**
         * Blue hour (-4°). The Blue hour is between {@link #NIGHT_HOUR} and
         * {@link #BLUE_HOUR}.
         *
         * @see <a href="https://en.wikipedia.org/wiki/Blue_hour">Wikipedia: Blue hour</a>
         */
        BLUE_HOUR(-4.0),

        /**
         * End of Blue hour (-8°).
         * <p>
         * "Night Hour" is not an official term, but just a name that is marking the
         * beginning/end of the Blue hour.
         */
        NIGHT_HOUR(-8.0);

        private final double angle;
        private final double angleRad;
        private final @Nullable Double position;

        Twilight(double angle) {
            this(angle, null);
        }

        Twilight(double angle, @Nullable Double position) {
            this.angle = angle;
            this.angleRad = toRadians(angle);
            this.position = position;
        }

        /**
         * Returns the sun's angle at the twilight position, in degrees.
         */
        public double getAngle() {
            return angle;
        }

        /**
         * Returns the sun's angle at the twilight position, in radians.
         */
        public double getAngleRad() {
            return angleRad;
        }

        /**
         * Returns {@code true} if this twilight position is topocentric. Then the
         * parallax and the atmospheric refraction is taken into account.
         */
        public boolean isTopocentric() {
            return position != null;
        }

        /**
         * Returns the angular position. {@code 0.0} means center of the sun. {@code 1.0}
         * means upper edge of the sun. {@code -1.0} means lower edge of the sun.
         * {@code null} means the angular position is not topocentric.
         */
        @Nullable
        private Double getAngularPosition() {
            return position;
        }
    }

    /**
     * Builder for {@link SunTimes}. Performs the computations based on the parameters,
     * and creates a {@link SunTimes} object that holds the result.
     */
    private static class SunTimesBuilder extends BaseBuilder<Parameters> implements Parameters {
        private double angle = Twilight.VISUAL.getAngleRad();
        private @Nullable Double position = Twilight.VISUAL.getAngularPosition();

        @Override
        public Parameters twilight(Twilight twilight) {
            this.angle = twilight.getAngleRad();
            this.position = twilight.getAngularPosition();
            return this;
        }

        @Override
        public Parameters twilight(double angle) {
            this.angle = toRadians(angle);
            this.position = null;
            return this;
        }

        @Override
        public SunTimes execute() {
            if (!hasLocation()) {
                throw new IllegalArgumentException("Geolocation is missing.");
            }

            JulianDate jd = getJulianDate();

            Double rise = null;
            Double set = null;
            Double noon = null;
            Double nadir = null;
            boolean alwaysUp = false;
            boolean alwaysDown = false;
            double ye;

            int hourStep;
            double lowerLimitHours, upperLimitHours;
            if (getDuration().isNegative()) {
                hourStep = -1;
                lowerLimitHours = getDuration().toMillis() / (60 * 60 * 1000.0);
                upperLimitHours = 0.0;
            } else {
                hourStep = 1;
                lowerLimitHours = 0.0;
                upperLimitHours = getDuration().toMillis() / (60 * 60 * 1000.0);;
            }

            int hour = 0;
            int minHours = (int) floor(lowerLimitHours);
            int maxHours = (int) ceil(upperLimitHours);

            double y_minus = correctedSunHeight(jd.atHour(hour - 1.0));
            double y_0 = correctedSunHeight(jd.atHour(hour));
            double y_plus = correctedSunHeight(jd.atHour(hour + 1.0));

            if (y_0 > 0.0) {
                alwaysUp = true;
            } else {
                alwaysDown = true;
            }

            while (hour <= maxHours && hour >= minHours) {
                QuadraticInterpolation qi = new QuadraticInterpolation(y_minus, y_0, y_plus);
                ye = qi.getYe();

                if (qi.getNumberOfRoots() == 1) {
                    double rt = qi.getRoot1() + hour;
                    if (y_minus < 0.0) {
                        if (rise == null && rt >= lowerLimitHours && rt < upperLimitHours) {
                            rise = rt;
                            alwaysDown = false;
                        }
                    } else {
                        if (set == null && rt >= lowerLimitHours && rt < upperLimitHours) {
                            set = rt;
                            alwaysUp = false;
                        }
                    }
                } else if (qi.getNumberOfRoots() == 2) {
                    if (rise == null) {
                        double rt = hour + (ye < 0.0 ? qi.getRoot2() : qi.getRoot1());
                        if (rt >= lowerLimitHours && rt < upperLimitHours) {
                            rise = rt;
                            alwaysDown = false;
                        }
                    }
                    if (set == null) {
                        double rt = hour + (ye < 0.0 ? qi.getRoot1() : qi.getRoot2());
                        if (rt >= lowerLimitHours && rt < upperLimitHours) {
                            set = rt;
                            alwaysUp = false;
                        }
                    }
                }

                double xeAbs = abs(qi.getXe());
                if (xeAbs <= 1.0) {
                    double xeHour = qi.getXe() + hour;
                    if (hourStep > 0 ? xeHour >= 0.0 : xeHour <= 0.0) {
                        if (qi.isMaximum()) {
                            if (noon == null) {
                                noon = xeHour;
                            }
                        } else {
                            if (nadir == null) {
                                nadir = xeHour;
                            }
                        }
                    }
                }

                if (rise != null && set != null && noon != null && nadir != null) {
                    break;
                }

                hour += hourStep;
                if (hourStep > 0) {
                    y_minus = y_0;
                    y_0 = y_plus;
                    y_plus = correctedSunHeight(jd.atHour(hour + 1.0));
                } else {
                    y_plus = y_0;
                    y_0 = y_minus;
                    y_minus = correctedSunHeight(jd.atHour(hour - 1.0));
                }
            }

            if (noon != null) {
                noon = readjustMax(noon, 2.0, 14, t -> correctedSunHeight(jd.atHour(t)));
                if (noon < lowerLimitHours || noon >= upperLimitHours) {
                    noon = null;
                }
            }

            if (nadir != null) {
                nadir = readjustMin(nadir, 2.0, 14, t -> correctedSunHeight(jd.atHour(t)));
                if (nadir < lowerLimitHours || nadir >= upperLimitHours) {
                    nadir = null;
                }
            }

            return new SunTimes(
                    rise != null ? jd.atHour(rise).getDateTime() : null,
                    set != null ? jd.atHour(set).getDateTime() : null,
                    noon != null ? jd.atHour(noon).getDateTime() : null,
                    nadir != null ? jd.atHour(nadir).getDateTime() : null,
                    alwaysUp,
                    alwaysDown
                );
        }

        /**
         * Computes the sun height at the given date and position.
         *
         * @param jd {@link JulianDate} to use
         * @return height, in radians
         */
        private double correctedSunHeight(JulianDate jd) {
            Vector pos = Sun.positionHorizontal(jd, getLatitudeRad(), getLongitudeRad());

            double hc = angle;
            if (position != null) {
                hc -= apparentRefraction(hc);
                hc += parallax(getElevation(), pos.getR());
                hc -= position * Sun.angularRadius(pos.getR());
            }

            return pos.getTheta() - hc;
        }
    }

    /**
     * Sunrise time. {@code null} if the sun does not rise that day.
     * <p>
     * Always returns a sunrise time if {@link Parameters#fullCycle()} was set.
     */
    @Nullable
    public ZonedDateTime getRise() {
        return rise;
    }

    /**
     * Sunset time. {@code null} if the sun does not set that day.
     * <p>
     * Always returns a sunset time if {@link Parameters#fullCycle()} was set.
     */
    @Nullable
    public ZonedDateTime getSet() {
        return set;
    }

    /**
     * The time when the sun reaches its highest point.
     * <p>
     * Use {@link #isAlwaysDown()} to find out if the highest point is still below the
     * twilight angle.
     */
    @Nullable
    public ZonedDateTime getNoon() {
        return noon;
    }

    /**
     * The time when the sun reaches its lowest point.
     * <p>
     * Use {@link #isAlwaysUp()} to find out if the lowest point is still above the
     * twilight angle.
     */
    @Nullable
    public ZonedDateTime getNadir() {
        return nadir;
    }

    /**
     * {@code true} if the sun never rises/sets, but is always above the twilight angle.
     */
    public boolean isAlwaysUp() {
        return alwaysUp;
    }

    /**
     * {@code true} if the sun never rises/sets, but is always below the twilight angle.
     */
    public boolean isAlwaysDown() {
        return alwaysDown;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("SunTimes[rise=").append(rise);
        sb.append(", set=").append(set);
        sb.append(", noon=").append(noon);
        sb.append(", nadir=").append(nadir);
        sb.append(", alwaysUp=").append(alwaysUp);
        sb.append(", alwaysDown=").append(alwaysDown);
        sb.append(']');
        return sb.toString();
    }

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/package-info.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

/**
 * This is the main package. It contains classes for calculating sun and moon data.
 */
@ReturnValuesAreNonnullByDefault
@DefaultAnnotationForParameters(NonNull.class)
@DefaultAnnotationForFields(NonNull.class)
package org.shredzone.commons.suncalc;

import edu.umd.cs.findbugs.annotations.DefaultAnnotationForFields;
import edu.umd.cs.findbugs.annotations.DefaultAnnotationForParameters;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.ReturnValuesAreNonnullByDefault;

================================================
FILE: src/main/java/org/shredzone/commons/suncalc/param/Builder.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc.param;

/**
 * An interface for the method that eventually executes the calculation.
 *
 * @param <T>
 *            Result type
 */
public interface Builder<T> {

    /**
     * Executes the calculation and returns the desired result.
     * <p>
     * The resulting object is immutable. You can change parameters, and then invoke
     * {@link #execute()} again, to get a new object with new results.
     *
     * @return Result of the calculation.
     */
    T execute();

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/param/GenericParameter.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2020 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc.param;

/**
 * Generic parameters and options.
 *
 * @param <T>
 *            Type of the final builder
 */
public interface GenericParameter<T> {

    /**
     * Creates a copy of the current parameters. The copy can be changed independently.
     */
    T copy();

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/param/LocationParameter.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc.param;

import static org.shredzone.commons.suncalc.util.ExtendedMath.dms;

/**
 * Location based parameters.
 * <p>
 * Use them to give information about the geolocation of the observer. If ommitted, the
 * coordinates of <a href="https://en.wikipedia.org/wiki/Null_Island">Null Island</a> are
 * used.
 *
 * @param <T>
 *            Type of the final builder
 */
@SuppressWarnings("unchecked")
public interface LocationParameter<T> {

    /**
     * Sets the latitude.
     *
     * @param lat
     *            Latitude, in degrees.
     * @return itself
     */
    T latitude(double lat);

    /**
     * Sets the longitude.
     *
     * @param lng
     *            Longitude, in degrees.
     * @return itself
     */
    T longitude(double lng);

    /**
     * Sets the elevation.
     *
     * @param h
     *            Elevation, in meters above sea level. Default: 0.0 m. Negative values
     *            are silently changed to the acceptable minimum of 0.0 m.
     * @return itself
     * @see #elevationFt(double)
     * @since 3.9
     */
    T elevation(double h);

    /**
     * Sets the elevation, in foot.
     *
     * @param ft
     *            Elevation, in foot above sea level. Default: 0.0 ft. Negative values are
     *            silently changed to the acceptable minimum of 0.0 ft.
     * @return itself
     * @see #elevation(double)
     * @since 3.9
     */
    default T elevationFt(double ft) {
        return elevation(ft * 0.3048);
    }

    /**
     * Sets the height.
     *
     * @param h
     *            Height, in meters above sea level. Default: 0.0 m. Negative values are
     *            silently changed to the acceptable minimum of 0.0 m.
     * @return itself
     * @deprecated Use {@link #elevation(double)} instead.
     */
    @Deprecated
    default T height(double h) {
        return elevation(h);
    }

    /**
     * Sets the height, in foot.
     *
     * @param ft
     *            Height, in foot above sea level. Default: 0.0 ft. Negative values are
     *            silently changed to the acceptable minimum of 0.0 ft.
     * @return itself
     * @since 3.8
     * @deprecated Use {@link #elevationFt(double)} instead.
     */
    @Deprecated
    default T heightFt(double ft) {
        return elevationFt(ft);
    }

    /**
     * Sets the geolocation.
     *
     * @param lat
     *            Latitude, in degrees.
     * @param lng
     *            Longitude, in degrees.
     * @return itself
     */
    default T at(double lat, double lng) {
        latitude(lat);
        longitude(lng);
        return (T) this;
    }

    /**
     * Sets the geolocation. In the given array, index 0 must contain the latitude, and
     * index 1 must contain the longitude. An optional index 2 may contain the elevation,
     * in meters.
     * <p>
     * This call is meant to be used for coordinates stored in constants.
     *
     * @param coords
     *            Array containing the latitude and longitude, in degrees.
     * @return itself
     */
    default T at(double[] coords) {
        if (coords.length != 2 && coords.length != 3) {
            throw new IllegalArgumentException("Array must contain 2 or 3 doubles");
        }
        if (coords.length == 3) {
            elevation(coords[2]);
        }
        return at(coords[0], coords[1]);
    }

    /**
     * Sets the latitude.
     *
     * @param d
     *            Degrees
     * @param m
     *            Minutes
     * @param s
     *            Seconds (and fraction of seconds)
     * @return itself
     */
    default T latitude(int d, int m, double s) {
        return latitude(dms(d, m, s));
    }

    /**
     * Sets the longitude.
     *
     * @param d
     *            Degrees
     * @param m
     *            Minutes
     * @param s
     *            Seconds (and fraction of seconds)
     * @return itself
     */
    default T longitude(int d, int m, double s) {
        return longitude(dms(d, m, s));
    }

    /**
     * Uses the same location as given in the {@link LocationParameter} at this moment.
     * <p>
     * Changes to the source parameter will not affect this parameter, though.
     *
     * @param l  {@link LocationParameter} to be used.
     * @return itself
     */
    T sameLocationAs(LocationParameter<?> l);

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/param/TimeParameter.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc.param;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;
import java.util.TimeZone;

/**
 * Time based parameters.
 * <p>
 * Use them to give information about the desired time. If ommitted, the current time and
 * the system's time zone is used.
 *
 * @param <T>
 *            Type of the final builder
 */
@SuppressWarnings("unchecked")
public interface TimeParameter<T> {

    /**
     * Sets date and time. Note that also seconds can be passed in for convenience, but
     * the results are not that accurate.
     *
     * @param year
     *            Year
     * @param month
     *            Month (1 = January, 2 = February, ...)
     * @param date
     *            Day of month
     * @param hour
     *            Hour of day
     * @param minute
     *            Minute
     * @param second
     *            Second
     * @return itself
     */
    T on(int year, int month, int date, int hour, int minute, int second);

    /**
     * Sets midnight of the year, month and date.
     *
     * @param year
     *            Year
     * @param month
     *            Month (1 = January, 2 = February, ...)
     * @param date
     *            Day of month
     * @return itself
     */
    default T on(int year, int month, int date) {
        return on(year, month, date, 0, 0, 0);
    }

    /**
     * Uses the given {@link ZonedDateTime} instance.
     *
     * @param dateTime
     *            {@link ZonedDateTime} to be used.
     * @return itself
     */
    T on(ZonedDateTime dateTime);

    /**
     * Uses the given {@link LocalDateTime} instance.
     *
     * @param dateTime
     *         {@link LocalDateTime} to be used.
     * @return itself
     */
    T on(LocalDateTime dateTime);

    /**
     * Uses the given {@link LocalDate} instance, and assumes midnight.
     *
     * @param date
     *         {@link LocalDate} to be used.
     * @return itself
     */
    T on(LocalDate date);

    /**
     * Uses the given {@link Instant} instance.
     *
     * @param instant
     *            {@link Instant} to be used.
     * @return itself
     */
    T on(Instant instant);

    /**
     * Uses the given {@link Date} instance.
     *
     * @param date
     *         {@link Date} to be used.
     * @return itself
     */
    default T on(Date date) {
        Objects.requireNonNull(date, "date");
        return on(date.toInstant());
    }

    /**
     * Uses the given {@link Calendar} instance.
     *
     * @param cal
     *         {@link Calendar} to be used
     * @return itself
     */
    default T on(Calendar cal) {
        Objects.requireNonNull(cal, "cal");
        return on(ZonedDateTime.ofInstant(cal.toInstant(), cal.getTimeZone().toZoneId()));
    }

    /**
     * Sets the current date and time. This is the default.
     *
     * @return itself
     */
    T now();

    /**
     * Sets the time to the start of the current date ("last midnight").
     *
     * @return itself
     */
    T midnight();

    /**
     * Adds a number of days to the current date.
     *
     * @param days
     *            Number of days to add
     * @return itself
     */
    T plusDays(int days);

    /**
     * Sets today, midnight.
     * <p>
     * It is the same as <code>now().midnight()</code>.
     *
     * @return itself
     */
    default T today() {
        now();
        midnight();
        return (T) this;
    }

    /**
     * Sets tomorrow, midnight.
     * <p>
     * It is the same as <code>now().midnight().plusDays(1)</code>.
     *
     * @return itself
     */
    default T tomorrow() {
        today();
        plusDays(1);
        return (T) this;
    }

    /**
     * Sets the given {@link ZoneId}. The local time is retained, so the parameter order
     * is not important.
     *
     * @param tz
     *            {@link ZoneId} to be used.
     * @return itself
     */
    T timezone(ZoneId tz);

    /**
     * Sets the given timezone. This is a convenience method that just invokes
     * {@link ZoneId#of(String)}.
     *
     * @param id
     *            ID of the time zone.
     * @return itself
     * @see ZoneId#of(String)
     */
    default T timezone(String id) {
        return timezone(ZoneId.of(id));
    }

    /**
     * Sets the system's timezone. This is the default.
     *
     * @return itself
     */
    default T localTime() {
        return timezone(ZoneId.systemDefault());
    }

    /**
     * Sets the time zone to UTC.
     *
     * @return itself
     */
    default T utc() {
        return timezone("UTC");
    }

    /**
     * Sets the {@link TimeZone}.
     *
     * @param tz {@link TimeZone} to be used
     * @return itself
     */
    default T timezone(TimeZone tz) {
        Objects.requireNonNull(tz, "tz");
        return timezone(tz.toZoneId());
    }

    /**
     * Uses the same time as given in the {@link TimeParameter}.
     * <p>
     * Changes to the source parameter will not affect this parameter, though.
     *
     * @param t  {@link TimeParameter} to be used.
     * @return itself
     */
    T sameTimeAs(TimeParameter<?> t);

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/param/WindowParameter.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2024 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc.param;

import java.time.Duration;

/**
 * Time window based parameters.
 * <p>
 * Use them to give information about the desired time window. If ommitted, a forward
 * window of 365 days is assumed.
 *
 * @since 3.11
 * @param <T>
 *            Type of the final builder
 */
public interface WindowParameter<T> {

    /**
     * Limits the calculation window to the given {@link Duration}.
     *
     * @param duration
     *         Duration of the calculation window. A negative duration sets a reverse time
     *         window, giving result times in the past.
     * @return itself
     */
    T limit(Duration duration);

    /**
     * Limits the time window to the next 24 hours.
     *
     * @return itself
     */
    default T oneDay() {
        return limit(Duration.ofDays(1L));
    }

    /**
     * Computes until all times are found.
     * <p>
     * This is the default.
     *
     * @return itself
     */
    default T fullCycle() {
        return limit(Duration.ofDays(365L));
    }

    /**
     * Sets a reverse calculation window. It will end at the given date.
     *
     * @return itself
     * @since 3.11
     */
    T reverse();

    /**
     * Sets a forward calculation window. It will start at the given date.
     * <p>
     * This is the default.
     *
     * @return itself
     * @since 3.11
     */
    T forward();

    /**
     * Uses the same window as given in the {@link WindowParameter}.
     * <p>
     * Changes to the source parameter will not affect this parameter, though.
     *
     * @param w
     *         {@link WindowParameter} to be used.
     * @return itself
     * @since 3.11
     */
    T sameWindowAs(WindowParameter<?> w);

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/param/package-info.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

/**
 * This package contains interfaces for setting common calculation parameters.
 */
@ReturnValuesAreNonnullByDefault
@DefaultAnnotationForParameters(NonNull.class)
@DefaultAnnotationForFields(NonNull.class)
package org.shredzone.commons.suncalc.param;

import edu.umd.cs.findbugs.annotations.DefaultAnnotationForFields;
import edu.umd.cs.findbugs.annotations.DefaultAnnotationForParameters;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.ReturnValuesAreNonnullByDefault;


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/util/BaseBuilder.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc.util;

import static java.lang.Math.max;
import static java.lang.Math.toRadians;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Objects;

import edu.umd.cs.findbugs.annotations.Nullable;
import org.shredzone.commons.suncalc.param.GenericParameter;
import org.shredzone.commons.suncalc.param.LocationParameter;
import org.shredzone.commons.suncalc.param.TimeParameter;
import org.shredzone.commons.suncalc.param.WindowParameter;

/**
 * A base implementation of {@link LocationParameter}, {@link TimeParameter}, and
 * {@link WindowParameter}.
 * <p>
 * For internal use only.
 *
 * @param <T>
 *         Type of the final builder
 */
@SuppressWarnings("unchecked")
public class BaseBuilder<T> implements GenericParameter<T>, LocationParameter<T>,
        TimeParameter<T>, WindowParameter<T>, Cloneable {

    private @Nullable Double lat = null;
    private @Nullable Double lng = null;
    private double elevation = 0.0;
    private ZonedDateTime dateTime = ZonedDateTime.now();
    private boolean reverse = false;
    private Duration duration = Duration.ofDays(365L);

    @Override
    public T on(ZonedDateTime dateTime) {
        this.dateTime = Objects.requireNonNull(dateTime, "dateTime");
        return (T) this;
    }

    @Override
    public T on(LocalDateTime dateTime) {
        Objects.requireNonNull(dateTime, "dateTime");
        return on(ZonedDateTime.of(dateTime, this.dateTime.getZone()));
    }

    @Override
    public T on(LocalDate date) {
        Objects.requireNonNull(date, "date");
        return on(ZonedDateTime.of(date, LocalTime.MIDNIGHT, dateTime.getZone()));
    }

    @Override
    public T on(Instant instant) {
        Objects.requireNonNull(instant, "instant");
        return on(ZonedDateTime.ofInstant(instant, dateTime.getZone()));
    }

    @Override
    public T on(int year, int month, int date, int hour, int minute, int second) {
        return on(ZonedDateTime.of(year, month, date, hour, minute, second, 0, dateTime.getZone()));
    }

    @Override
    public T now() {
        return on(ZonedDateTime.now(dateTime.getZone()));
    }

    @Override
    public T plusDays(int days) {
        return on(dateTime.plusDays(days));
    }

    @Override
    public T midnight() {
        return on(dateTime.truncatedTo(ChronoUnit.DAYS));
    }

    @Override
    public T timezone(ZoneId tz) {
        Objects.requireNonNull(tz, "tz");
        on(dateTime.withZoneSameLocal(tz));
        return (T) this;
    }

    @Override
    public T latitude(double lat) {
        if (lat < -90.0 || lat > 90.0) {
            throw new IllegalArgumentException("Latitude out of range, -90.0 <= " + lat + " <= 90.0");
        }
        this.lat = lat;
        return (T) this;
    }

    @Override
    public T longitude(double lng) {
        if (lng < -180.0 || lng > 180.0) {
            throw new IllegalArgumentException("Longitude out of range, -180.0 <= " + lng + " <= 180.0");
        }
        this.lng = lng;
        return (T) this;
    }

    @Override
    public T elevation(double h) {
        this.elevation = max(h, 0.0);
        return (T) this;
    }

    public T limit(Duration duration) {
        Objects.requireNonNull(duration, "duration");
        this.duration = duration;
        if (duration.isNegative()) {
            reverse();
        }
        return (T) this;
    }

    public T reverse() {
        reverse = true;
        return (T) this;
    }

    public T forward() {
        reverse = false;
        return (T) this;
    }

    @Override
    public T sameTimeAs(TimeParameter<?> t) {
        if (! (t instanceof BaseBuilder)) {
            throw new IllegalArgumentException("Cannot read the TimeParameter");
        }
        this.dateTime = ((BaseBuilder<?>) t).dateTime;
        return (T) this;
    }

    @Override
    public T sameLocationAs(LocationParameter<?> l) {
        if (! (l instanceof BaseBuilder)) {
            throw new IllegalArgumentException("Cannot read the LocationParameter");
        }
        BaseBuilder<?> origin = (BaseBuilder<?>) l;
        this.lat = origin.lat;
        this.lng = origin.lng;
        this.elevation = origin.elevation;
        return (T) this;
    }

    @Override
    public T sameWindowAs(WindowParameter<?> w) {
        if (! (w instanceof BaseBuilder)) {
            throw new IllegalArgumentException("Cannot read the WindowParameter");
        }
        BaseBuilder<?> origin = (BaseBuilder<?>) w;
        this.duration = origin.duration;
        this.reverse = origin.reverse;
        return (T) this;
    }

    @Override
    public T copy() {
        try {
            return (T) clone();
        } catch (CloneNotSupportedException ex) {
            throw new RuntimeException(ex); // Should never be thrown anyway
        }
    }

    /**
     * Returns the longitude.
     *
     * @return Longitude, in degrees.
     */
    public double getLongitude() {
        if (lng == null) {
            throw new IllegalStateException("longitude is not set");
        }
        return lng;
    }

    /**
     * Returns the latitude.
     *
     * @return Latitude, in degrees.
     */
    public double getLatitude() {
        if (lat == null) {
            throw new IllegalStateException("latitude is not set");
        }
        return lat;
    }

    /**
     * Returns the longitude.
     *
     * @return Longitude, in radians.
     */
    public double getLongitudeRad() {
        return toRadians(getLongitude());
    }

    /**
     * Returns the latitude.
     *
     * @return Latitude, in radians.
     */
    public double getLatitudeRad() {
        return toRadians(getLatitude());
    }

    /**
     * Returns the elevation, in meters above sea level.
     *
     * @return Elevation, meters above sea level
     */
    public double getElevation() {
        return elevation;
    }

    /**
     * Returns the {@link JulianDate} to be used.
     *
     * @return {@link JulianDate}
     */
    public JulianDate getJulianDate() {
        return new JulianDate(dateTime);
    }

    /**
     * Returns {@code true} if a geolocation has been set.
     *
     * @since 3.9
     */
    public boolean hasLocation() {
        return lat != null && lng != null;
    }

    /**
     * Unset the geolocation.
     *
     * @since 3.9
     */
    public void clearLocation() {
        lat = null;
        lng = null;
    }

    /**
     * Returns the duration of the time window.
     *
     * @since 3.11
     */
    public Duration getDuration() {
        if (reverse != duration.isNegative()) {
            return duration.negated();
        }
        return duration;
    }

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/util/ExtendedMath.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc.util;

import static java.lang.Math.*;

import java.util.Comparator;
import java.util.function.Function;

/**
 * Contains constants and mathematical operations that are not available in {@link Math}.
 */
public final class ExtendedMath {

    /**
     * PI * 2
     */
    public static final double PI2 = PI * 2.0;

    /**
     * Arc-Seconds per Radian.
     */
    public static final double ARCS = toDegrees(3600.0);

    /**
     * Mean radius of the earth, in kilometers.
     */
    public static final double EARTH_MEAN_RADIUS = 6371.0;

    /**
     * Refraction at the horizon, in radians.
     */
    public static final double REFRACTION_AT_HORIZON = PI / (tan(toRadians(7.31 / 4.4)) * 10800.0);

    private ExtendedMath() {
        // utility class without constructor
    }

    /**
     * Returns the decimal part of a value.
     *
     * @param a
     *            Value
     * @return Fraction of that value. It has the same sign as the input value.
     */
    public static double frac(double a) {
        return a % 1.0;
    }

    /**
     * Performs a safe check if the given double is actually zero (0.0).
     * <p>
     * Note that "almost zero" returns {@code false}, so this method should not be used
     * for comparing calculation results to zero.
     *
     * @param d
     *            double to check for zero.
     * @return {@code true} if the value was zero, or negative zero.
     */
    public static boolean isZero(double d) {
        // This should keep squid:S1244 silent...
        return !Double.isNaN(d) && round(signum(d)) == 0L;
    }

    /**
     * Converts equatorial coordinates to horizontal coordinates.
     *
     * @param tau
     *            Hour angle (radians)
     * @param dec
     *            Declination (radians)
     * @param dist
     *            Distance of the object
     * @param lat
     *            Latitude of the observer (radians)
     * @return {@link Vector} containing the horizontal coordinates
     */
    public static Vector equatorialToHorizontal(double tau, double dec, double dist, double lat) {
        return Matrix.rotateY(PI / 2.0 - lat).multiply(Vector.ofPolar(tau, dec, dist));
    }

    /**
     * Creates a rotational {@link Matrix} for converting equatorial to ecliptical
     * coordinates.
     *
     * @param t
     *            {@link JulianDate} to use
     * @return {@link Matrix} for converting equatorial to ecliptical coordinates
     */
    public static Matrix equatorialToEcliptical(JulianDate t) {
        double jc = t.getJulianCentury();
        double eps = toRadians(23.43929111 - (46.8150 + (0.00059 - 0.001813 * jc) * jc) * jc / 3600.0);
        return Matrix.rotateX(eps);
    }

    /**
     * Returns the parallax for objects at the horizon.
     *
     * @param elevation
     *            Observer's elevation, in meters above sea level. Must not be negative.
     * @param distance
     *            Distance of the object, in kilometers.
     * @return parallax, in radians
     */
    public static double parallax(double elevation, double distance) {
        return asin(EARTH_MEAN_RADIUS / distance)
             - acos(EARTH_MEAN_RADIUS / (EARTH_MEAN_RADIUS + (elevation / 1000.0)));
    }

    /**
     * Calculates the atmospheric refraction of an object at the given apparent altitude.
     * <p>
     * The result is only valid for positive altitude angles. If negative, 0.0 is
     * returned.
     * <p>
     * Assumes an atmospheric pressure of 1010 hPa and a temperature of 10 °C.
     *
     * @param ha
     *            Apparent altitude, in radians.
     * @return Refraction at this altitude
     * @see <a href="https://en.wikipedia.org/wiki/Atmospheric_refraction">Wikipedia:
     *      Atmospheric Refraction</a>
     */
    public static double apparentRefraction(double ha) {
        if (ha < 0.0) {
            return 0.0;
        }

        if (isZero(ha)) {
            return REFRACTION_AT_HORIZON;
        }

        return PI / (tan(toRadians(ha + (7.31 / (ha + 4.4)))) * 10800.0);
    }

    /**
     * Calculates the atmospheric refraction of an object at the given altitude.
     * <p>
     * The result is only valid for positive altitude angles. If negative, 0.0 is
     * returned.
     * <p>
     * Assumes an atmospheric pressure of 1010 hPa and a temperature of 10 °C.
     *
     * @param h
     *            True altitude, in radians.
     * @return Refraction at this altitude
     * @see <a href="https://en.wikipedia.org/wiki/Atmospheric_refraction">Wikipedia:
     *      Atmospheric Refraction</a>
     */
    public static double refraction(double h) {
        if (h < 0.0) {
            return 0.0;
        }

        // refraction formula, converted to radians
        return 0.000296706 / tan(h + 0.00312537 / (h + 0.0890118));
    }

    /**
     * Converts dms to double.
     *
     * @param d
     *            Degrees. Sign is used for result.
     * @param m
     *            Minutes. Sign is ignored.
     * @param s
     *            Seconds and fractions. Sign is ignored.
     * @return angle, in degrees
     */
    public static double dms(int d, int m, double s) {
        double sig = d < 0 ? -1.0 : 1.0;
        return sig * ((abs(s) / 60.0 + abs(m)) / 60.0 + abs(d));
    }

    /**
     * Locates the true maximum within the given time frame.
     *
     * @param time
     *         Base time
     * @param frame
     *         Time frame, which is added to and subtracted from the base time for the
     *         interval
     * @param depth
     *         Maximum recursion depth. For each recursion, the function is invoked once.
     * @param f
     *         Function to be used for calculation
     * @return time of the true maximum
     */
    public static double readjustMax(double time, double frame, int depth, Function<Double, Double> f) {
        double left = time - frame;
        double right = time + frame;
        double leftY = f.apply(left);
        double rightY = f.apply(right);

        return readjustInterval(left, right, leftY, rightY, depth, f, Double::compare);
    }

    /**
     * Locates the true minimum within the given time frame.
     *
     * @param time
     *         Base time
     * @param frame
     *         Time frame, which is added to and subtracted from the base time for the
     *         interval
     * @param depth
     *         Maximum recursion depth. For each recursion, the function is invoked once.
     * @param f
     *         Function to be used for calculation
     * @return time of the true minimum
     */
    public static double readjustMin(double time, double frame, int depth, Function<Double, Double> f) {
        double left = time - frame;
        double right = time + frame;
        double leftY = f.apply(left);
        double rightY = f.apply(right);

        return readjustInterval(left, right, leftY, rightY, depth, f, (yl, yr) -> Double.compare(yr, yl));
    }

    /**
     * Recursively find the true maximum/minimum within the given time frame.
     *
     * @param left
     *         Left interval border
     * @param right
     *         Right interval border
     * @param yl
     *         Function result at the left interval
     * @param yr
     *         Function result at the right interval
     * @param depth
     *         Maximum recursion depth. For each recursion, the function is invoked once.
     * @param f
     *         Function to invoke
     * @param cmp
     *         Comparator to decide whether the left or right side of the interval half is
     *         to be used
     * @return Position of the approximated minimum/maximum
     */
    private static double readjustInterval(double left, double right, double yl, double yr, int depth,
                                           Function<Double, Double> f, Comparator<Double> cmp) {
        if (depth <= 0) {
            return (cmp.compare(yl, yr) < 0) ? right : left;
        }

        double middle = (left + right) / 2.0;
        double ym = f.apply(middle);
        if (cmp.compare(yl, yr) < 0) {
            return readjustInterval(middle, right, ym, yr, depth - 1, f, cmp);
        } else {
            return readjustInterval(left, middle, yl, ym, depth - 1, f, cmp);
        }
    }

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/util/JulianDate.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc.util;

import static java.lang.Math.floor;
import static java.lang.Math.round;
import static org.shredzone.commons.suncalc.util.ExtendedMath.PI2;
import static org.shredzone.commons.suncalc.util.ExtendedMath.frac;

import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Objects;

/**
 * This class contains a Julian Date representation of a date.
 * <p>
 * Objects are immutable and threadsafe.
 */
public class JulianDate {

    private final ZonedDateTime dateTime;
    private final double mjd;

    /**
     * Creates a new {@link JulianDate}.
     *
     * @param time
     *            {@link ZonedDateTime} to use for the date.
     */
    public JulianDate(ZonedDateTime time) {
        dateTime = Objects.requireNonNull(time, "time");
        mjd = dateTime.toInstant().toEpochMilli() / 86400000.0 + 40587.0;
    }

    /**
     * Returns a {@link JulianDate} of the current date and the given hour.
     *
     * @param hour
     *            Hour of this date. This is a floating point value. Fractions are used
     *            for minutes and seconds.
     * @return {@link JulianDate} instance.
     */
    public JulianDate atHour(double hour) {
        return new JulianDate(dateTime.plusSeconds(round(hour * 60.0 * 60.0)));
    }

    /**
     * Returns a {@link JulianDate} of the given modified Julian date.
     *
     * @param mjd
     *            Modified Julian Date
     * @return {@link JulianDate} instance.
     */
    public JulianDate atModifiedJulianDate(double mjd) {
        Instant mjdi = Instant.ofEpochMilli(Math.round((mjd - 40587.0) * 86400000.0));
        return new JulianDate(ZonedDateTime.ofInstant(mjdi, dateTime.getZone()));
    }

    /**
     * Returns a {@link JulianDate} of the given Julian century.
     *
     * @param jc
     *            Julian Century
     * @return {@link JulianDate} instance.
     */
    public JulianDate atJulianCentury(double jc) {
        return atModifiedJulianDate(jc * 36525.0 + 51544.5);
    }

    /**
     * Returns this {@link JulianDate} as {@link ZonedDateTime} object.
     *
     * @return {@link ZonedDateTime} of this {@link JulianDate}.
     */
    public ZonedDateTime getDateTime() {
        return dateTime;
    }

    /**
     * Returns the Modified Julian Date.
     *
     * @return Modified Julian Date, UTC.
     */
    public double getModifiedJulianDate() {
        return mjd;
    }

    /**
     * Returns the Julian Centuries.
     *
     * @return Julian Centuries, based on J2000 epoch, UTC.
     */
    public double getJulianCentury() {
        return (mjd - 51544.5) / 36525.0;
    }

    /**
     * Returns the Greenwich Mean Sidereal Time of this Julian Date.
     *
     * @return GMST
     */
    public double getGreenwichMeanSiderealTime() {
        final double secs = 86400.0;

        double mjd0 = floor(mjd);
        double ut = (mjd - mjd0) * secs;
        double t0 = (mjd0 - 51544.5) / 36525.0;
        double t = (mjd - 51544.5) / 36525.0;

        double gmst = 24110.54841
                + 8640184.812866 * t0
                + 1.0027379093 * ut
                + (0.093104 - 6.2e-6 * t) * t * t;

        return (PI2 / secs) * (gmst % secs);
    }

    /**
     * Returns the earth's true anomaly of the current date.
     * <p>
     * A simple approximation is used here.
     *
     * @return True anomaly, in radians
     */
    public double getTrueAnomaly() {
        return PI2 * frac((dateTime.getDayOfYear() - 5.0) / 365.256363);
    }

    @Override
    public String toString() {
        return String.format("%dd %02dh %02dm %02ds",
                (long) mjd,
                (long) (mjd * 24 % 24),
                (long) (mjd * 24 * 60 % 60),
                (long) (mjd * 24 * 60 * 60 % 60));
    }

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/util/Matrix.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc.util;

import static java.lang.Math.cos;
import static java.lang.Math.sin;

import java.util.Arrays;

/**
 * A three dimensional matrix.
 * <p>
 * Objects are immutable and threadsafe.
 */
public class Matrix {

    private final double[] mx;

    private Matrix() {
        mx = new double[9];
    }

    private Matrix(double... values) {
        if (values == null || values.length != 9) {
            throw new IllegalArgumentException("requires 9 values");
        }
        mx = values;
    }

    /**
     * Creates an identity matrix.
     *
     * @return Identity {@link Matrix}
     */
    public static Matrix identity() {
        return new Matrix(
            1.0, 0.0, 0.0,
            0.0, 1.0, 0.0,
            0.0, 0.0, 1.0);
    }

    /**
     * Creates a matrix that rotates a vector by the given angle at the X axis.
     *
     * @param angle
     *            angle, in radians
     * @return Rotation {@link Matrix}
     */
    public static Matrix rotateX(double angle) {
        double s = sin(angle);
        double c = cos(angle);
        return new Matrix(
            1.0, 0.0, 0.0,
            0.0,   c,   s,
            0.0,  -s,   c
        );
    }

    /**
     * Creates a matrix that rotates a vector by the given angle at the Y axis.
     *
     * @param angle
     *            angle, in radians
     * @return Rotation {@link Matrix}
     */
    public static Matrix rotateY(double angle) {
        double s = sin(angle);
        double c = cos(angle);
        return new Matrix(
              c, 0.0,  -s,
            0.0, 1.0, 0.0,
              s, 0.0,   c
        );
    }

    /**
     * Creates a matrix that rotates a vector by the given angle at the Z axis.
     *
     * @param angle
     *            angle, in radians
     * @return Rotation {@link Matrix}
     */
    public static Matrix rotateZ(double angle) {
        double s = sin(angle);
        double c = cos(angle);
        return new Matrix(
              c,   s, 0.0,
             -s,   c, 0.0,
            0.0, 0.0, 1.0
        );
    }

    /**
     * Transposes this matrix.
     *
     * @return {@link Matrix} that is a transposition of this matrix.
     */
    public Matrix transpose() {
        Matrix result = new Matrix();
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                result.set(i, j, get(j, i));
            }
        }
        return result;
    }

    /**
     * Negates this matrix.
     *
     * @return {@link Matrix} that is a negation of this matrix.
     */
    public Matrix negate() {
        Matrix result = new Matrix();
        for (int i = 0; i < 9; i++) {
            result.mx[i] = -mx[i];
        }
        return result;
    }

    /**
     * Adds a matrix to this matrix.
     *
     * @param right
     *            {@link Matrix} to add
     * @return {@link Matrix} that is a sum of both matrices
     */
    public Matrix add(Matrix right) {
        Matrix result = new Matrix();
        for (int i = 0; i < 9; i++) {
            result.mx[i] = mx[i] + right.mx[i];
        }
        return result;
    }

    /**
     * Subtracts a matrix from this matrix.
     *
     * @param right
     *            {@link Matrix} to subtract
     * @return {@link Matrix} that is the difference of both matrices
     */
    public Matrix subtract(Matrix right) {
        Matrix result = new Matrix();
        for (int i = 0; i < 9; i++) {
            result.mx[i] = mx[i] - right.mx[i];
        }
        return result;
    }

    /**
     * Multiplies two matrices.
     *
     * @param right
     *            {@link Matrix} to multiply with
     * @return {@link Matrix} that is the product of both matrices
     */
    public Matrix multiply(Matrix right) {
        Matrix result = new Matrix();
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                double scalp = 0.0;
                for (int k = 0; k < 3; k++) {
                    scalp += get(i, k) * right.get(k, j);
                }
                result.set(i, j, scalp);
            }
        }
        return result;
    }

    /**
     * Performs a scalar multiplication.
     *
     * @param scalar
     *            Scalar to multiply with
     * @return {@link Matrix} that is the scalar product
     */
    public Matrix multiply(double scalar) {
        Matrix result = new Matrix();
        for (int i = 0; i < 9; i++) {
            result.mx[i] = mx[i] * scalar;
        }
        return result;
    }

    /**
     * Applies this matrix to a {@link Vector}.
     *
     * @param right
     *            {@link Vector} to multiply with
     * @return {@link Vector} that is the product of this matrix and the given vector
     */
    public Vector multiply(Vector right) {
        double[] vec = new double[] {right.getX(), right.getY(), right.getZ()};
        double[] result = new double[3];

        for (int i = 0; i < 3; i++) {
            double scalp = 0.0;
            for (int j = 0; j < 3; j++) {
                scalp += get(i, j) * vec[j];
            }
            result[i] = scalp;
        }

        return new Vector(result);
    }

    /**
     * Gets a value from the matrix.
     *
     * @param r
     *            Row number (0..2)
     * @param c
     *            Column number (0..2)
     * @return Value at that position
     */
    public double get(int r, int c) {
        if (r < 0 || r > 2 || c < 0 || c > 2) {
            throw new IllegalArgumentException("row/column out of range: " + r + ":" + c);
        }
        return mx[r * 3 + c];
    }

    /**
     * Changes a value in the matrix. As a {@link Matrix} object is immutable from the
     * outside, this method is private.
     *
     * @param r
     *            Row number (0..2)
     * @param c
     *            Column number (0..2)
     * @param v
     *            New value
     */
    private void set(int r, int c, double v) {
        if (r < 0 || r > 2 || c < 0 || c > 2) {
            throw new IllegalArgumentException("row/column out of range: " + r + ":" + c);
        }
        mx[r * 3 + c] = v;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof Matrix)) {
            return false;
        }
        return Arrays.equals(mx, ((Matrix) obj).mx);
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(mx);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (int ix = 0; ix < 9; ix++) {
            if (ix % 3 == 0) {
                sb.append('[');
            }
            sb.append(mx[ix]);
            if (ix % 3 == 2) {
                sb.append(']');
            }
            if (ix < 8) {
                sb.append(", ");
            }
        }
        sb.append(']');
        return sb.toString();
    }

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/util/Moon.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc.util;

import static java.lang.Math.*;
import static org.shredzone.commons.suncalc.util.ExtendedMath.*;

/**
 * Calculations and constants for the Moon.
 *
 * @see "Astronomy on the Personal Computer, 4th edition
 *      (Oliver Montenbruck, Thomas Pfleger) -
 *      ISBN 978-3-540-67221-0"
 */
public final class Moon {

    private static final double MOON_MEAN_RADIUS = 1737.1;

    private Moon() {
        // Utility class without constructor
    }

    /**
     * Calculates the equatorial position of the moon.
     *
     * @param date
     *            {@link JulianDate} to be used
     * @return {@link Vector} of equatorial moon position
     */
    public static Vector positionEquatorial(JulianDate date) {
        double T  = date.getJulianCentury();
        double L0 =       frac(0.606433 + 1336.855225 * T);
        double l  = PI2 * frac(0.374897 + 1325.552410 * T);
        double ls = PI2 * frac(0.993133 +   99.997361 * T);
        double D  = PI2 * frac(0.827361 + 1236.853086 * T);
        double F  = PI2 * frac(0.259086 + 1342.227825 * T);
        double D2 = 2.0 * D;
        double l2 = 2.0 * l;
        double F2 = 2.0 * F;

        double dL = 22640.0 * sin(l)
                  -  4586.0 * sin(l - D2)
                  +  2370.0 * sin(D2)
                  +   769.0 * sin(l2)
                  -   668.0 * sin(ls)
                  -   412.0 * sin(F2)
                  -   212.0 * sin(l2 - D2)
                  -   206.0 * sin(l + ls - D2)
                  +   192.0 * sin(l + D2)
                  -   165.0 * sin(ls - D2)
                  -   125.0 * sin(D)
                  -   110.0 * sin(l + ls)
                  +   148.0 * sin(l - ls)
                  -    55.0 * sin(F2 - D2);

        double S  = F + (dL + 412.0 * sin(F2) + 541.0 * sin(ls)) / ARCS;
        double h  = F - D2;
        double N  =  -526.0 * sin(h)
                  +    44.0 * sin(l + h)
                  -    31.0 * sin(-l + h)
                  -    23.0 * sin(ls + h)
                  +    11.0 * sin(-ls + h)
                  -    25.0 * sin(-l2 + F)
                  +    21.0 * sin(-l + F);

        double l_Moon = PI2 * frac(L0 + dL / 1296.0e3);
        double b_Moon = (18520.0 * sin(S) + N) / ARCS;

        double dt = 385000.5584
                  -  20905.3550 * cos(l)
                  -   3699.1109 * cos(D2 - l)
                  -   2955.9676 * cos(D2)
                  -    569.9251 * cos(l2);

        return Vector.ofPolar(l_Moon, b_Moon, dt);
    }

    /**
     * Calculates the geocentric position of the moon.
     *
     * @param date
     *            {@link JulianDate} to be used
     * @return {@link Vector} of geocentric moon position
     */
    public static Vector position(JulianDate date) {
        Matrix rotateMatrix = equatorialToEcliptical(date).transpose();
        return rotateMatrix.multiply(positionEquatorial(date));
    }

    /**
     * Calculates the horizontal position of the moon.
     *
     * @param date
     *            {@link JulianDate} to be used
     * @param lat
     *            Latitude, in radians
     * @param lng
     *            Longitute, in radians
     * @return {@link Vector} of horizontal moon position
     */
    public static Vector positionHorizontal(JulianDate date, double lat, double lng) {
        Vector mc = position(date);
        double h = date.getGreenwichMeanSiderealTime() + lng - mc.getPhi();
        return equatorialToHorizontal(h, mc.getTheta(), mc.getR(), lat);
    }

    /**
     * Calculates the topocentric position of the moon.
     * <p>
     * Atmospheric refraction is <em>not</em> taken into account.
     *
     * @param date
     *            {@link JulianDate} to be used
     * @param lat
     *            Latitude, in radians
     * @param lng
     *            Longitute, in radians
     * @param elev
     *            Elevation, in meters
     * @return {@link Vector} of topocentric moon position
     * @since 3.9
     */
    public static Vector positionTopocentric(JulianDate date, double lat, double lng, double elev) {
        Vector pos = positionHorizontal(date, lat, lng);
        return Vector.ofPolar(
                pos.getPhi(),
                pos.getTheta() - parallax(elev, pos.getR()),
                pos.getR()
        );
    }

    /**
     * Returns the angular radius of the moon.
     *
     * @param distance
     *            Distance of the moon, in kilometers.
     * @return Angular radius of the moon, in radians.
     * @see <a href="https://en.wikipedia.org/wiki/Angular_diameter">Wikipedia: Angular
     *      Diameter</a>
     */
    public static double angularRadius(double distance) {
        return asin(MOON_MEAN_RADIUS / distance);
    }

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/util/Pegasus.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2018 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc.util;

import static java.lang.Math.abs;

import java.util.function.Function;

/**
 * Finds the root of a function by using the Pegasus method.
 *
 * @see <a href="https://en.wikipedia.org/wiki/False_position_method">regula falsi</a>
 */
public class Pegasus {

    private static final int MAX_ITERATIONS = 30;

    /**
     * Find the root of the given function within the boundaries.
     *
     * @param lower
     *            Lower boundary
     * @param upper
     *            Upper boundary
     * @param accuracy
     *            Desired accuracy
     * @param f
     *            Function to be used for calculation
     * @return root that was found
     * @throws ArithmeticException
     *             if the root could not be found in the given accuracy within
     *             {@value #MAX_ITERATIONS} iterations.
     */
    public static Double calculate(double lower, double upper, double accuracy, Function<Double, Double> f) {
        double x1 = lower;
        double x2 = upper;

        double f1 = f.apply(x1);
        double f2 = f.apply(x2);

        if (f1 * f2 >= 0.0) {
            throw new ArithmeticException("No root within the given boundaries");
        }

        int i = MAX_ITERATIONS;

        while (i-- > 0) {
            double x3 = x2 - f2 / ((f2 - f1) / (x2 - x1));
            double f3 = f.apply(x3);

            if (f3 * f2 <= 0.0) {
                x1 = x2;
                f1 = f2;
                x2 = x3;
                f2 = f3;
            } else {
                f1 = f1 * f2 / (f2 + f3);
                x2 = x3;
                f2 = f3;
            }

            if (abs(x2 - x1) <= accuracy) {
                return abs(f1) < abs(f2) ? x1 : x2;
            }
        }

        throw new ArithmeticException("Maximum number of iterations exceeded");
    }

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/util/QuadraticInterpolation.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc.util;

import static java.lang.Math.abs;
import static java.lang.Math.sqrt;

/**
 * Calculates the roots and extremum of a quadratic equation.
 */
public class QuadraticInterpolation {

    private final double xe;
    private final double ye;
    private final double root1;
    private final double root2;
    private final int nRoot;
    private final boolean maximum;

    /**
     * Creates a new quadratic equation.
     *
     * @param yMinus
     *            y at x == -1
     * @param y0
     *            y at x == 0
     * @param yPlus
     *            y at x == 1
     */
    public QuadraticInterpolation(double yMinus, double y0, double yPlus) {
        double a = 0.5 * (yPlus + yMinus) - y0;
        double b = 0.5 * (yPlus - yMinus);
        double c = y0;

        xe = -b / (2.0 * a);
        ye = (a * xe + b) * xe + c;
        maximum = a < 0.0;
        double dis = b * b - 4.0 * a * c;

        int rootCount = 0;

        if (dis >= 0.0) {
            double dx = 0.5 * sqrt(dis) / abs(a);
            root1 = xe - dx;
            root2 = xe + dx;

            if (abs(root1) <= 1.0) {
                rootCount++;
            }

            if (abs(root2) <= 1.0) {
                rootCount++;
            }
        } else {
            root1 = Double.NaN;
            root2 = Double.NaN;
        }

        nRoot = rootCount;
    }

    /**
     * Returns X of extremum. Can be outside [-1 .. 1].
     *
     * @return X
     */
    public double getXe() {
        return xe;
    }

    /**
     * Returns the Y value at the extremum.
     *
     * @return Y
     */
    public double getYe() {
        return ye;
    }

    /**
     * Returns the first root that was found.
     *
     * @return X of first root
     */
    public double getRoot1() {
        return root1 < -1.0 ? root2 : root1;
    }

    /**
     * Returns the second root that was found.
     *
     * @return X of second root
     */
    public double getRoot2() {
        return root2;
    }

    /**
     * Returns the number of roots found in [-1 .. 1].
     *
     * @return Number of roots
     */
    public int getNumberOfRoots() {
        return nRoot;
    }

    /**
     * Returns whether the extremum is a minimum or a maximum.
     *
     * @return {@code true}: Extremum at xe is a maximum. {@code false}: Extremum at xe is
     *         a minimum.
     */
    public boolean isMaximum() {
        return maximum;
    }

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/util/Sun.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc.util;

import static java.lang.Math.*;
import static org.shredzone.commons.suncalc.util.ExtendedMath.*;

/**
 * Calculations and constants for the Sun.
 *
 * @see "Astronomy on the Personal Computer, 4th edition
 *      (Oliver Montenbruck, Thomas Pfleger) -
 *      ISBN 978-3-540-67221-0"
 */
public final class Sun {

    private static final double SUN_DISTANCE = 149598000.0;
    private static final double SUN_MEAN_RADIUS = 695700.0;

    private Sun() {
        // Utility class without constructor
    }

    /**
     * Calculates the equatorial position of the sun.
     *
     * @param date
     *            {@link JulianDate} to be used
     * @return {@link Vector} containing the sun position
     */
    public static Vector positionEquatorial(JulianDate date) {
        double T = date.getJulianCentury();
        double M = PI2 * frac(0.993133 + 99.997361 * T);
        double L = PI2 * frac(0.7859453 + M / PI2
            + (6893.0 * sin(M) + 72.0 * sin(2.0 * M) + 6191.2 * T) / 1296.0e3);

        double d = SUN_DISTANCE
            * (1 - 0.016718 * cos(date.getTrueAnomaly()));

        return Vector.ofPolar(L, 0.0, d);
    }

    /**
     * Calculates the geocentric position of the sun.
     *
     * @param date
     *            {@link JulianDate} to be used
     * @return {@link Vector} containing the sun position
     */
    public static Vector position(JulianDate date) {
        Matrix rotateMatrix = equatorialToEcliptical(date).transpose();
        return rotateMatrix.multiply(positionEquatorial(date));
    }

    /**
     * Calculates the horizontal position of the sun.
     *
     * @param date
     *            {@link JulianDate} to be used
     * @param lat
     *            Latitude, in radians
     * @param lng
     *            Longitute, in radians
     * @return {@link Vector} of horizontal sun position
     */
    public static Vector positionHorizontal(JulianDate date, double lat, double lng) {
        Vector mc = position(date);
        double h = date.getGreenwichMeanSiderealTime() + lng - mc.getPhi();
        return equatorialToHorizontal(h, mc.getTheta(), mc.getR(), lat);
    }

    /**
     * Calculates the topocentric position of the sun.
     * <p>
     * Atmospheric refraction is <em>not</em> taken into account.
     *
     * @param date
     *            {@link JulianDate} to be used
     * @param lat
     *            Latitude, in radians
     * @param lng
     *            Longitute, in radians
     * @param elev
     *            Elevation, in meters
     * @return {@link Vector} of topocentric sun position
     * @since 3.9
     */
    public static Vector positionTopocentric(JulianDate date, double lat, double lng, double elev) {
        Vector pos = positionHorizontal(date, lat, lng);
        return Vector.ofPolar(
                pos.getPhi(),
                pos.getTheta() - parallax(elev, pos.getR()),
                pos.getR()
        );
    }

    /**
     * Returns the angular radius of the sun.
     *
     * @param distance
     *            Distance of the sun, in kilometers.
     * @return Angular radius of the sun, in radians.
     * @see <a href="https://en.wikipedia.org/wiki/Angular_diameter">Wikipedia: Angular
     *      Diameter</a>
     */
    public static double angularRadius(double distance) {
        return asin(SUN_MEAN_RADIUS / distance);
    }

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/util/Vector.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc.util;

import static java.lang.Math.*;
import static org.shredzone.commons.suncalc.util.ExtendedMath.PI2;
import static org.shredzone.commons.suncalc.util.ExtendedMath.isZero;

import edu.umd.cs.findbugs.annotations.Nullable;

/**
 * A three dimensional vector.
 * <p>
 * Objects are is immutable and threadsafe.
 */
public class Vector {

    private final double x;
    private final double y;
    private final double z;
    private final Polar polar = new Polar();

    /**
     * Creates a new {@link Vector} of the given cartesian coordinates.
     *
     * @param x
     *            X coordinate
     * @param y
     *            Y coordinate
     * @param z
     *            Z coordinate
     */
    public Vector(double x, double y, double z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    /**
     * Creates a new {@link Vector} of the given cartesian coordinates.
     *
     * @param d
     *            Array of coordinates, must have 3 elements
     */
    public Vector(double[] d) {
        if (d.length != 3) {
            throw new IllegalArgumentException("invalid vector length");
        }
        this.x = d[0];
        this.y = d[1];
        this.z = d[2];
    }

    /**
     * Creates a new {@link Vector} of the given polar coordinates, with a radial distance
     * of 1.
     *
     * @param φ
     *            Azimuthal Angle
     * @param θ
     *            Polar Angle
     * @return Created {@link Vector}
     */
    public static Vector ofPolar(double φ, double θ) {
        return ofPolar(φ, θ, 1.0);
    }

    /**
     * Creates a new {@link Vector} of the given polar coordinates.
     *
     * @param φ
     *            Azimuthal Angle
     * @param θ
     *            Polar Angle
     * @param r
     *            Radial Distance
     * @return Created {@link Vector}
     */
    public static Vector ofPolar(double φ, double θ, double r) {
        double cosθ = cos(θ);
        Vector result = new Vector(
            r * cos(φ) * cosθ,
            r * sin(φ) * cosθ,
            r *          sin(θ)
        );
        result.polar.setPolar(φ, θ, r);
        return result;
    }

    /**
     * Returns the cartesian X coordinate.
     */
    public double getX() {
        return x;
    }

    /**
     * Returns the cartesian Y coordinate.
     */
    public double getY() {
        return y;
    }

    /**
     * Returns the cartesian Z coordinate.
     */
    public double getZ() {
        return z;
    }

    /**
     * Returns the azimuthal angle (φ) in radians.
     */
    public double getPhi() {
        return polar.getPhi();
    }

    /**
     * Returns the polar angle (θ) in radians.
     */
    public double getTheta() {
        return polar.getTheta();
    }

    /**
     * Returns the polar radial distance (r).
     */
    public double getR() {
        return polar.getR();
    }

    /**
     * Returns a {@link Vector} that is the sum of this {@link Vector} and the given
     * {@link Vector}.
     *
     * @param vec
     *            {@link Vector} to add
     * @return Resulting {@link Vector}
     */
    public Vector add(Vector vec) {
        return new Vector(
            x + vec.x,
            y + vec.y,
            z + vec.z
        );
    }

    /**
     * Returns a {@link Vector} that is the difference of this {@link Vector} and the
     * given {@link Vector}.
     *
     * @param vec
     *            {@link Vector} to subtract
     * @return Resulting {@link Vector}
     */
    public Vector subtract(Vector vec) {
        return new Vector(
            x - vec.x,
            y - vec.y,
            z - vec.z
        );
    }

    /**
     * Returns a {@link Vector} that is the scalar product of this {@link Vector} and the
     * given scalar.
     *
     * @param scalar
     *            Scalar to multiply
     * @return Resulting {@link Vector}
     */
    public Vector multiply(double scalar) {
        return new Vector(
            x * scalar,
            y * scalar,
            z * scalar
        );
    }

    /**
     * Returns the negation of this {@link Vector}.
     *
     * @return Resulting {@link Vector}
     */
    public Vector negate() {
        return new Vector(
            -x,
            -y,
            -z
        );
    }

    /**
     * Returns a {@link Vector} that is the cross product of this {@link Vector} and the
     * given {@link Vector}.
     *
     * @param right
     *            {@link Vector} to multiply
     * @return Resulting {@link Vector}
     */
    public Vector cross(Vector right) {
        return new Vector(
            y * right.z - z * right.y,
            z * right.x - x * right.z,
            x * right.y - y * right.x
        );
    }

    /**
     * Returns the dot product of this {@link Vector} and the given {@link Vector}.
     *
     * @param right
     *            {@link Vector} to multiply
     * @return Resulting dot product
     */
    public double dot(Vector right) {
        return x * right.x + y * right.y + z * right.z;
    }

    /**
     * Returns the norm of this {@link Vector}.
     *
     * @return Norm of this vector
     */
    public double norm() {
        return sqrt(dot(this));
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof Vector)) {
            return false;
        }

        Vector vec = (Vector) obj;
        return Double.compare(x, vec.x) == 0
            && Double.compare(y, vec.y) == 0
            && Double.compare(z, vec.z) == 0;
    }

    @Override
    public int hashCode() {
        return Double.valueOf(x).hashCode()
            ^  Double.valueOf(y).hashCode()
            ^  Double.valueOf(z).hashCode();
    }

    @Override
    public String toString() {
        return "(x=" + x + ", y=" + y + ", z=" + z + ")";
    }

    /**
     * Helper class for lazily computing the polar coordinates in an immutable Vector
     * object.
     */
    private class Polar {
        private @Nullable Double φ = null;
        private @Nullable Double θ = null;
        private @Nullable Double r = null;

        /**
         * Sets polar coordinates.
         *
         * @param φ
         *            Phi
         * @param θ
         *            Theta
         * @param r
         *            R
         */
        public synchronized void setPolar(double φ, double θ, double r) {
            this.φ = φ;
            this.θ = θ;
            this.r = r;
        }

        public synchronized double getPhi() {
            if (φ == null) {
                if (isZero(x) && isZero(y)) {
                    φ = 0.0;
                } else {
                    φ = atan2(y, x);
                }

                if (φ < 0.0) {
                    φ += PI2;
                }
            }
            return φ;
        }

        public synchronized double getTheta() {
            if (θ == null) {
                double ρSqr = x * x + y * y;

                if (isZero(z) && isZero(ρSqr)) {
                    θ = 0.0;
                } else {
                    θ = atan2(z, sqrt(ρSqr));
                }
            }
            return θ;
        }

        public synchronized double getR() {
            if (r == null) {
                r = sqrt(x * x + y * y + z * z);
            }
            return r;
        }
    }

}


================================================
FILE: src/main/java/org/shredzone/commons/suncalc/util/package-info.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

/**
 * This package contains internal utility methods.
 * <p>
 * Do not use in your code! This package will not be exported in a Java module.
 */
@ReturnValuesAreNonnullByDefault
@DefaultAnnotationForParameters(NonNull.class)
@DefaultAnnotationForFields(NonNull.class)
package org.shredzone.commons.suncalc.util;

import edu.umd.cs.findbugs.annotations.DefaultAnnotationForFields;
import edu.umd.cs.findbugs.annotations.DefaultAnnotationForParameters;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.ReturnValuesAreNonnullByDefault;


================================================
FILE: src/main/resources/.gitignore
================================================


================================================
FILE: src/test/java/org/shredzone/commons/suncalc/ExamplesTest.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2020 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc;

import java.time.Duration;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.TimeZone;

import org.junit.Ignore;
import org.junit.Test;

/**
 * These are some examples that are meant to be executed manually.
 *
 * @see <a href="https://shredzone.org/maven/commons-suncalc/examples.html">Example
 * chapter of the Documentation</a>
 */
@Ignore // No real unit tests, but meant to be run manually
public class ExamplesTest {

    @Test
    public void testTimezone() {
        // Our example takes place in Paris, so set the timezone accordingly.
        TimeZone.setDefault(TimeZone.getTimeZone("Europe/Paris"));

        SunTimes paris = SunTimes.compute()
                .on(2020, 5, 1)             // May 1st, 2020, starting midnight
                .latitude(48, 51, 24.0)     // Latitude of Paris: 48°51'24" N
                .longitude(2, 21, 6.0)      // Longitude:          2°21'06" E
                .execute();
        System.out.println("Sunrise in Paris: " + paris.getRise());
        System.out.println("Sunset in Paris:  " + paris.getSet());

        SunTimes newYork = SunTimes.compute()
                .on(2020, 5, 1)             // May 1st, 2020, starting midnight
                .at(40.712778, -74.005833)  // Coordinates of New York
                .execute();
        System.out.println("Sunrise in New York: " + newYork.getRise());
        System.out.println("Sunset in New York:  " + newYork.getSet());

        SunTimes newYorkTz = SunTimes.compute()
                .on(2020, 5, 1)             // May 1st, 2020, starting midnight
                .timezone("America/New_York")   // ...New York timezone
                .at(40.712778, -74.005833)  // Coordinates of New York
                .execute();
        System.out.println("Sunrise in New York: " + newYorkTz.getRise());
        System.out.println("Sunset in New York:  " + newYorkTz.getSet());
    }

    @Test
    public void testTimeWindow() {
        final double[] ALERT_CANADA = new double[] { 82.5, -62.316667 };
        final ZoneId ALERT_TZ = ZoneId.of("Canada/Eastern");

        SunTimes march = SunTimes.compute()
                .on(2020, 3, 15)            // March 15th, 2020, starting midnight
                .at(ALERT_CANADA)           // Coordinates are stored in an array
                .timezone(ALERT_TZ)
                .execute();
        System.out.println("Sunrise: " + march.getRise());
        System.out.println("Sunset:  " + march.getSet());

        SunTimes june = SunTimes.compute()
                .on(2020, 6, 15)            // June 15th, 2020, starting midnight
                .at(ALERT_CANADA)
                .timezone(ALERT_TZ)
                .execute();
        System.out.println("Sunrise: " + june.getRise());
        System.out.println("Sunset:  " + june.getSet());

        SunTimes juneReverse = SunTimes.compute()
                .on(2020, 6, 15)            // June 15th, 2020, starting midnight
                .at(ALERT_CANADA)
                .timezone(ALERT_TZ)
                .reverse()
                .execute();
        System.out.println("Sunrise: " + juneReverse.getRise());
        System.out.println("Sunset:  " + juneReverse.getSet());

        SunTimes june15OnlyCycle = SunTimes.compute()
                .on(2020, 6, 15)            // June 15th, 2020, starting midnight
                .at(ALERT_CANADA)
                .timezone(ALERT_TZ)
                .limit(Duration.ofHours(24))
                .execute();
        System.out.println("Sunset:  " + june15OnlyCycle.getSet());
        System.out.println("Sunrise: " + june15OnlyCycle.getRise());

        System.out.println("Sun is up all day:   " + june15OnlyCycle.isAlwaysUp());
        System.out.println("Sun is down all day: " + june15OnlyCycle.isAlwaysDown());
    }

    @Test
    public void testParameterRecycling() {
        final double[] COLOGNE = new double[] { 50.938056, 6.956944 };

        MoonTimes.Parameters parameters = MoonTimes.compute()
                .at(COLOGNE)
                .midnight();

        MoonTimes today = parameters.execute();
        System.out.println("Today, the moon rises in Cologne at " + today.getRise());

        parameters.tomorrow();
        MoonTimes tomorrow = parameters.execute();
        System.out.println("Tomorrow, the moon will rise in Cologne at " + tomorrow.getRise());
        System.out.println("But today, the moon still rises at " + today.getRise());
    }

    @Test
    public void testParameterRecyclingLoop() {
        MoonIllumination.Parameters parameters = MoonIllumination.compute()
                .on(2020, 1, 1);

        for (int i = 1; i <= 31; i++) {
            long percent = Math.round(parameters.execute().getFraction() * 100.0);
            System.out.println("On January " + i + " the moon was " + percent + "% lit.");
            parameters.plusDays(1);
        }
    }

    @Test
    public void testGoldenHour() {
        SunTimes.Parameters base = SunTimes.compute()
                .at(1.283333, 103.833333)            // Singapore
                .on(2020, 6, 1)
                .timezone("Asia/Singapore");

        for (int i = 0; i < 4; i++) {
            SunTimes blue = base
                    .copy()                          // Use a copy of base
                    .plusDays(i * 7)
                    .twilight(SunTimes.Twilight.BLUE_HOUR)      // Blue Hour
                    .execute();
            SunTimes golden = base
                    .copy()                          // Use a copy of base
                    .plusDays(i * 7)
                    .twilight(SunTimes.Twilight.GOLDEN_HOUR)    // Golden Hour
                    .execute();

            System.out.println("Morning golden hour starts at " + blue.getRise());
            System.out.println("Morning golden hour ends at   " + golden.getRise());
            System.out.println("Evening golden hour starts at " + golden.getSet());
            System.out.println("Evening golden hour ends at   " + blue.getSet());
        }
    }

    @Test
    public void testMoonPhase() {
        LocalDate date = LocalDate.of(2023, 1, 1);

        MoonPhase.Parameters parameters = MoonPhase.compute()
                .phase(MoonPhase.Phase.FULL_MOON);

        while (true) {
            MoonPhase moonPhase = parameters
                    .on(date)
                    .execute();
            LocalDate nextFullMoon = moonPhase
                    .getTime()
                    .toLocalDate();
            if (nextFullMoon.getYear() == 2024) {
                break;      // we've reached the next year
            }

            System.out.print(nextFullMoon);
            if (moonPhase.isMicroMoon()) {
                System.out.print(" (micromoon)");
            }
            if (moonPhase.isSuperMoon()) {
                System.out.print(" (supermoon)");
            }
            System.out.println();

            date = nextFullMoon.plusDays(1);
        }
    }

    @Test
    public void testPositions() {
        SunPosition.Parameters sunParam = SunPosition.compute()
                .at(35.689722, 139.692222)      // Tokyo
                .timezone("Asia/Tokyo")         // local time
                .on(2018, 11, 13, 10, 3, 24);   // 2018-11-13 10:03:24

        MoonPosition.Parameters moonParam = MoonPosition.compute()
                .sameLocationAs(sunParam)
                .sameTimeAs(sunParam);

        SunPosition sun = sunParam.execute();
        System.out.println(String.format(
                "The sun can be seen %.1f° clockwise from the North and "
                + "%.1f° above the horizon.\nIt is about %.0f km away right now.",
                sun.getAzimuth(),
                sun.getAltitude(),
                sun.getDistance()
        ));

        MoonPosition moon = moonParam.execute();
        System.out.println(String.format(
                "The moon can be seen %.1f° clockwise from the North and "
                + "%.1f° above the horizon.\nIt is about %.0f km away right now.",
                moon.getAzimuth(),
                moon.getAltitude(),
                moon.getDistance()
        ));
    }

}

================================================
FILE: src/test/java/org/shredzone/commons/suncalc/Locations.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc;

import java.time.ZoneId;

/**
 * Geocoordinates of some test locations.
 */
public final class Locations {

    /**
     * Cologne, Germany. A random city on the northern hemisphere.
     */
    public static final double[] COLOGNE = new double[] { 50.938056, 6.956944 };
    public static final ZoneId COLOGNE_TZ = ZoneId.of("Europe/Berlin");

    /**
     * Alert, Nunavut, Canada. The northernmost place in the world with a permanent
     * population.
     */
    public static final double[] ALERT = new double[] { 82.5, -62.316667 };
    public static final ZoneId ALERT_TZ = ZoneId.of("Canada/Eastern");

    /**
     * Wellington, New Zealand. A random city on the southern hemisphere, close to the
     * international date line.
     */
    public static final double[] WELLINGTON = new double[] { -41.2875, 174.776111 };
    public static final ZoneId WELLINGTON_TZ = ZoneId.of("Pacific/Auckland");

    /**
     * Puerto Williams, Chile. The southernmost town in the world.
     */
    public static final double[] PUERTO_WILLIAMS = new double[] { -54.933333, -67.616667 };
    public static final ZoneId PUERTO_WILLIAMS_TZ = ZoneId.of("America/Punta_Arenas");

    /**
     * Singapore. A random city close to the equator.
     */
    public static final double[] SINGAPORE = new double[] { 1.283333, 103.833333 };
    public static final ZoneId SINGAPORE_TZ = ZoneId.of("Asia/Singapore");

    /**
     * Martinique. To test a fix for issue #13.
     */
    public static final double[] MARTINIQUE = new double[] { 14.640725, -61.0112 };
    public static final ZoneId MARTINIQUE_TZ = ZoneId.of("America/Martinique");

    /**
     * Sydney. To test a fix for issue #14.
     */
    public static final double[] SYDNEY = new double[] { -33.744272, 151.231291 };
    public static final ZoneId SYDNEY_TZ = ZoneId.of("Australia/Sydney");

    /**
     * Santa Monica, CA. To test a fix for issue #18.
     */
    public static final double[] SANTA_MONICA = new double[] { 34.0, -118.5 };
    public static final ZoneId SANTA_MONICA_TZ = ZoneId.of("America/Los_Angeles");

    /**
     * Baghdad, Iraq. To test topocentric moon illumination.
     */
    public static final double[] BAGHDAD = new double[] { 33.338611, 44.393888 };
    public static final ZoneId BAGHDAD_TZ = ZoneId.of("Asia/Baghdad");

    /**
     * Cape Town, South Africa. To test topocentric moon illumination.
     */
    public static final double[] CAPETOWN = new double[] { -33.966666, 18.6 };
    public static final ZoneId CAPETOWN_TZ = ZoneId.of("Africa/Johannesburg");

}


================================================
FILE: src/test/java/org/shredzone/commons/suncalc/MoonIlluminationTest.java
================================================
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 Richard "Shred" Körber
 *   http://commons.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.commons.suncalc;

import static org.assertj.core.api.Assertions.assertThat;
import s
Download .txt
gitextract_52r2ik9m/

├── .gitignore
├── .gitlab-ci.yml
├── .project
├── LICENSE-APL.txt
├── README.md
├── pom.xml
└── src/
    ├── doc/
    │   ├── docs/
    │   │   ├── examples.md
    │   │   ├── faq.md
    │   │   ├── index.md
    │   │   ├── migration.md
    │   │   └── usage.md
    │   ├── mkdocs.yml
    │   └── theme/
    │       ├── breadcrumbs.html
    │       ├── css/
    │       │   ├── font.css
    │       │   ├── theme_custom.css
    │       │   └── theme_pygments.css
    │       ├── footer.html
    │       └── main.html
    ├── main/
    │   ├── java/
    │   │   ├── module-info.java
    │   │   └── org/
    │   │       └── shredzone/
    │   │           └── commons/
    │   │               └── suncalc/
    │   │                   ├── MoonIllumination.java
    │   │                   ├── MoonPhase.java
    │   │                   ├── MoonPosition.java
    │   │                   ├── MoonTimes.java
    │   │                   ├── SunPosition.java
    │   │                   ├── SunTimes.java
    │   │                   ├── package-info.java
    │   │                   ├── param/
    │   │                   │   ├── Builder.java
    │   │                   │   ├── GenericParameter.java
    │   │                   │   ├── LocationParameter.java
    │   │                   │   ├── TimeParameter.java
    │   │                   │   ├── WindowParameter.java
    │   │                   │   └── package-info.java
    │   │                   └── util/
    │   │                       ├── BaseBuilder.java
    │   │                       ├── ExtendedMath.java
    │   │                       ├── JulianDate.java
    │   │                       ├── Matrix.java
    │   │                       ├── Moon.java
    │   │                       ├── Pegasus.java
    │   │                       ├── QuadraticInterpolation.java
    │   │                       ├── Sun.java
    │   │                       ├── Vector.java
    │   │                       └── package-info.java
    │   └── resources/
    │       └── .gitignore
    └── test/
        ├── java/
        │   └── org/
        │       └── shredzone/
        │           └── commons/
        │               └── suncalc/
        │                   ├── ExamplesTest.java
        │                   ├── Locations.java
        │                   ├── MoonIlluminationTest.java
        │                   ├── MoonPhaseTest.java
        │                   ├── MoonPositionTest.java
        │                   ├── MoonTimesTest.java
        │                   ├── SunPositionTest.java
        │                   ├── SunTimesTest.java
        │                   └── util/
        │                       ├── BaseBuilderTest.java
        │                       ├── ExtendedMathTest.java
        │                       ├── JulianDateTest.java
        │                       ├── MatrixTest.java
        │                       ├── PegasusTest.java
        │                       ├── QuadraticInterpolationTest.java
        │                       └── VectorTest.java
        └── resources/
            └── .gitignore
Download .txt
SYMBOL INDEX (393 symbols across 35 files)

FILE: src/main/java/org/shredzone/commons/suncalc/MoonIllumination.java
  class MoonIllumination (line 35) | public class MoonIllumination {
    method MoonIllumination (line 44) | private MoonIllumination(double fraction, double phase, double angle,
    method compute (line 59) | public static Parameters compute() {
    type Parameters (line 66) | public interface Parameters extends
      method geocentric (line 78) | Parameters geocentric();
    class MoonIlluminationBuilder (line 86) | private static class MoonIlluminationBuilder extends BaseBuilder<Param...
      method geocentric (line 87) | @Override
      method execute (line 93) | @Override
    method getFraction (line 136) | public double getFraction() {
    method getPhase (line 147) | public double getPhase() {
    method getAngle (line 160) | public double getAngle() {
    method getClosestPhase (line 170) | public MoonPhase.Phase getClosestPhase() {
    method getElongation (line 181) | public double getElongation() {
    method getRadius (line 191) | public double getRadius() {
    method getCrescentWidth (line 201) | public double getCrescentWidth() {
    method toString (line 205) | @Override

FILE: src/main/java/org/shredzone/commons/suncalc/MoonPhase.java
  class MoonPhase (line 38) | public class MoonPhase {
    method MoonPhase (line 43) | private MoonPhase(ZonedDateTime time, double distance) {
    method compute (line 53) | public static Parameters compute() {
    type Parameters (line 60) | public interface Parameters extends
      method phase (line 74) | Parameters phase(Phase phase);
      method phase (line 84) | Parameters phase(double phase);
    type Phase (line 90) | public enum Phase {
      method toPhase (line 149) | public static Phase toPhase(double angle) {
      method Phase (line 186) | Phase(double angle) {
      method getAngle (line 194) | public double getAngle() {
      method getAngleRad (line 201) | public double getAngleRad() {
    class MoonPhaseBuilder (line 210) | private static class MoonPhaseBuilder extends BaseBuilder<Parameters> ...
      method phase (line 215) | @Override
      method phase (line 221) | @Override
      method execute (line 227) | @Override
      method moonphase (line 261) | private double moonphase(JulianDate jd, double t) {
    method getTime (line 276) | public ZonedDateTime getTime() {
    method getDistance (line 285) | public double getDistance() { return distance; }
    method isSuperMoon (line 296) | public boolean isSuperMoon() {
    method isMicroMoon (line 309) | public boolean isMicroMoon() {
    method toString (line 313) | @Override

FILE: src/main/java/org/shredzone/commons/suncalc/MoonPosition.java
  class MoonPosition (line 32) | public class MoonPosition {
    method MoonPosition (line 40) | private MoonPosition(double azimuth, double altitude, double trueAltit...
    method compute (line 53) | public static Parameters compute() {
    type Parameters (line 60) | public interface Parameters extends
    class MoonPositionBuilder (line 71) | private static class MoonPositionBuilder extends BaseBuilder<Parameter...
      method execute (line 72) | @Override
    method getAltitude (line 109) | public double getAltitude() {
    method getTrueAltitude (line 122) | public double getTrueAltitude() {
    method getAzimuth (line 132) | public double getAzimuth() {
    method getDistance (line 139) | public double getDistance() {
    method getParallacticAngle (line 146) | public double getParallacticAngle() {
    method toString (line 150) | @Override

FILE: src/main/java/org/shredzone/commons/suncalc/MoonTimes.java
  class MoonTimes (line 39) | public final class MoonTimes {
    method MoonTimes (line 46) | private MoonTimes(@Nullable ZonedDateTime rise, @Nullable ZonedDateTim...
    method compute (line 59) | public static Parameters compute() {
    type Parameters (line 66) | public static interface Parameters extends
    class MoonTimesBuilder (line 78) | private static class MoonTimesBuilder extends BaseBuilder<Parameters> ...
      method execute (line 81) | @Override
      method correctedMoonHeight (line 184) | private double correctedMoonHeight(JulianDate jd) {
    method getRise (line 196) | @Nullable
    method getSet (line 204) | @Nullable
    method isAlwaysUp (line 212) | public boolean isAlwaysUp() {
    method isAlwaysDown (line 219) | public boolean isAlwaysDown() {
    method toString (line 223) | @Override

FILE: src/main/java/org/shredzone/commons/suncalc/SunPosition.java
  class SunPosition (line 31) | public class SunPosition {
    method SunPosition (line 38) | private SunPosition(double azimuth, double altitude, double trueAltitu...
    method compute (line 50) | public static Parameters compute() {
    type Parameters (line 57) | public interface Parameters extends
    class SunPositionBuilder (line 68) | private static class SunPositionBuilder extends BaseBuilder<Parameters...
      method execute (line 69) | @Override
    method getAltitude (line 95) | public double getAltitude() {
    method getTrueAltitude (line 107) | public double getTrueAltitude() {
    method getAzimuth (line 117) | public double getAzimuth() {
    method getDistance (line 124) | public double getDistance() {
    method toString (line 128) | @Override

FILE: src/main/java/org/shredzone/commons/suncalc/SunTimes.java
  class SunTimes (line 37) | public class SunTimes {
    method SunTimes (line 46) | private SunTimes(@Nullable ZonedDateTime rise, @Nullable ZonedDateTime...
    method compute (line 62) | public static Parameters compute() {
    type Parameters (line 69) | public interface Parameters extends
      method twilight (line 85) | Parameters twilight(Twilight twilight);
      method twilight (line 95) | Parameters twilight(double angle);
    type Twilight (line 107) | public enum Twilight {
      method Twilight (line 176) | Twilight(double angle) {
      method Twilight (line 180) | Twilight(double angle, @Nullable Double position) {
      method getAngle (line 189) | public double getAngle() {
      method getAngleRad (line 196) | public double getAngleRad() {
      method isTopocentric (line 204) | public boolean isTopocentric() {
      method getAngularPosition (line 213) | @Nullable
    class SunTimesBuilder (line 223) | private static class SunTimesBuilder extends BaseBuilder<Parameters> i...
      method twilight (line 227) | @Override
      method twilight (line 234) | @Override
      method execute (line 241) | @Override
      method correctedSunHeight (line 379) | private double correctedSunHeight(JulianDate jd) {
    method getRise (line 398) | @Nullable
    method getSet (line 408) | @Nullable
    method getNoon (line 419) | @Nullable
    method getNadir (line 430) | @Nullable
    method isAlwaysUp (line 438) | public boolean isAlwaysUp() {
    method isAlwaysDown (line 445) | public boolean isAlwaysDown() {
    method toString (line 449) | @Override

FILE: src/main/java/org/shredzone/commons/suncalc/param/Builder.java
  type Builder (line 22) | public interface Builder<T> {
    method execute (line 32) | T execute();

FILE: src/main/java/org/shredzone/commons/suncalc/param/GenericParameter.java
  type GenericParameter (line 22) | public interface GenericParameter<T> {
    method copy (line 27) | T copy();

FILE: src/main/java/org/shredzone/commons/suncalc/param/LocationParameter.java
  type LocationParameter (line 28) | @SuppressWarnings("unchecked")
    method latitude (line 38) | T latitude(double lat);
    method longitude (line 47) | T longitude(double lng);
    method elevation (line 59) | T elevation(double h);
    method elevationFt (line 71) | default T elevationFt(double ft) {
    method height (line 84) | @Deprecated
    method heightFt (line 99) | @Deprecated
    method at (line 113) | default T at(double lat, double lng) {
    method at (line 130) | default T at(double[] coords) {
    method latitude (line 151) | default T latitude(int d, int m, double s) {
    method longitude (line 166) | default T longitude(int d, int m, double s) {
    method sameLocationAs (line 178) | T sameLocationAs(LocationParameter<?> l);

FILE: src/main/java/org/shredzone/commons/suncalc/param/TimeParameter.java
  type TimeParameter (line 35) | @SuppressWarnings("unchecked")
    method on (line 56) | T on(int year, int month, int date, int hour, int minute, int second);
    method on (line 69) | default T on(int year, int month, int date) {
    method on (line 80) | T on(ZonedDateTime dateTime);
    method on (line 89) | T on(LocalDateTime dateTime);
    method on (line 98) | T on(LocalDate date);
    method on (line 107) | T on(Instant instant);
    method on (line 116) | default T on(Date date) {
    method on (line 128) | default T on(Calendar cal) {
    method now (line 138) | T now();
    method midnight (line 145) | T midnight();
    method plusDays (line 154) | T plusDays(int days);
    method today (line 163) | default T today() {
    method tomorrow (line 176) | default T tomorrow() {
    method timezone (line 190) | T timezone(ZoneId tz);
    method timezone (line 201) | default T timezone(String id) {
    method localTime (line 210) | default T localTime() {
    method utc (line 219) | default T utc() {
    method timezone (line 229) | default T timezone(TimeZone tz) {
    method sameTimeAs (line 242) | T sameTimeAs(TimeParameter<?> t);

FILE: src/main/java/org/shredzone/commons/suncalc/param/WindowParameter.java
  type WindowParameter (line 28) | public interface WindowParameter<T> {
    method limit (line 38) | T limit(Duration duration);
    method oneDay (line 45) | default T oneDay() {
    method fullCycle (line 56) | default T fullCycle() {
    method reverse (line 66) | T reverse();
    method forward (line 76) | T forward();
    method sameWindowAs (line 88) | T sameWindowAs(WindowParameter<?> w);

FILE: src/main/java/org/shredzone/commons/suncalc/util/BaseBuilder.java
  class BaseBuilder (line 44) | @SuppressWarnings("unchecked")
    method on (line 55) | @Override
    method on (line 61) | @Override
    method on (line 67) | @Override
    method on (line 73) | @Override
    method on (line 79) | @Override
    method now (line 84) | @Override
    method plusDays (line 89) | @Override
    method midnight (line 94) | @Override
    method timezone (line 99) | @Override
    method latitude (line 106) | @Override
    method longitude (line 115) | @Override
    method elevation (line 124) | @Override
    method limit (line 130) | public T limit(Duration duration) {
    method reverse (line 139) | public T reverse() {
    method forward (line 144) | public T forward() {
    method sameTimeAs (line 149) | @Override
    method sameLocationAs (line 158) | @Override
    method sameWindowAs (line 170) | @Override
    method copy (line 181) | @Override
    method getLongitude (line 195) | public double getLongitude() {
    method getLatitude (line 207) | public double getLatitude() {
    method getLongitudeRad (line 219) | public double getLongitudeRad() {
    method getLatitudeRad (line 228) | public double getLatitudeRad() {
    method getElevation (line 237) | public double getElevation() {
    method getJulianDate (line 246) | public JulianDate getJulianDate() {
    method hasLocation (line 255) | public boolean hasLocation() {
    method clearLocation (line 264) | public void clearLocation() {
    method getDuration (line 274) | public Duration getDuration() {

FILE: src/main/java/org/shredzone/commons/suncalc/util/ExtendedMath.java
  class ExtendedMath (line 24) | public final class ExtendedMath {
    method ExtendedMath (line 46) | private ExtendedMath() {
    method frac (line 57) | public static double frac(double a) {
    method isZero (line 71) | public static boolean isZero(double d) {
    method equatorialToHorizontal (line 89) | public static Vector equatorialToHorizontal(double tau, double dec, do...
    method equatorialToEcliptical (line 101) | public static Matrix equatorialToEcliptical(JulianDate t) {
    method parallax (line 116) | public static double parallax(double elevation, double distance) {
    method apparentRefraction (line 135) | public static double apparentRefraction(double ha) {
    method refraction (line 161) | public static double refraction(double h) {
    method dms (line 181) | public static double dms(int d, int m, double s) {
    method readjustMax (line 200) | public static double readjustMax(double time, double frame, int depth,...
    method readjustMin (line 223) | public static double readjustMin(double time, double frame, int depth,...
    method readjustInterval (line 252) | private static double readjustInterval(double left, double right, doub...

FILE: src/main/java/org/shredzone/commons/suncalc/util/JulianDate.java
  class JulianDate (line 30) | public class JulianDate {
    method JulianDate (line 41) | public JulianDate(ZonedDateTime time) {
    method atHour (line 54) | public JulianDate atHour(double hour) {
    method atModifiedJulianDate (line 65) | public JulianDate atModifiedJulianDate(double mjd) {
    method atJulianCentury (line 77) | public JulianDate atJulianCentury(double jc) {
    method getDateTime (line 86) | public ZonedDateTime getDateTime() {
    method getModifiedJulianDate (line 95) | public double getModifiedJulianDate() {
    method getJulianCentury (line 104) | public double getJulianCentury() {
    method getGreenwichMeanSiderealTime (line 113) | public double getGreenwichMeanSiderealTime() {
    method getTrueAnomaly (line 136) | public double getTrueAnomaly() {
    method toString (line 140) | @Override

FILE: src/main/java/org/shredzone/commons/suncalc/util/Matrix.java
  class Matrix (line 26) | public class Matrix {
    method Matrix (line 30) | private Matrix() {
    method Matrix (line 34) | private Matrix(double... values) {
    method identity (line 46) | public static Matrix identity() {
    method rotateX (line 60) | public static Matrix rotateX(double angle) {
    method rotateY (line 77) | public static Matrix rotateY(double angle) {
    method rotateZ (line 94) | public static Matrix rotateZ(double angle) {
    method transpose (line 109) | public Matrix transpose() {
    method negate (line 124) | public Matrix negate() {
    method add (line 139) | public Matrix add(Matrix right) {
    method subtract (line 154) | public Matrix subtract(Matrix right) {
    method multiply (line 169) | public Matrix multiply(Matrix right) {
    method multiply (line 190) | public Matrix multiply(double scalar) {
    method multiply (line 205) | public Vector multiply(Vector right) {
    method get (line 229) | public double get(int r, int c) {
    method set (line 247) | private void set(int r, int c, double v) {
    method equals (line 254) | @Override
    method hashCode (line 262) | @Override
    method toString (line 267) | @Override

FILE: src/main/java/org/shredzone/commons/suncalc/util/Moon.java
  class Moon (line 26) | public final class Moon {
    method Moon (line 30) | private Moon() {
    method positionEquatorial (line 41) | public static Vector positionEquatorial(JulianDate date) {
    method position (line 96) | public static Vector position(JulianDate date) {
    method positionHorizontal (line 112) | public static Vector positionHorizontal(JulianDate date, double lat, d...
    method positionTopocentric (line 134) | public static Vector positionTopocentric(JulianDate date, double lat, ...
    method angularRadius (line 152) | public static double angularRadius(double distance) {

FILE: src/main/java/org/shredzone/commons/suncalc/util/Pegasus.java
  class Pegasus (line 25) | public class Pegasus {
    method calculate (line 45) | public static Double calculate(double lower, double upper, double accu...

FILE: src/main/java/org/shredzone/commons/suncalc/util/QuadraticInterpolation.java
  class QuadraticInterpolation (line 22) | public class QuadraticInterpolation {
    method QuadraticInterpolation (line 41) | public QuadraticInterpolation(double yMinus, double y0, double yPlus) {
    method getXe (line 78) | public double getXe() {
    method getYe (line 87) | public double getYe() {
    method getRoot1 (line 96) | public double getRoot1() {
    method getRoot2 (line 105) | public double getRoot2() {
    method getNumberOfRoots (line 114) | public int getNumberOfRoots() {
    method isMaximum (line 124) | public boolean isMaximum() {

FILE: src/main/java/org/shredzone/commons/suncalc/util/Sun.java
  class Sun (line 26) | public final class Sun {
    method Sun (line 31) | private Sun() {
    method positionEquatorial (line 42) | public static Vector positionEquatorial(JulianDate date) {
    method position (line 61) | public static Vector position(JulianDate date) {
    method positionHorizontal (line 77) | public static Vector positionHorizontal(JulianDate date, double lat, d...
    method positionTopocentric (line 99) | public static Vector positionTopocentric(JulianDate date, double lat, ...
    method angularRadius (line 117) | public static double angularRadius(double distance) {

FILE: src/main/java/org/shredzone/commons/suncalc/util/Vector.java
  class Vector (line 27) | public class Vector {
    method Vector (line 44) | public Vector(double x, double y, double z) {
    method Vector (line 56) | public Vector(double[] d) {
    method ofPolar (line 75) | public static Vector ofPolar(double φ, double θ) {
    method ofPolar (line 90) | public static Vector ofPolar(double φ, double θ, double r) {
    method getX (line 104) | public double getX() {
    method getY (line 111) | public double getY() {
    method getZ (line 118) | public double getZ() {
    method getPhi (line 125) | public double getPhi() {
    method getTheta (line 132) | public double getTheta() {
    method getR (line 139) | public double getR() {
    method add (line 151) | public Vector add(Vector vec) {
    method subtract (line 167) | public Vector subtract(Vector vec) {
    method multiply (line 183) | public Vector multiply(double scalar) {
    method negate (line 196) | public Vector negate() {
    method cross (line 212) | public Vector cross(Vector right) {
    method dot (line 227) | public double dot(Vector right) {
    method norm (line 236) | public double norm() {
    method equals (line 240) | @Override
    method hashCode (line 252) | @Override
    method toString (line 259) | @Override
    class Polar (line 268) | private class Polar {
      method setPolar (line 283) | public synchronized void setPolar(double φ, double θ, double r) {
      method getPhi (line 289) | public synchronized double getPhi() {
      method getTheta (line 304) | public synchronized double getTheta() {
      method getR (line 317) | public synchronized double getR() {

FILE: src/test/java/org/shredzone/commons/suncalc/ExamplesTest.java
  class ExamplesTest (line 30) | @Ignore // No real unit tests, but meant to be run manually
    method testTimezone (line 33) | @Test
    method testTimeWindow (line 62) | @Test
    method testParameterRecycling (line 105) | @Test
    method testParameterRecyclingLoop (line 122) | @Test
    method testGoldenHour (line 134) | @Test
    method testMoonPhase (line 160) | @Test
    method testPositions (line 191) | @Test

FILE: src/test/java/org/shredzone/commons/suncalc/Locations.java
  class Locations (line 21) | public final class Locations {

FILE: src/test/java/org/shredzone/commons/suncalc/MoonIlluminationTest.java
  class MoonIlluminationTest (line 26) | public class MoonIlluminationTest {
    method testNewMoon (line 31) | @Test
    method testWaxingHalfMoon (line 43) | @Test
    method testFullMoon (line 55) | @Test
    method testWaningHalfMoon (line 67) | @Test
    method testBaghdad1 (line 79) | @Test
    method testBaghdad2 (line 92) | @Test
    method testCapeTown1 (line 105) | @Test
    method testCapeTown2 (line 117) | @Test

FILE: src/test/java/org/shredzone/commons/suncalc/MoonPhaseTest.java
  class MoonPhaseTest (line 29) | public class MoonPhaseTest {
    method init (line 33) | @BeforeClass
    method testNewMoon (line 38) | @Test
    method testFirstQuarterMoon (line 51) | @Test
    method testFullMoon (line 64) | @Test
    method testLastQuarterMoon (line 77) | @Test
    method testToPhase (line 90) | @Test

FILE: src/test/java/org/shredzone/commons/suncalc/MoonPositionTest.java
  class MoonPositionTest (line 25) | public class MoonPositionTest {
    method testCologne (line 30) | @Test
    method testAlert (line 54) | @Test
    method testWellington (line 78) | @Test
    method testPuertoWilliams (line 102) | @Test
    method testSingapore (line 126) | @Test
    method testBaghdad (line 150) | @Test

FILE: src/test/java/org/shredzone/commons/suncalc/MoonTimesTest.java
  class MoonTimesTest (line 30) | public class MoonTimesTest {
    method init (line 32) | @BeforeClass
    method testCologne (line 37) | @Test
    method testAlert (line 52) | @Test
    method testWellington (line 81) | @Test
    method testPuertoWilliams (line 92) | @Test
    method testSingapore (line 100) | @Test
    method testSequence (line 107) | @Test
    method createDate (line 149) | private ZonedDateTime createDate(int year, int month, int day, int hou...

FILE: src/test/java/org/shredzone/commons/suncalc/SunPositionTest.java
  class SunPositionTest (line 25) | public class SunPositionTest {
    method testCologne (line 29) | @Test
    method testAlert (line 50) | @Test
    method testWellington (line 71) | @Test
    method testPuertoWilliams (line 101) | @Test
    method testSingapore (line 122) | @Test
    method testDistance (line 143) | @Test

FILE: src/test/java/org/shredzone/commons/suncalc/SunTimesTest.java
  class SunTimesTest (line 34) | public class SunTimesTest {
    method init (line 36) | @BeforeClass
    method testCologne (line 41) | @Test
    method testAlert (line 88) | @Test
    method testWellington (line 129) | @Test
    method testWellingtonReverse (line 136) | @Test
    method testPuertoWilliams (line 144) | @Test
    method testSingapore (line 151) | @Test
    method testMartinique (line 158) | @Test
    method testSydney (line 165) | @Test
    method testElevation (line 172) | @Test
    method testJustBeforeJustAfter (line 185) | @Test
    method testNoonNadirAzimuth (line 230) | @Test
    method testSequence (line 238) | @Test
    method createDate (line 280) | private ZonedDateTime createDate(int year, int month, int day, int hou...
    method assertNoonNadirPrecision (line 284) | private void assertNoonNadirPrecision(ZonedDateTime time, double[] loc...
    method assertTimes (line 304) | private void assertTimes(SunTimes t, String rise, String set, String n...
    method assertTimes (line 308) | private void assertTimes(SunTimes t, String rise, String set, String n...

FILE: src/test/java/org/shredzone/commons/suncalc/util/BaseBuilderTest.java
  class BaseBuilderTest (line 39) | public class BaseBuilderTest {
    method init (line 44) | @BeforeClass
    method testLocationParameters (line 49) | @Test
    method testBadLocations (line 120) | @Test
    method testTimeParameters (line 149) | @Test
    method testWindowParameters (line 278) | @Test
    method testCopy (line 337) | @Test
    method assertLatLng (line 386) | private void assertLatLng(TestBuilder p, double lat, double lng, doubl...
    method assertDate (line 394) | private void assertDate(TestBuilder p, int year, int month, int day,
    class TestBuilder (line 407) | private static class TestBuilder extends BaseBuilder<TestBuilder> {
      method now (line 408) | @Override

FILE: src/test/java/org/shredzone/commons/suncalc/util/ExtendedMathTest.java
  class ExtendedMathTest (line 25) | public class ExtendedMathTest {
    method testFrac (line 29) | @Test
    method testIsZero (line 40) | @Test
    method testDms (line 54) | @Test

FILE: src/test/java/org/shredzone/commons/suncalc/util/JulianDateTest.java
  class JulianDateTest (line 31) | public class JulianDateTest {
    method init (line 35) | @BeforeClass
    method testAtHour (line 40) | @Test
    method testModifiedJulianDate (line 55) | @Test
    method testJulianCentury (line 71) | @Test
    method testGreenwichMeanSiderealTime (line 83) | @Test
    method testTrueAnomaly (line 90) | @Test
    method testAtModifiedJulianDate (line 99) | @Test
    method testAtJulianCentury (line 109) | @Test
    method of (line 119) | private ZonedDateTime of(int year, int month, int day, int hour, int m...
    method assertDate (line 123) | private void assertDate(JulianDate jd, String date) {

FILE: src/test/java/org/shredzone/commons/suncalc/util/MatrixTest.java
  class MatrixTest (line 25) | public class MatrixTest {
    method testIdentity (line 30) | @Test
    method testRotateX (line 39) | @Test
    method testRotateY (line 48) | @Test
    method testRotateZ (line 57) | @Test
    method testTranspose (line 66) | @Test
    method testNegate (line 75) | @Test
    method testAdd (line 84) | @Test
    method testSubtract (line 100) | @Test
    method testMultiply (line 116) | @Test
    method testScalarMultiply (line 132) | @Test
    method testVectorMultiply (line 141) | @Test
    method testEquals (line 151) | @Test
    method testHashCode (line 165) | @Test
    method testToString (line 178) | @Test
    method assertValues (line 185) | private void assertValues(Matrix mx, double... values) {

FILE: src/test/java/org/shredzone/commons/suncalc/util/PegasusTest.java
  class PegasusTest (line 27) | public class PegasusTest {
    method testParabola (line 31) | @Test
    method testParabola2 (line 51) | @Test(expected = ArithmeticException.class)

FILE: src/test/java/org/shredzone/commons/suncalc/util/QuadraticInterpolationTest.java
  class QuadraticInterpolationTest (line 24) | public class QuadraticInterpolationTest {
    method testTwoRootsAndMinimum (line 28) | @Test
    method testTwoRootsAndMaximum (line 40) | @Test
    method testOneRoot (line 52) | @Test
    method testNoRoot (line 63) | @Test

FILE: src/test/java/org/shredzone/commons/suncalc/util/VectorTest.java
  class VectorTest (line 25) | public class VectorTest {
    method testConstructors (line 30) | @Test
    method testBadConstructor (line 53) | @Test(expected = IllegalArgumentException.class)
    method testAdd (line 58) | @Test
    method testSubtract (line 74) | @Test
    method testMultiply (line 90) | @Test
    method testNegate (line 100) | @Test
    method testCross (line 110) | @Test
    method testDot (line 121) | @Test
    method testNorm (line 130) | @Test
    method testEquals (line 138) | @Test
    method testHashCode (line 152) | @Test
    method testToString (line 165) | @Test
    method testToCartesian (line 172) | @Test
    method testToPolar (line 225) | @Test
Condensed preview — 59 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (293K chars).
[
  {
    "path": ".gitignore",
    "chars": 34,
    "preview": ".classpath\n.settings\ntarget\n/bin/\n"
  },
  {
    "path": ".gitlab-ci.yml",
    "chars": 137,
    "preview": "build:\n  tags:\n    - maven\n  script:\n    - mvn clean compile\n\ndeploy:\n  tags:\n    - maven\n  script:\n    - mvn -B install"
  },
  {
    "path": ".project",
    "chars": 544,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<projectDescription>\n\t<name>commons-suncalc</name>\n\t<comment></comment>\n\t<project"
  },
  {
    "path": "LICENSE-APL.txt",
    "chars": 11358,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "README.md",
    "chars": 2645,
    "preview": "# commons-suncalc ![build status](https://shredzone.org/badge/commons-suncalc.svg) ![maven central](https://shredzone.or"
  },
  {
    "path": "pom.xml",
    "chars": 8403,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
  },
  {
    "path": "src/doc/docs/examples.md",
    "chars": 15213,
    "preview": "# Examples\n\nIn this chapter, you will find code examples that demonstrate the use and the possibilities of the API. You "
  },
  {
    "path": "src/doc/docs/faq.md",
    "chars": 1798,
    "preview": "# FAQ\n\n## There is a different result on another website/app. Which one is right?\n\nProbably both. Calculating sun and mo"
  },
  {
    "path": "src/doc/docs/index.md",
    "chars": 2636,
    "preview": "# commons-suncalc\n\nA Java library for calculation of sun and moon positions and phases.\n\nThe source code can be found at"
  },
  {
    "path": "src/doc/docs/migration.md",
    "chars": 3228,
    "preview": "# Migration Guide\n\nThis document will help you migrate your code to the latest _suncalc_ version.\n\n## Version 3.9\n* `Moo"
  },
  {
    "path": "src/doc/docs/usage.md",
    "chars": 12168,
    "preview": "# Usage\n\n`commons-suncalc` offers six astronomical calculations:\n\n* [SunTimes](./apidocs/org/shredzone/commons/suncalc/S"
  },
  {
    "path": "src/doc/mkdocs.yml",
    "chars": 495,
    "preview": "site_name: commons-suncalc\nsite_author: Richard Körber\nsite_url: https://commons.shredzone.org/suncalc\nsite_dir: target/"
  },
  {
    "path": "src/doc/theme/breadcrumbs.html",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/doc/theme/css/font.css",
    "chars": 908,
    "preview": "/*\n * Lato\n * Copyright 2010-2011 tyPoland Lukasz Dziedzic\n * SIL Open Font License, 1.1\n */\n@font-face {\n  font-family:"
  },
  {
    "path": "src/doc/theme/css/theme_custom.css",
    "chars": 159,
    "preview": "\n.wy-nav-content {\n    max-width: none;\n}\n\n.codehilite pre code {\n    font-size: 15px;\n}\n\ntable {\n    margin-bottom: 2re"
  },
  {
    "path": "src/doc/theme/css/theme_pygments.css",
    "chars": 2740,
    "preview": "\n/* github pygments theme by @jwarby, https://github.com/jwarby/jekyll-pygments-themes */\n.codehilite .hll { background-"
  },
  {
    "path": "src/doc/theme/footer.html",
    "chars": 555,
    "preview": "<footer>\n  <div class=\"rst-footer-buttons\" role=\"navigation\" aria-label=\"footer navigation\">\n    {% if page.next_page %}"
  },
  {
    "path": "src/doc/theme/main.html",
    "chars": 601,
    "preview": "{% extends \"base.html\" %}\n\n{% block styles %}\n<link rel=\"stylesheet\" href=\"{{ 'css/font.css'|url }}\" />\n<link rel=\"style"
  },
  {
    "path": "src/main/java/module-info.java",
    "chars": 688,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2020 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/MoonIllumination.java",
    "chars": 7269,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/MoonPhase.java",
    "chars": 9165,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2018 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/MoonPosition.java",
    "chars": 5216,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/MoonTimes.java",
    "chars": 7981,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/SunPosition.java",
    "chars": 4428,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/SunTimes.java",
    "chars": 15165,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/package-info.java",
    "chars": 963,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/param/Builder.java",
    "chars": 967,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/param/GenericParameter.java",
    "chars": 757,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2020 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/param/LocationParameter.java",
    "chars": 4797,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/param/TimeParameter.java",
    "chars": 5740,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/param/WindowParameter.java",
    "chars": 2184,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2024 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/param/package-info.java",
    "chars": 965,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/util/BaseBuilder.java",
    "chars": 7317,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/util/ExtendedMath.java",
    "chars": 8753,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/util/JulianDate.java",
    "chars": 4255,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/util/Matrix.java",
    "chars": 7381,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/util/Moon.java",
    "chars": 5210,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/util/Pegasus.java",
    "chars": 2312,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2018 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/util/QuadraticInterpolation.java",
    "chars": 2926,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/util/Sun.java",
    "chars": 3858,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/util/Vector.java",
    "chars": 7823,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/java/org/shredzone/commons/suncalc/util/package-info.java",
    "chars": 1023,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/main/resources/.gitignore",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/test/java/org/shredzone/commons/suncalc/ExamplesTest.java",
    "chars": 8631,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2020 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/test/java/org/shredzone/commons/suncalc/Locations.java",
    "chars": 3053,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/test/java/org/shredzone/commons/suncalc/MoonIlluminationTest.java",
    "chars": 5261,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/test/java/org/shredzone/commons/suncalc/MoonPhaseTest.java",
    "chars": 4693,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2018 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/test/java/org/shredzone/commons/suncalc/MoonPositionTest.java",
    "chars": 7666,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/test/java/org/shredzone/commons/suncalc/MoonTimesTest.java",
    "chars": 6764,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/test/java/org/shredzone/commons/suncalc/SunPositionTest.java",
    "chars": 7405,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/test/java/org/shredzone/commons/suncalc/SunTimesTest.java",
    "chars": 14749,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/test/java/org/shredzone/commons/suncalc/util/BaseBuilderTest.java",
    "chars": 14703,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/test/java/org/shredzone/commons/suncalc/util/ExtendedMathTest.java",
    "chars": 2492,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/test/java/org/shredzone/commons/suncalc/util/JulianDateTest.java",
    "chars": 4446,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/test/java/org/shredzone/commons/suncalc/util/MatrixTest.java",
    "chars": 5383,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/test/java/org/shredzone/commons/suncalc/util/PegasusTest.java",
    "chars": 1633,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2018 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/test/java/org/shredzone/commons/suncalc/util/QuadraticInterpolationTest.java",
    "chars": 2315,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/test/java/org/shredzone/commons/suncalc/util/VectorTest.java",
    "chars": 8771,
    "preview": "/*\n * Shredzone Commons - suncalc\n *\n * Copyright (C) 2017 Richard \"Shred\" Körber\n *   http://commons.shredzone.org\n *\n "
  },
  {
    "path": "src/test/resources/.gitignore",
    "chars": 0,
    "preview": ""
  }
]

About this extraction

This page contains the full source code of the shred/commons-suncalc GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 59 files (272.2 KB), approximately 74.1k tokens, and a symbol index with 393 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!