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.chrono.IsoChronology; 13 14 import hunt.time.temporal.ChronoField; 15 16 17 import std.conv; 18 import hunt.stream.Common; 19 import hunt.time.Clock; 20 import hunt.time.Exceptions; 21 import hunt.time.Instant; 22 import hunt.time.LocalDate; 23 import hunt.time.LocalDateTime; 24 import hunt.time.Month; 25 import hunt.time.Period; 26 import hunt.time.Year; 27 import hunt.time.ZonedDateTime; 28 import hunt.time.ZoneId; 29 import hunt.time.ZoneOffset; 30 import hunt.time.format.ResolverStyle; 31 import hunt.time.format.TextStyle; 32 // import hunt.time.format.DateTimeFormatterBuilder; 33 import hunt.time.temporal.ChronoField; 34 import hunt.time.temporal.TemporalAccessor; 35 import hunt.time.temporal.TemporalField; 36 import hunt.time.temporal.ValueRange; 37 import hunt.time.temporal.TemporalQuery; 38 import hunt.time.Exceptions; 39 import hunt.collection.List; 40 // import hunt.time.util.Locale; 41 import hunt.collection.Map; 42 import hunt.time.chrono.AbstractChronology; 43 import hunt.time.chrono.Era; 44 import hunt.time.chrono.IsoEra; 45 import hunt.time.chrono.Chronology; 46 import hunt.time.chrono.ChronoLocalDateTime; 47 import hunt.time.chrono.ChronoLocalDateTimeImpl; 48 import hunt.time.chrono.ChronoZonedDateTime; 49 import hunt.time.chrono.ChronoZonedDateTimeImpl; 50 import hunt.time.chrono.ChronoLocalDate; 51 import hunt.time.LocalTime; 52 import hunt.time.util.QueryHelper; 53 import hunt.time.util.Common; 54 55 import hunt.Long; 56 import hunt.math.Helper; 57 import hunt.Exceptions; 58 import hunt.collection; 59 60 import hunt.util.Common; 61 import hunt.util.Comparator; 62 // import hunt.serialization.JsonSerializer; 63 64 import std.concurrency : initOnce; 65 66 /** 67 * The ISO calendar system. 68 * !(p) 69 * This chronology defines the rules of the ISO calendar system. 70 * This calendar system is based on the ISO-8601 standard, which is the 71 * !(i)de facto</i> world calendar. 72 * !(p) 73 * The fields are defined as follows: 74 * !(ul) 75 * !(li)era - There are two eras, 'Current Era' (CE) and 'Before Current Era' (BCE). 76 * !(li)year-of-era - The year-of-era is the same as the proleptic-year for the current CE era. 77 * For the BCE era before the ISO epoch the year increases from 1 upwards as time goes backwards. 78 * !(li)proleptic-year - The proleptic year is the same as the year-of-era for the 79 * current era. For the previous era, years have zero, then negative values. 80 * !(li)month-of-year - There are 12 months _in an ISO year, numbered from 1 to 12. 81 * !(li)day-of-month - There are between 28 and 31 days _in each of the ISO month, numbered from 1 to 31. 82 * Months 4, 6, 9 and 11 have 30 days, Months 1, 3, 5, 7, 8, 10 and 12 have 31 days. 83 * Month 2 has 28 days, or 29 _in a leap year. 84 * !(li)day-of-year - There are 365 days _in a standard ISO year and 366 _in a leap year. 85 * The days are numbered from 1 to 365 or 1 to 366. 86 * !(li)leap-year - Leap years occur every 4 years, except where the year is divisble by 100 and not divisble by 400. 87 * </ul> 88 * 89 * @implSpec 90 * This class is immutable and thread-safe. 91 * 92 * @since 1.8 93 */ 94 public final class IsoChronology : AbstractChronology { // , Serializable 95 96 /** 97 * Singleton instance of the ISO chronology. 98 */ 99 static IsoChronology INSTANCE() { 100 __gshared IsoChronology _INSTANCE; 101 return initOnce!(_INSTANCE)(new IsoChronology()); 102 } 103 104 private enum long DAYS_0000_TO_1970 = (146097 * 5L) - (30L * 365L + 7L); // taken from LocalDate 105 106 107 /** 108 * Restricted constructor. 109 */ 110 this() { 111 } 112 113 //----------------------------------------------------------------------- 114 /** 115 * Gets the ID of the chronology - 'ISO'. 116 * !(p) 117 * The ID uniquely identifies the {@code Chronology}. 118 * It can be used to lookup the {@code Chronology} using {@link Chronology#of(string)}. 119 * 120 * @return the chronology ID - 'ISO' 121 * @see #getCalendarType() 122 */ 123 // override 124 public string getId() { 125 return "ISO"; 126 } 127 128 /** 129 * Gets the calendar type of the underlying calendar system - 'iso8601'. 130 * !(p) 131 * The calendar type is an identifier defined by the 132 * !(em)Unicode Locale Data Markup Language (LDML)</em> specification. 133 * It can be used to lookup the {@code Chronology} using {@link Chronology#of(string)}. 134 * It can also be used as part of a locale, accessible via 135 * {@link Locale#getUnicodeLocaleType(string)} with the key 'ca'. 136 * 137 * @return the calendar system type - 'iso8601' 138 * @see #getId() 139 */ 140 // override 141 public string getCalendarType() { 142 return "iso8601"; 143 } 144 145 //----------------------------------------------------------------------- 146 /** 147 * Obtains an ISO local date from the era, year-of-era, month-of-year 148 * and day-of-month fields. 149 * 150 * @param era the ISO era, not null 151 * @param yearOfEra the ISO year-of-era 152 * @param month the ISO month-of-year 153 * @param dayOfMonth the ISO day-of-month 154 * @return the ISO local date, not null 155 * @throws DateTimeException if unable to create the date 156 * @throws ClassCastException if the type of {@code era} is not {@code IsoEra} 157 */ 158 // override // override with covariant return type 159 public LocalDate date(Era era, int yearOfEra, int month, int dayOfMonth) { 160 return date(prolepticYear(era, yearOfEra), month, dayOfMonth); 161 } 162 163 /** 164 * Obtains an ISO local date from the proleptic-year, month-of-year 165 * and day-of-month fields. 166 * !(p) 167 * This is equivalent to {@link LocalDate#of(int, int, int)}. 168 * 169 * @param prolepticYear the ISO proleptic-year 170 * @param month the ISO month-of-year 171 * @param dayOfMonth the ISO day-of-month 172 * @return the ISO local date, not null 173 * @throws DateTimeException if unable to create the date 174 */ 175 // override // override with covariant return type 176 public LocalDate date(int prolepticYear, int month, int dayOfMonth) { 177 return LocalDate.of(prolepticYear, month, dayOfMonth); 178 } 179 180 /** 181 * Obtains an ISO local date from the era, year-of-era and day-of-year fields. 182 * 183 * @param era the ISO era, not null 184 * @param yearOfEra the ISO year-of-era 185 * @param dayOfYear the ISO day-of-year 186 * @return the ISO local date, not null 187 * @throws DateTimeException if unable to create the date 188 */ 189 // override // override with covariant return type 190 public LocalDate dateYearDay(Era era, int yearOfEra, int dayOfYear) { 191 return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear); 192 } 193 194 /** 195 * Obtains an ISO local date from the proleptic-year and day-of-year fields. 196 * !(p) 197 * This is equivalent to {@link LocalDate#ofYearDay(int, int)}. 198 * 199 * @param prolepticYear the ISO proleptic-year 200 * @param dayOfYear the ISO day-of-year 201 * @return the ISO local date, not null 202 * @throws DateTimeException if unable to create the date 203 */ 204 // override // override with covariant return type 205 public LocalDate dateYearDay(int prolepticYear, int dayOfYear) { 206 return LocalDate.ofYearDay(prolepticYear, dayOfYear); 207 } 208 209 /** 210 * Obtains an ISO local date from the epoch-day. 211 * !(p) 212 * This is equivalent to {@link LocalDate#ofEpochDay(long)}. 213 * 214 * @param epochDay the epoch day 215 * @return the ISO local date, not null 216 * @throws DateTimeException if unable to create the date 217 */ 218 // override // override with covariant return type 219 public LocalDate dateEpochDay(long epochDay) { 220 return LocalDate.ofEpochDay(epochDay); 221 } 222 223 //----------------------------------------------------------------------- 224 /** 225 * Obtains an ISO local date from another date-time object. 226 * !(p) 227 * This is equivalent to {@link LocalDate#from(TemporalAccessor)}. 228 * 229 * @param temporal the date-time object to convert, not null 230 * @return the ISO local date, not null 231 * @throws DateTimeException if unable to create the date 232 */ 233 // override // override with covariant return type 234 public LocalDate date(TemporalAccessor temporal) { 235 return LocalDate.from(temporal); 236 } 237 238 //----------------------------------------------------------------------- 239 /** 240 * Gets the number of seconds from the epoch of 1970-01-01T00:00:00Z. 241 * !(p) 242 * The number of seconds is calculated using the year, 243 * month, day-of-month, hour, minute, second, and zoneOffset. 244 * 245 * @param prolepticYear the year, from MIN_YEAR to MAX_YEAR 246 * @param month the month-of-year, from 1 to 12 247 * @param dayOfMonth the day-of-month, from 1 to 31 248 * @param hour the hour-of-day, from 0 to 23 249 * @param minute the minute-of-hour, from 0 to 59 250 * @param second the second-of-minute, from 0 to 59 251 * @param zoneOffset the zone offset, not null 252 * @return the number of seconds relative to 1970-01-01T00:00:00Z, may be negative 253 * @throws DateTimeException if the value of any argument is _out of range, 254 * or if the day-of-month is invalid for the month-of-year 255 * @since 9 256 */ 257 // override 258 public long epochSecond(int prolepticYear, int month, int dayOfMonth, 259 int hour, int minute, int second, ZoneOffset zoneOffset) { 260 ChronoField.YEAR.checkValidValue(prolepticYear); 261 ChronoField.MONTH_OF_YEAR.checkValidValue(month); 262 ChronoField.DAY_OF_MONTH.checkValidValue(dayOfMonth); 263 ChronoField.HOUR_OF_DAY.checkValidValue(hour); 264 ChronoField.MINUTE_OF_HOUR.checkValidValue(minute); 265 ChronoField.SECOND_OF_MINUTE.checkValidValue(second); 266 assert(zoneOffset, "zoneOffset"); 267 if (dayOfMonth > 28) { 268 int dom = numberOfDaysOfMonth(prolepticYear, month); 269 if (dayOfMonth > dom) { 270 if (dayOfMonth == 29) { 271 throw new DateTimeException("Invalid date 'February 29' as '" ~ prolepticYear.to!string ~ "' is not a leap year"); 272 } else { 273 throw new DateTimeException("Invalid date '" ~ Month.of(month).name() ~ " " ~ dayOfMonth.to!string ~ "'"); 274 } 275 } 276 } 277 278 long totalDays = 0; 279 int timeinSec = 0; 280 totalDays += 365L * prolepticYear; 281 if (prolepticYear >= 0) { 282 totalDays += (prolepticYear + 3L) / 4 - (prolepticYear + 99L) / 100 + (prolepticYear + 399L) / 400; 283 } else { 284 totalDays -= prolepticYear / -4 - prolepticYear / -100 + prolepticYear / -400; 285 } 286 totalDays += (367 * month - 362) / 12; 287 totalDays += dayOfMonth - 1; 288 if (month > 2) { 289 totalDays--; 290 if (IsoChronology.INSTANCE.isLeapYear(prolepticYear) == false) { 291 totalDays--; 292 } 293 } 294 totalDays -= DAYS_0000_TO_1970; 295 timeinSec = (hour * 60 + minute ) * 60 + second; 296 return MathHelper.addExact(MathHelper.multiplyExact(totalDays, 86400L), timeinSec - zoneOffset.getTotalSeconds()); 297 } 298 299 long epochSecond(Era era, int yearOfEra, int month, int dayOfMonth, 300 int hour, int minute, int second, ZoneOffset zoneOffset) { 301 assert(era, "era"); 302 return epochSecond(prolepticYear(era, yearOfEra), month, dayOfMonth, hour, minute, second, zoneOffset); 303 } 304 305 /** 306 * Gets the number of days for the given month _in the given year. 307 * 308 * @param year the year to represent, from MIN_YEAR to MAX_YEAR 309 * @param month the month-of-year to represent, from 1 to 12 310 * @return the number of days for the given month _in the given year 311 */ 312 private int numberOfDaysOfMonth(int year, int month) { 313 int dom; 314 switch (month) { 315 case 2: 316 dom = (IsoChronology.INSTANCE.isLeapYear(year) ? 29 : 28); 317 break; 318 case 4: 319 case 6: 320 case 9: 321 case 11: 322 dom = 30; 323 break; 324 default: 325 dom = 31; 326 break; 327 } 328 return dom; 329 } 330 331 332 /** 333 * Obtains an ISO local date-time from another date-time object. 334 * !(p) 335 * This is equivalent to {@link LocalDateTime#from(TemporalAccessor)}. 336 * 337 * @param temporal the date-time object to convert, not null 338 * @return the ISO local date-time, not null 339 * @throws DateTimeException if unable to create the date-time 340 */ 341 // override // override with covariant return type 342 public ChronoLocalDateTime!(ChronoLocalDate) localDateTime(TemporalAccessor temporal) { 343 return cast(ChronoLocalDateTime!(ChronoLocalDate))(LocalDateTime.from(temporal)); 344 } 345 346 /** 347 * Obtains an ISO zoned date-time from another date-time object. 348 * !(p) 349 * This is equivalent to {@link ZonedDateTime#from(TemporalAccessor)}. 350 * 351 * @param temporal the date-time object to convert, not null 352 * @return the ISO zoned date-time, not null 353 * @throws DateTimeException if unable to create the date-time 354 */ 355 // override // override with covariant return type 356 public ChronoZonedDateTime!(ChronoLocalDate) zonedDateTime(TemporalAccessor temporal) { 357 return cast(ChronoZonedDateTime!(ChronoLocalDate))(ZonedDateTime.from(temporal)); 358 } 359 360 /** 361 * Obtains an ISO zoned date-time _in this chronology from an {@code Instant}. 362 * !(p) 363 * This is equivalent to {@link ZonedDateTime#ofInstant(Instant, ZoneId)}. 364 * 365 * @param instant the instant to create the date-time from, not null 366 * @param zone the time-zone, not null 367 * @return the zoned date-time, not null 368 * @throws DateTimeException if the result exceeds the supported range 369 */ 370 // override 371 public ChronoZonedDateTime!(ChronoLocalDate) zonedDateTime(Instant instant, ZoneId zone) { 372 return cast(ChronoZonedDateTime!(ChronoLocalDate))(ZonedDateTime.ofInstant(instant, zone)); 373 } 374 375 //----------------------------------------------------------------------- 376 /** 377 * Obtains the current ISO local date from the system clock _in the default time-zone. 378 * !(p) 379 * This will query the {@link Clock#systemDefaultZone() system clock} _in the default 380 * time-zone to obtain the current date. 381 * !(p) 382 * Using this method will prevent the ability to use an alternate clock for testing 383 * because the clock is hard-coded. 384 * 385 * @return the current ISO local date using the system clock and default time-zone, not null 386 * @throws DateTimeException if unable to create the date 387 */ 388 // override // override with covariant return type 389 public LocalDate dateNow() { 390 return dateNow(Clock.systemDefaultZone()); 391 } 392 393 /** 394 * Obtains the current ISO local date from the system clock _in the specified time-zone. 395 * !(p) 396 * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current date. 397 * Specifying the time-zone avoids dependence on the default time-zone. 398 * !(p) 399 * Using this method will prevent the ability to use an alternate clock for testing 400 * because the clock is hard-coded. 401 * 402 * @return the current ISO local date using the system clock, not null 403 * @throws DateTimeException if unable to create the date 404 */ 405 // override // override with covariant return type 406 public LocalDate dateNow(ZoneId zone) { 407 return dateNow(Clock.system(zone)); 408 } 409 410 /** 411 * Obtains the current ISO local date from the specified clock. 412 * !(p) 413 * This will query the specified clock to obtain the current date - today. 414 * Using this method allows the use of an alternate clock for testing. 415 * The alternate clock may be introduced using {@link Clock dependency injection}. 416 * 417 * @param clock the clock to use, not null 418 * @return the current ISO local date, not null 419 * @throws DateTimeException if unable to create the date 420 */ 421 // override // override with covariant return type 422 public LocalDate dateNow(Clock clock) { 423 assert(clock, "clock"); 424 return date(LocalDate.now(clock)); 425 } 426 427 //----------------------------------------------------------------------- 428 /** 429 * Checks if the year is a leap year, according to the ISO proleptic 430 * calendar system rules. 431 * !(p) 432 * This method applies the current rules for leap years across the whole time-line. 433 * In general, a year is a leap year if it is divisible by four without 434 * remainder. However, years divisible by 100, are not leap years, with 435 * the exception of years divisible by 400 which are. 436 * !(p) 437 * For example, 1904 is a leap year it is divisible by 4. 438 * 1900 was not a leap year as it is divisible by 100, however 2000 was a 439 * leap year as it is divisible by 400. 440 * !(p) 441 * The calculation is proleptic - applying the same rules into the far future and far past. 442 * This is historically inaccurate, but is correct for the ISO-8601 standard. 443 * 444 * @param prolepticYear the ISO proleptic year to check 445 * @return true if the year is leap, false otherwise 446 */ 447 // override 448 public bool isLeapYear(long prolepticYear) { 449 return ((prolepticYear & 3) == 0) && ((prolepticYear % 100) != 0 || (prolepticYear % 400) == 0); 450 } 451 452 // override 453 public int prolepticYear(Era era, int yearOfEra) { 454 if ((cast(IsoEra)(era) !is null) == false) { 455 throw new ClassCastException("Era must be IsoEra"); 456 } 457 return (era == IsoEra.CE ? yearOfEra : 1 - yearOfEra); 458 } 459 460 // override 461 public IsoEra eraOf(int eraValue) { 462 return IsoEra.of(eraValue); 463 } 464 465 // override 466 public List!(Era) eras() { 467 auto li = new ArrayList!Era(); 468 li.add(IsoEra.BCE); 469 li.add(IsoEra.CE); 470 return li; 471 } 472 473 //----------------------------------------------------------------------- 474 /** 475 * Resolves parsed {@code ChronoField} values into a date during parsing. 476 * !(p) 477 * Most {@code TemporalField} implementations are resolved using the 478 * resolve method on the field. By contrast, the {@code ChronoField} class 479 * defines fields that only have meaning relative to the chronology. 480 * As such, {@code ChronoField} date fields are resolved here _in the 481 * context of a specific chronology. 482 * !(p) 483 * {@code ChronoField} instances on the ISO calendar system are resolved 484 * as follows. 485 * !(ul) 486 * !(li){@code EPOCH_DAY} - If present, this is converted to a {@code LocalDate} 487 * and all other date fields are then cross-checked against the date. 488 * !(li){@code PROLEPTIC_MONTH} - If present, then it is split into the 489 * {@code YEAR} and {@code MONTH_OF_YEAR}. If the mode is strict or smart 490 * then the field is validated. 491 * !(li){@code YEAR_OF_ERA} and {@code ERA} - If both are present, then they 492 * are combined to form a {@code YEAR}. In lenient mode, the {@code YEAR_OF_ERA} 493 * range is not validated, _in smart and strict mode it is. The {@code ERA} is 494 * validated for range _in all three modes. If only the {@code YEAR_OF_ERA} is 495 * present, and the mode is smart or lenient, then the current era (CE/AD) 496 * is assumed. In strict mode, no era is assumed and the {@code YEAR_OF_ERA} is 497 * left untouched. If only the {@code ERA} is present, then it is left untouched. 498 * !(li){@code YEAR}, {@code MONTH_OF_YEAR} and {@code DAY_OF_MONTH} - 499 * If all three are present, then they are combined to form a {@code LocalDate}. 500 * In all three modes, the {@code YEAR} is validated. If the mode is smart or strict, 501 * then the month and day are validated, with the day validated from 1 to 31. 502 * If the mode is lenient, then the date is combined _in a manner equivalent to 503 * creating a date on the first of January _in the requested year, then adding 504 * the difference _in months, then the difference _in days. 505 * If the mode is smart, and the day-of-month is greater than the maximum for 506 * the year-month, then the day-of-month is adjusted to the last day-of-month. 507 * If the mode is strict, then the three fields must form a valid date. 508 * !(li){@code YEAR} and {@code DAY_OF_YEAR} - 509 * If both are present, then they are combined to form a {@code LocalDate}. 510 * In all three modes, the {@code YEAR} is validated. 511 * If the mode is lenient, then the date is combined _in a manner equivalent to 512 * creating a date on the first of January _in the requested year, then adding 513 * the difference _in days. 514 * If the mode is smart or strict, then the two fields must form a valid date. 515 * !(li){@code YEAR}, {@code MONTH_OF_YEAR}, {@code ALIGNED_WEEK_OF_MONTH} and 516 * {@code ALIGNED_DAY_OF_WEEK_IN_MONTH} - 517 * If all four are present, then they are combined to form a {@code LocalDate}. 518 * In all three modes, the {@code YEAR} is validated. 519 * If the mode is lenient, then the date is combined _in a manner equivalent to 520 * creating a date on the first of January _in the requested year, then adding 521 * the difference _in months, then the difference _in weeks, then _in days. 522 * If the mode is smart or strict, then the all four fields are validated to 523 * their outer ranges. The date is then combined _in a manner equivalent to 524 * creating a date on the first day of the requested year and month, then adding 525 * the amount _in weeks and days to reach their values. If the mode is strict, 526 * the date is additionally validated to check that the day and week adjustment 527 * did not change the month. 528 * !(li){@code YEAR}, {@code MONTH_OF_YEAR}, {@code ALIGNED_WEEK_OF_MONTH} and 529 * {@code DAY_OF_WEEK} - If all four are present, then they are combined to 530 * form a {@code LocalDate}. The approach is the same as described above for 531 * years, months and weeks _in {@code ALIGNED_DAY_OF_WEEK_IN_MONTH}. 532 * The day-of-week is adjusted as the next or same matching day-of-week once 533 * the years, months and weeks have been handled. 534 * !(li){@code YEAR}, {@code ALIGNED_WEEK_OF_YEAR} and {@code ALIGNED_DAY_OF_WEEK_IN_YEAR} - 535 * If all three are present, then they are combined to form a {@code LocalDate}. 536 * In all three modes, the {@code YEAR} is validated. 537 * If the mode is lenient, then the date is combined _in a manner equivalent to 538 * creating a date on the first of January _in the requested year, then adding 539 * the difference _in weeks, then _in days. 540 * If the mode is smart or strict, then the all three fields are validated to 541 * their outer ranges. The date is then combined _in a manner equivalent to 542 * creating a date on the first day of the requested year, then adding 543 * the amount _in weeks and days to reach their values. If the mode is strict, 544 * the date is additionally validated to check that the day and week adjustment 545 * did not change the year. 546 * !(li){@code YEAR}, {@code ALIGNED_WEEK_OF_YEAR} and {@code DAY_OF_WEEK} - 547 * If all three are present, then they are combined to form a {@code LocalDate}. 548 * The approach is the same as described above for years and weeks _in 549 * {@code ALIGNED_DAY_OF_WEEK_IN_YEAR}. The day-of-week is adjusted as the 550 * next or same matching day-of-week once the years and weeks have been handled. 551 * </ul> 552 * 553 * @param fieldValues the map of fields to values, which can be updated, not null 554 * @param resolverStyle the requested type of resolve, not null 555 * @return the resolved date, null if insufficient information to create a date 556 * @throws DateTimeException if the date cannot be resolved, typically 557 * because of a conflict _in the input data 558 */ 559 override // override for performance 560 public LocalDate resolveDate(Map!(TemporalField, Long) fieldValues, ResolverStyle resolverStyle) { 561 return cast(LocalDate) super.resolveDate(fieldValues, resolverStyle); 562 } 563 564 override // override for better proleptic algorithm 565 void resolveProlepticMonth(Map!(TemporalField, Long) fieldValues, ResolverStyle resolverStyle) { 566 Long pMonth = fieldValues.remove(ChronoField.PROLEPTIC_MONTH); 567 if (pMonth !is null) { 568 if (resolverStyle != ResolverStyle.LENIENT) { 569 ChronoField.PROLEPTIC_MONTH.checkValidValue(pMonth.longValue()); 570 } 571 addFieldValue(fieldValues, ChronoField.MONTH_OF_YEAR, MathHelper.floorMod(pMonth.longValue(), 12) + 1); 572 addFieldValue(fieldValues, ChronoField.YEAR, MathHelper.floorDiv(pMonth.longValue(), 12)); 573 } 574 } 575 576 override // override for enhanced behaviour 577 LocalDate resolveYearOfEra(Map!(TemporalField, Long) fieldValues, ResolverStyle resolverStyle) { 578 Long yoeLong = fieldValues.remove(ChronoField.YEAR_OF_ERA); 579 if (yoeLong !is null) { 580 if (resolverStyle != ResolverStyle.LENIENT) { 581 ChronoField.YEAR_OF_ERA.checkValidValue(yoeLong.longValue()); 582 } 583 Long era = fieldValues.remove(ChronoField.ERA); 584 if (era is null) { 585 Long year = fieldValues.get(ChronoField.YEAR); 586 if (resolverStyle == ResolverStyle.STRICT) { 587 // do not invent era if strict, but do cross-check with year 588 if (year !is null) { 589 addFieldValue(fieldValues, ChronoField.YEAR, (year > 0 ? yoeLong.longValue(): MathHelper.subtractExact(1, yoeLong.longValue()))); 590 } else { 591 // reinstate the field removed earlier, no cross-check issues 592 fieldValues.put(ChronoField.YEAR_OF_ERA, yoeLong); 593 } 594 } else { 595 // invent era 596 addFieldValue(fieldValues, ChronoField.YEAR, (year is null || year > 0 ? yoeLong.longValue(): MathHelper.subtractExact(1, yoeLong.longValue()))); 597 } 598 } else if (era.longValue() == 1L) { 599 addFieldValue(fieldValues, ChronoField.YEAR, yoeLong.longValue()); 600 } else if (era.longValue() == 0L) { 601 addFieldValue(fieldValues, ChronoField.YEAR, MathHelper.subtractExact(1, yoeLong.longValue())); 602 } else { 603 throw new DateTimeException("Invalid value for era: " ~ era.longValue().to!string); 604 } 605 } else if (fieldValues.containsKey(ChronoField.ERA)) { 606 ChronoField.ERA.checkValidValue(fieldValues.get(ChronoField.ERA).longValue()); // always validated 607 } 608 return null; 609 } 610 611 override // override for performance 612 LocalDate resolveYMD(Map !(TemporalField, Long) fieldValues, ResolverStyle resolverStyle) { 613 int y = ChronoField.YEAR.checkValidIntValue(fieldValues.remove(ChronoField.YEAR).longValue()); 614 if (resolverStyle == ResolverStyle.LENIENT) { 615 long months = MathHelper.subtractExact(fieldValues.remove(ChronoField.MONTH_OF_YEAR).longValue(), 1); 616 long days = MathHelper.subtractExact(fieldValues.remove(ChronoField.DAY_OF_MONTH).longValue(), 1); 617 return LocalDate.of(y, 1, 1).plusMonths(months).plusDays(days); 618 } 619 int moy = ChronoField.MONTH_OF_YEAR.checkValidIntValue(fieldValues.remove(ChronoField.MONTH_OF_YEAR).longValue()); 620 int dom = ChronoField.DAY_OF_MONTH.checkValidIntValue(fieldValues.remove(ChronoField.DAY_OF_MONTH).longValue()); 621 if (resolverStyle == ResolverStyle.SMART) { // previous valid 622 if (moy == 4 || moy == 6 || moy == 9 || moy == 11) { 623 dom = MathHelper.min(dom, 30); 624 } else if (moy == 2) { 625 dom = MathHelper.min(dom, Month.FEBRUARY.length(Year.isLeap(y))); 626 627 } 628 } 629 return LocalDate.of(y, moy, dom); 630 } 631 632 //----------------------------------------------------------------------- 633 // override 634 public ValueRange range(ChronoField field) { 635 return field.range(); 636 } 637 638 //----------------------------------------------------------------------- 639 /** 640 * Obtains a period for this chronology based on years, months and days. 641 * !(p) 642 * This returns a period tied to the ISO chronology using the specified 643 * years, months and days. See {@link Period} for further details. 644 * 645 * @param years the number of years, may be negative 646 * @param months the number of years, may be negative 647 * @param days the number of years, may be negative 648 * @return the ISO period, not null 649 */ 650 // override // override with covariant return type 651 public Period period(int years, int months, int days) { 652 return Period.of(years, months, days); 653 } 654 655 //----------------------------------------------------------------------- 656 /** 657 * Writes the Chronology using a 658 * <a href="{@docRoot}/serialized-form.html#hunt.time.chrono.Ser">dedicated serialized form</a>. 659 * @serialData 660 * !(pre) 661 * _out.writeByte(1); // identifies a Chronology 662 * _out.writeUTF(getId()); 663 * </pre> 664 * 665 * @return the instance of {@code Ser}, not null 666 */ 667 override 668 Object writeReplace() { 669 return super.writeReplace(); 670 } 671 672 /** 673 * Defend against malicious streams. 674 * 675 * @param s the stream to read 676 * @throws InvalidObjectException always 677 */ 678 ///@gxc 679 // private void readObject(ObjectInputStream s) /*throws InvalidObjectException*/ { 680 // throw new InvalidObjectException("Deserialization via serialization delegate"); 681 // } 682 683 override 684 public bool opEquals(Object obj) { 685 if (this is obj) { 686 return true; 687 } 688 if (cast(AbstractChronology)(obj) !is null) { 689 return compareTo(cast(AbstractChronology) obj) == 0; 690 } 691 return false; 692 } 693 694 override 695 public int compareTo(Chronology other) { 696 return getId().compare(other.getId()); 697 } 698 699 // override 700 public int opCmp(Chronology other) { 701 return getId().compare(other.getId()); 702 } 703 704 // mixin SerializationMember!(typeof(this)); 705 706 // override 707 // ChronoLocalDateTime!(ChronoLocalDate) localDateTime(TemporalAccessor temporal) { 708 // try { 709 // return date(temporal).atTime(LocalTime.from(temporal)); 710 // } catch (DateTimeException ex) { 711 // throw new DateTimeException("Unable to obtain ChronoLocalDateTime from TemporalAccessor: " ~ typeid(temporal).stringof, ex); 712 // } 713 // } 714 715 // override 716 // ChronoZonedDateTime!(ChronoLocalDate) zonedDateTime(TemporalAccessor temporal) { 717 // try { 718 // ZoneId zone = ZoneId.from(temporal); 719 // try { 720 // Instant instant = Instant.from(temporal); 721 // return zonedDateTime(instant, zone); 722 723 // } catch (DateTimeException ex1) { 724 // ChronoLocalDateTimeImpl!(ChronoLocalDate) cldt = ChronoLocalDateTimeImpl!ChronoLocalDate.ensureValid!ChronoLocalDate(this, localDateTime(temporal)); 725 // return ChronoZonedDateTimeImpl!ChronoLocalDate.ofBest!ChronoLocalDate(cldt, zone, null); 726 // } 727 // } catch (DateTimeException ex) { 728 // throw new DateTimeException("Unable to obtain ChronoZonedDateTime from TemporalAccessor: " ~ typeid(temporal).stringof, ex); 729 // } 730 // } 731 732 // override 733 // ChronoZonedDateTime!(ChronoLocalDate) zonedDateTime(Instant instant, ZoneId zone) { 734 // return ChronoZonedDateTimeImpl.ofInstant(this, instant, zone); 735 // } 736 737 // override 738 // string getDisplayName(TextStyle style, Locale locale) { 739 // TemporalAccessor temporal = new AnonymousClass1(); 740 // return new DateTimeFormatterBuilder().appendChronologyText(style).toFormatter(locale).format(temporal); 741 // } 742 743 // // override 744 // public long epochSecond(int prolepticYear, int month, int dayOfMonth, 745 // int hour, int minute, int second, ZoneOffset zoneOffset) { 746 // assert(zoneOffset, "zoneOffset"); 747 // ChronoField.HOUR_OF_DAY.checkValidValue(hour); 748 // ChronoField.MINUTE_OF_HOUR.checkValidValue(minute); 749 // ChronoField.SECOND_OF_MINUTE.checkValidValue(second); 750 // long daysInSec = MathHelper.multiplyExact(date(prolepticYear, month, dayOfMonth).toEpochDay(), 86400); 751 // long timeinSec = (hour * 60 + minute) * 60 + second; 752 // return MathHelper.addExact(daysInSec, timeinSec - zoneOffset.getTotalSeconds()); 753 // } 754 }