1 /* 2 * ==================================================================== 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * ==================================================================== 20 * 21 * This software consists of voluntary contributions made by many 22 * individuals on behalf of the Apache Software Foundation. For more 23 * information on the Apache Software Foundation, please see 24 * <http://www.apache.org/>. 25 * 26 */ 27 28 package org.apache.hc.core5.util; 29 30 import java.text.ParseException; 31 import java.time.Instant; 32 import java.time.ZoneOffset; 33 import java.time.format.DateTimeFormatter; 34 import java.time.format.DateTimeFormatterBuilder; 35 import java.util.concurrent.TimeUnit; 36 37 /** 38 * A deadline based on a UNIX time, the elapsed since 00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January 39 * 1970. 40 * 41 * @since 5.0 42 */ 43 public class Deadline { 44 45 /** 46 * The format used for parsing and formatting dates. 47 */ 48 public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; 49 50 /** 51 * A special internal value that marks a deadline as the longest possible. 52 */ 53 private static final long INTERNAL_MAX_VALUE = Long.MAX_VALUE; 54 55 /** 56 * A special internal value that marks a deadline as the shortest possible. 57 */ 58 private static final long INTERNAL_MIN_VALUE = 0; 59 60 /** 61 * The maximum (longest-lived) deadline. 62 */ 63 public static Deadline MAX_VALUE = new Deadline(INTERNAL_MAX_VALUE); 64 65 /** 66 * The minimum (shortest-lived) deadline. 67 */ 68 public static Deadline MIN_VALUE = new Deadline(INTERNAL_MIN_VALUE); 69 70 private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder() 71 .parseLenient() 72 .parseCaseInsensitive() 73 .appendPattern(DATE_FORMAT) 74 .toFormatter(); 75 76 /** 77 * Calculates a deadline with a given time in milliseconds plus a given time value. Non-positive time values 78 * represent an indefinite timeout without a deadline. 79 * 80 * @param timeMillis A time in UNIX milliseconds, usually the current time. 81 * @param timeValue time value to add to {@code timeMillis}. 82 * @return a deadline representing the current time plus the given time value. 83 */ 84 public static Deadline calculate(final long timeMillis, final TimeValue timeValue) { 85 if (TimeValue.isPositive(timeValue)) { 86 // TODO handle unlikely overflow 87 final long deadline = timeMillis + timeValue.toMilliseconds(); 88 return deadline < 0 ? Deadline.MAX_VALUE : Deadline.fromUnixMilliseconds(deadline); 89 } 90 return Deadline.MAX_VALUE; 91 } 92 93 /** 94 * Calculates a deadline from now plus a given time value. Non-positive time values 95 * represent an indefinite timeout without a deadline. 96 * 97 * @param timeValue time value to add to {@code timeMillis}. 98 * @return a deadline representing the current time plus the given time value. 99 */ 100 public static Deadline calculate(final TimeValue timeValue) { 101 return calculate(System.currentTimeMillis(), timeValue); 102 } 103 104 /** 105 * Creates a deadline from a UNIX time in milliseconds. 106 * 107 * @param value a UNIX time in milliseconds. 108 * @return a new deadline. 109 */ 110 public static Deadline fromUnixMilliseconds(final long value) { 111 if (value == INTERNAL_MAX_VALUE) { 112 return MAX_VALUE; 113 } 114 if (value == INTERNAL_MIN_VALUE) { 115 return MIN_VALUE; 116 } 117 return new Deadline(value); 118 } 119 120 /** 121 * Creates a deadline from a string in the format {@value #DATE_FORMAT}. 122 * 123 * @param source a string in the format {@value #DATE_FORMAT}. 124 * @return a deadline from a string in the format {@value #DATE_FORMAT}. 125 * @throws ParseException if the specified source string cannot be parsed. 126 */ 127 public static Deadline parse(final String source) throws ParseException { 128 if (source == null) { 129 return null; 130 } 131 final Instant instant = Instant.from(DATE_TIME_FORMATTER.parse(source)); 132 return fromUnixMilliseconds(instant.toEpochMilli()); 133 } 134 135 private volatile boolean frozen; 136 137 private volatile long lastCheck; 138 139 /* 140 * Internal representation is a UNIX time. 141 */ 142 private final long value; 143 144 /** 145 * Constructs a new instance with the given UNIX time in milliseconds. 146 * 147 * @param deadlineMillis UNIX time in milliseconds. 148 */ 149 private Deadline(final long deadlineMillis) { 150 super(); 151 this.value = deadlineMillis; 152 setLastCheck(); 153 } 154 155 @Override 156 public boolean equals(final Object obj) { 157 // Only take into account the deadline value. 158 if (this == obj) { 159 return true; 160 } 161 if (obj == null) { 162 return false; 163 } 164 if (getClass() != obj.getClass()) { 165 return false; 166 } 167 final Deadline other = (Deadline) obj; 168 return value == other.value; 169 } 170 171 @Override 172 public int hashCode() { 173 // Only take into account the deadline value. 174 return Long.hashCode(value); 175 } 176 177 /** 178 * Formats this deadline. 179 * 180 * @param overdueTimeUnit the time unit to show how much over the deadline we are. 181 * @return a formatted string. 182 */ 183 public String format(final TimeUnit overdueTimeUnit) { 184 return String.format("Deadline: %s, %s overdue", formatTarget(), TimeValue.of(remaining(), overdueTimeUnit)); 185 } 186 187 /** 188 * Formats the deadline value as a string in the format {@value #DATE_FORMAT}. 189 * 190 * @return a formatted string in the format {@value #DATE_FORMAT}. 191 */ 192 public String formatTarget() { 193 return DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(value).atOffset(ZoneOffset.UTC)); 194 } 195 196 public Deadline freeze() { 197 frozen = true; 198 return this; 199 } 200 201 /** 202 * Package private for testing. 203 * 204 * @return the last time we checked the current time. 205 */ 206 long getLastCheck() { 207 return lastCheck; 208 } 209 210 /** 211 * Gets the UNIX time deadline value. 212 * 213 * @return the UNIX time deadline value. 214 */ 215 public long getValue() { 216 return value; 217 } 218 219 /** 220 * Returns whether this deadline occurs before the given time in milliseconds. 221 * 222 * @param millis the time to compare. 223 * @return whether this deadline occurs before the given time in milliseconds. 224 */ 225 public boolean isBefore(final long millis) { 226 return value < millis; 227 } 228 229 /** 230 * Returns whether the deadline has expired. 231 * 232 * @return whether the deadline has expired. 233 */ 234 public boolean isExpired() { 235 setLastCheck(); 236 return value < this.lastCheck; 237 } 238 239 /** 240 * Returns whether this deadline is the maximum deadline. 241 * 242 * @return whether this deadline is the maximum deadline. 243 */ 244 public boolean isMax() { 245 return value == INTERNAL_MAX_VALUE; 246 } 247 248 /** 249 * Returns whether this deadline is the minimum deadline. 250 * 251 * @return whether this deadline is the minimum deadline. 252 */ 253 public boolean isMin() { 254 return value == INTERNAL_MIN_VALUE; 255 } 256 257 /** 258 * Returns whether this deadline has not expired. 259 * 260 * @return whether this deadline has not expired. 261 */ 262 public boolean isNotExpired() { 263 setLastCheck(); 264 return value >= this.lastCheck; 265 } 266 267 /** 268 * Returns the smaller of this and another {@code Deadline}. 269 * 270 * @param other another deadline. 271 * @return the smaller of {@code this} and {@code other}. 272 */ 273 public Deadline min(final Deadline other) { 274 return value <= other.value ? this : other; 275 } 276 277 /** 278 * Returns the difference in milliseconds between the deadline and now. 279 * 280 * @return the different in milliseconds between the deadline and now. 281 */ 282 public long remaining() { 283 setLastCheck(); 284 return value - lastCheck; 285 } 286 287 /** 288 * Returns the difference as a TimeValue between the deadline and now. 289 * 290 * @return Returns the different as a TimeValue between the deadline and now. 291 */ 292 public TimeValue remainingTimeValue() { 293 return TimeValue.of(remaining(), TimeUnit.MILLISECONDS); 294 } 295 296 private void setLastCheck() { 297 if (!frozen) { 298 this.lastCheck = System.currentTimeMillis(); 299 }} 300 301 @Override 302 public String toString() { 303 return formatTarget(); 304 } 305 306 }