1 /* 2 * hunt-time: A time library for D programming language. 3 * 4 * Copyright (C) 2015-2018 HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.time.chrono.ChronoLocalDateTime; 13 14 import hunt.time.temporal.ChronoField; 15 import hunt.time.temporal.ChronoUnit; 16 17 import hunt.stream.Common; 18 import hunt.time.Exceptions; 19 import hunt.time.Instant; 20 import hunt.time.LocalDateTime; 21 import hunt.time.LocalTime; 22 import hunt.time.ZoneId; 23 import hunt.time.ZoneOffset; 24 // import hunt.time.format.DateTimeFormatter; 25 import hunt.time.temporal.ChronoField; 26 import hunt.time.temporal.ChronoUnit; 27 import hunt.time.temporal.Temporal; 28 import hunt.time.temporal.TemporalAccessor; 29 import hunt.time.temporal.TemporalAdjuster; 30 import hunt.time.temporal.TemporalAmount; 31 import hunt.time.temporal.TemporalField; 32 import hunt.time.temporal.TemporalQueries; 33 import hunt.time.temporal.TemporalQuery; 34 import hunt.time.temporal.TemporalUnit; 35 import hunt.time.zone.ZoneRules; 36 import hunt.time.chrono.ChronoLocalDate; 37 import hunt.Functions; 38 import hunt.time.chrono.Chronology; 39 import hunt.time.chrono.ChronoZonedDateTime; 40 import hunt.util.Common; 41 import hunt.util.Comparator; 42 import hunt.time.util.QueryHelper; 43 44 /** 45 * A date-time without a time-zone _in an arbitrary chronology, intended 46 * for advanced globalization use cases. 47 * !(p) 48 * !(b)Most applications should declare method signatures, fields and variables 49 * as {@link LocalDateTime}, not this interface.</b> 50 * !(p) 51 * A {@code ChronoLocalDateTime} is the abstract representation of a local date-time 52 * where the {@code Chronology chronology}, or calendar system, is pluggable. 53 * The date-time is defined _in terms of fields expressed by {@link TemporalField}, 54 * where most common implementations are defined _in {@link ChronoField}. 55 * The chronology defines how the calendar system operates and the meaning of 56 * the standard fields. 57 * 58 * !(h3)When to use this interface</h3> 59 * The design of the API encourages the use of {@code LocalDateTime} rather than this 60 * interface, even _in the case where the application needs to deal with multiple 61 * calendar systems. The rationale for this is explored _in detail _in {@link ChronoLocalDate}. 62 * !(p) 63 * Ensure that the discussion _in {@code ChronoLocalDate} has been read and understood 64 * before using this interface. 65 * 66 * @implSpec 67 * This interface must be implemented with care to ensure other classes operate correctly. 68 * All implementations that can be instantiated must be final, immutable and thread-safe. 69 * Subclasses should be Serializable wherever possible. 70 * 71 * @param !(D) the concrete type for the date of this date-time 72 * @since 1.8 73 */ 74 public interface ChronoLocalDateTime(D) if(is(D : ChronoLocalDate)) 75 : Temporal, TemporalAdjuster, Comparable!(ChronoLocalDateTime!(ChronoLocalDate)) { 76 77 /** 78 * Gets a comparator that compares {@code ChronoLocalDateTime} _in 79 * time-line order ignoring the chronology. 80 * !(p) 81 * This comparator differs from the comparison _in {@link #compareTo} _in that it 82 * only compares the underlying date-time and not the chronology. 83 * This allows dates _in different calendar systems to be compared based 84 * on the position of the date-time on the local time-line. 85 * The underlying comparison is equivalent to comparing the epoch-day and nano-of-day. 86 * 87 * @return a comparator that compares _in time-line order ignoring the chronology 88 * @see #isAfter 89 * @see #isBefore 90 * @see #isEqual 91 */ 92 static Comparator!(ChronoLocalDateTime!(ChronoLocalDate)) timeLineOrder() { 93 return new class Comparator!(ChronoLocalDateTime!(ChronoLocalDate)) 94 { 95 int compare(ChronoLocalDateTime!(ChronoLocalDate) dateTime1, 96 ChronoLocalDateTime!(ChronoLocalDate) dateTime2) nothrow { 97 try { 98 int cmp = hunt.util.Comparator.compare(dateTime1.toLocalDate().toEpochDay(), dateTime2.toLocalDate().toEpochDay()); 99 if (cmp == 0) { 100 cmp = hunt.util.Comparator.compare(dateTime1.toLocalTime().toNanoOfDay(), dateTime2.toLocalTime().toNanoOfDay()); 101 } 102 return cmp; 103 } catch(Exception) { 104 // FIXME: Needing refactor or cleanup -@zxp at 12/29/2018, 11:29:43 PM 105 // 106 return 0; 107 } 108 } 109 }; 110 } 111 112 //----------------------------------------------------------------------- 113 /** 114 * Obtains an instance of {@code ChronoLocalDateTime} from a temporal object. 115 * !(p) 116 * This obtains a local date-time based on the specified temporal. 117 * A {@code TemporalAccessor} represents an arbitrary set of date and time information, 118 * which this factory converts to an instance of {@code ChronoLocalDateTime}. 119 * !(p) 120 * The conversion extracts and combines the chronology and the date-time 121 * from the temporal object. The behavior is equivalent to using 122 * {@link Chronology#localDateTime(TemporalAccessor)} with the extracted chronology. 123 * Implementations are permitted to perform optimizations such as accessing 124 * those fields that are equivalent to the relevant objects. 125 * !(p) 126 * This method matches the signature of the functional interface {@link TemporalQuery} 127 * allowing it to be used as a query via method reference, {@code ChronoLocalDateTime::from}. 128 * 129 * @param temporal the temporal object to convert, not null 130 * @return the date-time, not null 131 * @throws DateTimeException if unable to convert to a {@code ChronoLocalDateTime} 132 * @see Chronology#localDateTime(TemporalAccessor) 133 */ 134 static ChronoLocalDateTime!(ChronoLocalDate) from(TemporalAccessor temporal) { 135 if (cast(ChronoLocalDateTime)(temporal) !is null) { 136 return cast(ChronoLocalDateTime!(ChronoLocalDate)) temporal; 137 } 138 assert(temporal, "temporal"); 139 Chronology chrono = QueryHelper.query!Chronology(temporal,TemporalQueries.chronology()); 140 if (chrono is null) { 141 throw new DateTimeException("Unable to obtain ChronoLocalDateTime from TemporalAccessor: " ~ typeid(temporal).stringof); 142 } 143 return chrono.localDateTime(temporal); 144 } 145 146 //----------------------------------------------------------------------- 147 /** 148 * Gets the chronology of this date-time. 149 * !(p) 150 * The {@code Chronology} represents the calendar system _in use. 151 * The era and other fields _in {@link ChronoField} are defined by the chronology. 152 * 153 * @return the chronology, not null 154 */ 155 Chronology getChronology(); 156 // Chronology getChronology() { 157 // return toLocalDate().getChronology(); 158 // } 159 160 /** 161 * Gets the local date part of this date-time. 162 * !(p) 163 * This returns a local date with the same year, month and day 164 * as this date-time. 165 * 166 * @return the date part of this date-time, not null 167 */ 168 D toLocalDate(); 169 170 /** 171 * Gets the local time part of this date-time. 172 * !(p) 173 * This returns a local time with the same hour, minute, second and 174 * nanosecond as this date-time. 175 * 176 * @return the time part of this date-time, not null 177 */ 178 LocalTime toLocalTime(); 179 180 /** 181 * Checks if the specified field is supported. 182 * !(p) 183 * This checks if the specified field can be queried on this date-time. 184 * If false, then calling the {@link #range(TemporalField) range}, 185 * {@link #get(TemporalField) get} and {@link #_with(TemporalField, long)} 186 * methods will throw an exception. 187 * !(p) 188 * The set of supported fields is defined by the chronology and normally includes 189 * all {@code ChronoField} date and time fields. 190 * !(p) 191 * If the field is not a {@code ChronoField}, then the result of this method 192 * is obtained by invoking {@code TemporalField.isSupportedBy(TemporalAccessor)} 193 * passing {@code this} as the argument. 194 * Whether the field is supported is determined by the field. 195 * 196 * @param field the field to check, null returns false 197 * @return true if the field can be queried, false if not 198 */ 199 override 200 bool isSupported(TemporalField field); 201 202 /** 203 * Checks if the specified unit is supported. 204 * !(p) 205 * This checks if the specified unit can be added to or subtracted from this date-time. 206 * If false, then calling the {@link #plus(long, TemporalUnit)} and 207 * {@link #minus(long, TemporalUnit) minus} methods will throw an exception. 208 * !(p) 209 * The set of supported units is defined by the chronology and normally includes 210 * all {@code ChronoUnit} units except {@code FOREVER}. 211 * !(p) 212 * If the unit is not a {@code ChronoUnit}, then the result of this method 213 * is obtained by invoking {@code TemporalUnit.isSupportedBy(Temporal)} 214 * passing {@code this} as the argument. 215 * Whether the unit is supported is determined by the unit. 216 * 217 * @param unit the unit to check, null returns false 218 * @return true if the unit can be added/subtracted, false if not 219 */ 220 bool isSupported(TemporalUnit unit); 221 // override 222 // bool isSupported(TemporalUnit unit) { 223 // if (cast(ChronoUnit)(unit) !is null) { 224 // return unit != FOREVER; 225 // } 226 // return unit !is null && unit.isSupportedBy(this); 227 // } 228 229 //----------------------------------------------------------------------- 230 // override for covariant return type 231 /** 232 * {@inheritDoc} 233 * @throws DateTimeException {@inheritDoc} 234 * @throws ArithmeticException {@inheritDoc} 235 */ 236 ChronoLocalDateTime!(D) _with(TemporalAdjuster adjuster); 237 // override 238 // ChronoLocalDateTime!(D) _with(TemporalAdjuster adjuster) { 239 // return ChronoLocalDateTimeImpl.ensureValid(getChronology(), /* Temporal. */super._with(adjuster)); 240 // } 241 242 /** 243 * {@inheritDoc} 244 * @throws DateTimeException {@inheritDoc} 245 * @throws ArithmeticException {@inheritDoc} 246 */ 247 override 248 ChronoLocalDateTime!(D) _with(TemporalField field, long newValue); 249 250 /** 251 * {@inheritDoc} 252 * @throws DateTimeException {@inheritDoc} 253 * @throws ArithmeticException {@inheritDoc} 254 */ 255 ChronoLocalDateTime!(D) plus(TemporalAmount amount); 256 // override 257 // ChronoLocalDateTime!(D) plus(TemporalAmount amount) { 258 // return ChronoLocalDateTimeImpl.ensureValid(getChronology(), /* Temporal. */super.plus(amount)); 259 // } 260 261 /** 262 * {@inheritDoc} 263 * @throws DateTimeException {@inheritDoc} 264 * @throws ArithmeticException {@inheritDoc} 265 */ 266 override 267 ChronoLocalDateTime!(D) plus(long amountToAdd, TemporalUnit unit); 268 269 /** 270 * {@inheritDoc} 271 * @throws DateTimeException {@inheritDoc} 272 * @throws ArithmeticException {@inheritDoc} 273 */ 274 ChronoLocalDateTime!(D) minus(TemporalAmount amount); 275 // override 276 // ChronoLocalDateTime!(D) minus(TemporalAmount amount) { 277 // return ChronoLocalDateTimeImpl.ensureValid(getChronology(), /* Temporal. */super.minus(amount)); 278 // } 279 280 /** 281 * {@inheritDoc} 282 * @throws DateTimeException {@inheritDoc} 283 * @throws ArithmeticException {@inheritDoc} 284 */ 285 ChronoLocalDateTime!(D) minus(long amountToSubtract, TemporalUnit unit); 286 // override 287 // ChronoLocalDateTime!(D) minus(long amountToSubtract, TemporalUnit unit) { 288 // return ChronoLocalDateTimeImpl.ensureValid(getChronology(), /* Temporal. */super.minus(amountToSubtract, unit)); 289 // } 290 291 //----------------------------------------------------------------------- 292 /** 293 * Queries this date-time using the specified query. 294 * !(p) 295 * This queries this date-time using the specified query strategy object. 296 * The {@code TemporalQuery} object defines the logic to be used to 297 * obtain the result. Read the documentation of the query to understand 298 * what the result of this method will be. 299 * !(p) 300 * The result of this method is obtained by invoking the 301 * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the 302 * specified query passing {@code this} as the argument. 303 * 304 * @param !(R) the type of the result 305 * @param query the query to invoke, not null 306 * @return the query result, null may be returned (defined by the query) 307 * @throws DateTimeException if unable to query (defined by the query) 308 * @throws ArithmeticException if numeric overflow occurs (defined by the query) 309 */ 310 /*@SuppressWarnings("unchecked")*/ 311 R query(R)(TemporalQuery!(R) query); 312 // override 313 // R query(TemporalQuery!(R) query) { 314 // if (query == TemporalQueries.zoneId() || query == TemporalQueries.zone() || query == TemporalQueries.offset()) { 315 // return null; 316 // } else if (query == TemporalQueries.localTime()) { 317 // return cast(R) toLocalTime(); 318 // } else if (query == TemporalQueries.chronology()) { 319 // return cast(R) getChronology(); 320 // } else if (query == TemporalQueries.precision()) { 321 // return cast(R) NANOS; 322 // } 323 // // inline TemporalAccessor.super.query(query) as an optimization 324 // // non-JDK classes are not permitted to make this optimization 325 // return query.queryFrom(this); 326 // } 327 328 /** 329 * Adjusts the specified temporal object to have the same date and time as this object. 330 * !(p) 331 * This returns a temporal object of the same observable type as the input 332 * with the date and time changed to be the same as this. 333 * !(p) 334 * The adjustment is equivalent to using {@link Temporal#_with(TemporalField, long)} 335 * twice, passing {@link ChronoField#EPOCH_DAY} and 336 * {@link ChronoField#NANO_OF_DAY} as the fields. 337 * !(p) 338 * In most cases, it is clearer to reverse the calling pattern by using 339 * {@link Temporal#_with(TemporalAdjuster)}: 340 * !(pre) 341 * // these two lines are equivalent, but the second approach is recommended 342 * temporal = thisLocalDateTime.adjustInto(temporal); 343 * temporal = temporal._with(thisLocalDateTime); 344 * </pre> 345 * !(p) 346 * This instance is immutable and unaffected by this method call. 347 * 348 * @param temporal the target object to be adjusted, not null 349 * @return the adjusted object, not null 350 * @throws DateTimeException if unable to make the adjustment 351 * @throws ArithmeticException if numeric overflow occurs 352 */ 353 Temporal adjustInto(Temporal temporal); 354 // override 355 // Temporal adjustInto(Temporal temporal) { 356 // return temporal 357 // ._with(EPOCH_DAY, toLocalDate().toEpochDay()) 358 // ._with(NANO_OF_DAY, toLocalTime().toNanoOfDay()); 359 // } 360 361 /** 362 * Formats this date-time using the specified formatter. 363 * !(p) 364 * This date-time will be passed to the formatter to produce a string. 365 * !(p) 366 * The implementation must behave as follows: 367 * !(pre) 368 * return formatter.format(this); 369 * </pre> 370 * 371 * @param formatter the formatter to use, not null 372 * @return the formatted date-time string, not null 373 * @throws DateTimeException if an error occurs during printing 374 */ 375 // string format(DateTimeFormatter formatter); 376 // string format(DateTimeFormatter formatter) { 377 // assert(formatter, "formatter"); 378 // return formatter.format(this); 379 // } 380 381 //----------------------------------------------------------------------- 382 /** 383 * Combines this time with a time-zone to create a {@code ChronoZonedDateTime}. 384 * !(p) 385 * This returns a {@code ChronoZonedDateTime} formed from this date-time at the 386 * specified time-zone. The result will match this date-time as closely as possible. 387 * Time-zone rules, such as daylight savings, mean that not every local date-time 388 * is valid for the specified zone, thus the local date-time may be adjusted. 389 * !(p) 390 * The local date-time is resolved to a single instant on the time-line. 391 * This is achieved by finding a valid offset from UTC/Greenwich for the local 392 * date-time as defined by the {@link ZoneRules rules} of the zone ID. 393 *!(p) 394 * In most cases, there is only one valid offset for a local date-time. 395 * In the case of an overlap, where clocks are set back, there are two valid offsets. 396 * This method uses the earlier offset typically corresponding to "summer". 397 * !(p) 398 * In the case of a gap, where clocks jump forward, there is no valid offset. 399 * Instead, the local date-time is adjusted to be later by the length of the gap. 400 * For a typical one hour daylight savings change, the local date-time will be 401 * moved one hour later into the offset typically corresponding to "summer". 402 * !(p) 403 * To obtain the later offset during an overlap, call 404 * {@link ChronoZonedDateTime#withLaterOffsetAtOverlap()} on the result of this method. 405 * 406 * @param zone the time-zone to use, not null 407 * @return the zoned date-time formed from this date-time, not null 408 */ 409 ChronoZonedDateTime!(D) atZone(ZoneId zone); 410 411 //----------------------------------------------------------------------- 412 /** 413 * Converts this date-time to an {@code Instant}. 414 * !(p) 415 * This combines this local date-time and the specified offset to form 416 * an {@code Instant}. 417 * !(p) 418 * This implementation calculates from the epoch-day of the date and the 419 * second-of-day of the time. 420 * 421 * @param offset the offset to use for the conversion, not null 422 * @return an {@code Instant} representing the same instant, not null 423 */ 424 Instant toInstant(ZoneOffset offset); 425 // Instant toInstant(ZoneOffset offset) { 426 // return Instant.ofEpochSecond(toEpochSecond(offset), toLocalTime().getNano()); 427 // } 428 429 /** 430 * Converts this date-time to the number of seconds from the epoch 431 * of 1970-01-01T00:00:00Z. 432 * !(p) 433 * This combines this local date-time and the specified offset to calculate the 434 * epoch-second value, which is the number of elapsed seconds from 1970-01-01T00:00:00Z. 435 * Instants on the time-line after the epoch are positive, earlier are negative. 436 * !(p) 437 * This implementation calculates from the epoch-day of the date and the 438 * second-of-day of the time. 439 * 440 * @param offset the offset to use for the conversion, not null 441 * @return the number of seconds from the epoch of 1970-01-01T00:00:00Z 442 */ 443 long toEpochSecond(ZoneOffset offset); 444 // long toEpochSecond(ZoneOffset offset) { 445 // assert(offset, "offset"); 446 // long epochDay = toLocalDate().toEpochDay(); 447 // long secs = epochDay * 86400 + toLocalTime().toSecondOfDay(); 448 // secs -= offset.getTotalSeconds(); 449 // return secs; 450 // } 451 452 //----------------------------------------------------------------------- 453 /** 454 * Compares this date-time to another date-time, including the chronology. 455 * !(p) 456 * The comparison is based first on the underlying time-line date-time, then 457 * on the chronology. 458 * It is "consistent with equals", as defined by {@link Comparable}. 459 * !(p) 460 * For example, the following is the comparator order: 461 * !(ol) 462 * !(li){@code 2012-12-03T12:00 (ISO)}</li> 463 * !(li){@code 2012-12-04T12:00 (ISO)}</li> 464 * !(li){@code 2555-12-04T12:00 (ThaiBuddhist)}</li> 465 * !(li){@code 2012-12-05T12:00 (ISO)}</li> 466 * </ol> 467 * Values #2 and #3 represent the same date-time on the time-line. 468 * When two values represent the same date-time, the chronology ID is compared to distinguish them. 469 * This step is needed to make the ordering "consistent with equals". 470 * !(p) 471 * If all the date-time objects being compared are _in the same chronology, then the 472 * additional chronology stage is not required and only the local date-time is used. 473 * !(p) 474 * This implementation performs the comparison defined above. 475 * 476 * @param other the other date-time to compare to, not null 477 * @return the comparator value, negative if less, positive if greater 478 */ 479 // override 480 int compareTo(ChronoLocalDateTime!(ChronoLocalDate) other); 481 // override 482 // int compareTo(ChronoLocalDateTime!(ChronoLocalDate) other) { 483 // int cmp = toLocalDate().compareTo(other.toLocalDate()); 484 // if (cmp == 0) { 485 // cmp = toLocalTime().compareTo(other.toLocalTime()); 486 // if (cmp == 0) { 487 // cmp = getChronology().compareTo(other.getChronology()); 488 // } 489 // } 490 // return cmp; 491 // } 492 493 /** 494 * Checks if this date-time is after the specified date-time ignoring the chronology. 495 * !(p) 496 * This method differs from the comparison _in {@link #compareTo} _in that it 497 * only compares the underlying date-time and not the chronology. 498 * This allows dates _in different calendar systems to be compared based 499 * on the time-line position. 500 * !(p) 501 * This implementation performs the comparison based on the epoch-day 502 * and nano-of-day. 503 * 504 * @param other the other date-time to compare to, not null 505 * @return true if this is after the specified date-time 506 */ 507 bool isAfter(ChronoLocalDateTime!(ChronoLocalDate) other); 508 // bool isAfter(ChronoLocalDateTime!(ChronoLocalDate) other) { 509 // long thisEpDay = this.toLocalDate().toEpochDay(); 510 // long otherEpDay = other.toLocalDate().toEpochDay(); 511 // return thisEpDay > otherEpDay || 512 // (thisEpDay == otherEpDay && this.toLocalTime().toNanoOfDay() > other.toLocalTime().toNanoOfDay()); 513 // } 514 515 /** 516 * Checks if this date-time is before the specified date-time ignoring the chronology. 517 * !(p) 518 * This method differs from the comparison _in {@link #compareTo} _in that it 519 * only compares the underlying date-time and not the chronology. 520 * This allows dates _in different calendar systems to be compared based 521 * on the time-line position. 522 * !(p) 523 * This implementation performs the comparison based on the epoch-day 524 * and nano-of-day. 525 * 526 * @param other the other date-time to compare to, not null 527 * @return true if this is before the specified date-time 528 */ 529 bool isBefore(ChronoLocalDateTime!(ChronoLocalDate) other); 530 // bool isBefore(ChronoLocalDateTime!(ChronoLocalDate) other) { 531 // long thisEpDay = this.toLocalDate().toEpochDay(); 532 // long otherEpDay = other.toLocalDate().toEpochDay(); 533 // return thisEpDay < otherEpDay || 534 // (thisEpDay == otherEpDay && this.toLocalTime().toNanoOfDay() < other.toLocalTime().toNanoOfDay()); 535 // } 536 537 /** 538 * Checks if this date-time is equal to the specified date-time ignoring the chronology. 539 * !(p) 540 * This method differs from the comparison _in {@link #compareTo} _in that it 541 * only compares the underlying date and time and not the chronology. 542 * This allows date-times _in different calendar systems to be compared based 543 * on the time-line position. 544 * !(p) 545 * This implementation performs the comparison based on the epoch-day 546 * and nano-of-day. 547 * 548 * @param other the other date-time to compare to, not null 549 * @return true if the underlying date-time is equal to the specified date-time on the timeline 550 */ 551 bool isEqual(ChronoLocalDateTime!(ChronoLocalDate) other); 552 // bool isEqual(ChronoLocalDateTime!(ChronoLocalDate) other) { 553 // // Do the time check first, it is cheaper than computing EPOCH day. 554 // return this.toLocalTime().toNanoOfDay() == other.toLocalTime().toNanoOfDay() && 555 // this.toLocalDate().toEpochDay() == other.toLocalDate().toEpochDay(); 556 // } 557 558 /** 559 * Checks if this date-time is equal to another date-time, including the chronology. 560 * !(p) 561 * Compares this date-time with another ensuring that the date-time and chronology are the same. 562 * 563 * @param obj the object to check, null returns false 564 * @return true if this is equal to the other date 565 */ 566 // override 567 bool opEquals(Object obj); 568 569 /** 570 * A hash code for this date-time. 571 * 572 * @return a suitable hash code 573 */ 574 // override 575 size_t toHash() @trusted nothrow; 576 577 //----------------------------------------------------------------------- 578 /** 579 * Outputs this date-time as a {@code string}. 580 * !(p) 581 * The output will include the full local date-time. 582 * 583 * @return a string representation of this date-time, not null 584 */ 585 // override 586 string toString(); 587 588 }