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.TemporalAdjusters; 13 14 import hunt.time.temporal.ChronoField; 15 import hunt.time.temporal.ChronoUnit; 16 import hunt.time.temporal.Temporal; 17 import hunt.time.DayOfWeek; 18 import hunt.time.LocalDate; 19 import hunt.time.temporal.TemporalAdjuster; 20 21 // import hunt.util.function.UnaryOperator; 22 23 /** 24 * Common and useful TemporalAdjusters. 25 * !(p) 26 * Adjusters are a key tool for modifying temporal objects. 27 * They exist to externalize the process of adjustment, permitting different 28 * approaches, as per the strategy design pattern. 29 * Examples might be an adjuster that sets the date avoiding weekends, or one that 30 * sets the date to the last day of the month. 31 * !(p) 32 * There are two equivalent ways of using a {@code TemporalAdjuster}. 33 * The first is to invoke the method on the interface directly. 34 * The second is to use {@link Temporal#_with(TemporalAdjuster)}: 35 * !(pre) 36 * // these two lines are equivalent, but the second approach is recommended 37 * temporal = thisAdjuster.adjustInto(temporal); 38 * temporal = temporal._with(thisAdjuster); 39 * </pre> 40 * It is recommended to use the second approach, {@code _with(TemporalAdjuster)}, 41 * as it is a lot clearer to read _in code. 42 * !(p) 43 * This class contains a standard set of adjusters, available as static methods. 44 * These include: 45 * !(ul) 46 * !(li)finding the first or last day of the month 47 * !(li)finding the first day of next month 48 * !(li)finding the first or last day of the year 49 * !(li)finding the first day of next year 50 * !(li)finding the first or last day-of-week within a month, such as "first Wednesday _in June" 51 * !(li)finding the next or previous day-of-week, such as "next Thursday" 52 * </ul> 53 * 54 * @implSpec 55 * All the implementations supplied by the static methods are immutable. 56 * 57 * @see TemporalAdjuster 58 * @since 1.8 59 */ 60 public final class TemporalAdjusters { 61 62 /** 63 * Private constructor since this is a utility class. 64 */ 65 private this() { 66 } 67 68 //----------------------------------------------------------------------- 69 /** 70 * Obtains a {@code TemporalAdjuster} that wraps a date adjuster. 71 * !(p) 72 * The {@code TemporalAdjuster} is based on the low level {@code Temporal} interface. 73 * This method allows an adjustment from {@code LocalDate} to {@code LocalDate} 74 * to be wrapped to match the temporal-based interface. 75 * This is provided for convenience to make user-written adjusters simpler. 76 * !(p) 77 * In general, user-written adjusters should be static constants: 78 * !(pre){@code 79 * static TemporalAdjuster TWO_DAYS_LATER = 80 * TemporalAdjusters.ofDateAdjuster(date => date.plusDays(2)); 81 * }</pre> 82 * 83 * @param dateBasedAdjuster the date-based adjuster, not null 84 * @return the temporal adjuster wrapping on the date adjuster, not null 85 */ 86 ///@gxc 87 // public static TemporalAdjuster ofDateAdjuster(opUnary!(LocalDate) dateBasedAdjuster) { 88 // assert(dateBasedAdjuster, "dateBasedAdjuster"); 89 // return (temporal) => { 90 // LocalDate input = LocalDate.from(temporal); 91 // LocalDate output = dateBasedAdjuster.apply(input); 92 // return temporal._with(output); 93 // }; 94 // } 95 96 //----------------------------------------------------------------------- 97 /** 98 * Returns the "first day of month" adjuster, which returns a new date set to 99 * the first day of the current month. 100 * !(p) 101 * The ISO calendar system behaves as follows:!(br) 102 * The input 2011-01-15 will return 2011-01-01.!(br) 103 * The input 2011-02-15 will return 2011-02-01. 104 * !(p) 105 * The behavior is suitable for use with most calendar systems. 106 * It is equivalent to: 107 * !(pre) 108 * temporal._with(DAY_OF_MONTH, 1); 109 * </pre> 110 * 111 * @return the first day-of-month adjuster, not null 112 */ 113 public static TemporalAdjuster firstDayOfMonth() { 114 return new class TemporalAdjuster{ 115 Temporal adjustInto(Temporal temporal) 116 { 117 return temporal._with(ChronoField.DAY_OF_MONTH, 1); 118 } 119 }; 120 } 121 122 /** 123 * Returns the "last day of month" adjuster, which returns a new date set to 124 * the last day of the current month. 125 * !(p) 126 * The ISO calendar system behaves as follows:!(br) 127 * The input 2011-01-15 will return 2011-01-31.!(br) 128 * The input 2011-02-15 will return 2011-02-28.!(br) 129 * The input 2012-02-15 will return 2012-02-29 (leap year).!(br) 130 * The input 2011-04-15 will return 2011-04-30. 131 * !(p) 132 * The behavior is suitable for use with most calendar systems. 133 * It is equivalent to: 134 * !(pre) 135 * long lastDay = temporal.range(DAY_OF_MONTH).getMaximum(); 136 * temporal._with(DAY_OF_MONTH, lastDay); 137 * </pre> 138 * 139 * @return the last day-of-month adjuster, not null 140 */ 141 public static TemporalAdjuster lastDayOfMonth() { 142 return new class TemporalAdjuster{ 143 Temporal adjustInto(Temporal temporal) 144 { 145 return temporal._with(ChronoField.DAY_OF_MONTH, temporal.range(ChronoField.DAY_OF_MONTH).getMaximum()); 146 } 147 }; 148 } 149 150 /** 151 * Returns the "first day of next month" adjuster, which returns a new date set to 152 * the first day of the next month. 153 * !(p) 154 * The ISO calendar system behaves as follows:!(br) 155 * The input 2011-01-15 will return 2011-02-01.!(br) 156 * The input 2011-02-15 will return 2011-03-01. 157 * !(p) 158 * The behavior is suitable for use with most calendar systems. 159 * It is equivalent to: 160 * !(pre) 161 * temporal._with(DAY_OF_MONTH, 1).plus(1, MONTHS); 162 * </pre> 163 * 164 * @return the first day of next month adjuster, not null 165 */ 166 public static TemporalAdjuster firstDayOfNextMonth() { 167 return new class TemporalAdjuster{ 168 Temporal adjustInto(Temporal temporal) 169 { 170 return temporal._with(ChronoField.DAY_OF_MONTH, 1).plus(1, ChronoUnit.MONTHS); 171 } 172 }; 173 } 174 175 //----------------------------------------------------------------------- 176 /** 177 * Returns the "first day of year" adjuster, which returns a new date set to 178 * the first day of the current year. 179 * !(p) 180 * The ISO calendar system behaves as follows:!(br) 181 * The input 2011-01-15 will return 2011-01-01.!(br) 182 * The input 2011-02-15 will return 2011-01-01.!(br) 183 * !(p) 184 * The behavior is suitable for use with most calendar systems. 185 * It is equivalent to: 186 * !(pre) 187 * temporal._with(DAY_OF_YEAR, 1); 188 * </pre> 189 * 190 * @return the first day-of-year adjuster, not null 191 */ 192 public static TemporalAdjuster firstDayOfYear() { 193 return new class TemporalAdjuster{ 194 Temporal adjustInto(Temporal temporal) 195 { 196 return temporal._with(ChronoField.DAY_OF_YEAR, 1); 197 } 198 }; 199 } 200 201 /** 202 * Returns the "last day of year" adjuster, which returns a new date set to 203 * the last day of the current year. 204 * !(p) 205 * The ISO calendar system behaves as follows:!(br) 206 * The input 2011-01-15 will return 2011-12-31.!(br) 207 * The input 2011-02-15 will return 2011-12-31.!(br) 208 * !(p) 209 * The behavior is suitable for use with most calendar systems. 210 * It is equivalent to: 211 * !(pre) 212 * long lastDay = temporal.range(DAY_OF_YEAR).getMaximum(); 213 * temporal._with(DAY_OF_YEAR, lastDay); 214 * </pre> 215 * 216 * @return the last day-of-year adjuster, not null 217 */ 218 public static TemporalAdjuster lastDayOfYear() { 219 return new class TemporalAdjuster{ 220 Temporal adjustInto(Temporal temporal) 221 { 222 return temporal._with(ChronoField.DAY_OF_YEAR, temporal.range(ChronoField.DAY_OF_YEAR).getMaximum()); 223 } 224 }; 225 } 226 227 /** 228 * Returns the "first day of next year" adjuster, which returns a new date set to 229 * the first day of the next year. 230 * !(p) 231 * The ISO calendar system behaves as follows:!(br) 232 * The input 2011-01-15 will return 2012-01-01. 233 * !(p) 234 * The behavior is suitable for use with most calendar systems. 235 * It is equivalent to: 236 * !(pre) 237 * temporal._with(DAY_OF_YEAR, 1).plus(1, YEARS); 238 * </pre> 239 * 240 * @return the first day of next month adjuster, not null 241 */ 242 public static TemporalAdjuster firstDayOfNextYear() { 243 return new class TemporalAdjuster{ 244 Temporal adjustInto(Temporal temporal) 245 { 246 return temporal._with(ChronoField.DAY_OF_YEAR, 1).plus(1, ChronoUnit.YEARS); 247 } 248 }; 249 } 250 251 //----------------------------------------------------------------------- 252 /** 253 * Returns the first _in month adjuster, which returns a new date 254 * _in the same month with the first matching day-of-week. 255 * This is used for expressions like 'first Tuesday _in March'. 256 * !(p) 257 * The ISO calendar system behaves as follows:!(br) 258 * The input 2011-12-15 for (MONDAY) will return 2011-12-05.!(br) 259 * The input 2011-12-15 for (FRIDAY) will return 2011-12-02.!(br) 260 * !(p) 261 * The behavior is suitable for use with most calendar systems. 262 * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields 263 * and the {@code DAYS} unit, and assumes a seven day week. 264 * 265 * @param dayOfWeek the day-of-week, not null 266 * @return the first _in month adjuster, not null 267 */ 268 public static TemporalAdjuster firstInMonth(DayOfWeek dayOfWeek) { 269 return TemporalAdjusters.dayOfWeekInMonth(1, dayOfWeek); 270 } 271 272 /** 273 * Returns the last _in month adjuster, which returns a new date 274 * _in the same month with the last matching day-of-week. 275 * This is used for expressions like 'last Tuesday _in March'. 276 * !(p) 277 * The ISO calendar system behaves as follows:!(br) 278 * The input 2011-12-15 for (MONDAY) will return 2011-12-26.!(br) 279 * The input 2011-12-15 for (FRIDAY) will return 2011-12-30.!(br) 280 * !(p) 281 * The behavior is suitable for use with most calendar systems. 282 * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields 283 * and the {@code DAYS} unit, and assumes a seven day week. 284 * 285 * @param dayOfWeek the day-of-week, not null 286 * @return the first _in month adjuster, not null 287 */ 288 public static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek) { 289 return TemporalAdjusters.dayOfWeekInMonth(-1, dayOfWeek); 290 } 291 292 /** 293 * Returns the day-of-week _in month adjuster, which returns a new date 294 * with the ordinal day-of-week based on the month. 295 * This is used for expressions like the 'second Tuesday _in March'. 296 * !(p) 297 * The ISO calendar system behaves as follows:!(br) 298 * The input 2011-12-15 for (1,TUESDAY) will return 2011-12-06.!(br) 299 * The input 2011-12-15 for (2,TUESDAY) will return 2011-12-13.!(br) 300 * The input 2011-12-15 for (3,TUESDAY) will return 2011-12-20.!(br) 301 * The input 2011-12-15 for (4,TUESDAY) will return 2011-12-27.!(br) 302 * The input 2011-12-15 for (5,TUESDAY) will return 2012-01-03.!(br) 303 * The input 2011-12-15 for (-1,TUESDAY) will return 2011-12-27 (last _in month).!(br) 304 * The input 2011-12-15 for (-4,TUESDAY) will return 2011-12-06 (3 weeks before last _in month).!(br) 305 * The input 2011-12-15 for (-5,TUESDAY) will return 2011-11-29 (4 weeks before last _in month).!(br) 306 * The input 2011-12-15 for (0,TUESDAY) will return 2011-11-29 (last _in previous month).!(br) 307 * !(p) 308 * For a positive or zero ordinal, the algorithm is equivalent to finding the first 309 * day-of-week that matches within the month and then adding a number of weeks to it. 310 * For a negative ordinal, the algorithm is equivalent to finding the last 311 * day-of-week that matches within the month and then subtracting a number of weeks to it. 312 * The ordinal number of weeks is not validated and is interpreted leniently 313 * according to this algorithm. This definition means that an ordinal of zero finds 314 * the last matching day-of-week _in the previous month. 315 * !(p) 316 * The behavior is suitable for use with most calendar systems. 317 * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields 318 * and the {@code DAYS} unit, and assumes a seven day week. 319 * 320 * @param ordinal the week within the month, unbounded but typically from -5 to 5 321 * @param dayOfWeek the day-of-week, not null 322 * @return the day-of-week _in month adjuster, not null 323 */ 324 public static TemporalAdjuster dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek) { 325 assert(dayOfWeek, "dayOfWeek"); 326 int dowValue = dayOfWeek.getValue(); 327 if (ordinal >= 0) { 328 return new class TemporalAdjuster{ 329 Temporal adjustInto(Temporal temporal) 330 { 331 Temporal temp = temporal._with(ChronoField.DAY_OF_MONTH, 1); 332 int curDow = temp.get(ChronoField.DAY_OF_WEEK); 333 int dowDiff = (dowValue - curDow + 7) % 7; 334 dowDiff += (ordinal - 1L) * 7L; // safe from overflow 335 return temp.plus(dowDiff, ChronoUnit.DAYS); 336 } 337 }; 338 } else { 339 340 return new class TemporalAdjuster{ 341 Temporal adjustInto(Temporal temporal) 342 { 343 Temporal temp = temporal._with(ChronoField.DAY_OF_MONTH, temporal.range(ChronoField.DAY_OF_MONTH).getMaximum()); 344 int curDow = temp.get(ChronoField.DAY_OF_WEEK); 345 int daysDiff = dowValue - curDow; 346 daysDiff = (daysDiff == 0 ? 0 : (daysDiff > 0 ? daysDiff - 7 : daysDiff)); 347 daysDiff -= (-ordinal - 1L) * 7L; // safe from overflow 348 return temp.plus(daysDiff, ChronoUnit.DAYS); 349 } 350 }; 351 } 352 } 353 354 //----------------------------------------------------------------------- 355 /** 356 * Returns the next day-of-week adjuster, which adjusts the date to the 357 * first occurrence of the specified day-of-week after the date being adjusted. 358 * !(p) 359 * The ISO calendar system behaves as follows:!(br) 360 * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-17 (two days later).!(br) 361 * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-19 (four days later).!(br) 362 * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-22 (seven days later). 363 * !(p) 364 * The behavior is suitable for use with most calendar systems. 365 * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, 366 * and assumes a seven day week. 367 * 368 * @param dayOfWeek the day-of-week to move the date to, not null 369 * @return the next day-of-week adjuster, not null 370 */ 371 public static TemporalAdjuster next(DayOfWeek dayOfWeek) { 372 int dowValue = dayOfWeek.getValue(); 373 return new class TemporalAdjuster{ 374 Temporal adjustInto(Temporal temporal) 375 { 376 int calDow = temporal.get(ChronoField.DAY_OF_WEEK); 377 int daysDiff = calDow - dowValue; 378 return temporal.plus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, ChronoUnit.DAYS); 379 } 380 }; 381 } 382 383 /** 384 * Returns the next-or-same day-of-week adjuster, which adjusts the date to the 385 * first occurrence of the specified day-of-week after the date being adjusted 386 * unless it is already on that day _in which case the same object is returned. 387 * !(p) 388 * The ISO calendar system behaves as follows:!(br) 389 * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-17 (two days later).!(br) 390 * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-19 (four days later).!(br) 391 * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-15 (same as input). 392 * !(p) 393 * The behavior is suitable for use with most calendar systems. 394 * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, 395 * and assumes a seven day week. 396 * 397 * @param dayOfWeek the day-of-week to check for or move the date to, not null 398 * @return the next-or-same day-of-week adjuster, not null 399 */ 400 public static TemporalAdjuster nextOrSame(DayOfWeek dayOfWeek) { 401 int dowValue = dayOfWeek.getValue(); 402 return new class TemporalAdjuster{ 403 Temporal adjustInto(Temporal temporal) 404 { 405 int calDow = temporal.get(ChronoField.DAY_OF_WEEK); 406 if (calDow == dowValue) { 407 return temporal; 408 } 409 int daysDiff = calDow - dowValue; 410 return temporal.plus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, ChronoUnit.DAYS); 411 } 412 }; 413 } 414 415 /** 416 * Returns the previous day-of-week adjuster, which adjusts the date to the 417 * first occurrence of the specified day-of-week before the date being adjusted. 418 * !(p) 419 * The ISO calendar system behaves as follows:!(br) 420 * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-10 (five days earlier).!(br) 421 * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-12 (three days earlier).!(br) 422 * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-08 (seven days earlier). 423 * !(p) 424 * The behavior is suitable for use with most calendar systems. 425 * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, 426 * and assumes a seven day week. 427 * 428 * @param dayOfWeek the day-of-week to move the date to, not null 429 * @return the previous day-of-week adjuster, not null 430 */ 431 public static TemporalAdjuster previous(DayOfWeek dayOfWeek) { 432 int dowValue = dayOfWeek.getValue(); 433 return new class TemporalAdjuster{ 434 Temporal adjustInto(Temporal temporal) 435 { 436 int calDow = temporal.get(ChronoField.DAY_OF_WEEK); 437 int daysDiff = dowValue - calDow; 438 return temporal.minus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, ChronoUnit.DAYS); 439 } 440 }; 441 } 442 443 /** 444 * Returns the previous-or-same day-of-week adjuster, which adjusts the date to the 445 * first occurrence of the specified day-of-week before the date being adjusted 446 * unless it is already on that day _in which case the same object is returned. 447 * !(p) 448 * The ISO calendar system behaves as follows:!(br) 449 * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-10 (five days earlier).!(br) 450 * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-12 (three days earlier).!(br) 451 * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-15 (same as input). 452 * !(p) 453 * The behavior is suitable for use with most calendar systems. 454 * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, 455 * and assumes a seven day week. 456 * 457 * @param dayOfWeek the day-of-week to check for or move the date to, not null 458 * @return the previous-or-same day-of-week adjuster, not null 459 */ 460 public static TemporalAdjuster previousOrSame(DayOfWeek dayOfWeek) { 461 int dowValue = dayOfWeek.getValue(); 462 return new class TemporalAdjuster{ 463 Temporal adjustInto(Temporal temporal) 464 { 465 int calDow = temporal.get(ChronoField.DAY_OF_WEEK); 466 if (calDow == dowValue) { 467 return temporal; 468 } 469 int daysDiff = dowValue - calDow; 470 return temporal.minus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, ChronoUnit.DAYS); 471 } 472 }; 473 } 474 475 }