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.OffsetTime; 13 14 import hunt.time.LocalTime; 15 import hunt.time.temporal.ChronoField; 16 import hunt.time.temporal.ChronoUnit; 17 18 import hunt.Exceptions; 19 import hunt.stream.ObjectInput; 20 import hunt.stream.ObjectOutput; 21 22 //import hunt.io.ObjectInputStream; 23 import hunt.stream.Common; 24 // import hunt.time.format.DateTimeFormatter; 25 import hunt.time.format.DateTimeParseException; 26 import hunt.time.temporal.ChronoField; 27 import hunt.time.temporal.ChronoUnit; 28 import hunt.time.temporal.Temporal; 29 import hunt.time.temporal.TemporalAccessor; 30 import hunt.time.temporal.TemporalAdjuster; 31 import hunt.time.temporal.TemporalAmount; 32 import hunt.time.temporal.TemporalField; 33 import hunt.time.temporal.TemporalQueries; 34 import hunt.time.temporal.TemporalQuery; 35 import hunt.time.temporal.TemporalUnit; 36 import hunt.time.Exceptions; 37 import hunt.time.temporal.ValueRange; 38 import hunt.time.zone.ZoneRules; 39 import hunt.Functions; 40 import hunt.Long; 41 import hunt.math.Helper; 42 import hunt.time.ZoneOffset; 43 import hunt.time.Instant; 44 import hunt.time.ZoneId; 45 import hunt.time.Clock; 46 import hunt.time.OffsetDateTime; 47 import hunt.time.LocalDate; 48 import hunt.time.Exceptions; 49 import hunt.time.Ser; 50 import hunt.time.util.Common; 51 import hunt.util.Common; 52 import hunt.util.Comparator; 53 // import hunt.serialization.JsonSerializer; 54 55 import std.conv; 56 import std.concurrency : initOnce; 57 58 59 /** 60 * A time with an offset from UTC/Greenwich _in the ISO-8601 calendar system, 61 * such as {@code 10:15:30+01:00}. 62 * !(p) 63 * {@code OffsetTime} is an immutable date-time object that represents a time, often 64 * viewed as hour-minute-second-offset. 65 * This class stores all time fields, to a precision of nanoseconds, 66 * as well as a zone offset. 67 * For example, the value "13:45:30.123456789+02:00" can be stored 68 * _in an {@code OffsetTime}. 69 * 70 * !(p) 71 * This is a <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a> 72 * class; use of identity-sensitive operations (including reference equality 73 * ({@code ==}), identity hash code, or synchronization) on instances of 74 * {@code OffsetTime} may have unpredictable results and should be avoided. 75 * The {@code equals} method should be used for comparisons. 76 * 77 * @implSpec 78 * This class is immutable and thread-safe. 79 * 80 * @since 1.8 81 */ 82 final class OffsetTime 83 : Temporal, TemporalAdjuster, Comparable!(OffsetTime) { // , Serializable 84 85 /** 86 * The minimum supported {@code OffsetTime}, '00:00:00+18:00'. 87 * This is the time of midnight at the start of the day _in the maximum offset 88 * (larger offsets are earlier on the time-line). 89 * This combines {@link LocalTime#MIN} and {@link ZoneOffset#MAX}. 90 * This could be used by an application as a "far past" date. 91 */ 92 static OffsetTime MIN() { 93 __gshared OffsetTime _MIN; 94 return initOnce!(_MIN)(LocalTime.MIN.atOffset(ZoneOffset.MAX)); 95 } 96 97 /** 98 * The maximum supported {@code OffsetTime}, '23:59:59.999999999-18:00'. 99 * This is the time just before midnight at the end of the day _in the minimum offset 100 * (larger negative offsets are later on the time-line). 101 * This combines {@link LocalTime#MAX} and {@link ZoneOffset#MIN}. 102 * This could be used by an application as a "far future" date. 103 */ 104 static OffsetTime MAX() { 105 __gshared OffsetTime _MAX; 106 return initOnce!(_MAX)(LocalTime.MAX.atOffset(ZoneOffset.MIN)); 107 } 108 109 /** 110 * The local date-time. 111 */ 112 private LocalTime time; 113 114 /** 115 * The offset from UTC/Greenwich. 116 */ 117 private ZoneOffset offset; 118 119 120 //----------------------------------------------------------------------- 121 /** 122 * Obtains the current time from the system clock _in the default time-zone. 123 * !(p) 124 * This will query the {@link Clock#systemDefaultZone() system clock} _in the default 125 * time-zone to obtain the current time. 126 * The offset will be calculated from the time-zone _in the clock. 127 * !(p) 128 * Using this method will prevent the ability to use an alternate clock for testing 129 * because the clock is hard-coded. 130 * 131 * @return the current time using the system clock and default time-zone, not null 132 */ 133 static OffsetTime now() { 134 return now(Clock.systemDefaultZone()); 135 } 136 137 /** 138 * Obtains the current time from the system clock _in the specified time-zone. 139 * !(p) 140 * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current time. 141 * Specifying the time-zone avoids dependence on the default time-zone. 142 * The offset will be calculated from the specified time-zone. 143 * !(p) 144 * Using this method will prevent the ability to use an alternate clock for testing 145 * because the clock is hard-coded. 146 * 147 * @param zone the zone ID to use, not null 148 * @return the current time using the system clock, not null 149 */ 150 static OffsetTime now(ZoneId zone) { 151 return now(Clock.system(zone)); 152 } 153 154 /** 155 * Obtains the current time from the specified clock. 156 * !(p) 157 * This will query the specified clock to obtain the current time. 158 * The offset will be calculated from the time-zone _in the clock. 159 * !(p) 160 * Using this method allows the use of an alternate clock for testing. 161 * The alternate clock may be introduced using {@link Clock dependency injection}. 162 * 163 * @param clock the clock to use, not null 164 * @return the current time, not null 165 */ 166 static OffsetTime now(Clock clock) { 167 assert(clock, "clock"); 168 Instant now = clock.instant(); // called once 169 return ofInstant(now, clock.getZone().getRules().getOffset(now)); 170 } 171 172 //----------------------------------------------------------------------- 173 /** 174 * Obtains an instance of {@code OffsetTime} from a local time and an offset. 175 * 176 * @param time the local time, not null 177 * @param offset the zone offset, not null 178 * @return the offset time, not null 179 */ 180 static OffsetTime of(LocalTime time, ZoneOffset offset) { 181 return new OffsetTime(time, offset); 182 } 183 184 /** 185 * Obtains an instance of {@code OffsetTime} from an hour, minute, second and nanosecond. 186 * !(p) 187 * This creates an offset time with the four specified fields. 188 * !(p) 189 * This method exists primarily for writing test cases. 190 * Non test-code will typically use other methods to create an offset time. 191 * {@code LocalTime} has two additional convenience variants of the 192 * equivalent factory method taking fewer arguments. 193 * They are not provided here to reduce the footprint of the API. 194 * 195 * @param hour the hour-of-day to represent, from 0 to 23 196 * @param minute the minute-of-hour to represent, from 0 to 59 197 * @param second the second-of-minute to represent, from 0 to 59 198 * @param nanoOfSecond the nano-of-second to represent, from 0 to 999,999,999 199 * @param offset the zone offset, not null 200 * @return the offset time, not null 201 * @throws DateTimeException if the value of any field is _out of range 202 */ 203 static OffsetTime of(int hour, int minute, int second, int nanoOfSecond, ZoneOffset offset) { 204 return new OffsetTime(LocalTime.of(hour, minute, second, nanoOfSecond), offset); 205 } 206 207 //----------------------------------------------------------------------- 208 /** 209 * Obtains an instance of {@code OffsetTime} from an {@code Instant} and zone ID. 210 * !(p) 211 * This creates an offset time with the same instant as that specified. 212 * Finding the offset from UTC/Greenwich is simple as there is only one valid 213 * offset for each instant. 214 * !(p) 215 * The date component of the instant is dropped during the conversion. 216 * This means that the conversion can never fail due to the instant being 217 * _out of the valid range of dates. 218 * 219 * @param instant the instant to create the time from, not null 220 * @param zone the time-zone, which may be an offset, not null 221 * @return the offset time, not null 222 */ 223 static OffsetTime ofInstant(Instant instant, ZoneId zone) { 224 assert(instant, "instant"); 225 assert(zone, "zone"); 226 ZoneRules rules = zone.getRules(); 227 ZoneOffset offset = rules.getOffset(instant); 228 long localSecond = instant.getEpochSecond() + offset.getTotalSeconds(); // overflow caught later 229 int secsOfDay = MathHelper.floorMod(localSecond, LocalTime.SECONDS_PER_DAY); 230 LocalTime time = LocalTime.ofNanoOfDay(secsOfDay * LocalTime.NANOS_PER_SECOND + instant.getNano()); 231 return new OffsetTime(time, offset); 232 } 233 234 //----------------------------------------------------------------------- 235 /** 236 * Obtains an instance of {@code OffsetTime} from a temporal object. 237 * !(p) 238 * This obtains an offset time based on the specified temporal. 239 * A {@code TemporalAccessor} represents an arbitrary set of date and time information, 240 * which this factory converts to an instance of {@code OffsetTime}. 241 * !(p) 242 * The conversion extracts and combines the {@code ZoneOffset} and the 243 * {@code LocalTime} from the temporal object. 244 * Implementations are permitted to perform optimizations such as accessing 245 * those fields that are equivalent to the relevant objects. 246 * !(p) 247 * This method matches the signature of the functional interface {@link TemporalQuery} 248 * allowing it to be used as a query via method reference, {@code OffsetTime::from}. 249 * 250 * @param temporal the temporal object to convert, not null 251 * @return the offset time, not null 252 * @throws DateTimeException if unable to convert to an {@code OffsetTime} 253 */ 254 static OffsetTime from(TemporalAccessor temporal) { 255 if (cast(OffsetTime)(temporal) !is null) { 256 return cast(OffsetTime) temporal; 257 } 258 try { 259 LocalTime time = LocalTime.from(temporal); 260 ZoneOffset offset = ZoneOffset.from(temporal); 261 return new OffsetTime(time, offset); 262 } catch (DateTimeException ex) { 263 throw new DateTimeException("Unable to obtain OffsetTime from TemporalAccessor: " ~ 264 typeid(temporal).name ~ " of type " ~ typeid(temporal).stringof, ex); 265 } 266 } 267 268 //----------------------------------------------------------------------- 269 /** 270 * Obtains an instance of {@code OffsetTime} from a text string such as {@code 10:15:30+01:00}. 271 * !(p) 272 * The string must represent a valid time and is parsed using 273 * {@link hunt.time.format.DateTimeFormatter#ISO_OFFSET_TIME}. 274 * 275 * @param text the text to parse such as "10:15:30+01:00", not null 276 * @return the parsed local time, not null 277 * @throws DateTimeParseException if the text cannot be parsed 278 */ 279 // static OffsetTime parse(string text) { 280 // return parse(text, DateTimeFormatter.ISO_OFFSET_TIME); 281 // } 282 283 /** 284 * Obtains an instance of {@code OffsetTime} from a text string using a specific formatter. 285 * !(p) 286 * The text is parsed using the formatter, returning a time. 287 * 288 * @param text the text to parse, not null 289 * @param formatter the formatter to use, not null 290 * @return the parsed offset time, not null 291 * @throws DateTimeParseException if the text cannot be parsed 292 */ 293 // static OffsetTime parse(string text, DateTimeFormatter formatter) { 294 // assert(formatter, "formatter"); 295 // return formatter.parse(text, new class TemporalQuery!OffsetTime{ 296 // OffsetTime queryFrom(TemporalAccessor temporal) 297 // { 298 // if (cast(OffsetTime)(temporal) !is null) { 299 // return cast(OffsetTime) temporal; 300 // } 301 // try { 302 // LocalTime time = LocalTime.from(temporal); 303 // ZoneOffset offset = ZoneOffset.from(temporal); 304 // return new OffsetTime(time, offset); 305 // } catch (DateTimeException ex) { 306 // throw new DateTimeException("Unable to obtain OffsetTime from TemporalAccessor: " ~ 307 // typeid(temporal).name ~ " of type " ~ typeid(temporal).stringof, ex); 308 // } 309 // } 310 // }); 311 // } 312 313 //----------------------------------------------------------------------- 314 /** 315 * Constructor. 316 * 317 * @param time the local time, not null 318 * @param offset the zone offset, not null 319 */ 320 private this(LocalTime time, ZoneOffset offset) { 321 this.time = time; 322 this.offset = offset; 323 } 324 325 /** 326 * Returns a new time based on this one, returning {@code this} where possible. 327 * 328 * @param time the time to create with, not null 329 * @param offset the zone offset to create with, not null 330 */ 331 private OffsetTime _with(LocalTime time, ZoneOffset offset) { 332 if (this.time == time && this.offset == (offset)) { 333 return this; 334 } 335 return new OffsetTime(time, offset); 336 } 337 338 //----------------------------------------------------------------------- 339 /** 340 * Checks if the specified field is supported. 341 * !(p) 342 * This checks if this time can be queried for the specified field. 343 * If false, then calling the {@link #range(TemporalField) range}, 344 * {@link #get(TemporalField) get} and {@link #_with(TemporalField, long)} 345 * methods will throw an exception. 346 * !(p) 347 * If the field is a {@link ChronoField} then the query is implemented here. 348 * The supported fields are: 349 * !(ul) 350 * !(li){@code NANO_OF_SECOND} 351 * !(li){@code NANO_OF_DAY} 352 * !(li){@code MICRO_OF_SECOND} 353 * !(li){@code MICRO_OF_DAY} 354 * !(li){@code MILLI_OF_SECOND} 355 * !(li){@code MILLI_OF_DAY} 356 * !(li){@code SECOND_OF_MINUTE} 357 * !(li){@code SECOND_OF_DAY} 358 * !(li){@code MINUTE_OF_HOUR} 359 * !(li){@code MINUTE_OF_DAY} 360 * !(li){@code HOUR_OF_AMPM} 361 * !(li){@code CLOCK_HOUR_OF_AMPM} 362 * !(li){@code HOUR_OF_DAY} 363 * !(li){@code CLOCK_HOUR_OF_DAY} 364 * !(li){@code AMPM_OF_DAY} 365 * !(li){@code OFFSET_SECONDS} 366 * </ul> 367 * All other {@code ChronoField} instances will return false. 368 * !(p) 369 * If the field is not a {@code ChronoField}, then the result of this method 370 * is obtained by invoking {@code TemporalField.isSupportedBy(TemporalAccessor)} 371 * passing {@code this} as the argument. 372 * Whether the field is supported is determined by the field. 373 * 374 * @param field the field to check, null returns false 375 * @return true if the field is supported on this time, false if not 376 */ 377 override 378 bool isSupported(TemporalField field) { 379 if (cast(ChronoField)(field) !is null) { 380 return field.isTimeBased() || field == ChronoField.OFFSET_SECONDS; 381 } 382 return field !is null && field.isSupportedBy(this); 383 } 384 385 /** 386 * Checks if the specified unit is supported. 387 * !(p) 388 * This checks if the specified unit can be added to, or subtracted from, this offset-time. 389 * If false, then calling the {@link #plus(long, TemporalUnit)} and 390 * {@link #minus(long, TemporalUnit) minus} methods will throw an exception. 391 * !(p) 392 * If the unit is a {@link ChronoUnit} then the query is implemented here. 393 * The supported units are: 394 * !(ul) 395 * !(li){@code NANOS} 396 * !(li){@code MICROS} 397 * !(li){@code MILLIS} 398 * !(li){@code SECONDS} 399 * !(li){@code MINUTES} 400 * !(li){@code HOURS} 401 * !(li){@code HALF_DAYS} 402 * </ul> 403 * All other {@code ChronoUnit} instances will return false. 404 * !(p) 405 * If the unit is not a {@code ChronoUnit}, then the result of this method 406 * is obtained by invoking {@code TemporalUnit.isSupportedBy(Temporal)} 407 * passing {@code this} as the argument. 408 * Whether the unit is supported is determined by the unit. 409 * 410 * @param unit the unit to check, null returns false 411 * @return true if the unit can be added/subtracted, false if not 412 */ 413 override // override for Javadoc 414 bool isSupported(TemporalUnit unit) { 415 if (cast(ChronoUnit)(unit) !is null) { 416 return unit.isTimeBased(); 417 } 418 return unit !is null && unit.isSupportedBy(this); 419 } 420 421 //----------------------------------------------------------------------- 422 /** 423 * Gets the range of valid values for the specified field. 424 * !(p) 425 * The range object expresses the minimum and maximum valid values for a field. 426 * This time is used to enhance the accuracy of the returned range. 427 * If it is not possible to return the range, because the field is not supported 428 * or for some other reason, an exception is thrown. 429 * !(p) 430 * If the field is a {@link ChronoField} then the query is implemented here. 431 * The {@link #isSupported(TemporalField) supported fields} will return 432 * appropriate range instances. 433 * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. 434 * !(p) 435 * If the field is not a {@code ChronoField}, then the result of this method 436 * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} 437 * passing {@code this} as the argument. 438 * Whether the range can be obtained is determined by the field. 439 * 440 * @param field the field to query the range for, not null 441 * @return the range of valid values for the field, not null 442 * @throws DateTimeException if the range for the field cannot be obtained 443 * @throws UnsupportedTemporalTypeException if the field is not supported 444 */ 445 override 446 ValueRange range(TemporalField field) { 447 if (cast(ChronoField)(field) !is null) { 448 if (field == ChronoField.OFFSET_SECONDS) { 449 return field.range(); 450 } 451 return time.range(field); 452 } 453 return field.rangeRefinedBy(this); 454 } 455 456 /** 457 * Gets the value of the specified field from this time as an {@code int}. 458 * !(p) 459 * This queries this time for the value of the specified field. 460 * The returned value will always be within the valid range of values for the field. 461 * If it is not possible to return the value, because the field is not supported 462 * or for some other reason, an exception is thrown. 463 * !(p) 464 * If the field is a {@link ChronoField} then the query is implemented here. 465 * The {@link #isSupported(TemporalField) supported fields} will return valid 466 * values based on this time, except {@code NANO_OF_DAY} and {@code MICRO_OF_DAY} 467 * which are too large to fit _in an {@code int} and throw an {@code UnsupportedTemporalTypeException}. 468 * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. 469 * !(p) 470 * If the field is not a {@code ChronoField}, then the result of this method 471 * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} 472 * passing {@code this} as the argument. Whether the value can be obtained, 473 * and what the value represents, is determined by the field. 474 * 475 * @param field the field to get, not null 476 * @return the value for the field 477 * @throws DateTimeException if a value for the field cannot be obtained or 478 * the value is outside the range of valid values for the field 479 * @throws UnsupportedTemporalTypeException if the field is not supported or 480 * the range of values exceeds an {@code int} 481 * @throws ArithmeticException if numeric overflow occurs 482 */ 483 override // override for Javadoc 484 int get(TemporalField field) { 485 return /* Temporal. super.*/super_get(field); 486 } 487 int super_get(TemporalField field) { 488 ValueRange range = range(field); 489 if (range.isIntValue() == false) { 490 throw new UnsupportedTemporalTypeException("Invalid field " ~ typeid(field).name ~ " for get() method, use getLong() instead"); 491 } 492 long value = getLong(field); 493 if (range.isValidValue(value) == false) { 494 throw new DateTimeException("Invalid value for " ~ typeid(field).name ~ " (valid values " ~ range.toString ~ "): " ~ value.to!string); 495 } 496 return cast(int) value; 497 } 498 499 /** 500 * Gets the value of the specified field from this time as a {@code long}. 501 * !(p) 502 * This queries this time for the value of the specified field. 503 * If it is not possible to return the value, because the field is not supported 504 * or for some other reason, an exception is thrown. 505 * !(p) 506 * If the field is a {@link ChronoField} then the query is implemented here. 507 * The {@link #isSupported(TemporalField) supported fields} will return valid 508 * values based on this time. 509 * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. 510 * !(p) 511 * If the field is not a {@code ChronoField}, then the result of this method 512 * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} 513 * passing {@code this} as the argument. Whether the value can be obtained, 514 * and what the value represents, is determined by the field. 515 * 516 * @param field the field to get, not null 517 * @return the value for the field 518 * @throws DateTimeException if a value for the field cannot be obtained 519 * @throws UnsupportedTemporalTypeException if the field is not supported 520 * @throws ArithmeticException if numeric overflow occurs 521 */ 522 override 523 long getLong(TemporalField field) { 524 if (cast(ChronoField)(field) !is null) { 525 if (field == ChronoField.OFFSET_SECONDS) { 526 return offset.getTotalSeconds(); 527 } 528 return time.getLong(field); 529 } 530 return field.getFrom(this); 531 } 532 533 //----------------------------------------------------------------------- 534 /** 535 * Gets the zone offset, such as '+01:00'. 536 * !(p) 537 * This is the offset of the local time from UTC/Greenwich. 538 * 539 * @return the zone offset, not null 540 */ 541 ZoneOffset getOffset() { 542 return offset; 543 } 544 545 /** 546 * Returns a copy of this {@code OffsetTime} with the specified offset ensuring 547 * that the result has the same local time. 548 * !(p) 549 * This method returns an object with the same {@code LocalTime} and the specified {@code ZoneOffset}. 550 * No calculation is needed or performed. 551 * For example, if this time represents {@code 10:30+02:00} and the offset specified is 552 * {@code +03:00}, then this method will return {@code 10:30+03:00}. 553 * !(p) 554 * To take into account the difference between the offsets, and adjust the time fields, 555 * use {@link #withOffsetSameInstant}. 556 * !(p) 557 * This instance is immutable and unaffected by this method call. 558 * 559 * @param offset the zone offset to change to, not null 560 * @return an {@code OffsetTime} based on this time with the requested offset, not null 561 */ 562 OffsetTime withOffsetSameLocal(ZoneOffset offset) { 563 return offset !is null && offset == (this.offset) ? this : new OffsetTime(time, offset); 564 } 565 566 /** 567 * Returns a copy of this {@code OffsetTime} with the specified offset ensuring 568 * that the result is at the same instant on an implied day. 569 * !(p) 570 * This method returns an object with the specified {@code ZoneOffset} and a {@code LocalTime} 571 * adjusted by the difference between the two offsets. 572 * This will result _in the old and new objects representing the same instant on an implied day. 573 * This is useful for finding the local time _in a different offset. 574 * For example, if this time represents {@code 10:30+02:00} and the offset specified is 575 * {@code +03:00}, then this method will return {@code 11:30+03:00}. 576 * !(p) 577 * To change the offset without adjusting the local time use {@link #withOffsetSameLocal}. 578 * !(p) 579 * This instance is immutable and unaffected by this method call. 580 * 581 * @param offset the zone offset to change to, not null 582 * @return an {@code OffsetTime} based on this time with the requested offset, not null 583 */ 584 OffsetTime withOffsetSameInstant(ZoneOffset offset) { 585 if (offset == (this.offset)) { 586 return this; 587 } 588 int difference = offset.getTotalSeconds() - this.offset.getTotalSeconds(); 589 LocalTime adjusted = time.plusSeconds(difference); 590 return new OffsetTime(adjusted, offset); 591 } 592 593 //----------------------------------------------------------------------- 594 /** 595 * Gets the {@code LocalTime} part of this date-time. 596 * !(p) 597 * This returns a {@code LocalTime} with the same hour, minute, second and 598 * nanosecond as this date-time. 599 * 600 * @return the time part of this date-time, not null 601 */ 602 LocalTime toLocalTime() { 603 return time; 604 } 605 606 //----------------------------------------------------------------------- 607 /** 608 * Gets the hour-of-day field. 609 * 610 * @return the hour-of-day, from 0 to 23 611 */ 612 int getHour() { 613 return time.getHour(); 614 } 615 616 /** 617 * Gets the minute-of-hour field. 618 * 619 * @return the minute-of-hour, from 0 to 59 620 */ 621 int getMinute() { 622 return time.getMinute(); 623 } 624 625 /** 626 * Gets the second-of-minute field. 627 * 628 * @return the second-of-minute, from 0 to 59 629 */ 630 int getSecond() { 631 return time.getSecond(); 632 } 633 634 /** 635 * Gets the nano-of-second field. 636 * 637 * @return the nano-of-second, from 0 to 999,999,999 638 */ 639 int getNano() { 640 return time.getNano(); 641 } 642 643 //----------------------------------------------------------------------- 644 /** 645 * Returns an adjusted copy of this time. 646 * !(p) 647 * This returns an {@code OffsetTime}, based on this one, with the time adjusted. 648 * The adjustment takes place using the specified adjuster strategy object. 649 * Read the documentation of the adjuster to understand what adjustment will be made. 650 * !(p) 651 * A simple adjuster might simply set the one of the fields, such as the hour field. 652 * A more complex adjuster might set the time to the last hour of the day. 653 * !(p) 654 * The classes {@link LocalTime} and {@link ZoneOffset} implement {@code TemporalAdjuster}, 655 * thus this method can be used to change the time or offset: 656 * !(pre) 657 * result = offsetTime._with(time); 658 * result = offsetTime._with(offset); 659 * </pre> 660 * !(p) 661 * The result of this method is obtained by invoking the 662 * {@link TemporalAdjuster#adjustInto(Temporal)} method on the 663 * specified adjuster passing {@code this} as the argument. 664 * !(p) 665 * This instance is immutable and unaffected by this method call. 666 * 667 * @param adjuster the adjuster to use, not null 668 * @return an {@code OffsetTime} based on {@code this} with the adjustment made, not null 669 * @throws DateTimeException if the adjustment cannot be made 670 * @throws ArithmeticException if numeric overflow occurs 671 */ 672 override 673 OffsetTime _with(TemporalAdjuster adjuster) { 674 // optimizations 675 if (cast(LocalTime)(adjuster) !is null) { 676 return _with(cast(LocalTime) adjuster, offset); 677 } else if (cast(ZoneOffset)(adjuster) !is null) { 678 return _with(time, cast(ZoneOffset) adjuster); 679 } else if (cast(OffsetTime)(adjuster) !is null) { 680 return cast(OffsetTime) adjuster; 681 } 682 return cast(OffsetTime) adjuster.adjustInto(this); 683 } 684 685 /** 686 * Returns a copy of this time with the specified field set to a new value. 687 * !(p) 688 * This returns an {@code OffsetTime}, based on this one, with the value 689 * for the specified field changed. 690 * This can be used to change any supported field, such as the hour, minute or second. 691 * If it is not possible to set the value, because the field is not supported or for 692 * some other reason, an exception is thrown. 693 * !(p) 694 * If the field is a {@link ChronoField} then the adjustment is implemented here. 695 * !(p) 696 * The {@code OFFSET_SECONDS} field will return a time with the specified offset. 697 * The local time is unaltered. If the new offset value is outside the valid range 698 * then a {@code DateTimeException} will be thrown. 699 * !(p) 700 * The other {@link #isSupported(TemporalField) supported fields} will behave as per 701 * the matching method on {@link LocalTime#_with(TemporalField, long)} LocalTime}. 702 * In this case, the offset is not part of the calculation and will be unchanged. 703 * !(p) 704 * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. 705 * !(p) 706 * If the field is not a {@code ChronoField}, then the result of this method 707 * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)} 708 * passing {@code this} as the argument. In this case, the field determines 709 * whether and how to adjust the instant. 710 * !(p) 711 * This instance is immutable and unaffected by this method call. 712 * 713 * @param field the field to set _in the result, not null 714 * @param newValue the new value of the field _in the result 715 * @return an {@code OffsetTime} based on {@code this} with the specified field set, not null 716 * @throws DateTimeException if the field cannot be set 717 * @throws UnsupportedTemporalTypeException if the field is not supported 718 * @throws ArithmeticException if numeric overflow occurs 719 */ 720 override 721 OffsetTime _with(TemporalField field, long newValue) { 722 if (cast(ChronoField)(field) !is null) { 723 if (field == ChronoField.OFFSET_SECONDS) { 724 ChronoField f = cast(ChronoField) field; 725 return _with(time, ZoneOffset.ofTotalSeconds(f.checkValidIntValue(newValue))); 726 } 727 return _with(time._with(field, newValue), offset); 728 } 729 return cast(OffsetTime)(field.adjustInto(this, newValue)); 730 } 731 732 //----------------------------------------------------------------------- 733 /** 734 * Returns a copy of this {@code OffsetTime} with the hour-of-day altered. 735 * !(p) 736 * The offset does not affect the calculation and will be the same _in the result. 737 * !(p) 738 * This instance is immutable and unaffected by this method call. 739 * 740 * @param hour the hour-of-day to set _in the result, from 0 to 23 741 * @return an {@code OffsetTime} based on this time with the requested hour, not null 742 * @throws DateTimeException if the hour value is invalid 743 */ 744 OffsetTime withHour(int hour) { 745 return _with(time.withHour(hour), offset); 746 } 747 748 /** 749 * Returns a copy of this {@code OffsetTime} with the minute-of-hour altered. 750 * !(p) 751 * The offset does not affect the calculation and will be the same _in the result. 752 * !(p) 753 * This instance is immutable and unaffected by this method call. 754 * 755 * @param minute the minute-of-hour to set _in the result, from 0 to 59 756 * @return an {@code OffsetTime} based on this time with the requested minute, not null 757 * @throws DateTimeException if the minute value is invalid 758 */ 759 OffsetTime withMinute(int minute) { 760 return _with(time.withMinute(minute), offset); 761 } 762 763 /** 764 * Returns a copy of this {@code OffsetTime} with the second-of-minute altered. 765 * !(p) 766 * The offset does not affect the calculation and will be the same _in the result. 767 * !(p) 768 * This instance is immutable and unaffected by this method call. 769 * 770 * @param second the second-of-minute to set _in the result, from 0 to 59 771 * @return an {@code OffsetTime} based on this time with the requested second, not null 772 * @throws DateTimeException if the second value is invalid 773 */ 774 OffsetTime withSecond(int second) { 775 return _with(time.withSecond(second), offset); 776 } 777 778 /** 779 * Returns a copy of this {@code OffsetTime} with the nano-of-second altered. 780 * !(p) 781 * The offset does not affect the calculation and will be the same _in the result. 782 * !(p) 783 * This instance is immutable and unaffected by this method call. 784 * 785 * @param nanoOfSecond the nano-of-second to set _in the result, from 0 to 999,999,999 786 * @return an {@code OffsetTime} based on this time with the requested nanosecond, not null 787 * @throws DateTimeException if the nanos value is invalid 788 */ 789 OffsetTime withNano(int nanoOfSecond) { 790 return _with(time.withNano(nanoOfSecond), offset); 791 } 792 793 //----------------------------------------------------------------------- 794 /** 795 * Returns a copy of this {@code OffsetTime} with the time truncated. 796 * !(p) 797 * Truncation returns a copy of the original time with fields 798 * smaller than the specified unit set to zero. 799 * For example, truncating with the {@link ChronoUnit#MINUTES minutes} unit 800 * will set the second-of-minute and nano-of-second field to zero. 801 * !(p) 802 * The unit must have a {@linkplain TemporalUnit#getDuration() duration} 803 * that divides into the length of a standard day without remainder. 804 * This includes all supplied time units on {@link ChronoUnit} and 805 * {@link ChronoUnit#DAYS DAYS}. Other units throw an exception. 806 * !(p) 807 * The offset does not affect the calculation and will be the same _in the result. 808 * !(p) 809 * This instance is immutable and unaffected by this method call. 810 * 811 * @param unit the unit to truncate to, not null 812 * @return an {@code OffsetTime} based on this time with the time truncated, not null 813 * @throws DateTimeException if unable to truncate 814 * @throws UnsupportedTemporalTypeException if the unit is not supported 815 */ 816 OffsetTime truncatedTo(TemporalUnit unit) { 817 return _with(time.truncatedTo(unit), offset); 818 } 819 820 //----------------------------------------------------------------------- 821 /** 822 * Returns a copy of this time with the specified amount added. 823 * !(p) 824 * This returns an {@code OffsetTime}, based on this one, with the specified amount added. 825 * The amount is typically {@link Duration} but may be any other type implementing 826 * the {@link TemporalAmount} interface. 827 * !(p) 828 * The calculation is delegated to the amount object by calling 829 * {@link TemporalAmount#addTo(Temporal)}. The amount implementation is free 830 * to implement the addition _in any way it wishes, however it typically 831 * calls back to {@link #plus(long, TemporalUnit)}. Consult the documentation 832 * of the amount implementation to determine if it can be successfully added. 833 * !(p) 834 * This instance is immutable and unaffected by this method call. 835 * 836 * @param amountToAdd the amount to add, not null 837 * @return an {@code OffsetTime} based on this time with the addition made, not null 838 * @throws DateTimeException if the addition cannot be made 839 * @throws ArithmeticException if numeric overflow occurs 840 */ 841 override 842 OffsetTime plus(TemporalAmount amountToAdd) { 843 return cast(OffsetTime) amountToAdd.addTo(this); 844 } 845 846 /** 847 * Returns a copy of this time with the specified amount added. 848 * !(p) 849 * This returns an {@code OffsetTime}, based on this one, with the amount 850 * _in terms of the unit added. If it is not possible to add the amount, because the 851 * unit is not supported or for some other reason, an exception is thrown. 852 * !(p) 853 * If the field is a {@link ChronoUnit} then the addition is implemented by 854 * {@link LocalTime#plus(long, TemporalUnit)}. 855 * The offset is not part of the calculation and will be unchanged _in the result. 856 * !(p) 857 * If the field is not a {@code ChronoUnit}, then the result of this method 858 * is obtained by invoking {@code TemporalUnit.addTo(Temporal, long)} 859 * passing {@code this} as the argument. In this case, the unit determines 860 * whether and how to perform the addition. 861 * !(p) 862 * This instance is immutable and unaffected by this method call. 863 * 864 * @param amountToAdd the amount of the unit to add to the result, may be negative 865 * @param unit the unit of the amount to add, not null 866 * @return an {@code OffsetTime} based on this time with the specified amount added, not null 867 * @throws DateTimeException if the addition cannot be made 868 * @throws UnsupportedTemporalTypeException if the unit is not supported 869 * @throws ArithmeticException if numeric overflow occurs 870 */ 871 override 872 OffsetTime plus(long amountToAdd, TemporalUnit unit) { 873 if (cast(ChronoUnit)(unit) !is null) { 874 return _with(time.plus(amountToAdd, unit), offset); 875 } 876 return cast(OffsetTime)(unit.addTo(this, amountToAdd)); 877 } 878 879 //----------------------------------------------------------------------- 880 /** 881 * Returns a copy of this {@code OffsetTime} with the specified number of hours added. 882 * !(p) 883 * This adds the specified number of hours to this time, returning a new time. 884 * The calculation wraps around midnight. 885 * !(p) 886 * This instance is immutable and unaffected by this method call. 887 * 888 * @param hours the hours to add, may be negative 889 * @return an {@code OffsetTime} based on this time with the hours added, not null 890 */ 891 OffsetTime plusHours(long hours) { 892 return _with(time.plusHours(hours), offset); 893 } 894 895 /** 896 * Returns a copy of this {@code OffsetTime} with the specified number of minutes added. 897 * !(p) 898 * This adds the specified number of minutes to this time, returning a new time. 899 * The calculation wraps around midnight. 900 * !(p) 901 * This instance is immutable and unaffected by this method call. 902 * 903 * @param minutes the minutes to add, may be negative 904 * @return an {@code OffsetTime} based on this time with the minutes added, not null 905 */ 906 OffsetTime plusMinutes(long minutes) { 907 return _with(time.plusMinutes(minutes), offset); 908 } 909 910 /** 911 * Returns a copy of this {@code OffsetTime} with the specified number of seconds added. 912 * !(p) 913 * This adds the specified number of seconds to this time, returning a new time. 914 * The calculation wraps around midnight. 915 * !(p) 916 * This instance is immutable and unaffected by this method call. 917 * 918 * @param seconds the seconds to add, may be negative 919 * @return an {@code OffsetTime} based on this time with the seconds added, not null 920 */ 921 OffsetTime plusSeconds(long seconds) { 922 return _with(time.plusSeconds(seconds), offset); 923 } 924 925 /** 926 * Returns a copy of this {@code OffsetTime} with the specified number of nanoseconds added. 927 * !(p) 928 * This adds the specified number of nanoseconds to this time, returning a new time. 929 * The calculation wraps around midnight. 930 * !(p) 931 * This instance is immutable and unaffected by this method call. 932 * 933 * @param nanos the nanos to add, may be negative 934 * @return an {@code OffsetTime} based on this time with the nanoseconds added, not null 935 */ 936 OffsetTime plusNanos(long nanos) { 937 return _with(time.plusNanos(nanos), offset); 938 } 939 940 //----------------------------------------------------------------------- 941 /** 942 * Returns a copy of this time with the specified amount subtracted. 943 * !(p) 944 * This returns an {@code OffsetTime}, based on this one, with the specified amount subtracted. 945 * The amount is typically {@link Duration} but may be any other type implementing 946 * the {@link TemporalAmount} interface. 947 * !(p) 948 * The calculation is delegated to the amount object by calling 949 * {@link TemporalAmount#subtractFrom(Temporal)}. The amount implementation is free 950 * to implement the subtraction _in any way it wishes, however it typically 951 * calls back to {@link #minus(long, TemporalUnit)}. Consult the documentation 952 * of the amount implementation to determine if it can be successfully subtracted. 953 * !(p) 954 * This instance is immutable and unaffected by this method call. 955 * 956 * @param amountToSubtract the amount to subtract, not null 957 * @return an {@code OffsetTime} based on this time with the subtraction made, not null 958 * @throws DateTimeException if the subtraction cannot be made 959 * @throws ArithmeticException if numeric overflow occurs 960 */ 961 override 962 OffsetTime minus(TemporalAmount amountToSubtract) { 963 return cast(OffsetTime) amountToSubtract.subtractFrom(this); 964 } 965 966 /** 967 * Returns a copy of this time with the specified amount subtracted. 968 * !(p) 969 * This returns an {@code OffsetTime}, based on this one, with the amount 970 * _in terms of the unit subtracted. If it is not possible to subtract the amount, 971 * because the unit is not supported or for some other reason, an exception is thrown. 972 * !(p) 973 * This method is equivalent to {@link #plus(long, TemporalUnit)} with the amount negated. 974 * See that method for a full description of how addition, and thus subtraction, works. 975 * !(p) 976 * This instance is immutable and unaffected by this method call. 977 * 978 * @param amountToSubtract the amount of the unit to subtract from the result, may be negative 979 * @param unit the unit of the amount to subtract, not null 980 * @return an {@code OffsetTime} based on this time with the specified amount subtracted, not null 981 * @throws DateTimeException if the subtraction cannot be made 982 * @throws UnsupportedTemporalTypeException if the unit is not supported 983 * @throws ArithmeticException if numeric overflow occurs 984 */ 985 override 986 OffsetTime minus(long amountToSubtract, TemporalUnit unit) { 987 return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit)); 988 } 989 990 //----------------------------------------------------------------------- 991 /** 992 * Returns a copy of this {@code OffsetTime} with the specified number of hours subtracted. 993 * !(p) 994 * This subtracts the specified number of hours from this time, returning a new time. 995 * The calculation wraps around midnight. 996 * !(p) 997 * This instance is immutable and unaffected by this method call. 998 * 999 * @param hours the hours to subtract, may be negative 1000 * @return an {@code OffsetTime} based on this time with the hours subtracted, not null 1001 */ 1002 OffsetTime minusHours(long hours) { 1003 return _with(time.minusHours(hours), offset); 1004 } 1005 1006 /** 1007 * Returns a copy of this {@code OffsetTime} with the specified number of minutes subtracted. 1008 * !(p) 1009 * This subtracts the specified number of minutes from this time, returning a new time. 1010 * The calculation wraps around midnight. 1011 * !(p) 1012 * This instance is immutable and unaffected by this method call. 1013 * 1014 * @param minutes the minutes to subtract, may be negative 1015 * @return an {@code OffsetTime} based on this time with the minutes subtracted, not null 1016 */ 1017 OffsetTime minusMinutes(long minutes) { 1018 return _with(time.minusMinutes(minutes), offset); 1019 } 1020 1021 /** 1022 * Returns a copy of this {@code OffsetTime} with the specified number of seconds subtracted. 1023 * !(p) 1024 * This subtracts the specified number of seconds from this time, returning a new time. 1025 * The calculation wraps around midnight. 1026 * !(p) 1027 * This instance is immutable and unaffected by this method call. 1028 * 1029 * @param seconds the seconds to subtract, may be negative 1030 * @return an {@code OffsetTime} based on this time with the seconds subtracted, not null 1031 */ 1032 OffsetTime minusSeconds(long seconds) { 1033 return _with(time.minusSeconds(seconds), offset); 1034 } 1035 1036 /** 1037 * Returns a copy of this {@code OffsetTime} with the specified number of nanoseconds subtracted. 1038 * !(p) 1039 * This subtracts the specified number of nanoseconds from this time, returning a new time. 1040 * The calculation wraps around midnight. 1041 * !(p) 1042 * This instance is immutable and unaffected by this method call. 1043 * 1044 * @param nanos the nanos to subtract, may be negative 1045 * @return an {@code OffsetTime} based on this time with the nanoseconds subtracted, not null 1046 */ 1047 OffsetTime minusNanos(long nanos) { 1048 return _with(time.minusNanos(nanos), offset); 1049 } 1050 1051 //----------------------------------------------------------------------- 1052 /** 1053 * Queries this time using the specified query. 1054 * !(p) 1055 * This queries this time using the specified query strategy object. 1056 * The {@code TemporalQuery} object defines the logic to be used to 1057 * obtain the result. Read the documentation of the query to understand 1058 * what the result of this method will be. 1059 * !(p) 1060 * The result of this method is obtained by invoking the 1061 * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the 1062 * specified query passing {@code this} as the argument. 1063 * 1064 * @param !(R) the type of the result 1065 * @param query the query to invoke, not null 1066 * @return the query result, null may be returned (defined by the query) 1067 * @throws DateTimeException if unable to query (defined by the query) 1068 * @throws ArithmeticException if numeric overflow occurs (defined by the query) 1069 */ 1070 /*@SuppressWarnings("unchecked")*/ 1071 // override 1072 R query(R)(TemporalQuery!(R) query) { 1073 if (query == TemporalQueries.offset() || query == TemporalQueries.zone()) { 1074 return cast(R) offset; 1075 } else if (((query == TemporalQueries.zoneId()) | (query == TemporalQueries.chronology())) || query == TemporalQueries.localDate()) { 1076 return null; 1077 } else if (query == TemporalQueries.localTime()) { 1078 return cast(R) time; 1079 } else if (query == TemporalQueries.precision()) { 1080 return cast(R) (ChronoUnit.NANOS); 1081 } 1082 // inline TemporalAccessor.super.query(query) as an optimization 1083 // non-JDK classes are not permitted to make this optimization 1084 return query.queryFrom(this); 1085 } 1086 1087 /** 1088 * Adjusts the specified temporal object to have the same offset and time 1089 * as this object. 1090 * !(p) 1091 * This returns a temporal object of the same observable type as the input 1092 * with the offset and time changed to be the same as this. 1093 * !(p) 1094 * The adjustment is equivalent to using {@link Temporal#_with(TemporalField, long)} 1095 * twice, passing {@link ChronoField#NANO_OF_DAY} and 1096 * {@link ChronoField#OFFSET_SECONDS} as the fields. 1097 * !(p) 1098 * In most cases, it is clearer to reverse the calling pattern by using 1099 * {@link Temporal#_with(TemporalAdjuster)}: 1100 * !(pre) 1101 * // these two lines are equivalent, but the second approach is recommended 1102 * temporal = thisOffsetTime.adjustInto(temporal); 1103 * temporal = temporal._with(thisOffsetTime); 1104 * </pre> 1105 * !(p) 1106 * This instance is immutable and unaffected by this method call. 1107 * 1108 * @param temporal the target object to be adjusted, not null 1109 * @return the adjusted object, not null 1110 * @throws DateTimeException if unable to make the adjustment 1111 * @throws ArithmeticException if numeric overflow occurs 1112 */ 1113 override 1114 Temporal adjustInto(Temporal temporal) { 1115 return temporal 1116 ._with(ChronoField.NANO_OF_DAY, time.toNanoOfDay()) 1117 ._with(ChronoField.OFFSET_SECONDS, offset.getTotalSeconds()); 1118 } 1119 1120 /** 1121 * Calculates the amount of time until another time _in terms of the specified unit. 1122 * !(p) 1123 * This calculates the amount of time between two {@code OffsetTime} 1124 * objects _in terms of a single {@code TemporalUnit}. 1125 * The start and end points are {@code this} and the specified time. 1126 * The result will be negative if the end is before the start. 1127 * For example, the amount _in hours between two times can be calculated 1128 * using {@code startTime.until(endTime, HOURS)}. 1129 * !(p) 1130 * The {@code Temporal} passed to this method is converted to a 1131 * {@code OffsetTime} using {@link #from(TemporalAccessor)}. 1132 * If the offset differs between the two times, then the specified 1133 * end time is normalized to have the same offset as this time. 1134 * !(p) 1135 * The calculation returns a whole number, representing the number of 1136 * complete units between the two times. 1137 * For example, the amount _in hours between 11:30Z and 13:29Z will only 1138 * be one hour as it is one minute short of two hours. 1139 * !(p) 1140 * There are two equivalent ways of using this method. 1141 * The first is to invoke this method. 1142 * The second is to use {@link TemporalUnit#between(Temporal, Temporal)}: 1143 * !(pre) 1144 * // these two lines are equivalent 1145 * amount = start.until(end, MINUTES); 1146 * amount = MINUTES.between(start, end); 1147 * </pre> 1148 * The choice should be made based on which makes the code more readable. 1149 * !(p) 1150 * The calculation is implemented _in this method for {@link ChronoUnit}. 1151 * The units {@code NANOS}, {@code MICROS}, {@code MILLIS}, {@code SECONDS}, 1152 * {@code MINUTES}, {@code HOURS} and {@code HALF_DAYS} are supported. 1153 * Other {@code ChronoUnit} values will throw an exception. 1154 * !(p) 1155 * If the unit is not a {@code ChronoUnit}, then the result of this method 1156 * is obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)} 1157 * passing {@code this} as the first argument and the converted input temporal 1158 * as the second argument. 1159 * !(p) 1160 * This instance is immutable and unaffected by this method call. 1161 * 1162 * @param endExclusive the end time, exclusive, which is converted to an {@code OffsetTime}, not null 1163 * @param unit the unit to measure the amount _in, not null 1164 * @return the amount of time between this time and the end time 1165 * @throws DateTimeException if the amount cannot be calculated, or the end 1166 * temporal cannot be converted to an {@code OffsetTime} 1167 * @throws UnsupportedTemporalTypeException if the unit is not supported 1168 * @throws ArithmeticException if numeric overflow occurs 1169 */ 1170 override 1171 long until(Temporal endExclusive, TemporalUnit unit) { 1172 OffsetTime end = OffsetTime.from(endExclusive); 1173 if (cast(ChronoUnit)(unit) !is null) { 1174 long nanosUntil = end.toEpochNano() - toEpochNano(); // no overflow 1175 auto f = cast(ChronoUnit) unit; 1176 { 1177 if( f == ChronoUnit.NANOS) return nanosUntil; 1178 if( f == ChronoUnit.MICROS) return nanosUntil / 1000; 1179 if( f == ChronoUnit.MILLIS) return nanosUntil / 1000_000; 1180 if( f == ChronoUnit.SECONDS) return nanosUntil / LocalTime.NANOS_PER_SECOND; 1181 if( f == ChronoUnit.MINUTES) return nanosUntil / LocalTime.NANOS_PER_MINUTE; 1182 if( f == ChronoUnit.HOURS) return nanosUntil / LocalTime.NANOS_PER_HOUR; 1183 if( f == ChronoUnit.HALF_DAYS) return nanosUntil / (12 * LocalTime.NANOS_PER_HOUR); 1184 } 1185 throw new UnsupportedTemporalTypeException("Unsupported unit : " ~ f.toString); 1186 } 1187 return unit.between(this, end); 1188 } 1189 1190 /** 1191 * Formats this time using the specified formatter. 1192 * !(p) 1193 * This time will be passed to the formatter to produce a string. 1194 * 1195 * @param formatter the formatter to use, not null 1196 * @return the formatted time string, not null 1197 * @throws DateTimeException if an error occurs during printing 1198 */ 1199 // string format(DateTimeFormatter formatter) { 1200 // assert(formatter, "formatter"); 1201 // return formatter.format(this); 1202 // } 1203 1204 //----------------------------------------------------------------------- 1205 /** 1206 * Combines this time with a date to create an {@code OffsetDateTime}. 1207 * !(p) 1208 * This returns an {@code OffsetDateTime} formed from this time and the specified date. 1209 * All possible combinations of date and time are valid. 1210 * 1211 * @param date the date to combine with, not null 1212 * @return the offset date-time formed from this time and the specified date, not null 1213 */ 1214 OffsetDateTime atDate(LocalDate date) { 1215 return OffsetDateTime.of(date, time, offset); 1216 } 1217 1218 //----------------------------------------------------------------------- 1219 /** 1220 * Converts this time to epoch nanos based on 1970-01-01Z. 1221 * 1222 * @return the epoch nanos value 1223 */ 1224 private long toEpochNano() { 1225 long nod = time.toNanoOfDay(); 1226 long offsetNanos = offset.getTotalSeconds() * LocalTime.NANOS_PER_SECOND; 1227 return nod - offsetNanos; 1228 } 1229 1230 /** 1231 * Converts this {@code OffsetTime} to the number of seconds since the epoch 1232 * of 1970-01-01T00:00:00Z. 1233 * !(p) 1234 * This combines this offset time with the specified date to calculate the 1235 * epoch-second value, which is the number of elapsed seconds from 1236 * 1970-01-01T00:00:00Z. 1237 * Instants on the time-line after the epoch are positive, earlier 1238 * are negative. 1239 * 1240 * @param date the localdate, not null 1241 * @return the number of seconds since the epoch of 1970-01-01T00:00:00Z, may be negative 1242 * @since 9 1243 */ 1244 long toEpochSecond(LocalDate date) { 1245 assert(date, "date"); 1246 long epochDay = date.toEpochDay(); 1247 long secs = epochDay * 86400 + time.toSecondOfDay(); 1248 secs -= offset.getTotalSeconds(); 1249 return secs; 1250 } 1251 1252 //----------------------------------------------------------------------- 1253 /** 1254 * Compares this {@code OffsetTime} to another time. 1255 * !(p) 1256 * The comparison is based first on the UTC equivalent instant, then on the local time. 1257 * It is "consistent with equals", as defined by {@link Comparable}. 1258 * !(p) 1259 * For example, the following is the comparator order: 1260 * !(ol) 1261 * !(li){@code 10:30+01:00}</li> 1262 * !(li){@code 11:00+01:00}</li> 1263 * !(li){@code 12:00+02:00}</li> 1264 * !(li){@code 11:30+01:00}</li> 1265 * !(li){@code 12:00+01:00}</li> 1266 * !(li){@code 12:30+01:00}</li> 1267 * </ol> 1268 * Values #2 and #3 represent the same instant on the time-line. 1269 * When two values represent the same instant, the local time is compared 1270 * to distinguish them. This step is needed to make the ordering 1271 * consistent with {@code equals()}. 1272 * !(p) 1273 * To compare the underlying local time of two {@code TemporalAccessor} instances, 1274 * use {@link ChronoField#NANO_OF_DAY} as a comparator. 1275 * 1276 * @param other the other time to compare to, not null 1277 * @return the comparator value, negative if less, positive if greater 1278 */ 1279 // override 1280 int compareTo(OffsetTime other) { 1281 if (offset == (other.offset)) { 1282 return time.compareTo(other.time); 1283 } 1284 int compare = compare(toEpochNano(), other.toEpochNano()); 1285 if (compare == 0) { 1286 compare = time.compareTo(other.time); 1287 } 1288 return compare; 1289 } 1290 1291 override int opCmp(OffsetTime other) { 1292 if (offset == (other.offset)) { 1293 return time.compareTo(other.time); 1294 } 1295 int compare = compare(toEpochNano(), other.toEpochNano()); 1296 if (compare == 0) { 1297 compare = time.compareTo(other.time); 1298 } 1299 return compare; 1300 } 1301 1302 //----------------------------------------------------------------------- 1303 /** 1304 * Checks if the instant of this {@code OffsetTime} is after that of the 1305 * specified time applying both times to a common date. 1306 * !(p) 1307 * This method differs from the comparison _in {@link #compareTo} _in that it 1308 * only compares the instant of the time. This is equivalent to converting both 1309 * times to an instant using the same date and comparing the instants. 1310 * 1311 * @param other the other time to compare to, not null 1312 * @return true if this is after the instant of the specified time 1313 */ 1314 bool isAfter(OffsetTime other) { 1315 return toEpochNano() > other.toEpochNano(); 1316 } 1317 1318 /** 1319 * Checks if the instant of this {@code OffsetTime} is before that of the 1320 * specified time applying both times to a common date. 1321 * !(p) 1322 * This method differs from the comparison _in {@link #compareTo} _in that it 1323 * only compares the instant of the time. This is equivalent to converting both 1324 * times to an instant using the same date and comparing the instants. 1325 * 1326 * @param other the other time to compare to, not null 1327 * @return true if this is before the instant of the specified time 1328 */ 1329 bool isBefore(OffsetTime other) { 1330 return toEpochNano() < other.toEpochNano(); 1331 } 1332 1333 /** 1334 * Checks if the instant of this {@code OffsetTime} is equal to that of the 1335 * specified time applying both times to a common date. 1336 * !(p) 1337 * This method differs from the comparison _in {@link #compareTo} and {@link #equals} 1338 * _in that it only compares the instant of the time. This is equivalent to converting both 1339 * times to an instant using the same date and comparing the instants. 1340 * 1341 * @param other the other time to compare to, not null 1342 * @return true if this is equal to the instant of the specified time 1343 */ 1344 bool isEqual(OffsetTime other) { 1345 return toEpochNano() == other.toEpochNano(); 1346 } 1347 1348 //----------------------------------------------------------------------- 1349 /** 1350 * Checks if this time is equal to another time. 1351 * !(p) 1352 * The comparison is based on the local-time and the offset. 1353 * To compare for the same instant on the time-line, use {@link #isEqual(OffsetTime)}. 1354 * !(p) 1355 * Only objects of type {@code OffsetTime} are compared, other types return false. 1356 * To compare the underlying local time of two {@code TemporalAccessor} instances, 1357 * use {@link ChronoField#NANO_OF_DAY} as a comparator. 1358 * 1359 * @param obj the object to check, null returns false 1360 * @return true if this is equal to the other time 1361 */ 1362 override 1363 bool opEquals(Object obj) { 1364 if (this is obj) { 1365 return true; 1366 } 1367 if (cast(OffsetTime)(obj) !is null) { 1368 OffsetTime other = cast(OffsetTime) obj; 1369 return time == (other.time) && offset == (other.offset); 1370 } 1371 return false; 1372 } 1373 1374 /** 1375 * A hash code for this time. 1376 * 1377 * @return a suitable hash code 1378 */ 1379 override 1380 size_t toHash() @trusted nothrow { 1381 return time.toHash() ^ offset.toHash(); 1382 } 1383 1384 //----------------------------------------------------------------------- 1385 /** 1386 * Outputs this time as a {@code string}, such as {@code 10:15:30+01:00}. 1387 * !(p) 1388 * The output will be one of the following ISO-8601 formats: 1389 * !(ul) 1390 * !(li){@code HH:mmXXXXX}</li> 1391 * !(li){@code HH:mm:ssXXXXX}</li> 1392 * !(li){@code HH:mm:ss.SSSXXXXX}</li> 1393 * !(li){@code HH:mm:ss.SSSSSSXXXXX}</li> 1394 * !(li){@code HH:mm:ss.SSSSSSSSSXXXXX}</li> 1395 * </ul> 1396 * The format used will be the shortest that outputs the full value of 1397 * the time where the omitted parts are implied to be zero. 1398 * 1399 * @return a string representation of this time, not null 1400 */ 1401 override 1402 string toString() { 1403 return time.toString() ~ offset.toString(); 1404 } 1405 1406 //----------------------------------------------------------------------- 1407 /** 1408 * Writes the object using a 1409 * <a href="{@docRoot}/serialized-form.html#hunt.time.Ser">dedicated serialized form</a>. 1410 * @serialData 1411 * !(pre) 1412 * _out.writeByte(9); // identifies an OffsetTime 1413 * // the <a href="{@docRoot}/serialized-form.html#hunt.time.LocalTime">time</a> excluding the one byte header 1414 * // the <a href="{@docRoot}/serialized-form.html#hunt.time.ZoneOffset">offset</a> excluding the one byte header 1415 * </pre> 1416 * 1417 * @return the instance of {@code Ser}, not null 1418 */ 1419 private Object writeReplace() { 1420 return new Ser(Ser.OFFSET_TIME_TYPE, this); 1421 } 1422 1423 /** 1424 * Defend against malicious streams. 1425 * 1426 * @param s the stream to read 1427 * @throws InvalidObjectException always 1428 */ 1429 ///@gxc 1430 // private void readObject(ObjectInputStream s) /*throws InvalidObjectException*/ { 1431 // throw new InvalidObjectException("Deserialization via serialization delegate"); 1432 // } 1433 1434 // void writeExternal(ObjectOutput _out) /*throws IOException*/ { 1435 // time.writeExternal(_out); 1436 // offset.writeExternal(_out); 1437 // } 1438 1439 // static OffsetTime readExternal(ObjectInput _in) /*throws IOException, ClassNotFoundException*/ { 1440 // LocalTime time = LocalTime.readExternal(_in); 1441 // ZoneOffset offset = ZoneOffset.readExternal(_in); 1442 // return OffsetTime.of(time, offset); 1443 // } 1444 1445 1446 // mixin SerializationMember!(typeof(this)); 1447 }