UNPKG

13.4 kBJavaScriptView Raw
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
7import {Enum} from '../Enum';
8import {requireNonNull} from '../assert';
9import {DateTimeException} from '../errors';
10import {MathUtil} from '../MathUtil';
11
12import {DayOfWeek} from '../DayOfWeek';
13import {LocalDate} from '../LocalDate';
14import {Month} from '../Month';
15import {Year} from '../Year';
16
17import {ChronoField} from '../temporal/ChronoField';
18import {ResolverStyle} from '../format/ResolverStyle';
19import {TemporalAdjusters} from '../temporal/TemporalAdjusters';
20
21export 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
233export function _init() {
234 IsoChronology.INSTANCE = new IsoChronology('IsoChronology');
235}