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.WeekFields; 13 14 import hunt.time.temporal.ChronoField; 15 import hunt.time.temporal.ChronoUnit; 16 17 import hunt.Exceptions; 18 19 //import hunt.io.ObjectInputStream; 20 import hunt.time.Exceptions; 21 import hunt.time.DayOfWeek; 22 import hunt.time.chrono.ChronoLocalDate; 23 import hunt.time.chrono.Chronology; 24 import hunt.time.format.ResolverStyle; 25 import hunt.time.temporal.TemporalField; 26 import hunt.time.temporal.TemporalUnit; 27 import hunt.time.temporal.ValueRange; 28 import hunt.time.temporal.Temporal; 29 import hunt.time.temporal.TemporalAccessor; 30 import hunt.time.temporal.IsoFields; 31 import hunt.time.util.Common; 32 33 import hunt.collection.HashMap; 34 import hunt.collection.Map; 35 import hunt.Exceptions; 36 import hunt.stream.Common; 37 import hunt.Long; 38 import hunt.math.Helper; 39 import hunt.util.Common; 40 import hunt.util.Comparator; 41 import hunt.util.Locale; 42 // import hunt.serialization.JsonSerializer; 43 44 import std.conv; 45 46 // import sun.util.locale.provider.CalendarDataUtility; 47 // import sun.util.locale.provider.LocaleProviderAdapter; 48 // import sun.util.locale.provider.LocaleResources; 49 50 /** 51 * Localized definitions of the day-of-week, week-of-month and week-of-year fields. 52 * !(p) 53 * A standard week is seven days long, but cultures have different definitions for some 54 * other aspects of a week. This class represents the definition of the week, for the 55 * purpose of providing {@link TemporalField} instances. 56 * !(p) 57 * WeekFields provides five fields, 58 * {@link #dayOfWeek()}, {@link #_weekOfMonth()}, {@link #weekOfYear()}, 59 * {@link #_weekOfWeekBasedYear()}, and {@link #_weekBasedYear()} 60 * that provide access to the values from any {@linkplain Temporal temporal object}. 61 * !(p) 62 * The computations for day-of-week, week-of-month, and week-of-year are based 63 * on the {@linkplain ChronoField#YEAR proleptic-year}, 64 * {@linkplain ChronoField#MONTH_OF_YEAR month-of-year}, 65 * {@linkplain ChronoField#DAY_OF_MONTH day-of-month}, and 66 * {@linkplain ChronoField#DAY_OF_WEEK ISO day-of-week} which are based on the 67 * {@linkplain ChronoField#EPOCH_DAY epoch-day} and the chronology. 68 * The values may not be aligned with the {@linkplain ChronoField#YEAR_OF_ERA year-of-Era} 69 * depending on the Chronology. 70 * !(p)A week is defined by: 71 * !(ul) 72 * !(li)The first day-of-week. 73 * For example, the ISO-8601 standard considers Monday to be the first day-of-week. 74 * !(li)The minimal number of days _in the first week. 75 * For example, the ISO-8601 standard counts the first week as needing at least 4 days. 76 * </ul> 77 * Together these two values allow a year or month to be divided into weeks. 78 * 79 * !(h3)Week of Month</h3> 80 * One field is used: week-of-month. 81 * The calculation ensures that weeks never overlap a month boundary. 82 * The month is divided into periods where each period starts on the defined first day-of-week. 83 * The earliest period is referred to as week 0 if it has less than the minimal number of days 84 * and week 1 if it has at least the minimal number of days. 85 * 86 * <table class=striped style="text-align: left"> 87 * !(caption)Examples of WeekFields</caption> 88 * !(thead) 89 * !(tr)<th scope="col">Date</th><th scope="col">Day-of-week</th> 90 * <th scope="col">First day: Monday!(br)Minimal days: 4</th><th scope="col">First day: Monday!(br)Minimal days: 5</th></tr> 91 * </thead> 92 * !(tbody) 93 * !(tr)<th scope="row">2008-12-31</th>!(td)Wednesday</td> 94 * !(td)Week 5 of December 2008</td>!(td)Week 5 of December 2008</td></tr> 95 * !(tr)<th scope="row">2009-01-01</th>!(td)Thursday</td> 96 * !(td)Week 1 of January 2009</td>!(td)Week 0 of January 2009</td></tr> 97 * !(tr)<th scope="row">2009-01-04</th>!(td)Sunday</td> 98 * !(td)Week 1 of January 2009</td>!(td)Week 0 of January 2009</td></tr> 99 * !(tr)<th scope="row">2009-01-05</th>!(td)Monday</td> 100 * !(td)Week 2 of January 2009</td>!(td)Week 1 of January 2009</td></tr> 101 * </tbody> 102 * </table> 103 * 104 * !(h3)Week of Year</h3> 105 * One field is used: week-of-year. 106 * The calculation ensures that weeks never overlap a year boundary. 107 * The year is divided into periods where each period starts on the defined first day-of-week. 108 * The earliest period is referred to as week 0 if it has less than the minimal number of days 109 * and week 1 if it has at least the minimal number of days. 110 * 111 * !(h3)Week Based Year</h3> 112 * Two fields are used for week-based-year, one for the 113 * {@link #_weekOfWeekBasedYear() week-of-week-based-year} and one for 114 * {@link #_weekBasedYear() week-based-year}. In a week-based-year, each week 115 * belongs to only a single year. Week 1 of a year is the first week that 116 * starts on the first day-of-week and has at least the minimum number of days. 117 * The first and last weeks of a year may contain days from the 118 * previous calendar year or next calendar year respectively. 119 * 120 * <table class=striped style="text-align: left;"> 121 * !(caption)Examples of WeekFields for week-based-year</caption> 122 * !(thead) 123 * !(tr)<th scope="col">Date</th><th scope="col">Day-of-week</th> 124 * <th scope="col">First day: Monday!(br)Minimal days: 4</th><th scope="col">First day: Monday!(br)Minimal days: 5</th></tr> 125 * </thead> 126 * !(tbody) 127 * !(tr)<th scope="row">2008-12-31</th>!(td)Wednesday</td> 128 * !(td)Week 1 of 2009</td>!(td)Week 53 of 2008</td></tr> 129 * !(tr)<th scope="row">2009-01-01</th>!(td)Thursday</td> 130 * !(td)Week 1 of 2009</td>!(td)Week 53 of 2008</td></tr> 131 * !(tr)<th scope="row">2009-01-04</th>!(td)Sunday</td> 132 * !(td)Week 1 of 2009</td>!(td)Week 53 of 2008</td></tr> 133 * !(tr)<th scope="row">2009-01-05</th>!(td)Monday</td> 134 * !(td)Week 2 of 2009</td>!(td)Week 1 of 2009</td></tr> 135 * </tbody> 136 * </table> 137 * 138 * @implSpec 139 * This class is immutable and thread-safe. 140 * 141 * @since 1.8 142 */ 143 public final class WeekFields // : Serializable 144 { 145 // implementation notes 146 // querying week-of-month or week-of-year should return the week value bound within the month/year 147 // however, setting the week value should be lenient (use plus/minus weeks) 148 // allow week-of-month outer _range [0 to 6] 149 // allow week-of-year outer _range [0 to 54] 150 // this is because callers shouldn't be expected to know the details of validity 151 152 /** 153 * The cache of rules by firstDayOfWeek plus minimalDays. 154 * Initialized first to be available for definition of ISO, etc. 155 */ 156 // private static final ConcurrentMap!(string, WeekFields) CACHE = new ConcurrentHashMap!()(4, 0.75f, 2); 157 // __gshared HashMap!(string, WeekFields) CACHE; 158 159 /** 160 * The ISO-8601 definition, where a week starts on Monday and the first week 161 * has a minimum of 4 days. 162 * !(p) 163 * The ISO-8601 standard defines a calendar system based on weeks. 164 * It uses the week-based-year and week-of-week-based-year concepts to split 165 * up the passage of days instead of the standard year/month/day. 166 * !(p) 167 * Note that the first week may start _in the previous calendar year. 168 * Note also that the first few days of a calendar year may be _in the 169 * week-based-year corresponding to the previous calendar year. 170 */ 171 __gshared WeekFields _ISO; 172 173 /** 174 * The common definition of a week that starts on Sunday and the first week 175 * has a minimum of 1 day. 176 * !(p) 177 * Defined as starting on Sunday and with a minimum of 1 day _in the month. 178 * This week definition is _in use _in the US and other European countries. 179 */ 180 // __gshared WeekFields SUNDAY_START; 181 182 /** 183 * The unit that represents week-based-years for the purpose of addition and subtraction. 184 * !(p) 185 * This allows a number of week-based-years to be added to, or subtracted from, a date. 186 * The unit is equal to either 52 or 53 weeks. 187 * The estimated duration of a week-based-year is the same as that of a standard ISO 188 * year at {@code 365.2425 Days}. 189 * !(p) 190 * The rules for addition add the number of week-based-years to the existing value 191 * for the week-based-year field retaining the week-of-week-based-year 192 * and day-of-week, unless the week number it too large for the target year. 193 * In that case, the week is set to the last week of the year 194 * with the same day-of-week. 195 * !(p) 196 * This unit is an immutable and thread-safe singleton. 197 */ 198 // __gshared TemporalUnit WEEK_BASED_YEARS; 199 200 /** 201 * Serialization version. 202 */ 203 private enum long serialVersionUID = -1177360819670808121L; 204 205 /** 206 * The first day-of-week. 207 */ 208 private DayOfWeek firstDayOfWeek; 209 /** 210 * The minimal number of days _in the first week. 211 */ 212 private int minimalDays; 213 /** 214 * The field used to access the computed DayOfWeek. 215 */ 216 private /*transient*/ TemporalField _dayOfWeek; 217 /** 218 * The field used to access the computed WeekOfMonth. 219 */ 220 private /*transient*/ TemporalField _weekOfMonth; 221 /** 222 * The field used to access the computed WeekOfYear. 223 */ 224 private /*transient*/ TemporalField _weekOfYear; 225 /** 226 * The field that represents the week-of-week-based-year. 227 * !(p) 228 * This field allows the week of the week-based-year value to be queried and set. 229 * !(p) 230 * This unit is an immutable and thread-safe singleton. 231 */ 232 private /*transient*/ TemporalField _weekOfWeekBasedYear; 233 /** 234 * The field that represents the week-based-year. 235 * !(p) 236 * This field allows the week-based-year value to be queried and set. 237 * !(p) 238 * This unit is an immutable and thread-safe singleton. 239 */ 240 private /*transient*/ TemporalField _weekBasedYear; 241 242 public void do_init() 243 { 244 _dayOfWeek = ComputedDayOfField.ofDayOfWeekField(this); 245 _weekOfMonth = ComputedDayOfField.ofWeekOfMonthField(this); 246 _weekOfYear = ComputedDayOfField.ofWeekOfYearField(this); 247 _weekOfWeekBasedYear = ComputedDayOfField.ofWeekOfWeekBasedYearField(this); 248 _weekBasedYear = ComputedDayOfField.ofWeekBasedYearField(this); 249 } 250 251 public static ref WeekFields ISO() 252 { 253 if(_ISO is null) 254 { 255 _ISO = new WeekFields(DayOfWeek.MONDAY, 4); 256 _ISO.do_init(); 257 } 258 return _ISO; 259 } 260 261 // shared static this() 262 // { 263 // CACHE = new HashMap!(string, WeekFields)(4, 0.75f /* , 2 */ ); 264 mixin(MakeGlobalVar!(HashMap!(string, WeekFields))("CACHE",` new HashMap!(string, WeekFields)(4, 0.75f /* , 2 */ )`)); 265 // ISO = new WeekFields(DayOfWeek.MONDAY, 4); 266 // mixin(MakeGlobalVar!(WeekFields)("ISO",` new WeekFields(DayOfWeek.MONDAY, 4)`)); 267 268 // ISO.do_init(); 269 // SUNDAY_START = WeekFields.of(DayOfWeek.SUNDAY, 1); 270 mixin(MakeGlobalVar!(WeekFields)("SUNDAY_START",`WeekFields.of(DayOfWeek.SUNDAY, 1)`)); 271 // WEEK_BASED_YEARS = IsoFields.WEEK_BASED_YEARS; 272 mixin(MakeGlobalVar!(TemporalUnit)("WEEK_BASED_YEARS",`IsoFields.WEEK_BASED_YEARS`)); 273 274 // } 275 //----------------------------------------------------------------------- 276 /** 277 * Obtains an instance of {@code WeekFields} appropriate for a locale. 278 * !(p) 279 * This will look up appropriate values from the provider of localization data. 280 * If the locale contains "fw" (First day of week) and/or "rg" 281 * (Region Override) <a href="../../util/Locale.html#def_locale_extension"> 282 * Unicode extensions</a>, returned instance will reflect the values specified with 283 * those extensions. If both "fw" and "rg" are specified, the value from 284 * the "fw" extension supersedes the implicit one from the "rg" extension. 285 * 286 * @param locale the locale to use, not null 287 * @return the week-definition, not null 288 */ 289 public static WeekFields of(Locale locale) 290 { 291 assert(locale, "locale"); 292 293 ///@gxc 294 // int calDow = CalendarDataUtility.retrieveFirstDayOfWeek(locale); 295 // DayOfWeek dow = DayOfWeek.SUNDAY.plus(calDow - 1); 296 // int minDays = CalendarDataUtility.retrieveMinimalDaysInFirstWeek(locale); 297 DayOfWeek dow = DayOfWeek.SUNDAY.plus(2 - 1); 298 int minDays = 7; 299 return WeekFields.of(dow, minDays); 300 } 301 302 /** 303 * Obtains an instance of {@code WeekFields} from the first day-of-week and minimal days. 304 * !(p) 305 * The first day-of-week defines the ISO {@code DayOfWeek} that is day 1 of the week. 306 * The minimal number of days _in the first week defines how many days must be present 307 * _in a month or year, starting from the first day-of-week, before the week is counted 308 * as the first week. A value of 1 will count the first day of the month or year as part 309 * of the first week, whereas a value of 7 will require the whole seven days to be _in 310 * the new month or year. 311 * !(p) 312 * WeekFields instances are singletons; for each unique combination 313 * of {@code firstDayOfWeek} and {@code minimalDaysInFirstWeek} 314 * the same instance will be returned. 315 * 316 * @param firstDayOfWeek the first day of the week, not null 317 * @param minimalDaysInFirstWeek the minimal number of days _in the first week, from 1 to 7 318 * @return the week-definition, not null 319 * @throws IllegalArgumentException if the minimal days value is less than one 320 * or greater than 7 321 */ 322 public static WeekFields of(DayOfWeek firstDayOfWeek, int minimalDaysInFirstWeek) 323 { 324 string key = firstDayOfWeek.toString() ~ minimalDaysInFirstWeek.to!string; 325 WeekFields rules = CACHE.get(key); 326 if (rules is null) 327 { 328 rules = new WeekFields(firstDayOfWeek, minimalDaysInFirstWeek); 329 rules.do_init(); 330 CACHE.putIfAbsent(key, rules); 331 rules = CACHE.get(key); 332 } 333 return rules; 334 } 335 336 //----------------------------------------------------------------------- 337 /** 338 * Creates an instance of the definition. 339 * 340 * @param firstDayOfWeek the first day of the week, not null 341 * @param minimalDaysInFirstWeek the minimal number of days _in the first week, from 1 to 7 342 * @throws IllegalArgumentException if the minimal days value is invalid 343 */ 344 this(DayOfWeek firstDayOfWeek, int minimalDaysInFirstWeek) 345 { 346 assert(firstDayOfWeek, "firstDayOfWeek"); 347 if (minimalDaysInFirstWeek < 1 || minimalDaysInFirstWeek > 7) 348 { 349 throw new IllegalArgumentException("Minimal number of days is invalid"); 350 } 351 this.firstDayOfWeek = firstDayOfWeek; 352 this.minimalDays = minimalDaysInFirstWeek; 353 } 354 355 //----------------------------------------------------------------------- 356 /** 357 * Restore the state of a WeekFields from the stream. 358 * Check that the values are valid. 359 * 360 * @param s the stream to read 361 * @throws InvalidObjectException if the serialized object has an invalid 362 * value for firstDayOfWeek or minimalDays. 363 * @throws ClassNotFoundException if a class cannot be resolved 364 */ 365 ///@gxc 366 // private void readObject(ObjectInputStream s) 367 // /*throws IOException, ClassNotFoundException, InvalidObjectException*/ 368 // { 369 // s.defaultReadObject(); 370 // if (firstDayOfWeek is null) { 371 // throw new InvalidObjectException("firstDayOfWeek is null"); 372 // } 373 374 // if (minimalDays < 1 || minimalDays > 7) { 375 // throw new InvalidObjectException("Minimal number of days is invalid"); 376 // } 377 // } 378 379 /** 380 * Return the singleton WeekFields associated with the 381 * {@code firstDayOfWeek} and {@code minimalDays}. 382 * @return the singleton WeekFields for the firstDayOfWeek and minimalDays. 383 * @throws InvalidObjectException if the serialized object has invalid 384 * values for firstDayOfWeek or minimalDays. 385 */ 386 private Object readResolve() /*throws InvalidObjectException*/ 387 { 388 try 389 { 390 return WeekFields.of(firstDayOfWeek, minimalDays); 391 } 392 catch (IllegalArgumentException iae) 393 { 394 throw new InvalidObjectException("Invalid serialized WeekFields: " ~ iae.msg); 395 } 396 } 397 398 //----------------------------------------------------------------------- 399 /** 400 * Gets the first day-of-week. 401 * !(p) 402 * The first day-of-week varies by culture. 403 * For example, the US uses Sunday, while France and the ISO-8601 standard use Monday. 404 * This method returns the first day using the standard {@code DayOfWeek} enum. 405 * 406 * @return the first day-of-week, not null 407 */ 408 public DayOfWeek getFirstDayOfWeek() 409 { 410 return firstDayOfWeek; 411 } 412 413 /** 414 * Gets the minimal number of days _in the first week. 415 * !(p) 416 * The number of days considered to define the first week of a month or year 417 * varies by culture. 418 * For example, the ISO-8601 requires 4 days (more than half a week) to 419 * be present before counting the first week. 420 * 421 * @return the minimal number of days _in the first week of a month or year, from 1 to 7 422 */ 423 public int getMinimalDaysInFirstWeek() 424 { 425 return minimalDays; 426 } 427 428 //----------------------------------------------------------------------- 429 /** 430 * Returns a field to access the day of week based on this {@code WeekFields}. 431 * !(p) 432 * This is similar to {@link ChronoField#DAY_OF_WEEK} but uses values for 433 * the day-of-week based on this {@code WeekFields}. 434 * The days are numbered from 1 to 7 where the 435 * {@link #getFirstDayOfWeek() first day-of-week} is assigned the value 1. 436 * !(p) 437 * For example, if the first day-of-week is Sunday, then that will have the 438 * value 1, with other days ranging from Monday as 2 to Saturday as 7. 439 * !(p) 440 * In the resolving phase of parsing, a localized day-of-week will be converted 441 * to a standardized {@code ChronoField} day-of-week. 442 * The day-of-week must be _in the valid _range 1 to 7. 443 * Other fields _in this class build dates using the standardized day-of-week. 444 * 445 * @return a field providing access to the day-of-week with localized numbering, not null 446 */ 447 public TemporalField dayOfWeek() 448 { 449 return _dayOfWeek; 450 } 451 452 /** 453 * Returns a field to access the week of month based on this {@code WeekFields}. 454 * !(p) 455 * This represents the concept of the count of weeks within the month where weeks 456 * start on a fixed day-of-week, such as Monday. 457 * This field is typically used with {@link WeekFields#dayOfWeek()}. 458 * !(p) 459 * Week one (1) is the week starting on the {@link WeekFields#getFirstDayOfWeek} 460 * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days _in the month. 461 * Thus, week one may start up to {@code minDays} days before the start of the month. 462 * If the first week starts after the start of the month then the period before is week zero (0). 463 * !(p) 464 * For example:!(br) 465 * - if the 1st day of the month is a Monday, week one starts on the 1st and there is no week zero!(br) 466 * - if the 2nd day of the month is a Monday, week one starts on the 2nd and the 1st is _in week zero!(br) 467 * - if the 4th day of the month is a Monday, week one starts on the 4th and the 1st to 3rd is _in week zero!(br) 468 * - if the 5th day of the month is a Monday, week two starts on the 5th and the 1st to 4th is _in week one!(br) 469 * !(p) 470 * This field can be used with any calendar system. 471 * !(p) 472 * In the resolving phase of parsing, a date can be created from a year, 473 * week-of-month, month-of-year and day-of-week. 474 * !(p) 475 * In {@linkplain ResolverStyle#STRICT strict mode}, all four fields are 476 * validated against their _range of valid values. The week-of-month field 477 * is validated to ensure that the resulting month is the month requested. 478 * !(p) 479 * In {@linkplain ResolverStyle#SMART smart mode}, all four fields are 480 * validated against their _range of valid values. The week-of-month field 481 * is validated from 0 to 6, meaning that the resulting date can be _in a 482 * different month to that specified. 483 * !(p) 484 * In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week 485 * are validated against the _range of valid values. The resulting date is calculated 486 * equivalent to the following four stage approach. 487 * First, create a date on the first day of the first week of January _in the requested year. 488 * Then take the month-of-year, subtract one, and add the amount _in months to the date. 489 * Then take the week-of-month, subtract one, and add the amount _in weeks to the date. 490 * Finally, adjust to the correct day-of-week within the localized week. 491 * 492 * @return a field providing access to the week-of-month, not null 493 */ 494 public TemporalField weekOfMonth() 495 { 496 return _weekOfMonth; 497 } 498 499 /** 500 * Returns a field to access the week of year based on this {@code WeekFields}. 501 * !(p) 502 * This represents the concept of the count of weeks within the year where weeks 503 * start on a fixed day-of-week, such as Monday. 504 * This field is typically used with {@link WeekFields#dayOfWeek()}. 505 * !(p) 506 * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek} 507 * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days _in the year. 508 * Thus, week one may start up to {@code minDays} days before the start of the year. 509 * If the first week starts after the start of the year then the period before is week zero (0). 510 * !(p) 511 * For example:!(br) 512 * - if the 1st day of the year is a Monday, week one starts on the 1st and there is no week zero!(br) 513 * - if the 2nd day of the year is a Monday, week one starts on the 2nd and the 1st is _in week zero!(br) 514 * - if the 4th day of the year is a Monday, week one starts on the 4th and the 1st to 3rd is _in week zero!(br) 515 * - if the 5th day of the year is a Monday, week two starts on the 5th and the 1st to 4th is _in week one!(br) 516 * !(p) 517 * This field can be used with any calendar system. 518 * !(p) 519 * In the resolving phase of parsing, a date can be created from a year, 520 * week-of-year and day-of-week. 521 * !(p) 522 * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are 523 * validated against their _range of valid values. The week-of-year field 524 * is validated to ensure that the resulting year is the year requested. 525 * !(p) 526 * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are 527 * validated against their _range of valid values. The week-of-year field 528 * is validated from 0 to 54, meaning that the resulting date can be _in a 529 * different year to that specified. 530 * !(p) 531 * In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week 532 * are validated against the _range of valid values. The resulting date is calculated 533 * equivalent to the following three stage approach. 534 * First, create a date on the first day of the first week _in the requested year. 535 * Then take the week-of-year, subtract one, and add the amount _in weeks to the date. 536 * Finally, adjust to the correct day-of-week within the localized week. 537 * 538 * @return a field providing access to the week-of-year, not null 539 */ 540 public TemporalField weekOfYear() 541 { 542 return _weekOfYear; 543 } 544 545 /** 546 * Returns a field to access the week of a week-based-year based on this {@code WeekFields}. 547 * !(p) 548 * This represents the concept of the count of weeks within the year where weeks 549 * start on a fixed day-of-week, such as Monday and each week belongs to exactly one year. 550 * This field is typically used with {@link WeekFields#dayOfWeek()} and 551 * {@link WeekFields#_weekBasedYear()}. 552 * !(p) 553 * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek} 554 * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days _in the year. 555 * If the first week starts after the start of the year then the period before 556 * is _in the last week of the previous year. 557 * !(p) 558 * For example:!(br) 559 * - if the 1st day of the year is a Monday, week one starts on the 1st!(br) 560 * - if the 2nd day of the year is a Monday, week one starts on the 2nd and 561 * the 1st is _in the last week of the previous year!(br) 562 * - if the 4th day of the year is a Monday, week one starts on the 4th and 563 * the 1st to 3rd is _in the last week of the previous year!(br) 564 * - if the 5th day of the year is a Monday, week two starts on the 5th and 565 * the 1st to 4th is _in week one!(br) 566 * !(p) 567 * This field can be used with any calendar system. 568 * !(p) 569 * In the resolving phase of parsing, a date can be created from a week-based-year, 570 * week-of-year and day-of-week. 571 * !(p) 572 * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are 573 * validated against their _range of valid values. The week-of-year field 574 * is validated to ensure that the resulting week-based-year is the 575 * week-based-year requested. 576 * !(p) 577 * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are 578 * validated against their _range of valid values. The week-of-week-based-year field 579 * is validated from 1 to 53, meaning that the resulting date can be _in the 580 * following week-based-year to that specified. 581 * !(p) 582 * In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week 583 * are validated against the _range of valid values. The resulting date is calculated 584 * equivalent to the following three stage approach. 585 * First, create a date on the first day of the first week _in the requested week-based-year. 586 * Then take the week-of-week-based-year, subtract one, and add the amount _in weeks to the date. 587 * Finally, adjust to the correct day-of-week within the localized week. 588 * 589 * @return a field providing access to the week-of-week-based-year, not null 590 */ 591 public TemporalField weekOfWeekBasedYear() 592 { 593 return _weekOfWeekBasedYear; 594 } 595 596 /** 597 * Returns a field to access the year of a week-based-year based on this {@code WeekFields}. 598 * !(p) 599 * This represents the concept of the year where weeks start on a fixed day-of-week, 600 * such as Monday and each week belongs to exactly one year. 601 * This field is typically used with {@link WeekFields#dayOfWeek()} and 602 * {@link WeekFields#_weekOfWeekBasedYear()}. 603 * !(p) 604 * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek} 605 * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days _in the year. 606 * Thus, week one may start before the start of the year. 607 * If the first week starts after the start of the year then the period before 608 * is _in the last week of the previous year. 609 * !(p) 610 * This field can be used with any calendar system. 611 * !(p) 612 * In the resolving phase of parsing, a date can be created from a week-based-year, 613 * week-of-year and day-of-week. 614 * !(p) 615 * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are 616 * validated against their _range of valid values. The week-of-year field 617 * is validated to ensure that the resulting week-based-year is the 618 * week-based-year requested. 619 * !(p) 620 * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are 621 * validated against their _range of valid values. The week-of-week-based-year field 622 * is validated from 1 to 53, meaning that the resulting date can be _in the 623 * following week-based-year to that specified. 624 * !(p) 625 * In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week 626 * are validated against the _range of valid values. The resulting date is calculated 627 * equivalent to the following three stage approach. 628 * First, create a date on the first day of the first week _in the requested week-based-year. 629 * Then take the week-of-week-based-year, subtract one, and add the amount _in weeks to the date. 630 * Finally, adjust to the correct day-of-week within the localized week. 631 * 632 * @return a field providing access to the week-based-year, not null 633 */ 634 public TemporalField weekBasedYear() 635 { 636 return _weekBasedYear; 637 } 638 639 //----------------------------------------------------------------------- 640 /** 641 * Checks if this {@code WeekFields} is equal to the specified object. 642 * !(p) 643 * The comparison is based on the entire state of the rules, which is 644 * the first day-of-week and minimal days. 645 * 646 * @param object the other rules to compare to, null returns false 647 * @return true if this is equal to the specified rules 648 */ 649 override public bool opEquals(Object object) 650 { 651 if (this is object) 652 { 653 return true; 654 } 655 if (cast(WeekFields)(object) !is null) 656 { 657 return toHash() == object.toHash(); 658 } 659 return false; 660 } 661 662 /** 663 * A hash code for this {@code WeekFields}. 664 * 665 * @return a suitable hash code 666 */ 667 override public size_t toHash() @trusted nothrow 668 { 669 try 670 { 671 return firstDayOfWeek.ordinal() * 7 + minimalDays; 672 } 673 catch (Exception e) 674 { 675 } 676 return int.init; 677 } 678 679 //----------------------------------------------------------------------- 680 /** 681 * A string representation of this {@code WeekFields} instance. 682 * 683 * @return the string representation, not null 684 */ 685 override public string toString() 686 { 687 return "WeekFields[" ~ firstDayOfWeek.toString ~ ',' ~ minimalDays.to!string ~ ']'; 688 } 689 690 // mixin SerializationMember!(typeof(this)); 691 692 //----------------------------------------------------------------------- 693 /** 694 * Field type that computes DayOfWeek, WeekOfMonth, and WeekOfYear 695 * based on a WeekFields. 696 * A separate Field instance is required for each different WeekFields; 697 * combination of start of week and minimum number of days. 698 * Constructors are provided to create fields for DayOfWeek, WeekOfMonth, 699 * and WeekOfYear. 700 */ 701 static class ComputedDayOfField : TemporalField 702 { 703 704 /** 705 * Returns a field to access the day of week, 706 * computed based on a WeekFields. 707 * !(p) 708 * The WeekDefintion of the first day of the week is used with 709 * the ISO DAY_OF_WEEK field to compute week boundaries. 710 */ 711 static ComputedDayOfField ofDayOfWeekField(WeekFields weekDef) 712 { 713 return new ComputedDayOfField("DayOfWeek", weekDef, 714 ChronoUnit.DAYS, ChronoUnit.WEEKS, DAY_OF_WEEK_RANGE); 715 } 716 717 /** 718 * Returns a field to access the week of month, 719 * computed based on a WeekFields. 720 * @see WeekFields#_weekOfMonth() 721 */ 722 static ComputedDayOfField ofWeekOfMonthField(WeekFields weekDef) 723 { 724 return new ComputedDayOfField("WeekOfMonth", weekDef, 725 ChronoUnit.WEEKS, ChronoUnit.MONTHS, WEEK_OF_MONTH_RANGE); 726 } 727 728 /** 729 * Returns a field to access the week of year, 730 * computed based on a WeekFields. 731 * @see WeekFields#weekOfYear() 732 */ 733 static ComputedDayOfField ofWeekOfYearField(WeekFields weekDef) 734 { 735 return new ComputedDayOfField("WeekOfYear", weekDef, 736 ChronoUnit.WEEKS, ChronoUnit.YEARS, WEEK_OF_YEAR_RANGE); 737 } 738 739 /** 740 * Returns a field to access the week of week-based-year, 741 * computed based on a WeekFields. 742 * @see WeekFields#_weekOfWeekBasedYear() 743 */ 744 static ComputedDayOfField ofWeekOfWeekBasedYearField(WeekFields weekDef) 745 { 746 return new ComputedDayOfField("WeekOfWeekBasedYear", weekDef, 747 ChronoUnit.WEEKS, IsoFields.WEEK_BASED_YEARS, WEEK_OF_WEEK_BASED_YEAR_RANGE); 748 } 749 750 /** 751 * Returns a field to access the week of week-based-year, 752 * computed based on a WeekFields. 753 * @see WeekFields#_weekBasedYear() 754 */ 755 static ComputedDayOfField ofWeekBasedYearField(WeekFields weekDef) 756 { 757 return new ComputedDayOfField("WeekBasedYear", weekDef, 758 IsoFields.WEEK_BASED_YEARS, ChronoUnit.FOREVER, ChronoField.YEAR.range()); 759 } 760 761 /** 762 * Return a new week-based-year date of the Chronology, year, week-of-year, 763 * and dow of week. 764 * @param chrono The chronology of the new date 765 * @param yowby the year of the week-based-year 766 * @param wowby the week of the week-based-year 767 * @param dow the day of the week 768 * @return a ChronoLocalDate for the requested year, week of year, and day of week 769 */ 770 private ChronoLocalDate ofWeekBasedYear(Chronology chrono, int yowby, int wowby, int dow) 771 { 772 ChronoLocalDate date = chrono.date(yowby, 1, 1); 773 int ldow = localizedDayOfWeek(date); 774 int offset = startOfWeekOffset(1, ldow); 775 776 // Clamp the week of year to keep it _in the same year 777 int yearLen = date.lengthOfYear(); 778 int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek()); 779 wowby = MathHelper.min(wowby, newYearWeek - 1); 780 781 int days = -offset + (dow - 1) + (wowby - 1) * 7; 782 return date.plus(days, ChronoUnit.DAYS); 783 } 784 785 private string name; 786 private WeekFields weekDef; 787 private TemporalUnit baseUnit; 788 private TemporalUnit rangeUnit; 789 private ValueRange _range; 790 791 this(string name, WeekFields weekDef, TemporalUnit baseUnit, 792 TemporalUnit rangeUnit, ValueRange _range) 793 { 794 this.name = name; 795 this.weekDef = weekDef; 796 this.baseUnit = baseUnit; 797 this.rangeUnit = rangeUnit; 798 this._range = _range; 799 } 800 801 // __gshared ValueRange DAY_OF_WEEK_RANGE; 802 // __gshared ValueRange WEEK_OF_MONTH_RANGE; 803 // __gshared ValueRange WEEK_OF_YEAR_RANGE; 804 // __gshared ValueRange WEEK_OF_WEEK_BASED_YEAR_RANGE; 805 806 // shared static this() 807 // { 808 // DAY_OF_WEEK_RANGE = ValueRange.of(1, 7); 809 mixin(MakeGlobalVar!(ValueRange)("DAY_OF_WEEK_RANGE",`ValueRange.of(1, 7)`)); 810 // WEEK_OF_MONTH_RANGE = ValueRange.of(0, 1, 4, 6); 811 mixin(MakeGlobalVar!(ValueRange)("WEEK_OF_MONTH_RANGE",`ValueRange.of(0, 1, 4, 6)`)); 812 813 // WEEK_OF_YEAR_RANGE = ValueRange.of(0, 1, 52, 54); 814 mixin(MakeGlobalVar!(ValueRange)("WEEK_OF_YEAR_RANGE",`ValueRange.of(0, 1, 52, 54)`)); 815 816 // WEEK_OF_WEEK_BASED_YEAR_RANGE = ValueRange.of(1, 52, 53); 817 mixin(MakeGlobalVar!(ValueRange)("WEEK_OF_WEEK_BASED_YEAR_RANGE",`ValueRange.of(1, 52, 53)`)); 818 819 // } 820 821 override public long getFrom(TemporalAccessor temporal) 822 { 823 if (rangeUnit == ChronoUnit.WEEKS) 824 { // day-of-week 825 return localizedDayOfWeek(temporal); 826 } 827 else if (rangeUnit == ChronoUnit.MONTHS) 828 { // week-of-month 829 return localizedWeekOfMonth(temporal); 830 } 831 else if (rangeUnit == ChronoUnit.YEARS) 832 { // week-of-year 833 return localizedWeekOfYear(temporal); 834 } 835 else if (rangeUnit == WEEK_BASED_YEARS) 836 { 837 return localizedWeekOfWeekBasedYear(temporal); 838 } 839 else if (rangeUnit == ChronoUnit.FOREVER) 840 { 841 return localizedWeekBasedYear(temporal); 842 } 843 else 844 { 845 throw new IllegalStateException( 846 "unreachable, rangeUnit: " ~ rangeUnit.toString ~ ", this: " ~ this 847 .toString); 848 } 849 } 850 851 private int localizedDayOfWeek(TemporalAccessor temporal) 852 { 853 int sow = weekDef.getFirstDayOfWeek().getValue(); 854 int isoDow = temporal.get(ChronoField.DAY_OF_WEEK); 855 return MathHelper.floorMod(isoDow - sow, 7) + 1; 856 } 857 858 private int localizedDayOfWeek(int isoDow) 859 { 860 int sow = weekDef.getFirstDayOfWeek().getValue(); 861 return MathHelper.floorMod(isoDow - sow, 7) + 1; 862 } 863 864 private long localizedWeekOfMonth(TemporalAccessor temporal) 865 { 866 int dow = localizedDayOfWeek(temporal); 867 int dom = temporal.get(ChronoField.DAY_OF_MONTH); 868 int offset = startOfWeekOffset(dom, dow); 869 return computeWeek(offset, dom); 870 } 871 872 private long localizedWeekOfYear(TemporalAccessor temporal) 873 { 874 int dow = localizedDayOfWeek(temporal); 875 int doy = temporal.get(ChronoField.DAY_OF_YEAR); 876 int offset = startOfWeekOffset(doy, dow); 877 return computeWeek(offset, doy); 878 } 879 880 /** 881 * Returns the year of week-based-year for the temporal. 882 * The year can be the previous year, the current year, or the next year. 883 * @param temporal a date of any chronology, not null 884 * @return the year of week-based-year for the date 885 */ 886 private int localizedWeekBasedYear(TemporalAccessor temporal) 887 { 888 int dow = localizedDayOfWeek(temporal); 889 int year = temporal.get(ChronoField.YEAR); 890 int doy = temporal.get(ChronoField.DAY_OF_YEAR); 891 int offset = startOfWeekOffset(doy, dow); 892 int week = computeWeek(offset, doy); 893 if (week == 0) 894 { 895 // Day is _in end of week of previous year; return the previous year 896 return year - 1; 897 } 898 else 899 { 900 // If getting close to end of year, use higher precision logic 901 // Check if date of year is _in partial week associated with next year 902 ValueRange dayRange = temporal.range(ChronoField.DAY_OF_YEAR); 903 int yearLen = cast(int) dayRange.getMaximum(); 904 int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek()); 905 if (week >= newYearWeek) 906 { 907 return year + 1; 908 } 909 } 910 return year; 911 } 912 913 /** 914 * Returns the week of week-based-year for the temporal. 915 * The week can be part of the previous year, the current year, 916 * or the next year depending on the week start and minimum number 917 * of days. 918 * @param temporal a date of any chronology 919 * @return the week of the year 920 * @see #localizedWeekBasedYear(hunt.time.temporal.TemporalAccessor) 921 */ 922 private int localizedWeekOfWeekBasedYear(TemporalAccessor temporal) 923 { 924 int dow = localizedDayOfWeek(temporal); 925 int doy = temporal.get(ChronoField.DAY_OF_YEAR); 926 int offset = startOfWeekOffset(doy, dow); 927 int week = computeWeek(offset, doy); 928 if (week == 0) 929 { 930 // Day is _in end of week of previous year 931 // Recompute from the last day of the previous year 932 ChronoLocalDate date = Chronology.from(temporal).date(temporal); 933 date = date.minus(doy, ChronoUnit.DAYS); // Back down into previous year 934 return localizedWeekOfWeekBasedYear(date); 935 } 936 else if (week > 50) 937 { 938 // If getting close to end of year, use higher precision logic 939 // Check if date of year is _in partial week associated with next year 940 ValueRange dayRange = temporal.range(ChronoField.DAY_OF_YEAR); 941 int yearLen = cast(int) dayRange.getMaximum(); 942 int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek()); 943 if (week >= newYearWeek) 944 { 945 // Overlaps with week of following year; reduce to week _in following year 946 week = week - newYearWeek + 1; 947 } 948 } 949 return week; 950 } 951 952 /** 953 * Returns an offset to align week start with a day of month or day of year. 954 * 955 * @param day the day; 1 through infinity 956 * @param dow the day of the week of that day; 1 through 7 957 * @return an offset _in days to align a day with the start of the first 'full' week 958 */ 959 private int startOfWeekOffset(int day, int dow) 960 { 961 // offset of first day corresponding to the day of week _in first 7 days (zero origin) 962 int weekStart = MathHelper.floorMod(day - dow, 7); 963 int offset = -weekStart; 964 if (weekStart + 1 > weekDef.getMinimalDaysInFirstWeek()) 965 { 966 // The previous week has the minimum days _in the current month to be a 'week' 967 offset = 7 - weekStart; 968 } 969 return offset; 970 } 971 972 /** 973 * Returns the week number computed from the reference day and reference dayOfWeek. 974 * 975 * @param offset the offset to align a date with the start of week 976 * from {@link #startOfWeekOffset}. 977 * @param day the day for which to compute the week number 978 * @return the week number where zero is used for a partial week and 1 for the first full week 979 */ 980 private int computeWeek(int offset, int day) 981 { 982 return ((7 + offset + (day - 1)) / 7); 983 } 984 985 /*@SuppressWarnings("unchecked")*/ 986 override public Temporal adjustInto(Temporal temporal, long newValue) 987 /* if (is(R : Temporal)) */ 988 { 989 // Check the new value and get the old value of the field 990 int newVal = _range.checkValidIntValue(newValue, this); // lenient check _range 991 int currentVal = temporal.get(this); 992 if (newVal == currentVal) 993 { 994 return temporal; 995 } 996 997 if (rangeUnit == ChronoUnit.FOREVER) 998 { // replace year of WeekBasedYear 999 // Create a new date object with the same chronology, 1000 // the desired year and the same week and dow. 1001 int idow = temporal.get(weekDef.dayOfWeek); 1002 int wowby = temporal.get(weekDef._weekOfWeekBasedYear); 1003 return cast(Temporal) ofWeekBasedYear(Chronology.from(temporal), 1004 cast(int) newValue, wowby, idow); 1005 } 1006 else 1007 { 1008 // Compute the difference and add that using the base unit of the field 1009 return cast(Temporal) temporal.plus(newVal - currentVal, baseUnit); 1010 } 1011 } 1012 1013 override public ChronoLocalDate resolve(Map!(TemporalField, Long) fieldValues, 1014 TemporalAccessor partialTemporal, ResolverStyle resolverStyle) 1015 { 1016 long value = fieldValues.get(this).longValue(); 1017 int newValue = MathHelper.toIntExact(value); // broad limit makes overflow checking lighter 1018 // first convert localized day-of-week to ISO day-of-week 1019 // doing this first handles case where both ISO and localized were parsed and might mismatch 1020 // day-of-week is always strict as two different day-of-week values makes lenient complex 1021 if (rangeUnit == ChronoUnit.WEEKS) 1022 { // day-of-week 1023 int checkedValue = _range.checkValidIntValue(value, this); // no leniency as too complex 1024 int startDow = weekDef.getFirstDayOfWeek().getValue(); 1025 long isoDow = MathHelper.floorMod((startDow - 1) + (checkedValue - 1), 7) + 1; 1026 fieldValues.remove(this); 1027 fieldValues.put(ChronoField.DAY_OF_WEEK, new Long(isoDow)); 1028 return null; 1029 } 1030 1031 // can only build date if ISO day-of-week is present 1032 if (fieldValues.containsKey(ChronoField.DAY_OF_WEEK) == false) 1033 { 1034 return null; 1035 } 1036 int isoDow = ChronoField.DAY_OF_WEEK.checkValidIntValue( 1037 fieldValues.get(ChronoField.DAY_OF_WEEK).longValue()); 1038 int dow = localizedDayOfWeek(isoDow); 1039 1040 // build date 1041 Chronology chrono = Chronology.from(partialTemporal); 1042 if (fieldValues.containsKey(ChronoField.YEAR)) 1043 { 1044 int year = ChronoField.YEAR.checkValidIntValue(fieldValues.get(ChronoField.YEAR) 1045 .longValue()); // validate 1046 if (rangeUnit == ChronoUnit.MONTHS 1047 && fieldValues.containsKey(ChronoField.MONTH_OF_YEAR)) 1048 { // week-of-month 1049 long month = fieldValues.get(ChronoField.MONTH_OF_YEAR).longValue(); // not validated yet 1050 return resolveWoM(fieldValues, chrono, year, month, 1051 newValue, dow, resolverStyle); 1052 } 1053 if (rangeUnit == ChronoUnit.YEARS) 1054 { // week-of-year 1055 return resolveWoY(fieldValues, chrono, year, newValue, dow, resolverStyle); 1056 } 1057 } 1058 else if ((rangeUnit == WEEK_BASED_YEARS || rangeUnit == ChronoUnit.FOREVER) 1059 && fieldValues.containsKey(weekDef._weekBasedYear) 1060 && fieldValues.containsKey(weekDef._weekOfWeekBasedYear)) 1061 { // week-of-week-based-year and year-of-week-based-year 1062 return resolveWBY(fieldValues, chrono, dow, resolverStyle); 1063 } 1064 return null; 1065 } 1066 1067 private ChronoLocalDate resolveWoM(Map!(TemporalField, Long) fieldValues, Chronology chrono, 1068 int year, long month, long wom, int localDow, ResolverStyle resolverStyle) 1069 { 1070 ChronoLocalDate date; 1071 if (resolverStyle == ResolverStyle.LENIENT) 1072 { 1073 date = chrono.date(year, 1, 1).plus(MathHelper.subtractExact(month, 1074 1), ChronoUnit.MONTHS); 1075 long weeks = MathHelper.subtractExact(wom, localizedWeekOfMonth(date)); 1076 int days = localDow - localizedDayOfWeek(date); // safe from overflow 1077 date = date.plus(MathHelper.addExact(MathHelper.multiplyExact(weeks, 7), 1078 days), ChronoUnit.DAYS); 1079 } 1080 else 1081 { 1082 int monthValid = ChronoField.MONTH_OF_YEAR.checkValidIntValue(month); // validate 1083 date = chrono.date(year, monthValid, 1); 1084 int womInt = _range.checkValidIntValue(wom, this); // validate 1085 int weeks = cast(int)(womInt - localizedWeekOfMonth(date)); // safe from overflow 1086 int days = localDow - localizedDayOfWeek(date); // safe from overflow 1087 date = date.plus(weeks * 7 + days, ChronoUnit.DAYS); 1088 if (resolverStyle == ResolverStyle.STRICT 1089 && date.getLong(ChronoField.MONTH_OF_YEAR) != month) 1090 { 1091 throw new DateTimeException( 1092 "Strict mode rejected resolved date as it is _in a different month"); 1093 } 1094 } 1095 fieldValues.remove(this); 1096 fieldValues.remove(ChronoField.YEAR); 1097 fieldValues.remove(ChronoField.MONTH_OF_YEAR); 1098 fieldValues.remove(ChronoField.DAY_OF_WEEK); 1099 return date; 1100 } 1101 1102 private ChronoLocalDate resolveWoY(Map!(TemporalField, Long) fieldValues, 1103 Chronology chrono, int year, long woy, int localDow, ResolverStyle resolverStyle) 1104 { 1105 ChronoLocalDate date = chrono.date(year, 1, 1); 1106 if (resolverStyle == ResolverStyle.LENIENT) 1107 { 1108 long weeks = MathHelper.subtractExact(woy, localizedWeekOfYear(date)); 1109 int days = localDow - localizedDayOfWeek(date); // safe from overflow 1110 date = date.plus(MathHelper.addExact(MathHelper.multiplyExact(weeks, 7), 1111 days), ChronoUnit.DAYS); 1112 } 1113 else 1114 { 1115 int womInt = _range.checkValidIntValue(woy, this); // validate 1116 int weeks = cast(int)(womInt - localizedWeekOfYear(date)); // safe from overflow 1117 int days = localDow - localizedDayOfWeek(date); // safe from overflow 1118 date = date.plus(weeks * 7 + days, ChronoUnit.DAYS); 1119 if (resolverStyle == ResolverStyle.STRICT && date.getLong(ChronoField.YEAR) != year) 1120 { 1121 throw new DateTimeException( 1122 "Strict mode rejected resolved date as it is _in a different year"); 1123 } 1124 } 1125 fieldValues.remove(this); 1126 fieldValues.remove(ChronoField.YEAR); 1127 fieldValues.remove(ChronoField.DAY_OF_WEEK); 1128 return date; 1129 } 1130 1131 private ChronoLocalDate resolveWBY(Map!(TemporalField, Long) fieldValues, 1132 Chronology chrono, int localDow, ResolverStyle resolverStyle) 1133 { 1134 int yowby = weekDef._weekBasedYear.range() 1135 .checkValidIntValue(fieldValues.get(weekDef._weekBasedYear) 1136 .longValue(), weekDef._weekBasedYear); 1137 ChronoLocalDate date; 1138 if (resolverStyle == ResolverStyle.LENIENT) 1139 { 1140 date = ofWeekBasedYear(chrono, yowby, 1, localDow); 1141 long wowby = fieldValues.get(weekDef._weekOfWeekBasedYear).longValue(); 1142 long weeks = MathHelper.subtractExact(wowby, 1); 1143 date = date.plus(weeks, ChronoUnit.WEEKS); 1144 } 1145 else 1146 { 1147 int wowby = weekDef._weekOfWeekBasedYear.range() 1148 .checkValidIntValue(fieldValues.get(weekDef._weekOfWeekBasedYear) 1149 .longValue(), weekDef._weekOfWeekBasedYear); // validate 1150 date = ofWeekBasedYear(chrono, yowby, wowby, localDow); 1151 if (resolverStyle == ResolverStyle.STRICT && localizedWeekBasedYear(date) != yowby) 1152 { 1153 throw new DateTimeException( 1154 "Strict mode rejected resolved date as it is _in a different week-based-year"); 1155 } 1156 } 1157 fieldValues.remove(this); 1158 fieldValues.remove(weekDef._weekBasedYear); 1159 fieldValues.remove(weekDef._weekOfWeekBasedYear); 1160 fieldValues.remove(ChronoField.DAY_OF_WEEK); 1161 return date; 1162 } 1163 1164 //----------------------------------------------------------------------- 1165 override public string getDisplayName(Locale locale) 1166 { 1167 assert(locale, "locale"); 1168 if (rangeUnit == ChronoUnit.YEARS) 1169 { // only have values for week-of-year 1170 ///@gxc 1171 // LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased() 1172 // .getLocaleResources( 1173 // CalendarDataUtility.findRegionOverride(locale)); 1174 // ResourceBundle rb = lr.getJavaTimeFormatData(); 1175 // return rb.containsKey("field.week") ? rb.getString("field.week") : name; 1176 implementationMissing(); 1177 return null; 1178 } 1179 return name; 1180 } 1181 1182 override public TemporalUnit getBaseUnit() 1183 { 1184 return baseUnit; 1185 } 1186 1187 override public TemporalUnit getRangeUnit() 1188 { 1189 return rangeUnit; 1190 } 1191 1192 override public bool isDateBased() 1193 { 1194 return true; 1195 } 1196 1197 override public bool isTimeBased() 1198 { 1199 return false; 1200 } 1201 1202 override public ValueRange range() 1203 { 1204 return _range; 1205 } 1206 1207 //----------------------------------------------------------------------- 1208 override public bool isSupportedBy(TemporalAccessor temporal) 1209 { 1210 if (temporal.isSupported(ChronoField.DAY_OF_WEEK)) 1211 { 1212 if (rangeUnit == ChronoUnit.WEEKS) 1213 { // day-of-week 1214 return true; 1215 } 1216 else if (rangeUnit == ChronoUnit.MONTHS) 1217 { // week-of-month 1218 return temporal.isSupported(ChronoField.DAY_OF_MONTH); 1219 } 1220 else if (rangeUnit == ChronoUnit.YEARS) 1221 { // week-of-year 1222 return temporal.isSupported(ChronoField.DAY_OF_YEAR); 1223 } 1224 else if (rangeUnit == WEEK_BASED_YEARS) 1225 { 1226 return temporal.isSupported(ChronoField.DAY_OF_YEAR); 1227 } 1228 else if (rangeUnit == ChronoUnit.FOREVER) 1229 { 1230 return temporal.isSupported(ChronoField.YEAR); 1231 } 1232 } 1233 return false; 1234 } 1235 1236 override public ValueRange rangeRefinedBy(TemporalAccessor temporal) 1237 { 1238 if (rangeUnit == ChronoUnit.WEEKS) 1239 { // day-of-week 1240 return _range; 1241 } 1242 else if (rangeUnit == ChronoUnit.MONTHS) 1243 { // week-of-month 1244 return rangeByWeek(temporal, ChronoField.DAY_OF_MONTH); 1245 } 1246 else if (rangeUnit == ChronoUnit.YEARS) 1247 { // week-of-year 1248 return rangeByWeek(temporal, ChronoField.DAY_OF_YEAR); 1249 } 1250 else if (rangeUnit == WEEK_BASED_YEARS) 1251 { 1252 return rangeWeekOfWeekBasedYear(temporal); 1253 } 1254 else if (rangeUnit == ChronoUnit.FOREVER) 1255 { 1256 return ChronoField.YEAR.range(); 1257 } 1258 else 1259 { 1260 throw new IllegalStateException( 1261 "unreachable, rangeUnit: " ~ rangeUnit.toString ~ ", this: " ~ this 1262 .toString); 1263 } 1264 } 1265 1266 /** 1267 * Map the field _range to a week _range 1268 * @param temporal the temporal 1269 * @param field the field to get the _range of 1270 * @return the ValueRange with the _range adjusted to weeks. 1271 */ 1272 private ValueRange rangeByWeek(TemporalAccessor temporal, TemporalField field) 1273 { 1274 int dow = localizedDayOfWeek(temporal); 1275 int offset = startOfWeekOffset(temporal.get(field), dow); 1276 ValueRange fieldRange = temporal.range(field); 1277 return ValueRange.of(computeWeek(offset, cast(int) fieldRange.getMinimum()), 1278 computeWeek(offset, cast(int) fieldRange.getMaximum())); 1279 } 1280 1281 /** 1282 * Map the field _range to a week _range of a week year. 1283 * @param temporal the temporal 1284 * @return the ValueRange with the _range adjusted to weeks. 1285 */ 1286 private ValueRange rangeWeekOfWeekBasedYear(TemporalAccessor temporal) 1287 { 1288 if (!temporal.isSupported(ChronoField.DAY_OF_YEAR)) 1289 { 1290 return WEEK_OF_YEAR_RANGE; 1291 } 1292 int dow = localizedDayOfWeek(temporal); 1293 int doy = temporal.get(ChronoField.DAY_OF_YEAR); 1294 int offset = startOfWeekOffset(doy, dow); 1295 int week = computeWeek(offset, doy); 1296 if (week == 0) 1297 { 1298 // Day is _in end of week of previous year 1299 // Recompute from the last day of the previous year 1300 ChronoLocalDate date = Chronology.from(temporal).date(temporal); 1301 date = date.minus(doy + 7, ChronoUnit.DAYS); // Back down into previous year 1302 return rangeWeekOfWeekBasedYear(date); 1303 } 1304 // Check if day of year is _in partial week associated with next year 1305 ValueRange dayRange = temporal.range(ChronoField.DAY_OF_YEAR); 1306 int yearLen = cast(int) dayRange.getMaximum(); 1307 int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek()); 1308 1309 if (week >= newYearWeek) 1310 { 1311 // Overlaps with weeks of following year; recompute from a week _in following year 1312 ChronoLocalDate date = Chronology.from(temporal).date(temporal); 1313 date = date.plus(yearLen - doy + 1 + 7, ChronoUnit.DAYS); 1314 return rangeWeekOfWeekBasedYear(date); 1315 } 1316 return ValueRange.of(1, newYearWeek - 1); 1317 } 1318 1319 //----------------------------------------------------------------------- 1320 override public string toString() 1321 { 1322 return name ~ "[" ~ weekDef.toString() ~ "]"; 1323 } 1324 1325 override int opCmp(TemporalField o) 1326 { 1327 if(cast(ComputedDayOfField)o !is null) 1328 { 1329 auto obj = cast(ComputedDayOfField)o; 1330 return compare(this.toString,obj.toString); 1331 } 1332 return 0; 1333 } 1334 } 1335 }