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 }