fork download
  1. /*
  2.  * Copyright (c) 2016 Wolfgang Johannes Kohnen <wjkohnen@users.noreply.github.com>
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  * http://w...content-available-to-author-only...e.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  */
  16. package com.ko_sys.av.airac;
  17.  
  18. import net.jcip.annotations.Immutable;
  19. import org.jetbrains.annotations.Contract;
  20. import org.jetbrains.annotations.NotNull;
  21. import org.jetbrains.annotations.Nullable;
  22.  
  23. import java.io.Serializable;
  24. import java.time.Duration;
  25. import java.time.Instant;
  26. import java.time.ZoneOffset;
  27. import java.time.ZonedDateTime;
  28. import java.time.format.DateTimeFormatter;
  29. import java.util.Objects;
  30. import java.util.regex.Matcher;
  31. import java.util.regex.Pattern;
  32.  
  33. /**
  34.  * An AIRAC cycle.
  35.  * <p>
  36.  * Provides calculations on Aeronautical Information Regulation And Control (AIRAC) cycle identifiers and effective
  37.  * calendar dates.
  38.  * <p>
  39.  * This class is immutable and thread-safe.
  40.  * <p>
  41.  * Regular, planned Aeronautical Information Publications (AIP) as defined by the International Civil Aviation
  42.  * Organization (ICAO) are published and become effective at fixed dates. This class implements the AIRAC cycle
  43.  * definition as published in the ICAO Aeronautical Information Services Manual (DOC 8126; AN/872; 6th Edition; 2003).
  44.  * Test cases validate officially documented dates from 1998 until 2020, including the rare case of a 14th cycle in the
  45.  * year 2020.
  46.  * <p>
  47.  * This class assumes that AIRAC cycles are effective from the effective date at 00:00:00 UTC until 27 days later at
  48.  * 23:59:59 UTC. However that is not correct:
  49.  * <p>
  50.  * ICAO DOC 8126, 6th Edition (2003), paragraph 2.6.4:
  51.  * <p>
  52.  * "In addition to the use of a predetermined schedule of effective AIRAC dates, Coordinated Universal Time (UTC)
  53.  * must also be used to indicate the time when the AIRAC information will become effective. Since Annex 15,
  54.  * paragraph 3.2.3 specifies that the Gregorian calendar and UTC must be used as the temporal reference system for
  55.  * international civil aviation, in addition to AIRAC dates, 00:01 UTC must be used to indicate the time when the
  56.  * AIRAC-based information will become effective."
  57.  * <p>
  58.  * However this is a "wontfix", because that may just confuse users.
  59.  * <p>
  60.  * This implementation will silently produce bogus data before the internal epoch of 1901-01-10. Contrary to the
  61.  * original Golang implementation this class does not appear to have a significant upper time boundary. At least
  62.  * values corresponding to dates between 1901-01-10 up to year 2200 are plausible.
  63.  * <p>
  64.  * This class only provides calculations on effective dates, not publication or reception dates etc. Although effective
  65.  * dates are clearly defined and are consistent at least between 1998 until 2020, the derivative dates changed
  66.  * historically.[citation needed]
  67.  *
  68.  * @since 1.8
  69.  */
  70. @Immutable
  71. public class Airac implements Comparable<Airac>, Serializable {
  72. /**
  73. * ICAO DOC 8126, 6th edition (2003); Paragraph 2.6.2 b):
  74. * the AIRAC effective dates must be in accordance
  75. * with the predetermined, internationally agreed
  76. * schedule of effective dates based on an interval of
  77. * 28 days, including 29 January 1998
  78. */
  79. static final Duration durationCycle = Duration.ofDays(28);
  80. /**
  81. * All calculations use the UTC time zone.
  82. */
  83. private static final ZoneOffset UTC = ZoneOffset.UTC;
  84. /**
  85. * Magic effective date of a fictive AIRAC cycle that aligns with the documented cycles, e.g. 1998-01-29.
  86. */
  87. static final Instant epoch = Instant.from(ZonedDateTime.of(1901, 1, 10, 0, 0, 0, 0, UTC));
  88.  
  89. /**
  90. * Serialization version.
  91. */
  92. private static final long serialVersionUID = 19010110L;
  93.  
  94. /**
  95. * Pattern of AIRAC cycle identifiers (yyoo).
  96. */
  97. private static final Pattern identifierPattern = Pattern.compile("^(\\d{2})(\\d{2})$");
  98.  
  99. /**
  100. * Internal serial of this cycle, relative to {@code epoch}.
  101. */
  102. private final int serial;
  103.  
  104. /**
  105. * Create an instance of an AIRAC with a {@code serial} relative to the {@code epoch}.
  106. *
  107. * @param serial the {@code serial} relative to the {@code epoch}
  108. */
  109. private Airac(int serial) {
  110. this.serial = serial;
  111. }
  112.  
  113. /**
  114. * Obtains an instance of {@code Airac} that occurred at {@link Instant}.
  115. * <p>
  116. * Will silently produce bogus data, when instant is before the internal epoch of 1901-01-10.
  117. *
  118. * @param instant the point in time at which the AIRAC cycle of interest was current, not null
  119. * @return an instance of {@code Airac} that occurred at {@link Instant}
  120. */
  121. @NotNull
  122. public static Airac fromInstant(Instant instant) {
  123. Objects.requireNonNull(instant, "instant");
  124. Duration between = Duration.between(epoch, instant);
  125. int cycles = (int) (between.getSeconds() / durationCycle.getSeconds());
  126. return new Airac(cycles);
  127. }
  128.  
  129. /**
  130. * Obtains an instance of {@code Airac} that is represented by the identifier {@code yyoo}.
  131. * <p>
  132. * The String {@code yyoo} must consist of the last two digits of the year and the ordinal, each with leading zeros.
  133. * This works for years between 1964 and 2063. Identifiers between "6401" and "9913" are interpreted as AIRAC cycles
  134. * between the years 1964 and 1999 inclusive. AIRAC cycles between "0001" and "6313" are interpreted as AIRAC cycles
  135. * between the years 2000 and 2063 inclusive.
  136. *
  137. * @param yyoo the identifier of an AIRAC cycle ({@code YYOO}), not null.
  138. * @return An instance of an AIRAC cycle that is represented by the identifier.
  139. * @throws IllegalArgumentException if the identifier {@code yyoo} is null, malformed or implied cycle does not
  140. * exist.
  141. */
  142. @NotNull
  143. @Contract("null -> fail")
  144. public static Airac fromIdentifier(@Nullable String yyoo) {
  145. Objects.requireNonNull(yyoo);
  146.  
  147. Matcher m = identifierPattern.matcher(yyoo);
  148. if (!m.find()) {
  149. throw new IllegalArgumentException("illegal AIRAC identifier: " + yyoo);
  150. }
  151.  
  152. final int year;
  153. final int yy = Integer.parseInt(m.group(1));
  154. if (yy > 63) {
  155. year = 1900 + yy;
  156. } else {
  157. year = 2000 + yy;
  158. }
  159. final int ordinal = Integer.parseInt(m.group(2));
  160.  
  161. Airac lastAiracOfPreviousYear = fromInstant(Instant.from(ZonedDateTime.of(year - 1, 12, 31, 0, 0, 0, 0, UTC)));
  162. Airac airac = new Airac(lastAiracOfPreviousYear.serial + ordinal);
  163.  
  164. if (airac.getYear() != year) {
  165. throw new IllegalArgumentException(String.format("year %d does not have %d cycles", year, ordinal));
  166. }
  167.  
  168. return airac;
  169. }
  170.  
  171. /**
  172. * Returns the effective date (instant) of this AIRAC cycle.
  173. *
  174. * @return the effective date (instant) of this AIRAC cycle
  175. */
  176. @NotNull
  177. public Instant getEffective() {
  178. return epoch.plus(durationCycle.multipliedBy(serial));
  179. }
  180.  
  181. /**
  182. * Returns the ordinal for this AIRAC cycle's identifier.
  183. *
  184. * @return the ordinal for this AIRAC cycle's identifier
  185. */
  186. public int getOrdinal() {
  187. return (getEffective().atZone(UTC).getDayOfYear() - 1) / 28 + 1;
  188. }
  189.  
  190. /**
  191. * Returns the year for this AIRAC cycle's identifier.
  192. *
  193. * @return the year for this AIRAC cycle's identifier
  194. */
  195. public int getYear() {
  196. return getEffective().atZone(UTC).getYear();
  197. }
  198.  
  199. /**
  200. * Returns a short representation of this AIRAC cycle as in "YYOO".
  201. *
  202. * @return a short representation of this AIRAC cycle
  203. */
  204. @NotNull
  205. @Override
  206. public String toString() {
  207. return String.format("%02d%02d", getYear() % 100, getOrdinal());
  208. }
  209.  
  210. /**
  211. * Returns a verbose representation of this AIRAC cycle as in
  212. * "YYOO (effective: YYYY-MM-DD; expires: YYYY-MM-DD)"
  213. *
  214. * @return a verbose representation of this AIRAC cycle
  215. */
  216. @NotNull
  217. public String toLongString() {
  218. return String.format("%02d%02d (effective: %s; expires: %s)",
  219. getYear() % 100,
  220. getOrdinal(),
  221. getEffective().atZone(UTC).format(DateTimeFormatter.ISO_LOCAL_DATE),
  222. getNext().getEffective().minusSeconds(1).atZone(UTC).format(DateTimeFormatter.ISO_LOCAL_DATE)
  223. );
  224. }
  225.  
  226. /**
  227. * Returns the successor AIRAC cycle to this AIRAC cycle.
  228. *
  229. * @return the successor AIRAC cycle to this AIRAC cycle
  230. */
  231. @NotNull
  232. public Airac getNext() {
  233. return new Airac(serial + 1);
  234. }
  235.  
  236. /**
  237. * Returns the predecessor AIRAC cycle to this AIRAC cycle.
  238. *
  239. * @return the predecessor AIRAC cycle to this AIRAC cycle
  240. */
  241. @NotNull
  242. public Airac getPrevious() {
  243. return new Airac(serial - 1);
  244. }
  245.  
  246. /**
  247. * Indicates whether some other AIRAC cycle is "equal to" this one.
  248. *
  249. * @param obj the reference AIRAC cycle with which to compare.
  250. * @return {@code true} if this AIRAC cycle is the same as the obj
  251. * argument; {@code false} otherwise.
  252. */
  253. @Override
  254. public boolean equals(Object obj) {
  255. if (this == obj) return true;
  256. if (!(obj instanceof Airac)) return false;
  257.  
  258. Airac other = (Airac) obj;
  259.  
  260. return serial == other.serial;
  261. }
  262.  
  263. /**
  264. * Returns a hash code value for the AIRAC cycle.
  265. *
  266. * @return a hash code value for this AIRAC cycle.
  267. */
  268. @Override
  269. public int hashCode() {
  270. return serial;
  271. }
  272.  
  273. /**
  274. * Compares this AIRAC cycle to the specified AIRAC cycle.
  275. * <p>
  276. * It is "consistent with equals", as defined by {@link Comparable}.
  277. *
  278. * @param otherCycle the other AIRAC cycle to be compared to, not null.
  279. * @return a negative integer, zero, or a positive integer as this AIRAC cycle
  280. * is less than, equal to, or greater than the specified AIRAC cycle.
  281. * @throws NullPointerException if the specified AIRAC cycle is null
  282. */
  283. @Override
  284. public int compareTo(@NotNull Airac otherCycle) {
  285. Objects.requireNonNull(otherCycle);
  286. return Integer.compare(serial, otherCycle.serial);
  287. }
  288. }
