1 | /**
|
2 | * @copyright (c) 2016, Philipp Thürwächter & Pattrick Hüper
|
3 | * @copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
|
4 | * @license BSD-3-Clause (see LICENSE in the root directory of this source tree)
|
5 | */
|
6 |
|
7 | import {assert} from '../assert';
|
8 | import {DateTimeException, IllegalArgumentException} from '../errors';
|
9 | import {MathUtil} from '../MathUtil';
|
10 |
|
11 | /**
|
12 | * The range of valid values for a date-time field.
|
13 | *
|
14 | * All TemporalField instances have a valid range of values.
|
15 | * For example, the ISO day-of-month runs from 1 to somewhere between 28 and 31.
|
16 | * This class captures that valid range.
|
17 | *
|
18 | * It is important to be aware of the limitations of this class.
|
19 | * Only the minimum and maximum values are provided.
|
20 | * It is possible for there to be invalid values within the outer range.
|
21 | * For example, a weird field may have valid values of 1, 2, 4, 6, 7, thus
|
22 | * have a range of '1 - 7', despite that fact that values 3 and 5 are invalid.
|
23 | *
|
24 | * Instances of this class are not tied to a specific field.
|
25 | */
|
26 | export class ValueRange {
|
27 |
|
28 | /**
|
29 | *
|
30 | * @param {!number} minSmallest
|
31 | * @param {!number} minLargest
|
32 | * @param {!number} maxSmallest
|
33 | * @param {!number} maxLargest
|
34 | * @private
|
35 | */
|
36 | constructor(minSmallest, minLargest, maxSmallest, maxLargest) {
|
37 | assert(!(minSmallest > minLargest), 'Smallest minimum value \'' + minSmallest +
|
38 | '\' must be less than largest minimum value \'' + minLargest + '\'', IllegalArgumentException);
|
39 | assert(!(maxSmallest > maxLargest), 'Smallest maximum value \'' + maxSmallest +
|
40 | '\' must be less than largest maximum value \'' + maxLargest + '\'', IllegalArgumentException);
|
41 | assert(!(minLargest > maxLargest), 'Minimum value \'' + minLargest +
|
42 | '\' must be less than maximum value \'' + maxLargest + '\'', IllegalArgumentException);
|
43 |
|
44 | this._minSmallest = minSmallest;
|
45 | this._minLargest = minLargest;
|
46 | this._maxLargest = maxLargest;
|
47 | this._maxSmallest = maxSmallest;
|
48 | }
|
49 |
|
50 | /**
|
51 | * Is the value range fixed and fully known.
|
52 | *
|
53 | * For example, the ISO day-of-month runs from 1 to between 28 and 31.
|
54 | * Since there is uncertainty about the maximum value, the range is not fixed.
|
55 | * However, for the month of January, the range is always 1 to 31, thus it is fixed.
|
56 | *
|
57 | * @return {boolean} true if the set of values is fixed
|
58 | */
|
59 | isFixed() {
|
60 | return this._minSmallest === this._minLargest && this._maxSmallest === this._maxLargest;
|
61 | }
|
62 |
|
63 | /**
|
64 | *
|
65 | * @returns {number}
|
66 | */
|
67 | minimum(){
|
68 | return this._minSmallest;
|
69 | }
|
70 |
|
71 | /**
|
72 | *
|
73 | * @returns {number}
|
74 | */
|
75 | largestMinimum(){
|
76 | return this._minLargest;
|
77 | }
|
78 |
|
79 | /**
|
80 | *
|
81 | * @returns {number}
|
82 | */
|
83 | maximum(){
|
84 | return this._maxLargest;
|
85 | }
|
86 |
|
87 | /**
|
88 | *
|
89 | * @returns {number}
|
90 | */
|
91 | smallestMaximum(){
|
92 | return this._maxSmallest;
|
93 | }
|
94 |
|
95 | /**
|
96 | *
|
97 | * @returns {boolean}
|
98 | */
|
99 | isValidValue(value) {
|
100 | return (this.minimum() <= value && value <= this.maximum());
|
101 | }
|
102 |
|
103 | /**
|
104 | *
|
105 | * @param {number} value
|
106 | * @param {TemporalField} field
|
107 | */
|
108 | checkValidValue(value, field) {
|
109 | let msg;
|
110 | if (!this.isValidValue(value)) {
|
111 | if (field != null) {
|
112 | msg = ('Invalid value for ' + field + ' (valid values ' + (this.toString()) + '): ') + value;
|
113 | } else {
|
114 | msg = ('Invalid value (valid values ' + (this.toString()) + '): ') + value;
|
115 | }
|
116 | return assert(false, msg, DateTimeException);
|
117 | }
|
118 | return value;
|
119 | }
|
120 |
|
121 | /**
|
122 | * Checks that the specified value is valid and fits in an `int`.
|
123 | *
|
124 | * This validates that the value is within the valid range of values and that
|
125 | * all valid values are within the bounds of an `int`.
|
126 | * The field is only used to improve the error message.
|
127 | *
|
128 | * @param {number} value - the value to check
|
129 | * @param {TemporalField} field - the field being checked, may be null
|
130 | * @return {number} the value that was passed in
|
131 | * @see #isValidIntValue(long)
|
132 | */
|
133 | checkValidIntValue(value, field) {
|
134 | if (this.isValidIntValue(value) === false) {
|
135 | throw new DateTimeException('Invalid int value for ' + field + ': ' + value);
|
136 | }
|
137 | return value;
|
138 | }
|
139 |
|
140 | /**
|
141 | * Checks if the value is within the valid range and that all values
|
142 | * in the range fit in an `int`.
|
143 | *
|
144 | * This method combines {@link isIntValue} and {@link isValidValue}.
|
145 | *
|
146 | * @param {number} value - the value to check
|
147 | * @return true if the value is valid and fits in an `int`
|
148 | */
|
149 | isValidIntValue(value) {
|
150 | return this.isIntValue() && this.isValidValue(value);
|
151 | }
|
152 |
|
153 | /**
|
154 | * Checks if all values in the range fit in an `int`.
|
155 | *
|
156 | * This checks that all valid values are within the bounds of an `int`.
|
157 | *
|
158 | * For example, the ISO month-of-year has values from 1 to 12, which fits in an `int`.
|
159 | * By comparison, ISO nano-of-day runs from 1 to 86,400,000,000,000 which does not fit in an `int`.
|
160 | *
|
161 | * This implementation uses {@link getMinimum} and {@link getMaximum}.
|
162 | *
|
163 | * @return boolean if a valid value always fits in an `int`
|
164 | */
|
165 | isIntValue() { // should be isSafeIntegerValue
|
166 | return this.minimum() >= MathUtil.MIN_SAFE_INTEGER && this.maximum() <= MathUtil.MAX_SAFE_INTEGER;
|
167 | }
|
168 |
|
169 | /**
|
170 | * Checks if this range is equal to another range.
|
171 | *
|
172 | * The comparison is based on the four values, minimum, largest minimum,
|
173 | * smallest maximum and maximum.
|
174 | * Only objects of type {@link ValueRange} are compared, other types return false.
|
175 | *
|
176 | * @param {*} other - the object to check, null returns false
|
177 | * @return {boolean} true if this is equal to the other range
|
178 | */
|
179 | equals(other) {
|
180 | if (other === this) {
|
181 | return true;
|
182 | }
|
183 | if (other instanceof ValueRange) {
|
184 | return this._minSmallest === other._minSmallest && this._minLargest === other._minLargest &&
|
185 | this._maxSmallest === other._maxSmallest && this._maxLargest === other._maxLargest;
|
186 | }
|
187 | return false;
|
188 | }
|
189 |
|
190 | /**
|
191 | * A hash code for this range.
|
192 | *
|
193 | * @return {number} a suitable hash code
|
194 | */
|
195 | hashCode() {
|
196 | return MathUtil.hashCode(this._minSmallest, this._minLargest, this._maxSmallest, this._maxLargest);
|
197 | }
|
198 |
|
199 | /*
|
200 | * Outputs this range as a String.
|
201 | *
|
202 | * The format will be '{min}/{largestMin} - {smallestMax}/{max}',
|
203 | * where the largestMin or smallestMax sections may be omitted, together
|
204 | * with associated slash, if they are the same as the min or max.
|
205 | *
|
206 | * @return {string} a string representation of this range, not null
|
207 | */
|
208 | toString() {
|
209 | let str = this.minimum() + (this.minimum() !== this.largestMinimum() ? '/' + (this.largestMinimum()) : '');
|
210 | str += ' - ';
|
211 | str += this.smallestMaximum() + (this.smallestMaximum() !== this.maximum() ? '/' + (this.maximum()) : '');
|
212 | return str;
|
213 | }
|
214 |
|
215 | /*
|
216 | * called with 2 params: Obtains a fixed value range.
|
217 | *
|
218 | * This factory obtains a range where the minimum and maximum values are fixed.
|
219 | * For example, the ISO month-of-year always runs from 1 to 12.
|
220 | *
|
221 | * @param min the minimum value
|
222 | * @param max the maximum value
|
223 | * @return the ValueRange for min, max, not null
|
224 |
|
225 | * called with 3 params: Obtains a variable value range.
|
226 | *
|
227 | * This factory obtains a range where the minimum value is fixed and the maximum value may vary.
|
228 | * For example, the ISO day-of-month always starts at 1, but ends between 28 and 31.
|
229 | *
|
230 | * @param min the minimum value
|
231 | * @param maxSmallest the smallest maximum value
|
232 | * @param maxLargest the largest maximum value
|
233 | * @return the ValueRange for min, smallest max, largest max, not null
|
234 |
|
235 | * called with 4 params: Obtains a fully variable value range.
|
236 | *
|
237 | * This factory obtains a range where both the minimum and maximum value may vary.
|
238 | *
|
239 | * @param minSmallest the smallest minimum value
|
240 | * @param minLargest the largest minimum value
|
241 | * @param maxSmallest the smallest maximum value
|
242 | * @param maxLargest the largest maximum value
|
243 | *
|
244 | * @return {ValueRange} the ValueRange for smallest min, largest min, smallest max, largest max, not null
|
245 | */
|
246 | static of() {
|
247 | if (arguments.length === 2) {
|
248 | return new ValueRange(arguments[0], arguments[0], arguments[1], arguments[1]);
|
249 | } else if (arguments.length === 3) {
|
250 | return new ValueRange(arguments[0], arguments[0], arguments[1], arguments[2]);
|
251 | } else if (arguments.length === 4) {
|
252 | return new ValueRange(arguments[0], arguments[1], arguments[2], arguments[3]);
|
253 | } else {
|
254 | return assert(false, 'Invalid number of arguments ' + arguments.length, IllegalArgumentException);
|
255 | }
|
256 | }
|
257 | }
|