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.Clock; 13 14 import hunt.stream.Common; 15 import hunt.Exceptions; 16 import hunt.math.Helper; 17 import hunt.time.Constants; 18 import hunt.time.ZoneId; 19 import hunt.time.ZoneRegion; 20 import hunt.time.Duration; 21 import hunt.time.Instant; 22 import hunt.time.ZoneOffset; 23 import hunt.time.util.Common; 24 import hunt.util.DateTime; 25 26 import core.time : convert; 27 import std.conv; 28 import std.math; 29 30 import std.concurrency : initOnce; 31 32 /** 33 * A clock providing access to the current _instant, date and time using a time-zone. 34 * !(p) 35 * Instances of this class are used to find the current _instant, which can be 36 * interpreted using the stored time-zone to find the current date and time. 37 * As such, a clock can be used instead of {@link System#currentTimeMillis()} 38 * and {@link TimeZone#getDefault()}. 39 * !(p) 40 * Use of a {@code Clock} is optional. All key date-time classes also have a 41 * {@code now()} factory method that uses the system clock _in the default time zone. 42 * The primary purpose of this abstraction is to allow alternate clocks to be 43 * plugged _in as and when required. Applications use an object to obtain the 44 * current time rather than a static method. This can simplify testing. 45 * !(p) 46 * Best practice for applications is to pass a {@code Clock} into any method 47 * that requires the current _instant. A dependency injection framework is one 48 * way to achieve this: 49 * !(pre) 50 * class MyBean { 51 * private Clock clock; // dependency inject 52 * ... 53 * void process(LocalDate eventDate) { 54 * if (eventDate.isBefore(LocalDate.now(clock)) { 55 * ... 56 * } 57 * } 58 * } 59 * </pre> 60 * This approach allows an alternate clock, such as {@link #fixed(Instant, ZoneId) fixed} 61 * or {@link #_offset(Clock, Duration) _offset} to be used during testing. 62 * !(p) 63 * The {@code system} factory methods provide clocks based on the best available 64 * system clock This may use {@link System#currentTimeMillis()}, or a higher 65 * resolution clock if one is available. 66 * 67 * @implSpec 68 * This abstract class must be implemented with care to ensure other classes operate correctly. 69 * All implementations that can be instantiated must be final, immutable and thread-safe. 70 * !(p) 71 * The principal methods are defined to allow the throwing of an exception. 72 * In normal use, no exceptions will be thrown, however one possible implementation would be to 73 * obtain the time from a central time server across the network. Obviously, _in this case the 74 * lookup could fail, and so the method is permitted to throw an exception. 75 * !(p) 76 * The returned instants from {@code Clock} work on a time-scale that ignores leap seconds, 77 * as described _in {@link Instant}. If the implementation wraps a source that provides leap 78 * second information, then a mechanism should be used to "smooth" the leap second. 79 * The Java Time-Scale mandates the use of UTC-SLS, however clock implementations may choose 80 * how accurate they are with the time-scale so long as they document how they work. 81 * Implementations are therefore not required to actually perform the UTC-SLS slew or to 82 * otherwise be aware of leap seconds. 83 * !(p) 84 * Implementations should implement {@code Serializable} wherever possible and must 85 * document whether or not they do support serialization. 86 * 87 * @implNote 88 * The clock implementation provided here is based on the same underlying clock 89 * as {@link System#currentTimeMillis()}, but may have a precision finer than 90 * milliseconds if available. 91 * However, little to no guarantee is provided about the accuracy of the 92 * underlying clock. Applications requiring a more accurate clock must implement 93 * this abstract class themselves using a different external clock, such as an 94 * NTP server. 95 * 96 * @since 1.8 97 */ 98 abstract class Clock { 99 100 /** 101 * Obtains a clock that returns the current _instant using the best available 102 * system clock, converting to date and time using the UTC time-zone. 103 * !(p) 104 * This clock, rather than {@link #systemDefaultZone()}, should be used when 105 * you need the current _instant without the date or time. 106 * !(p) 107 * This clock is based on the best available system clock. 108 * This may use {@link System#currentTimeMillis()}, or a higher resolution 109 * clock if one is available. 110 * !(p) 111 * Conversion from _instant to date or time uses the {@linkplain ZoneOffset#UTC UTC time-zone}. 112 * !(p) 113 * The returned implementation is immutable, thread-safe and {@code Serializable}. 114 * It is equivalent to {@code system(ZoneOffset.UTC)}. 115 * 116 * @return a clock that uses the best available system clock _in the UTC zone, not null 117 */ 118 static Clock systemUTC() { 119 return SystemClock.UTC; 120 } 121 122 /** 123 * Obtains a clock that returns the current _instant using the best available 124 * system clock, converting to date and time using the default time-zone. 125 * !(p) 126 * This clock is based on the best available system clock. 127 * This may use {@link System#currentTimeMillis()}, or a higher resolution 128 * clock if one is available. 129 * !(p) 130 * Using this method hard codes a dependency to the default time-zone into your application. 131 * It is recommended to avoid this and use a specific time-zone whenever possible. 132 * The {@link #systemUTC() UTC clock} should be used when you need the current _instant 133 * without the date or time. 134 * !(p) 135 * The returned implementation is immutable, thread-safe and {@code Serializable}. 136 * It is equivalent to {@code system(ZoneId.systemDefault())}. 137 * 138 * @return a clock that uses the best available system clock _in the default zone, not null 139 * @see ZoneId#systemDefault() 140 */ 141 static Clock systemDefaultZone() { 142 // return new SystemClock(ZoneRegion.systemDefault()); 143 return SystemClock.DEFAULT(); 144 } 145 146 /** 147 * Obtains a clock that returns the current _instant using the best available 148 * system clock. 149 * !(p) 150 * This clock is based on the best available system clock. 151 * This may use {@link System#currentTimeMillis()}, or a higher resolution 152 * clock if one is available. 153 * !(p) 154 * Conversion from _instant to date or time uses the specified time-zone. 155 * !(p) 156 * The returned implementation is immutable, thread-safe and {@code Serializable}. 157 * 158 * @param zone the time-zone to use to convert the _instant to date-time, not null 159 * @return a clock that uses the best available system clock _in the specified zone, not null 160 */ 161 static Clock system(ZoneId zone) { 162 assert(zone, "zone"); 163 if (zone == ZoneOffset.UTC) { 164 return SystemClock.UTC; 165 } 166 return new SystemClock(zone); 167 } 168 169 //------------------------------------------------------------------------- 170 /** 171 * Obtains a clock that returns the current _instant ticking _in whole milliseconds 172 * using the best available system clock. 173 * !(p) 174 * This clock will always have the nano-of-second field truncated to milliseconds. 175 * This ensures that the visible time ticks _in whole milliseconds. 176 * The underlying clock is the best available system clock, equivalent to 177 * using {@link #system(ZoneId)}. 178 * !(p) 179 * Implementations may use a caching strategy for performance reasons. 180 * As such, it is possible that the start of the millisecond observed via this 181 * clock will be later than that observed directly via the underlying clock. 182 * !(p) 183 * The returned implementation is immutable, thread-safe and {@code Serializable}. 184 * It is equivalent to {@code tick(system(zone), Duration.ofMillis(1))}. 185 * 186 * @param zone the time-zone to use to convert the _instant to date-time, not null 187 * @return a clock that ticks _in whole milliseconds using the specified zone, not null 188 * @since 9 189 */ 190 static Clock tickMillis(ZoneId zone) { 191 return new TickClock(system(zone), TimeConstant.NANOS_PER_MILLI); 192 } 193 194 //------------------------------------------------------------------------- 195 /** 196 * Obtains a clock that returns the current _instant ticking _in whole seconds 197 * using the best available system clock. 198 * !(p) 199 * This clock will always have the nano-of-second field set to zero. 200 * This ensures that the visible time ticks _in whole seconds. 201 * The underlying clock is the best available system clock, equivalent to 202 * using {@link #system(ZoneId)}. 203 * !(p) 204 * Implementations may use a caching strategy for performance reasons. 205 * As such, it is possible that the start of the second observed via this 206 * clock will be later than that observed directly via the underlying clock. 207 * !(p) 208 * The returned implementation is immutable, thread-safe and {@code Serializable}. 209 * It is equivalent to {@code tick(system(zone), Duration.ofSeconds(1))}. 210 * 211 * @param zone the time-zone to use to convert the _instant to date-time, not null 212 * @return a clock that ticks _in whole seconds using the specified zone, not null 213 */ 214 static Clock tickSeconds(ZoneId zone) { 215 return new TickClock(system(zone), TimeConstant.NANOS_PER_SECOND); 216 } 217 218 /** 219 * Obtains a clock that returns the current _instant ticking _in whole minutes 220 * using the best available system clock. 221 * !(p) 222 * This clock will always have the nano-of-second and second-of-minute fields set to zero. 223 * This ensures that the visible time ticks _in whole minutes. 224 * The underlying clock is the best available system clock, equivalent to 225 * using {@link #system(ZoneId)}. 226 * !(p) 227 * Implementations may use a caching strategy for performance reasons. 228 * As such, it is possible that the start of the minute observed via this 229 * clock will be later than that observed directly via the underlying clock. 230 * !(p) 231 * The returned implementation is immutable, thread-safe and {@code Serializable}. 232 * It is equivalent to {@code tick(system(zone), Duration.ofMinutes(1))}. 233 * 234 * @param zone the time-zone to use to convert the _instant to date-time, not null 235 * @return a clock that ticks _in whole minutes using the specified zone, not null 236 */ 237 static Clock tickMinutes(ZoneId zone) { 238 return new TickClock(system(zone), TimeConstant.NANOS_PER_MINUTE); 239 } 240 241 /** 242 * Obtains a clock that returns instants from the specified clock truncated 243 * to the nearest occurrence of the specified duration. 244 * !(p) 245 * This clock will only tick as per the specified duration. Thus, if the duration 246 * is half a second, the clock will return instants truncated to the half second. 247 * !(p) 248 * The tick duration must be positive. If it has a part smaller than a whole 249 * millisecond, then the whole duration must divide into one second without 250 * leaving a remainder. All normal tick durations will match these criteria, 251 * including any multiple of hours, minutes, seconds and milliseconds, and 252 * sensible nanosecond durations, such as 20ns, 250,000ns and 500,000ns. 253 * !(p) 254 * A duration of zero or one nanosecond would have no truncation effect. 255 * Passing one of these will return the underlying clock. 256 * !(p) 257 * Implementations may use a caching strategy for performance reasons. 258 * As such, it is possible that the start of the requested duration observed 259 * via this clock will be later than that observed directly via the underlying clock. 260 * !(p) 261 * The returned implementation is immutable, thread-safe and {@code Serializable} 262 * providing that the base clock is. 263 * 264 * @param baseClock the base clock to base the ticking clock on, not null 265 * @param tickDuration the duration of each visible tick, not negative, not null 266 * @return a clock that ticks _in whole units of the duration, not null 267 * @throws IllegalArgumentException if the duration is negative, or has a 268 * part smaller than a whole millisecond such that the whole duration is not 269 * divisible into one second 270 * @throws ArithmeticException if the duration is too large to be represented as nanos 271 */ 272 static Clock tick(Clock baseClock, Duration tickDuration) { 273 assert(baseClock, "baseClock"); 274 assert(tickDuration, "tickDuration"); 275 if (tickDuration.isNegative()) { 276 throw new IllegalArgumentException("Tick duration must not be negative"); 277 } 278 long tickNanos = tickDuration.toNanos(); 279 if (tickNanos % 1000_000 == 0) { 280 // ok, no fraction of millisecond 281 } else if (1000_000_000 % tickNanos == 0) { 282 // ok, divides into one second without remainder 283 } else { 284 throw new IllegalArgumentException("Invalid tick duration"); 285 } 286 if (tickNanos <= 1) { 287 return baseClock; 288 } 289 return new TickClock(baseClock, tickNanos); 290 } 291 292 //----------------------------------------------------------------------- 293 /** 294 * Obtains a clock that always returns the same _instant. 295 * !(p) 296 * This clock simply returns the specified _instant. 297 * As such, it is not a clock _in the conventional sense. 298 * The main use case for this is _in testing, where the fixed clock ensures 299 * tests are not dependent on the current clock. 300 * !(p) 301 * The returned implementation is immutable, thread-safe and {@code Serializable}. 302 * 303 * @param fixedInstant the _instant to use as the clock, not null 304 * @param zone the time-zone to use to convert the _instant to date-time, not null 305 * @return a clock that always returns the same _instant, not null 306 */ 307 static Clock fixed(Instant fixedInstant, ZoneId zone) { 308 assert(fixedInstant, "fixedInstant"); 309 assert(zone, "zone"); 310 return new FixedClock(fixedInstant, zone); 311 } 312 313 //------------------------------------------------------------------------- 314 /** 315 * Obtains a clock that returns instants from the specified clock with the 316 * specified duration added 317 * !(p) 318 * This clock wraps another clock, returning instants that are later by the 319 * specified duration. If the duration is negative, the instants will be 320 * earlier than the current date and time. 321 * The main use case for this is to simulate running _in the future or _in the past. 322 * !(p) 323 * A duration of zero would have no offsetting effect. 324 * Passing zero will return the underlying clock. 325 * !(p) 326 * The returned implementation is immutable, thread-safe and {@code Serializable} 327 * providing that the base clock is. 328 * 329 * @param baseClock the base clock to add the duration to, not null 330 * @param offsetDuration the duration to add, not null 331 * @return a clock based on the base clock with the duration added, not null 332 */ 333 static Clock _offset(Clock baseClock, Duration offsetDuration) { 334 assert(baseClock, "baseClock"); 335 assert(offsetDuration, "offsetDuration"); 336 if (offsetDuration.opEquals(Duration.ZERO)) { 337 return baseClock; 338 } 339 return new OffsetClock(baseClock, offsetDuration); 340 } 341 342 //----------------------------------------------------------------------- 343 /** 344 * Constructor accessible by subclasses. 345 */ 346 protected this() { 347 } 348 349 //----------------------------------------------------------------------- 350 /** 351 * Gets the time-zone being used to create dates and times. 352 * !(p) 353 * A clock will typically obtain the current _instant and then convert that 354 * to a date or time using a time-zone. This method returns the time-zone used. 355 * 356 * @return the time-zone being used to interpret instants, not null 357 */ 358 abstract ZoneId getZone(); 359 360 /** 361 * Returns a copy of this clock with a different time-zone. 362 * !(p) 363 * A clock will typically obtain the current _instant and then convert that 364 * to a date or time using a time-zone. This method returns a clock with 365 * similar properties but using a different time-zone. 366 * 367 * @param zone the time-zone to change to, not null 368 * @return a clock based on this clock with the specified time-zone, not null 369 */ 370 abstract Clock withZone(ZoneId zone); 371 372 //------------------------------------------------------------------------- 373 /** 374 * Gets the current millisecond _instant of the clock. 375 * !(p) 376 * This returns the millisecond-based _instant, measured from 1970-01-01T00:00Z (UTC). 377 * This is equivalent to the definition of {@link System#currentTimeMillis()}. 378 * !(p) 379 * Most applications should avoid this method and use {@link Instant} to represent 380 * an _instant on the time-line rather than a raw millisecond value. 381 * This method is provided to allow the use of the clock _in high performance use cases 382 * where the creation of an object would be unacceptable. 383 * !(p) 384 * The default implementation currently calls {@link #_instant}. 385 * 386 * @return the current millisecond _instant from this clock, measured from 387 * the Java epoch of 1970-01-01T00:00Z (UTC), not null 388 * @throws DateTimeException if the _instant cannot be obtained, not thrown by most implementations 389 */ 390 long millis() { 391 return instant().toEpochMilli(); 392 } 393 394 //----------------------------------------------------------------------- 395 /** 396 * Gets the current _instant of the clock. 397 * !(p) 398 * This returns an _instant representing the current _instant as defined by the clock. 399 * 400 * @return the current _instant from this clock, not null 401 * @throws DateTimeException if the _instant cannot be obtained, not thrown by most implementations 402 */ 403 abstract Instant instant(); 404 405 //----------------------------------------------------------------------- 406 /** 407 * Checks if this clock is equal to another clock. 408 * !(p) 409 * Clocks should override this method to compare equals based on 410 * their state and to meet the contract of {@link Object#equals}. 411 * If not overridden, the behavior is defined by {@link Object#equals} 412 * 413 * @param obj the object to check, null returns false 414 * @return true if this is equal to the other clock 415 */ 416 override 417 bool opEquals(Object obj) { 418 return super.opEquals(obj); 419 } 420 421 /** 422 * A hash code for this clock. 423 * !(p) 424 * Clocks should override this method based on 425 * their state and to meet the contract of {@link Object#hashCode}. 426 * If not overridden, the behavior is defined by {@link Object#hashCode} 427 * 428 * @return a suitable hash code 429 */ 430 override 431 size_t toHash() @trusted nothrow { 432 return super.toHash(); 433 } 434 435 //----------------------------------------------------------------------- 436 /** 437 * Implementation of a clock that always returns the latest time from 438 * {@link System#currentTimeMillis()}. 439 */ 440 static final class SystemClock : Clock { // , Serializable 441 442 static long OFFSET_SEED() { 443 __gshared long _o; 444 // return initOnce!(_o)(DateTime.currentTimeMillis()/1000); 445 // FIXME: Needing refactor or cleanup -@zhangxueping at 4/15/2019, 1:15:39 PM 446 // 447 return initOnce!(_o)(DateTime.currentTimeMillis()/1000 - 1024); 448 } 449 450 static SystemClock UTC() { 451 __gshared SystemClock _UTC; 452 return initOnce!(_UTC)(new SystemClock(ZoneOffset.UTC)); 453 } 454 455 static SystemClock DEFAULT() { 456 __gshared SystemClock _DEFAULT; 457 return initOnce!(_DEFAULT)(new SystemClock(ZoneRegion.systemDefault())); 458 } 459 460 461 private ZoneId zone; 462 // We don't actually need a volatile here. 463 // We don't care if _offset is set or read concurrently by multiple 464 // threads - we just need a value which is 'recent enough' - _in other 465 // words something that has been updated at least once _in the last 466 // 2^32 secs (~136 years). And even if we by chance see an invalid 467 // _offset, the worst that can happen is that we will get a -1 value 468 // from getNanoTimeAdjustment, forcing us to update the _offset 469 // once again. 470 private long _offset; 471 472 this(ZoneId zone) { 473 this.zone = zone; 474 this._offset = OFFSET_SEED; 475 } 476 override 477 ZoneId getZone() { 478 return zone; 479 } 480 override 481 Clock withZone(ZoneId zone) { 482 if (zone.opEquals(this.zone)) { // intentional NPE 483 return this; 484 } 485 return new SystemClock(zone); 486 } 487 488 override 489 long millis() { 490 // System.currentTimeMillis() and VM.getNanoTimeAdjustment(_offset) 491 // use the same time source - System.currentTimeMillis() simply 492 // limits the resolution to milliseconds. 493 // So we take the faster path and call System.currentTimeMillis() 494 // directly - _in order to avoid the performance penalty of 495 // VM.getNanoTimeAdjustment(_offset) which is less efficient. 496 return DateTime.currentTimeMillis(); 497 } 498 499 override 500 Instant instant() { 501 // Take a local copy of _offset. _offset can be updated concurrently 502 // by other threads (even if we haven't made it volatile) so we will 503 // work with a local copy. 504 // long localOffset = _offset; 505 // long adjustment = VM.getNanoTimeAdjustment(localOffset); 506 507 // if (adjustment == -1) { 508 // // -1 is a sentinel value returned by VM.getNanoTimeAdjustment 509 // // when the _offset it is given is too far off the current UTC 510 // // time. In principle, this should not happen unless the 511 // // JVM has run for more than ~136 years (not likely) or 512 // // someone is fiddling with the system time, or the _offset is 513 // // by chance at 1ns _in the future (very unlikely). 514 // // We can easily recover from all these conditions by bringing 515 // // back the _offset _in range and retry. 516 517 // // bring back the _offset _in range. We use -1024 to make 518 // // it more unlikely to hit the 1ns _in the future condition. 519 // localOffset = System.currentTimeMillis()/1000 - 1024; 520 521 // // retry 522 // // adjustment = VM.getNanoTimeAdjustment(localOffset); 523 524 // if (adjustment == -1) { 525 // // Should not happen: we just recomputed a new _offset. 526 // // It should have fixed the issue. 527 // throw new InternalError("Offset " ~ localOffset.to!string ~ " is not _in range"); 528 // } else { 529 // // OK - recovery succeeded. Update the _offset for the 530 // // next call... 531 // _offset = localOffset; 532 // } 533 // } 534 long nsecs = DateTime.currentTimeNsecs(); 535 long localOffset = convert!("nsecs", "seconds")(nsecs); 536 long adjustment = nsecs - localOffset * 1000_000_000L; 537 // import hunt.logging; 538 // import std.string; 539 // version(HUNT_DEBUG) logDebug("(nsecs : %s , msecs : %s , offset: %s )".format(nsecs,localOffset,adjustment)); 540 return Instant.ofEpochSecond(localOffset, adjustment); 541 } 542 543 override 544 bool opEquals(Object obj) { 545 if (cast(SystemClock)(obj) !is null) { 546 return zone.opEquals((cast(SystemClock) obj).zone); 547 } 548 return false; 549 } 550 551 override 552 size_t toHash() @trusted nothrow { 553 return zone.toHash() + 1; 554 } 555 override 556 string toString() { 557 return "SystemClock[" ~ zone.to!string ~ "]"; 558 } 559 ///@gxc 560 // private void readObject(ObjectInputStream _is) { 561 // // ensure that _offset is initialized 562 // _is.defaultReadObject(); 563 // _offset = OFFSET_SEED; 564 // } 565 } 566 567 //----------------------------------------------------------------------- 568 /** 569 * Implementation of a clock that always returns the same _instant. 570 * This is typically used for testing. 571 */ 572 static final class FixedClock : Clock { //, Serializable 573 private Instant _instant; 574 private ZoneId zone; 575 576 this(Instant fixedInstant, ZoneId zone) { 577 this._instant = fixedInstant; 578 this.zone = zone; 579 } 580 override 581 ZoneId getZone() { 582 return zone; 583 } 584 override 585 Clock withZone(ZoneId zone) { 586 if (zone.opEquals(this.zone)) { // intentional NPE 587 return this; 588 } 589 return new FixedClock(_instant, zone); 590 } 591 override 592 long millis() { 593 return _instant.toEpochMilli(); 594 } 595 override 596 Instant instant() { 597 return _instant; 598 } 599 override 600 bool opEquals(Object obj) { 601 if (cast(FixedClock)(obj) !is null) { 602 FixedClock other = cast(FixedClock) obj; 603 return _instant.opEquals(other._instant) && zone.opEquals(other.zone); 604 } 605 return false; 606 } 607 override 608 size_t toHash() @trusted nothrow { 609 return _instant.toHash() ^ zone.toHash(); 610 } 611 override 612 string toString() { 613 return "FixedClock[" ~ _instant.toString ~ "," ~ zone.toString ~ "]"; 614 } 615 } 616 617 //----------------------------------------------------------------------- 618 /** 619 * Implementation of a clock that adds an _offset to an underlying clock. 620 */ 621 static final class OffsetClock : Clock { // , Serializable 622 private enum long serialVersionUID = 2007484719125426256L; 623 private Clock baseClock; 624 private Duration _offset; 625 626 this(Clock baseClock, Duration _offset) { 627 this.baseClock = baseClock; 628 this._offset = _offset; 629 } 630 override 631 ZoneId getZone() { 632 return baseClock.getZone(); 633 } 634 override 635 Clock withZone(ZoneId zone) { 636 if (zone.opEquals(baseClock.getZone())) { // intentional NPE 637 return this; 638 } 639 return new OffsetClock(baseClock.withZone(zone), _offset); 640 } 641 override 642 long millis() { 643 return MathHelper.addExact(baseClock.millis(), _offset.toMillis()); 644 } 645 override 646 Instant instant() { 647 return baseClock.instant().plus(_offset); 648 } 649 override 650 bool opEquals(Object obj) { 651 if (cast(OffsetClock)(obj) !is null) { 652 OffsetClock other = cast(OffsetClock) obj; 653 return baseClock.opEquals(other.baseClock) && _offset.opEquals(other._offset); 654 } 655 return false; 656 } 657 override 658 size_t toHash() @trusted nothrow { 659 return baseClock.toHash() ^ _offset.toHash(); 660 } 661 override 662 string toString() { 663 return "OffsetClock[" ~ baseClock.toString ~ "," ~ _offset.toString ~ "]"; 664 } 665 } 666 667 //----------------------------------------------------------------------- 668 /** 669 * Implementation of a clock that adds an _offset to an underlying clock. 670 */ 671 static final class TickClock : Clock { //, Serializable 672 private enum long serialVersionUID = 6504659149906368850L; 673 private Clock baseClock; 674 private long tickNanos; 675 676 this(Clock baseClock, long tickNanos) { 677 this.baseClock = baseClock; 678 this.tickNanos = tickNanos; 679 } 680 override 681 ZoneId getZone() { 682 return baseClock.getZone(); 683 } 684 override 685 Clock withZone(ZoneId zone) { 686 if (zone.opEquals(baseClock.getZone())) { // intentional NPE 687 return this; 688 } 689 return new TickClock(baseClock.withZone(zone), tickNanos); 690 } 691 override 692 long millis() { 693 long millis = baseClock.millis(); 694 return millis - MathHelper.floorMod(millis, (tickNanos / 1000_000L)); 695 } 696 override 697 Instant instant() { 698 if ((tickNanos % 1000_000) == 0) { 699 long millis = baseClock.millis(); 700 return Instant.ofEpochMilli(millis - MathHelper.floorMod(millis , (tickNanos / 1000_000L))); 701 } 702 Instant _instant = baseClock.instant(); 703 long nanos = _instant.getNano(); 704 long adjust = MathHelper.floorMod(nanos , tickNanos); 705 return _instant.minusNanos(adjust); 706 } 707 override 708 bool opEquals(Object obj) { 709 if (cast(TickClock)(obj) !is null) { 710 TickClock other = cast(TickClock) obj; 711 return baseClock.opEquals(other.baseClock) && tickNanos == other.tickNanos; 712 } 713 return false; 714 } 715 override 716 size_t toHash() @trusted nothrow { 717 return baseClock.toHash() ^ (cast(int) (tickNanos ^ (tickNanos >>> 32))); 718 } 719 override 720 string toString() { 721 return "TickClock[" ~ baseClock.toString ~ "," ~ Duration.ofNanos(tickNanos).toString ~ "]"; 722 } 723 } 724 725 }