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 {Enum} from '../Enum';
|
8 | import {requireNonNull} from '../assert';
|
9 | import {DateTimeException} from '../errors';
|
10 | import {MathUtil} from '../MathUtil';
|
11 |
|
12 | import {DayOfWeek} from '../DayOfWeek';
|
13 | import {LocalDate} from '../LocalDate';
|
14 | import {Month} from '../Month';
|
15 | import {Year} from '../Year';
|
16 |
|
17 | import {ChronoField} from '../temporal/ChronoField';
|
18 | import {ResolverStyle} from '../format/ResolverStyle';
|
19 | import {TemporalAdjusters} from '../temporal/TemporalAdjusters';
|
20 |
|
21 | export class IsoChronology extends Enum{
|
22 | /**
|
23 | * Checks if the year is a leap year, according to the ISO proleptic
|
24 | * calendar system rules.
|
25 | *
|
26 | * This method applies the current rules for leap years across the whole time-line.
|
27 | * In general, a year is a leap year if it is divisible by four without
|
28 | * remainder. However, years divisible by 100, are not leap years, with
|
29 | * the exception of years divisible by 400 which are.
|
30 | *
|
31 | * For example, 1904 is a leap year it is divisible by 4.
|
32 | * 1900 was not a leap year as it is divisible by 100, however 2000 was a
|
33 | * leap year as it is divisible by 400.
|
34 | *
|
35 | * The calculation is proleptic - applying the same rules into the far future and far past.
|
36 | * This is historically inaccurate, but is correct for the ISO-8601 standard.
|
37 | *
|
38 | * @param {number} prolepticYear - the ISO proleptic year to check
|
39 | * @return {boolean} true if the year is leap, false otherwise
|
40 | */
|
41 | static isLeapYear(prolepticYear) {
|
42 | return ((prolepticYear & 3) === 0) && ((prolepticYear % 100) !== 0 || (prolepticYear % 400) === 0);
|
43 | }
|
44 |
|
45 | /**
|
46 | * Updates the map of field-values during resolution.
|
47 | *
|
48 | * @param {EnumMap} fieldValues the fieldValues map to update, not null
|
49 | * @param {ChronoField} field the field to update, not null
|
50 | * @param {number} value the value to update, not null
|
51 | * @throws DateTimeException if a conflict occurs
|
52 | */
|
53 | _updateResolveMap(fieldValues, field, value) {
|
54 | // TODO: this function is in Chronology in threetenbp, maybe needs to be moved?
|
55 | requireNonNull(fieldValues, 'fieldValues');
|
56 | requireNonNull(field, 'field');
|
57 | const current = fieldValues.get(field);
|
58 | if (current != null && current !== value) {
|
59 | throw new DateTimeException('Invalid state, field: ' + field + ' ' + current + ' conflicts with ' + field + ' ' + value);
|
60 | }
|
61 | fieldValues.put(field, value);
|
62 | }
|
63 |
|
64 | resolveDate(fieldValues, resolverStyle) {
|
65 | if (fieldValues.containsKey(ChronoField.EPOCH_DAY)) {
|
66 | return LocalDate.ofEpochDay(fieldValues.remove(ChronoField.EPOCH_DAY));
|
67 | }
|
68 |
|
69 | // normalize fields
|
70 | const prolepticMonth = fieldValues.remove(ChronoField.PROLEPTIC_MONTH);
|
71 | if (prolepticMonth != null) {
|
72 | if (resolverStyle !== ResolverStyle.LENIENT) {
|
73 | ChronoField.PROLEPTIC_MONTH.checkValidValue(prolepticMonth);
|
74 | }
|
75 | this._updateResolveMap(fieldValues, ChronoField.MONTH_OF_YEAR, MathUtil.floorMod(prolepticMonth, 12) + 1);
|
76 | this._updateResolveMap(fieldValues, ChronoField.YEAR, MathUtil.floorDiv(prolepticMonth, 12));
|
77 | }
|
78 |
|
79 | // eras
|
80 | const yoeLong = fieldValues.remove(ChronoField.YEAR_OF_ERA);
|
81 | if (yoeLong != null) {
|
82 | if (resolverStyle !== ResolverStyle.LENIENT) {
|
83 | ChronoField.YEAR_OF_ERA.checkValidValue(yoeLong);
|
84 | }
|
85 | const era = fieldValues.remove(ChronoField.ERA);
|
86 | if (era == null) {
|
87 | const year = fieldValues.get(ChronoField.YEAR);
|
88 | if (resolverStyle === ResolverStyle.STRICT) {
|
89 | // do not invent era if strict, but do cross-check with year
|
90 | if (year != null) {
|
91 | this._updateResolveMap(fieldValues, ChronoField.YEAR, (year > 0 ? yoeLong: MathUtil.safeSubtract(1, yoeLong)));
|
92 | } else {
|
93 | // reinstate the field removed earlier, no cross-check issues
|
94 | fieldValues.put(ChronoField.YEAR_OF_ERA, yoeLong);
|
95 | }
|
96 | } else {
|
97 | // invent era
|
98 | this._updateResolveMap(fieldValues, ChronoField.YEAR, (year == null || year > 0 ? yoeLong: MathUtil.safeSubtract(1, yoeLong)));
|
99 | }
|
100 | } else if (era === 1) {
|
101 | this._updateResolveMap(fieldValues, ChronoField.YEAR, yoeLong);
|
102 | } else if (era === 0) {
|
103 | this._updateResolveMap(fieldValues, ChronoField.YEAR, MathUtil.safeSubtract(1, yoeLong));
|
104 | } else {
|
105 | throw new DateTimeException('Invalid value for era: ' + era);
|
106 | }
|
107 | } else if (fieldValues.containsKey(ChronoField.ERA)) {
|
108 | ChronoField.ERA.checkValidValue(fieldValues.get(ChronoField.ERA)); // always validated
|
109 | }
|
110 |
|
111 | // build date
|
112 | if (fieldValues.containsKey(ChronoField.YEAR)) {
|
113 | if (fieldValues.containsKey(ChronoField.MONTH_OF_YEAR)) {
|
114 | if (fieldValues.containsKey(ChronoField.DAY_OF_MONTH)) {
|
115 | const y = ChronoField.YEAR.checkValidIntValue(fieldValues.remove(ChronoField.YEAR));
|
116 | const moy = fieldValues.remove(ChronoField.MONTH_OF_YEAR);
|
117 | let dom = fieldValues.remove(ChronoField.DAY_OF_MONTH);
|
118 | if (resolverStyle === ResolverStyle.LENIENT) {
|
119 | const months = moy - 1;
|
120 | const days = dom - 1;
|
121 | return LocalDate.of(y, 1, 1).plusMonths(months).plusDays(days);
|
122 | } else if (resolverStyle === ResolverStyle.SMART){
|
123 | ChronoField.DAY_OF_MONTH.checkValidValue(dom);
|
124 | if (moy === 4 || moy === 6 || moy === 9 || moy === 11) {
|
125 | dom = Math.min(dom, 30);
|
126 | } else if (moy === 2) {
|
127 | dom = Math.min(dom, Month.FEBRUARY.length(Year.isLeap(y)));
|
128 | }
|
129 | return LocalDate.of(y, moy, dom);
|
130 | } else {
|
131 | return LocalDate.of(y, moy, dom);
|
132 | }
|
133 | }
|
134 | /*
|
135 | if (fieldValues.containsKey(ALIGNED_WEEK_OF_MONTH)) {
|
136 | if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) {
|
137 | int y = ChronoField.YEAR.checkValidIntValue(fieldValues.remove(ChronoField.YEAR));
|
138 | if (resolverStyle == ResolverStyle.LENIENT) {
|
139 | long months = Jdk8Methods.safeSubtract(fieldValues.remove(ChronoField.MONTH_OF_YEAR), 1);
|
140 | long weeks = Jdk8Methods.safeSubtract(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), 1);
|
141 | long days = Jdk8Methods.safeSubtract(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH), 1);
|
142 | return LocalDate.of(y, 1, 1).plusMonths(months).plusWeeks(weeks).plusDays(days);
|
143 | }
|
144 | int moy = ChronoField.MONTH_OF_YEAR.checkValidIntValue(fieldValues.remove(ChronoField.MONTH_OF_YEAR));
|
145 | int aw = ALIGNED_WEEK_OF_MONTH.checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_MONTH));
|
146 | int ad = ALIGNED_DAY_OF_WEEK_IN_MONTH.checkValidIntValue(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH));
|
147 | LocalDate date = LocalDate.of(y, moy, 1).plusDays((aw - 1) * 7 + (ad - 1));
|
148 | if (resolverStyle == ResolverStyle.STRICT && date.get(ChronoField.MONTH_OF_YEAR) != moy) {
|
149 | throw new DateTimeException("Strict mode rejected date parsed to a different month");
|
150 | }
|
151 | return date;
|
152 | }
|
153 | if (fieldValues.containsKey(DAY_OF_WEEK)) {
|
154 | int y = ChronoField.YEAR.checkValidIntValue(fieldValues.remove(ChronoField.YEAR));
|
155 | if (resolverStyle == ResolverStyle.LENIENT) {
|
156 | long months = Jdk8Methods.safeSubtract(fieldValues.remove(ChronoField.MONTH_OF_YEAR), 1);
|
157 | long weeks = Jdk8Methods.safeSubtract(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), 1);
|
158 | long days = Jdk8Methods.safeSubtract(fieldValues.remove(DAY_OF_WEEK), 1);
|
159 | return LocalDate.of(y, 1, 1).plusMonths(months).plusWeeks(weeks).plusDays(days);
|
160 | }
|
161 | int moy = ChronoField.MONTH_OF_YEAR.checkValidIntValue(fieldValues.remove(ChronoField.MONTH_OF_YEAR));
|
162 | int aw = ALIGNED_WEEK_OF_MONTH.checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_MONTH));
|
163 | int dow = DAY_OF_WEEK.checkValidIntValue(fieldValues.remove(DAY_OF_WEEK));
|
164 | LocalDate date = LocalDate.of(y, moy, 1).plusWeeks(aw - 1).with(nextOrSame(DayOfWeek.of(dow)));
|
165 | if (resolverStyle == ResolverStyle.STRICT && date.get(ChronoField.MONTH_OF_YEAR) != moy) {
|
166 | throw new DateTimeException("Strict mode rejected date parsed to a different month");
|
167 | }
|
168 | return date;
|
169 | }
|
170 | }
|
171 | */
|
172 | }
|
173 | if (fieldValues.containsKey(ChronoField.DAY_OF_YEAR)) {
|
174 | const y = ChronoField.YEAR.checkValidIntValue(fieldValues.remove(ChronoField.YEAR));
|
175 | if (resolverStyle === ResolverStyle.LENIENT) {
|
176 | const days = MathUtil.safeSubtract(fieldValues.remove(ChronoField.DAY_OF_YEAR), 1);
|
177 | return LocalDate.ofYearDay(y, 1).plusDays(days);
|
178 | }
|
179 | const doy = ChronoField.DAY_OF_YEAR.checkValidIntValue(fieldValues.remove(ChronoField.DAY_OF_YEAR));
|
180 | return LocalDate.ofYearDay(y, doy);
|
181 | }
|
182 | if (fieldValues.containsKey(ChronoField.ALIGNED_WEEK_OF_YEAR)) {
|
183 | if (fieldValues.containsKey(ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR)) {
|
184 | const y = ChronoField.YEAR.checkValidIntValue(fieldValues.remove(ChronoField.YEAR));
|
185 | if (resolverStyle === ResolverStyle.LENIENT) {
|
186 | const weeks = MathUtil.safeSubtract(fieldValues.remove(ChronoField.ALIGNED_WEEK_OF_YEAR), 1);
|
187 | const days = MathUtil.safeSubtract(fieldValues.remove(ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR), 1);
|
188 | return LocalDate.of(y, 1, 1).plusWeeks(weeks).plusDays(days);
|
189 | }
|
190 | const aw = ChronoField.ALIGNED_WEEK_OF_YEAR.checkValidIntValue(fieldValues.remove(ChronoField.ALIGNED_WEEK_OF_YEAR));
|
191 | const ad = ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR.checkValidIntValue(fieldValues.remove(ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR));
|
192 | const date = LocalDate.of(y, 1, 1).plusDays((aw - 1) * 7 + (ad - 1));
|
193 | if (resolverStyle === ResolverStyle.STRICT && date.get(ChronoField.YEAR) !== y) {
|
194 | throw new DateTimeException('Strict mode rejected date parsed to a different year');
|
195 | }
|
196 | return date;
|
197 | }
|
198 | if (fieldValues.containsKey(ChronoField.DAY_OF_WEEK)) {
|
199 | const y = ChronoField.YEAR.checkValidIntValue(fieldValues.remove(ChronoField.YEAR));
|
200 | if (resolverStyle === ResolverStyle.LENIENT) {
|
201 | const weeks = MathUtil.safeSubtract(fieldValues.remove(ChronoField.ALIGNED_WEEK_OF_YEAR), 1);
|
202 | const days = MathUtil.safeSubtract(fieldValues.remove(ChronoField.DAY_OF_WEEK), 1);
|
203 | return LocalDate.of(y, 1, 1).plusWeeks(weeks).plusDays(days);
|
204 | }
|
205 | const aw = ChronoField.ALIGNED_WEEK_OF_YEAR.checkValidIntValue(fieldValues.remove(ChronoField.ALIGNED_WEEK_OF_YEAR));
|
206 | const dow = ChronoField.DAY_OF_WEEK.checkValidIntValue(fieldValues.remove(ChronoField.DAY_OF_WEEK));
|
207 | const date = LocalDate.of(y, 1, 1).plusWeeks(aw - 1).with(TemporalAdjusters.nextOrSame(DayOfWeek.of(dow)));
|
208 | if (resolverStyle === ResolverStyle.STRICT && date.get(ChronoField.YEAR) !== y) {
|
209 | throw new DateTimeException('Strict mode rejected date parsed to a different month');
|
210 | }
|
211 | return date;
|
212 | }
|
213 | }
|
214 | }
|
215 | return null;
|
216 | }
|
217 |
|
218 | /**
|
219 | * Obtains an ISO local date from another date-time object.
|
220 | * <p>
|
221 | * This is equivalent to {@link LocalDate#from(TemporalAccessor)}.
|
222 | *
|
223 | * @param temporal the date-time object to convert, not null
|
224 | * @return the ISO local date, not null
|
225 | * @throws DateTimeException if unable to create the date
|
226 | */
|
227 | date(temporal) {
|
228 | return LocalDate.from(temporal);
|
229 | }
|
230 |
|
231 | }
|
232 |
|
233 | export function _init() {
|
234 | IsoChronology.INSTANCE = new IsoChronology('IsoChronology');
|
235 | }
|