1 /* 2 * hunt-time: A time library for D programming language. 3 * 4 * Copyright (C) 2015-2018 HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.time.temporal.ValueRange; 13 14 import hunt.Exceptions; 15 import std.conv; 16 import hunt.Integer; 17 import hunt.util.StringBuilder; 18 import hunt.stream.Common; 19 import hunt.time.Exceptions; 20 import hunt.time.temporal.TemporalField; 21 import hunt.util.Common; 22 // import hunt.serialization.JsonSerializer; 23 24 /** 25 * The range of valid values for a date-time field. 26 * !(p) 27 * All {@link TemporalField} instances have a valid range of values. 28 * For example, the ISO day-of-month runs from 1 to somewhere between 28 and 31. 29 * This class captures that valid range. 30 * !(p) 31 * It is important to be aware of the limitations of this class. 32 * Only the minimum and maximum values are provided. 33 * It is possible for there to be invalid values within the outer range. 34 * For example, a weird field may have valid values of 1, 2, 4, 6, 7, thus 35 * have a range of '1 - 7', despite that fact that values 3 and 5 are invalid. 36 * !(p) 37 * Instances of this class are not tied to a specific field. 38 * 39 * @implSpec 40 * This class is immutable and thread-safe. 41 * 42 * @since 1.8 43 */ 44 public final class ValueRange { // : Serializable 45 46 47 /** 48 * The smallest minimum value. 49 */ 50 private long minSmallest; 51 /** 52 * The largest minimum value. 53 */ 54 private long minLargest; 55 /** 56 * The smallest maximum value. 57 */ 58 private long maxSmallest; 59 /** 60 * The largest maximum value. 61 */ 62 private long maxLargest; 63 64 /** 65 * Obtains a fixed value range. 66 * !(p) 67 * This factory obtains a range where the minimum and maximum values are fixed. 68 * For example, the ISO month-of-year always runs from 1 to 12. 69 * 70 * @param min the minimum value 71 * @param max the maximum value 72 * @return the ValueRange for min, max, not null 73 * @throws IllegalArgumentException if the minimum is greater than the maximum 74 */ 75 public static ValueRange of(long min, long max) { 76 if (min > max) { 77 throw new IllegalArgumentException("Minimum value must be less than maximum value"); 78 } 79 return new ValueRange(min, min, max, max); 80 } 81 82 /** 83 * Obtains a variable value range. 84 * !(p) 85 * This factory obtains a range where the minimum value is fixed and the maximum value may vary. 86 * For example, the ISO day-of-month always starts at 1, but ends between 28 and 31. 87 * 88 * @param min the minimum value 89 * @param maxSmallest the smallest maximum value 90 * @param maxLargest the largest maximum value 91 * @return the ValueRange for min, smallest max, largest max, not null 92 * @throws IllegalArgumentException if 93 * the minimum is greater than the smallest maximum, 94 * or the smallest maximum is greater than the largest maximum 95 */ 96 public static ValueRange of(long min, long maxSmallest, long maxLargest) { 97 return of(min, min, maxSmallest, maxLargest); 98 } 99 100 /** 101 * Obtains a fully variable value range. 102 * !(p) 103 * This factory obtains a range where both the minimum and maximum value may vary. 104 * 105 * @param minSmallest the smallest minimum value 106 * @param minLargest the largest minimum value 107 * @param maxSmallest the smallest maximum value 108 * @param maxLargest the largest maximum value 109 * @return the ValueRange for smallest min, largest min, smallest max, largest max, not null 110 * @throws IllegalArgumentException if 111 * the smallest minimum is greater than the smallest maximum, 112 * or the smallest maximum is greater than the largest maximum 113 * or the largest minimum is greater than the largest maximum 114 */ 115 public static ValueRange of(long minSmallest, long minLargest, long maxSmallest, long maxLargest) { 116 if (minSmallest > minLargest) { 117 throw new IllegalArgumentException("Smallest minimum value must be less than largest minimum value"); 118 } 119 if (maxSmallest > maxLargest) { 120 throw new IllegalArgumentException("Smallest maximum value must be less than largest maximum value"); 121 } 122 if (minLargest > maxLargest) { 123 throw new IllegalArgumentException("Minimum value must be less than maximum value"); 124 } 125 return new ValueRange(minSmallest, minLargest, maxSmallest, maxLargest); 126 } 127 128 /** 129 * Restrictive constructor. 130 * 131 * @param minSmallest the smallest minimum value 132 * @param minLargest the largest minimum value 133 * @param maxSmallest the smallest minimum value 134 * @param maxLargest the largest minimum value 135 */ 136 private this(long minSmallest, long minLargest, long maxSmallest, long maxLargest) { 137 this.minSmallest = minSmallest; 138 this.minLargest = minLargest; 139 this.maxSmallest = maxSmallest; 140 this.maxLargest = maxLargest; 141 } 142 143 //----------------------------------------------------------------------- 144 /** 145 * Is the value range fixed and fully known. 146 * !(p) 147 * For example, the ISO day-of-month runs from 1 to between 28 and 31. 148 * Since there is uncertainty about the maximum value, the range is not fixed. 149 * However, for the month of January, the range is always 1 to 31, thus it is fixed. 150 * 151 * @return true if the set of values is fixed 152 */ 153 public bool isFixed() { 154 return minSmallest == minLargest && maxSmallest == maxLargest; 155 } 156 157 //----------------------------------------------------------------------- 158 /** 159 * Gets the minimum value that the field can take. 160 * !(p) 161 * For example, the ISO day-of-month always starts at 1. 162 * The minimum is therefore 1. 163 * 164 * @return the minimum value for this field 165 */ 166 public long getMinimum() { 167 return minSmallest; 168 } 169 170 /** 171 * Gets the largest possible minimum value that the field can take. 172 * !(p) 173 * For example, the ISO day-of-month always starts at 1. 174 * The largest minimum is therefore 1. 175 * 176 * @return the largest possible minimum value for this field 177 */ 178 public long getLargestMinimum() { 179 return minLargest; 180 } 181 182 /** 183 * Gets the smallest possible maximum value that the field can take. 184 * !(p) 185 * For example, the ISO day-of-month runs to between 28 and 31 days. 186 * The smallest maximum is therefore 28. 187 * 188 * @return the smallest possible maximum value for this field 189 */ 190 public long getSmallestMaximum() { 191 return maxSmallest; 192 } 193 194 /** 195 * Gets the maximum value that the field can take. 196 * !(p) 197 * For example, the ISO day-of-month runs to between 28 and 31 days. 198 * The maximum is therefore 31. 199 * 200 * @return the maximum value for this field 201 */ 202 public long getMaximum() { 203 return maxLargest; 204 } 205 206 //----------------------------------------------------------------------- 207 /** 208 * Checks if all values _in the range fit _in an {@code int}. 209 * !(p) 210 * This checks that all valid values are within the bounds of an {@code int}. 211 * !(p) 212 * For example, the ISO month-of-year has values from 1 to 12, which fits _in an {@code int}. 213 * By comparison, ISO nano-of-day runs from 1 to 86,400,000,000,000 which does not fit _in an {@code int}. 214 * !(p) 215 * This implementation uses {@link #getMinimum()} and {@link #getMaximum()}. 216 * 217 * @return true if a valid value always fits _in an {@code int} 218 */ 219 public bool isIntValue() { 220 return getMinimum() >= Integer.MIN_VALUE && getMaximum() <= Integer.MAX_VALUE; 221 } 222 223 /** 224 * Checks if the value is within the valid range. 225 * !(p) 226 * This checks that the value is within the stored range of values. 227 * 228 * @param value the value to check 229 * @return true if the value is valid 230 */ 231 public bool isValidValue(long value) { 232 return (value >= getMinimum() && value <= getMaximum()); 233 } 234 235 /** 236 * Checks if the value is within the valid range and that all values 237 * _in the range fit _in an {@code int}. 238 * !(p) 239 * This method combines {@link #isIntValue()} and {@link #isValidValue(long)}. 240 * 241 * @param value the value to check 242 * @return true if the value is valid and fits _in an {@code int} 243 */ 244 public bool isValidIntValue(long value) { 245 return isIntValue() && isValidValue(value); 246 } 247 248 /** 249 * Checks that the specified value is valid. 250 * !(p) 251 * This validates that the value is within the valid range of values. 252 * The field is only used to improve the error message. 253 * 254 * @param value the value to check 255 * @param field the field being checked, may be null 256 * @return the value that was passed _in 257 * @see #isValidValue(long) 258 */ 259 public long checkValidValue(long value, TemporalField field) { 260 if (isValidValue(value) == false) { 261 throw new DateTimeException(genInvalidFieldMessage(field, value)); 262 } 263 return value; 264 } 265 266 /** 267 * Checks that the specified value is valid and fits _in an {@code int}. 268 * !(p) 269 * This validates that the value is within the valid range of values and that 270 * all valid values are within the bounds of an {@code int}. 271 * The field is only used to improve the error message. 272 * 273 * @param value the value to check 274 * @param field the field being checked, may be null 275 * @return the value that was passed _in 276 * @see #isValidIntValue(long) 277 */ 278 public int checkValidIntValue(long value, TemporalField field) { 279 if (isValidIntValue(value) == false) { 280 throw new DateTimeException(genInvalidFieldMessage(field, value)); 281 } 282 return cast(int) value; 283 } 284 285 private string genInvalidFieldMessage(TemporalField field, long value) { 286 if (field !is null) { 287 return "Invalid value for " ~ field.toString ~ " (valid values " ~ this.toString ~ "): " ~ value.to!string; 288 } else { 289 return "Invalid value (valid values " ~ this.toString ~ "): " ~ value.to!string; 290 } 291 } 292 293 //----------------------------------------------------------------------- 294 /** 295 * Restore the state of an ValueRange from the stream. 296 * Check that the values are valid. 297 * 298 * @param s the stream to read 299 * @throws InvalidObjectException if 300 * the smallest minimum is greater than the smallest maximum, 301 * or the smallest maximum is greater than the largest maximum 302 * or the largest minimum is greater than the largest maximum 303 * @throws ClassNotFoundException if a class cannot be resolved 304 */ 305 ///@gxc 306 // private void readObject(ObjectInputStream s) 307 // /*throws IOException, ClassNotFoundException, InvalidObjectException*/ 308 // { 309 // s.defaultReadObject(); 310 // if (minSmallest > minLargest) { 311 // throw new InvalidObjectException("Smallest minimum value must be less than largest minimum value"); 312 // } 313 // if (maxSmallest > maxLargest) { 314 // throw new InvalidObjectException("Smallest maximum value must be less than largest maximum value"); 315 // } 316 // if (minLargest > maxLargest) { 317 // throw new InvalidObjectException("Minimum value must be less than maximum value"); 318 // } 319 // } 320 321 //----------------------------------------------------------------------- 322 /** 323 * Checks if this range is equal to another range. 324 * !(p) 325 * The comparison is based on the four values, minimum, largest minimum, 326 * smallest maximum and maximum. 327 * Only objects of type {@code ValueRange} are compared, other types return false. 328 * 329 * @param obj the object to check, null returns false 330 * @return true if this is equal to the other range 331 */ 332 override 333 public bool opEquals(Object obj) { 334 if (obj == this) { 335 return true; 336 } 337 if (cast(ValueRange)(obj) !is null) { 338 ValueRange other = cast(ValueRange) obj; 339 return minSmallest == other.minSmallest && minLargest == other.minLargest && 340 maxSmallest == other.maxSmallest && maxLargest == other.maxLargest; 341 } 342 return false; 343 } 344 345 /** 346 * A hash code for this range. 347 * 348 * @return a suitable hash code 349 */ 350 override 351 public size_t toHash() @trusted nothrow { 352 long hash = minSmallest + (minLargest << 16) + (minLargest >> 48) + 353 (maxSmallest << 32) + (maxSmallest >> 32) + (maxLargest << 48) + 354 (maxLargest >> 16); 355 return cast(int) (hash ^ (hash >>> 32)); 356 } 357 358 //----------------------------------------------------------------------- 359 /** 360 * Outputs this range as a {@code string}. 361 * !(p) 362 * The format will be '{min}/{largestMin} - {smallestMax}/{max}', 363 * where the largestMin or smallestMax sections may be omitted, together 364 * with associated slash, if they are the same as the min or max. 365 * 366 * @return a string representation of this range, not null 367 */ 368 override 369 public string toString() { 370 StringBuilder buf = new StringBuilder(); 371 buf.append(minSmallest); 372 if (minSmallest != minLargest) { 373 buf.append('/').append(minLargest); 374 } 375 buf.append(" - ").append(maxSmallest); 376 if (maxSmallest != maxLargest) { 377 buf.append('/').append(maxLargest); 378 } 379 return buf.toString(); 380 } 381 382 // mixin SerializationMember!(typeof(this)); 383 }