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.TemporalQueries; 13 14 import hunt.time.temporal.ChronoField; 15 16 import hunt.time.LocalDate; 17 import hunt.time.LocalTime; 18 import hunt.time.ZoneId; 19 import hunt.time.ZoneOffset; 20 import hunt.time.chrono.Chronology; 21 import hunt.time.temporal.TemporalQuery; 22 import hunt.time.temporal.TemporalUnit; 23 import hunt.time.temporal.TemporalAccessor; 24 import hunt.time.util.QueryHelper; 25 import hunt.time.util.Common; 26 /** 27 * Common implementations of {@code TemporalQuery}. 28 * !(p) 29 * This class provides common implementations of {@link TemporalQuery}. 30 * These are defined here as they must be constants, and the definition 31 * of lambdas does not guarantee that. By assigning them once here, 32 * they become 'normal' Java constants. 33 * !(p) 34 * Queries are a key tool for extracting information from temporal objects. 35 * They exist to externalize the process of querying, permitting different 36 * approaches, as per the strategy design pattern. 37 * Examples might be a query that checks if the date is the day before February 29th 38 * _in a leap year, or calculates the number of days to your next birthday. 39 * !(p) 40 * The {@link TemporalField} interface provides another mechanism for querying 41 * temporal objects. That interface is limited to returning a {@code long}. 42 * By contrast, queries can return any type. 43 * !(p) 44 * There are two equivalent ways of using a {@code TemporalQuery}. 45 * The first is to invoke the method on this interface directly. 46 * The second is to use {@link TemporalAccessor#query(TemporalQuery)}: 47 * !(pre) 48 * // these two lines are equivalent, but the second approach is recommended 49 * temporal = thisQuery.queryFrom(temporal); 50 * temporal = temporal.query(thisQuery); 51 * </pre> 52 * It is recommended to use the second approach, {@code query(TemporalQuery)}, 53 * as it is a lot clearer to read _in code. 54 * !(p) 55 * The most common implementations are method references, such as 56 * {@code LocalDate.from} and {@code ZoneId::from}. 57 * Additional common queries are provided to return: 58 * !(ul) 59 * !(li) a Chronology, 60 * !(li) a LocalDate, 61 * !(li) a LocalTime, 62 * !(li) a ZoneOffset, 63 * !(li) a precision, 64 * !(li) a zone, or 65 * !(li) a zoneId. 66 * </ul> 67 * 68 * @since 1.8 69 */ 70 public final class TemporalQueries 71 { 72 // note that it is vital that each method supplies a constant, not a 73 // calculated value, as they will be checked for using == 74 // it is also vital that each constant is different (due to the == checking) 75 // as such, alterations to this code must be done with care 76 77 /** 78 * Private constructor since this is a utility class. 79 */ 80 private this() 81 { 82 } 83 84 //----------------------------------------------------------------------- 85 // special constants should be used to extract information from a TemporalAccessor 86 // that cannot be derived _in other ways 87 // Javadoc added here, so as to pretend they are more normal than they really are 88 89 /** 90 * A strict query for the {@code ZoneId}. 91 * !(p) 92 * This queries a {@code TemporalAccessor} for the zone. 93 * The zone is only returned if the date-time conceptually contains a {@code ZoneId}. 94 * It will not be returned if the date-time only conceptually has an {@code ZoneOffset}. 95 * Thus a {@link hunt.time.ZonedDateTime} will return the result of {@code getZone()}, 96 * but an {@link hunt.time.OffsetDateTime} will return null. 97 * !(p) 98 * In most cases, applications should use {@link #zone()} as this query is too strict. 99 * !(p) 100 * The result from JDK classes implementing {@code TemporalAccessor} is as follows:!(br) 101 * {@code LocalDate} returns null!(br) 102 * {@code LocalTime} returns null!(br) 103 * {@code LocalDateTime} returns null!(br) 104 * {@code ZonedDateTime} returns the associated zone!(br) 105 * {@code OffsetTime} returns null!(br) 106 * {@code OffsetDateTime} returns null!(br) 107 * {@code ChronoLocalDate} returns null!(br) 108 * {@code ChronoLocalDateTime} returns null!(br) 109 * {@code ChronoZonedDateTime} returns the associated zone!(br) 110 * {@code Era} returns null!(br) 111 * {@code DayOfWeek} returns null!(br) 112 * {@code Month} returns null!(br) 113 * {@code Year} returns null!(br) 114 * {@code YearMonth} returns null!(br) 115 * {@code MonthDay} returns null!(br) 116 * {@code ZoneOffset} returns null!(br) 117 * {@code Instant} returns null!(br) 118 * 119 * @return a query that can obtain the zone ID of a temporal, not null 120 */ 121 public static TemporalQuery!(ZoneId) zoneId() 122 { 123 return TemporalQueries.ZONE_ID; 124 } 125 126 /** 127 * A query for the {@code Chronology}. 128 * !(p) 129 * This queries a {@code TemporalAccessor} for the chronology. 130 * If the target {@code TemporalAccessor} represents a date, or part of a date, 131 * then it should return the chronology that the date is expressed _in. 132 * As a result of this definition, objects only representing time, such as 133 * {@code LocalTime}, will return null. 134 * !(p) 135 * The result from JDK classes implementing {@code TemporalAccessor} is as follows:!(br) 136 * {@code LocalDate} returns {@code IsoChronology.INSTANCE}!(br) 137 * {@code LocalTime} returns null (does not represent a date)!(br) 138 * {@code LocalDateTime} returns {@code IsoChronology.INSTANCE}!(br) 139 * {@code ZonedDateTime} returns {@code IsoChronology.INSTANCE}!(br) 140 * {@code OffsetTime} returns null (does not represent a date)!(br) 141 * {@code OffsetDateTime} returns {@code IsoChronology.INSTANCE}!(br) 142 * {@code ChronoLocalDate} returns the associated chronology!(br) 143 * {@code ChronoLocalDateTime} returns the associated chronology!(br) 144 * {@code ChronoZonedDateTime} returns the associated chronology!(br) 145 * {@code Era} returns the associated chronology!(br) 146 * {@code DayOfWeek} returns null (shared across chronologies)!(br) 147 * {@code Month} returns {@code IsoChronology.INSTANCE}!(br) 148 * {@code Year} returns {@code IsoChronology.INSTANCE}!(br) 149 * {@code YearMonth} returns {@code IsoChronology.INSTANCE}!(br) 150 * {@code MonthDay} returns null {@code IsoChronology.INSTANCE}!(br) 151 * {@code ZoneOffset} returns null (does not represent a date)!(br) 152 * {@code Instant} returns null (does not represent a date)!(br) 153 * !(p) 154 * The method {@link hunt.time.chrono.Chronology#from(TemporalAccessor)} can be used as a 155 * {@code TemporalQuery} via a method reference, {@code Chronology::from}. 156 * That method is equivalent to this query, except that it throws an 157 * exception if a chronology cannot be obtained. 158 * 159 * @return a query that can obtain the chronology of a temporal, not null 160 */ 161 public static TemporalQuery!(Chronology) chronology() 162 { 163 return TemporalQueries.CHRONO; 164 } 165 166 /** 167 * A query for the smallest supported unit. 168 * !(p) 169 * This queries a {@code TemporalAccessor} for the time precision. 170 * If the target {@code TemporalAccessor} represents a consistent or complete date-time, 171 * date or time then this must return the smallest precision actually supported. 172 * Note that fields such as {@code NANO_OF_DAY} and {@code NANO_OF_SECOND} 173 * are defined to always return ignoring the precision, thus this is the only 174 * way to find the actual smallest supported unit. 175 * For example, were {@code GregorianCalendar} to implement {@code TemporalAccessor} 176 * it would return a precision of {@code MILLIS}. 177 * !(p) 178 * The result from JDK classes implementing {@code TemporalAccessor} is as follows:!(br) 179 * {@code LocalDate} returns {@code DAYS}!(br) 180 * {@code LocalTime} returns {@code NANOS}!(br) 181 * {@code LocalDateTime} returns {@code NANOS}!(br) 182 * {@code ZonedDateTime} returns {@code NANOS}!(br) 183 * {@code OffsetTime} returns {@code NANOS}!(br) 184 * {@code OffsetDateTime} returns {@code NANOS}!(br) 185 * {@code ChronoLocalDate} returns {@code DAYS}!(br) 186 * {@code ChronoLocalDateTime} returns {@code NANOS}!(br) 187 * {@code ChronoZonedDateTime} returns {@code NANOS}!(br) 188 * {@code Era} returns {@code ERAS}!(br) 189 * {@code DayOfWeek} returns {@code DAYS}!(br) 190 * {@code Month} returns {@code MONTHS}!(br) 191 * {@code Year} returns {@code YEARS}!(br) 192 * {@code YearMonth} returns {@code MONTHS}!(br) 193 * {@code MonthDay} returns null (does not represent a complete date or time)!(br) 194 * {@code ZoneOffset} returns null (does not represent a date or time)!(br) 195 * {@code Instant} returns {@code NANOS}!(br) 196 * 197 * @return a query that can obtain the precision of a temporal, not null 198 */ 199 public static TemporalQuery!(TemporalUnit) precision() 200 { 201 return TemporalQueries.PRECISION; 202 } 203 204 //----------------------------------------------------------------------- 205 // non-special constants are standard queries that derive information from other information 206 /** 207 * A lenient query for the {@code ZoneId}, falling back to the {@code ZoneOffset}. 208 * !(p) 209 * This queries a {@code TemporalAccessor} for the zone. 210 * It first tries to obtain the zone, using {@link #zoneId()}. 211 * If that is not found it tries to obtain the {@link #offset()}. 212 * Thus a {@link hunt.time.ZonedDateTime} will return the result of {@code getZone()}, 213 * while an {@link hunt.time.OffsetDateTime} will return the result of {@code getOffset()}. 214 * !(p) 215 * In most cases, applications should use this query rather than {@code #zoneId()}. 216 * !(p) 217 * The method {@link ZoneId#from(TemporalAccessor)} can be used as a 218 * {@code TemporalQuery} via a method reference, {@code ZoneId::from}. 219 * That method is equivalent to this query, except that it throws an 220 * exception if a zone cannot be obtained. 221 * 222 * @return a query that can obtain the zone ID or offset of a temporal, not null 223 */ 224 public static TemporalQuery!(ZoneId) zone() 225 { 226 return TemporalQueries.ZONE; 227 } 228 229 /** 230 * A query for {@code ZoneOffset} returning null if not found. 231 * !(p) 232 * This returns a {@code TemporalQuery} that can be used to query a temporal 233 * object for the offset. The query will return null if the temporal 234 * object cannot supply an offset. 235 * !(p) 236 * The query implementation examines the {@link ChronoField#OFFSET_SECONDS OFFSET_SECONDS} 237 * field and uses it to create a {@code ZoneOffset}. 238 * !(p) 239 * The method {@link hunt.time.ZoneOffset#from(TemporalAccessor)} can be used as a 240 * {@code TemporalQuery} via a method reference, {@code ZoneOffset::from}. 241 * This query and {@code ZoneOffset::from} will return the same result if the 242 * temporal object contains an offset. If the temporal object does not contain 243 * an offset, then the method reference will throw an exception, whereas this 244 * query will return null. 245 * 246 * @return a query that can obtain the offset of a temporal, not null 247 */ 248 public static TemporalQuery!(ZoneOffset) offset() 249 { 250 return TemporalQueries.OFFSET; 251 } 252 253 /** 254 * A query for {@code LocalDate} returning null if not found. 255 * !(p) 256 * This returns a {@code TemporalQuery} that can be used to query a temporal 257 * object for the local date. The query will return null if the temporal 258 * object cannot supply a local date. 259 * !(p) 260 * The query implementation examines the {@link ChronoField#EPOCH_DAY EPOCH_DAY} 261 * field and uses it to create a {@code LocalDate}. 262 * !(p) 263 * The method {@link ZoneOffset#from(TemporalAccessor)} can be used as a 264 * {@code TemporalQuery} via a method reference, {@code LocalDate.from}. 265 * This query and {@code LocalDate.from} will return the same result if the 266 * temporal object contains a date. If the temporal object does not contain 267 * a date, then the method reference will throw an exception, whereas this 268 * query will return null. 269 * 270 * @return a query that can obtain the date of a temporal, not null 271 */ 272 public static TemporalQuery!(LocalDate) localDate() 273 { 274 return TemporalQueries.LOCAL_DATE; 275 } 276 277 /** 278 * A query for {@code LocalTime} returning null if not found. 279 * !(p) 280 * This returns a {@code TemporalQuery} that can be used to query a temporal 281 * object for the local time. The query will return null if the temporal 282 * object cannot supply a local time. 283 * !(p) 284 * The query implementation examines the {@link ChronoField#NANO_OF_DAY NANO_OF_DAY} 285 * field and uses it to create a {@code LocalTime}. 286 * !(p) 287 * The method {@link ZoneOffset#from(TemporalAccessor)} can be used as a 288 * {@code TemporalQuery} via a method reference, {@code LocalTime.from}. 289 * This query and {@code LocalTime.from} will return the same result if the 290 * temporal object contains a time. If the temporal object does not contain 291 * a time, then the method reference will throw an exception, whereas this 292 * query will return null. 293 * 294 * @return a query that can obtain the time of a temporal, not null 295 */ 296 public static TemporalQuery!(LocalTime) localTime() 297 { 298 return TemporalQueries.LOCAL_TIME; 299 } 300 301 //----------------------------------------------------------------------- 302 /** 303 * A strict query for the {@code ZoneId}. 304 */ 305 // __gshared TemporalQuery!(ZoneId) ZONE_ID; 306 307 /** 308 * A query for the {@code Chronology}. 309 */ 310 //__gshared TemporalQuery!(Chronology) CHRONO; 311 312 /** 313 * A query for the smallest supported unit. 314 */ 315 //__gshared TemporalQuery!(TemporalUnit) PRECISION; 316 317 //----------------------------------------------------------------------- 318 /** 319 * A query for {@code ZoneOffset} returning null if not found. 320 */ 321 //__gshared TemporalQuery!(ZoneOffset) OFFSET; 322 323 /** 324 * A lenient query for the {@code ZoneId}, falling back to the {@code ZoneOffset}. 325 */ 326 //__gshared TemporalQuery!(ZoneId) ZONE; 327 328 /** 329 * A query for {@code LocalDate} returning null if not found. 330 */ 331 //__gshared TemporalQuery!(LocalDate) LOCAL_DATE; 332 333 /** 334 * A query for {@code LocalTime} returning null if not found. 335 */ 336 //__gshared TemporalQuery!(LocalTime) LOCAL_TIME; 337 338 // shared static this() 339 // { 340 // ZONE_ID = new class TemporalQuery!(ZoneId) 341 // { 342 // override public ZoneId queryFrom(TemporalAccessor temporal) 343 // { 344 // return QueryHelper.query!ZoneId(temporal, TemporalQueries.ZONE_ID); 345 // } 346 347 // override public string toString() 348 // { 349 // return "ZoneId"; 350 // } 351 // }; 352 353 mixin(MakeGlobalVar!(TemporalQuery!(ZoneId))("ZONE_ID",` new class TemporalQuery!(ZoneId) 354 { 355 override public ZoneId queryFrom(TemporalAccessor temporal) 356 { 357 return QueryHelper.query!ZoneId(temporal, TemporalQueries.ZONE_ID); 358 } 359 360 override public string toString() 361 { 362 return "ZoneId"; 363 } 364 }`)); 365 366 /** 367 * A query for the {@code Chronology}. 368 */ 369 // CHRONO = new class TemporalQuery!(Chronology) 370 // { 371 // override public Chronology queryFrom(TemporalAccessor temporal) 372 // { 373 // return QueryHelper.query!Chronology(temporal, TemporalQueries.CHRONO); 374 // } 375 376 // override public string toString() 377 // { 378 // return "Chronology"; 379 // } 380 // }; 381 382 mixin(MakeGlobalVar!(TemporalQuery!(Chronology))("CHRONO",`new class TemporalQuery!(Chronology) 383 { 384 override public Chronology queryFrom(TemporalAccessor temporal) 385 { 386 return QueryHelper.query!Chronology(temporal, TemporalQueries.CHRONO); 387 } 388 389 override public string toString() 390 { 391 return "Chronology"; 392 } 393 }`)); 394 395 /** 396 * A query for the smallest supported unit. 397 */ 398 // PRECISION = new class TemporalQuery!(TemporalUnit) 399 // { 400 // override public TemporalUnit queryFrom(TemporalAccessor temporal) 401 // { 402 // return QueryHelper.query!TemporalUnit(temporal, TemporalQueries.PRECISION); 403 // } 404 405 // override public string toString() 406 // { 407 // return "Precision"; 408 // } 409 // }; 410 411 mixin(MakeGlobalVar!(TemporalQuery!(TemporalUnit))("PRECISION",`new class TemporalQuery!(TemporalUnit) 412 { 413 override public TemporalUnit queryFrom(TemporalAccessor temporal) 414 { 415 return QueryHelper.query!TemporalUnit(temporal, TemporalQueries.PRECISION); 416 } 417 418 override public string toString() 419 { 420 return "Precision"; 421 } 422 }`)); 423 424 //----------------------------------------------------------------------- 425 /** 426 * A query for {@code ZoneOffset} returning null if not found. 427 */ 428 // OFFSET = new class TemporalQuery!(ZoneOffset) 429 // { 430 // override public ZoneOffset queryFrom(TemporalAccessor temporal) 431 // { 432 // if (temporal.isSupported(ChronoField.OFFSET_SECONDS)) 433 // { 434 // return ZoneOffset.ofTotalSeconds(temporal.get(ChronoField.OFFSET_SECONDS)); 435 // } 436 // return null; 437 // } 438 439 // override public string toString() 440 // { 441 // return "ZoneOffset"; 442 // } 443 // }; 444 445 mixin(MakeGlobalVar!(TemporalQuery!(ZoneOffset))("OFFSET",`new class TemporalQuery!(ZoneOffset) 446 { 447 override public ZoneOffset queryFrom(TemporalAccessor temporal) 448 { 449 if (temporal.isSupported(ChronoField.OFFSET_SECONDS)) 450 { 451 return ZoneOffset.ofTotalSeconds(temporal.get(ChronoField.OFFSET_SECONDS)); 452 } 453 return null; 454 } 455 456 override public string toString() 457 { 458 return "ZoneOffset"; 459 } 460 }`)); 461 462 /** 463 * A lenient query for the {@code ZoneId}, falling back to the {@code ZoneOffset}. 464 */ 465 // ZONE = new class TemporalQuery!(ZoneId) 466 // { 467 // override public ZoneId queryFrom(TemporalAccessor temporal) 468 // { 469 // ZoneId zone = QueryHelper.query!ZoneId(temporal, ZONE_ID); 470 // return (zone !is null ? zone : QueryHelper.query!ZoneOffset(temporal, OFFSET)); 471 // } 472 473 // override public string toString() 474 // { 475 // return "Zone"; 476 // } 477 // }; 478 479 mixin(MakeGlobalVar!(TemporalQuery!(ZoneId))("ZONE",`new class TemporalQuery!(ZoneId) 480 { 481 override public ZoneId queryFrom(TemporalAccessor temporal) 482 { 483 ZoneId zone = QueryHelper.query!ZoneId(temporal, ZONE_ID); 484 return (zone !is null ? zone : QueryHelper.query!ZoneOffset(temporal, OFFSET)); 485 } 486 487 override public string toString() 488 { 489 return "Zone"; 490 } 491 }`)); 492 /** 493 * A query for {@code LocalDate} returning null if not found. 494 */ 495 // LOCAL_DATE = new class TemporalQuery!(LocalDate) 496 // { 497 // override public LocalDate queryFrom(TemporalAccessor temporal) 498 // { 499 // if (temporal.isSupported(ChronoField.EPOCH_DAY)) 500 // { 501 // return LocalDate.ofEpochDay(temporal.getLong(ChronoField.EPOCH_DAY)); 502 // } 503 // return null; 504 // } 505 506 // override public string toString() 507 // { 508 // return "LocalDate"; 509 // } 510 // }; 511 512 513 mixin(MakeGlobalVar!(TemporalQuery!(LocalDate))("LOCAL_DATE",`new class TemporalQuery!(LocalDate) 514 { 515 override public LocalDate queryFrom(TemporalAccessor temporal) 516 { 517 if (temporal.isSupported(ChronoField.EPOCH_DAY)) 518 { 519 return LocalDate.ofEpochDay(temporal.getLong(ChronoField.EPOCH_DAY)); 520 } 521 return null; 522 } 523 524 override public string toString() 525 { 526 return "LocalDate"; 527 } 528 }`)); 529 530 /** 531 * A query for {@code LocalTime} returning null if not found. 532 */ 533 // LOCAL_TIME = new class TemporalQuery!(LocalTime) 534 // { 535 // override public LocalTime queryFrom(TemporalAccessor temporal) 536 // { 537 // if (temporal.isSupported(ChronoField.NANO_OF_DAY)) 538 // { 539 // return LocalTime.ofNanoOfDay(temporal.getLong(ChronoField.NANO_OF_DAY)); 540 // } 541 // return null; 542 // } 543 544 // override public string toString() 545 // { 546 // return "LocalTime"; 547 // } 548 // }; 549 mixin(MakeGlobalVar!(TemporalQuery!(LocalTime))("LOCAL_TIME",`new class TemporalQuery!(LocalTime) 550 { 551 override public LocalTime queryFrom(TemporalAccessor temporal) 552 { 553 if (temporal.isSupported(ChronoField.NANO_OF_DAY)) 554 { 555 return LocalTime.ofNanoOfDay(temporal.getLong(ChronoField.NANO_OF_DAY)); 556 } 557 return null; 558 } 559 560 override public string toString() 561 { 562 return "LocalTime"; 563 } 564 }`)); 565 // } 566 }