Compilation error #stdin compilation error #stdout 0s 0KB
stdin
Standard input is empty
compilation info
Main.java:71: error: class Airac is public, should be declared in a file named Airac.java
public class Airac implements Comparable<Airac>, Serializable {
       ^
Main.java:18: error: package net.jcip.annotations does not exist
import net.jcip.annotations.Immutable;
                           ^
Main.java:19: error: package org.jetbrains.annotations does not exist
import org.jetbrains.annotations.Contract;
                                ^
Main.java:20: error: package org.jetbrains.annotations does not exist
import org.jetbrains.annotations.NotNull;
                                ^
Main.java:21: error: package org.jetbrains.annotations does not exist
import org.jetbrains.annotations.Nullable;
                                ^
Main.java:70: error: cannot find symbol
@Immutable
 ^
  symbol: class Immutable
Main.java:121: error: cannot find symbol
	@NotNull
	 ^
  symbol:   class NotNull
  location: class Airac
Main.java:144: error: cannot find symbol
	public static Airac fromIdentifier(@Nullable String yyoo) {
	                                    ^
  symbol:   class Nullable
  location: class Airac
Main.java:142: error: cannot find symbol
	@NotNull
	 ^
  symbol:   class NotNull
  location: class Airac
Main.java:143: error: cannot find symbol
	@Contract("null -> fail")
	 ^
  symbol:   class Contract
  location: class Airac
Main.java:176: error: cannot find symbol
	@NotNull
	 ^
  symbol:   class NotNull
  location: class Airac
Main.java:204: error: cannot find symbol
	@NotNull
	 ^
  symbol:   class NotNull
  location: class Airac
Main.java:216: error: cannot find symbol
	@NotNull
	 ^
  symbol:   class NotNull
  location: class Airac
Main.java:231: error: cannot find symbol
	@NotNull
	 ^
  symbol:   class NotNull
  location: class Airac
Main.java:241: error: cannot find symbol
	@NotNull
	 ^
  symbol:   class NotNull
  location: class Airac
Main.java:284: error: cannot find symbol
	public int compareTo(@NotNull Airac otherCycle) {
	                      ^
  symbol:   class NotNull
  location: class Airac
16 errors
stdout
Standard output is empty