001package jmri.jmrit.simpleclock; 002 003import java.awt.event.ActionEvent; 004import java.awt.event.ActionListener; 005import java.beans.PropertyChangeListener; 006import java.time.Instant; 007import java.util.Calendar; 008import java.util.Date; 009 010import jmri.*; 011import jmri.jmrix.internal.InternalSystemConnectionMemo; 012 013/** 014 * Provide basic Timebase implementation from system clock. 015 * <p> 016 * This implementation provides for the internal clock and for one hardware 017 * clock. A number of hooks and comments are provided below for implementing 018 * multiple hardware clocks should that ever be done. 019 * <p> 020 * The setTimeValue member is the fast time when the clock started. The 021 * startAtTime member is the wall-clock time when the clock was started. 022 * Together, those can be used to calculate the current fast time. 023 * <p> 024 * The pauseTime member is used to indicate that the Timebase was paused. If 025 * non-null, it indicates the current fast time when the clock was paused. 026 * 027 * @author Bob Jacobsen Copyright (C) 2004, 2007 Dave Duchamp - 2007 028 * additions/revisions for handling one hardware clock 029 */ 030public class SimpleTimebase extends jmri.implementation.AbstractNamedBean implements Timebase { 031 032 public static final double MINIMUM_RATE = 0.1; 033 public static final double MAXIMUM_RATE = 100; 034 035 protected final SystemConnectionMemo memo; 036 037 public SimpleTimebase(InternalSystemConnectionMemo memo) { 038 super("SIMPLECLOCK"); 039 this.memo = memo; 040 // initialize time-containing memory 041 try { 042 clockMemory = InstanceManager.memoryManagerInstance().provideMemory(memo.getSystemPrefix()+"MCURRENTTIME"); 043 clockMemory.setValue("--"); 044 } catch (IllegalArgumentException ex) { 045 log.warn("Unable to create CURRENTTIME time memory variable"); 046 } 047 048 init(); 049 050 } 051 052 final void init(){ 053 054 // set to start counting from now 055 setTime(new Date()); 056 pauseTime = null; 057 // initialize start/stop sensor for time running 058 try { 059 clockSensor = InstanceManager.sensorManagerInstance().provideSensor(memo.getSystemPrefix()+"SCLOCKRUNNING"); 060 clockSensor.setKnownState(Sensor.ACTIVE); 061 clockSensor.addPropertyChangeListener(this::clockSensorChanged); 062 } catch (JmriException e) { 063 log.warn("Exception setting CLOCKRUNNING sensor ACTIVE", e); 064 } 065 // initialize rate factor-containing memory 066 if (InstanceManager.getNullableDefault(MemoryManager.class) != null) { 067 // only try to create memory if memories are supported 068 try { 069 factorMemory = InstanceManager.memoryManagerInstance() 070 .provideMemory(memo.getSystemPrefix()+"MRATEFACTOR"); 071 factorMemory.setValue(userGetRate()); 072 } catch (IllegalArgumentException ex) { 073 log.warn("Unable to create RATEFACTOR time memory variable"); 074 } 075 } 076 077 } 078 079 /** 080 * {@inheritDoc} 081 */ 082 @Override 083 public String getBeanType() { 084 return Bundle.getMessage("BeanNameTime"); 085 } 086 087 /** 088 * {@inheritDoc} 089 */ 090 @Override 091 public Date getTime() { 092 // is clock stopped? 093 if (pauseTime != null) { 094 return new Date(pauseTime.getTime()); // to ensure not modified outside 095 } // clock running 096 long elapsedMSec = (new Date()).getTime() - startAtTime.getTime(); 097 long nowMSec = setTimeValue.getTime() + (long) (mFactor * elapsedMSec); 098 return new Date(nowMSec); 099 } 100 101 /** 102 * {@inheritDoc} 103 */ 104 @Override 105 public void setTime(Date d) { 106 startAtTime = new Date(); // set now in wall clock time 107 setTimeValue = new Date(d.getTime()); // to ensure not modified from outside 108 if ( synchronizeWithHardware && InstanceManager.getDefault(ClockControl.class) != hardwareTimeSource) { 109 // send new time to all hardware clocks, except the hardware time source if there is one 110 // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks 111 InstanceManager.getDefault(ClockControl.class).setTime(d); 112 } 113 if (pauseTime != null) { 114 pauseTime = setTimeValue; // if stopped, continue stopped at new time 115 } 116 handleAlarm(null); 117 } 118 119 /** 120 * {@inheritDoc} 121 */ 122 @Override 123 public void setTime(Instant i) { 124 setTime(Date.from(i)); 125 } 126 127 /** 128 * {@inheritDoc} 129 */ 130 @Override 131 public void userSetTime(Date d) { 132 // this call only results from user changing fast clock time in Setup Fast Clock 133 startAtTime = new Date(); // set now in wall clock time 134 setTimeValue = new Date(d.getTime()); // to ensure not modified from outside 135 if (synchronizeWithHardware) { 136 // send new time to all hardware clocks, including the hardware time source if there is one 137 // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks 138 InstanceManager.getDefault(jmri.ClockControl.class).setTime(d); 139 } else if (!internalMaster && (hardwareTimeSource != null)) { 140 // if not synchronizing, send to the hardware time source if there is one 141 hardwareTimeSource.setTime(d); 142 } 143 if (pauseTime != null) { 144 pauseTime = setTimeValue; // if stopped, continue stopped at new time 145 } 146 handleAlarm(null); 147 } 148 149 /** 150 * {@inheritDoc} 151 */ 152 @Override 153 public void setRun(boolean run) { 154 if (run && pauseTime != null) { 155 // starting of stopped clock 156 setTime(pauseTime); 157 if (synchronizeWithHardware) { 158 // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks 159 InstanceManager.getDefault(ClockControl.class).startHardwareClock(getTime()); 160 } else if (!internalMaster && hardwareTimeSource != null) { 161 hardwareTimeSource.startHardwareClock(getTime()); 162 } 163 pauseTime = null; 164 if (clockSensor != null) { 165 try { 166 clockSensor.setKnownState(Sensor.ACTIVE); 167 } catch (JmriException e) { 168 log.warn("Exception setting ISClockRunning sensor ACTIVE", e); 169 } 170 } 171 } else if (!run && pauseTime == null) { 172 // stopping of running clock: 173 // Store time it was stopped, and stop it 174 pauseTime = getTime(); 175 if (synchronizeWithHardware) { 176 // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks 177 InstanceManager.getDefault(ClockControl.class).stopHardwareClock(); 178 } else if (!internalMaster && hardwareTimeSource != null) { 179 hardwareTimeSource.stopHardwareClock(); 180 } 181 if (clockSensor != null) { 182 try { 183 clockSensor.setKnownState(Sensor.INACTIVE); 184 } catch (jmri.JmriException e) { 185 log.warn("Exception setting ISClockRunning sensor INACTIVE", e); 186 } 187 } 188 } 189 firePropertyChange(PROPERTY_CHANGE_RUN, !run, run); // old, then new 190 handleAlarm(null); 191 } 192 193 /** 194 * {@inheritDoc} 195 */ 196 @Override 197 public boolean getRun() { 198 return pauseTime == null; 199 } 200 201 /** 202 * {@inheritDoc} 203 */ 204 @Override 205 public void setRate(double factor) throws TimebaseRateException { 206 checkRateValid(factor); 207 if (internalMaster && (!notInitialized)) { 208 log.error("Probable Error - questionable attempt to change fast clock rate"); 209 } 210 double oldFactor = mFactor; 211 Date now = getTime(); 212 // actually make the change 213 mFactor = factor; 214 if (internalMaster || notInitialized) { 215 hardwareFactor = factor; 216 } 217 if (internalMaster || (synchronizeWithHardware && notInitialized)) { 218 // send new rate to all hardware clocks, except the hardware time source if there is one 219 // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks 220 if (InstanceManager.getDefault(ClockControl.class) != hardwareTimeSource) { 221 InstanceManager.getDefault(ClockControl.class).setRate(factor); 222 } 223 } 224 // make sure time is right with new rate 225 setTime(now); 226 // notify listeners if internal master 227 if (internalMaster) { 228 firePropertyChange("rate", oldFactor, factor); // old, then new 229 } 230 handleAlarm(null); 231 } 232 233 /** 234 * {@inheritDoc} 235 */ 236 @Override 237 public void userSetRate(double factor) throws TimebaseRateException { 238 // this call is used when user changes fast clock rate either in Setup Fast Clock or via a ClockControl 239 // implementation 240 checkRateValid(factor); 241 double oldFactor = hardwareFactor; 242 Date now = getTime(); 243 // actually make the change 244 mFactor = factor; 245 hardwareFactor = factor; 246 if (synchronizeWithHardware) { 247 // send new rate to all hardware clocks, including the hardware time source if there is one 248 // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks 249 InstanceManager.getDefault(ClockControl.class).setRate(factor); 250 } else if (!internalMaster && (hardwareTimeSource != null)) { 251 // if not synchronizing, send to the hardware time source if there is one 252 hardwareTimeSource.setRate(factor); 253 } 254 // make sure time is right with new rate 255 setTime(now); 256 // update memory 257 updateMemory(factor); 258 // notify listeners 259 firePropertyChange("rate", oldFactor, factor); // old, then new 260 handleAlarm(null); 261 } 262 263 private void checkRateValid(double factor) throws TimebaseRateException { 264 if (factor < MINIMUM_RATE || factor > MAXIMUM_RATE) { 265 log.error("rate of {} is out of reasonable range {} - {}", factor, MINIMUM_RATE, MAXIMUM_RATE); 266 throw new TimebaseRateException(Bundle.getMessage("IncorrectRate", factor, MINIMUM_RATE, MAXIMUM_RATE)); 267 } 268 } 269 270 /** 271 * {@inheritDoc} 272 */ 273 @Override 274 public double getRate() { 275 return mFactor; 276 } 277 278 /** 279 * {@inheritDoc} 280 */ 281 @Override 282 public double userGetRate() { 283 return ( internalMaster ? mFactor : hardwareFactor); 284 } 285 286 /** 287 * {@inheritDoc} 288 */ 289 @Override 290 public void setInternalMaster(boolean master, boolean update) { 291 if (master != internalMaster) { 292 internalMaster = master; 293 if (internalMaster) { 294 mFactor = hardwareFactor; // get rid of any fiddled rate present 295 } 296 if (update) { 297 // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks 298 InstanceManager.getDefault(ClockControl.class).initializeHardwareClock(userGetRate(), 299 getTime(), false); 300 } 301 302 if (internalMaster) { 303 masterName = ""; 304 hardwareTimeSource = null; 305 } else { 306 // Note if there are multiple hardware clocks, this should be changed to correctly 307 // identify which hardware clock has been chosen-currently assumes only one 308 hardwareTimeSource = InstanceManager.getDefault(ClockControl.class); 309 masterName = hardwareTimeSource.getHardwareClockName(); 310 } 311 firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed 312 } 313 } 314 315 /** 316 * {@inheritDoc} 317 */ 318 @Override 319 public boolean getInternalMaster() { 320 return internalMaster; 321 } 322 323 /** 324 * {@inheritDoc} 325 */ 326 @Override 327 public void setMasterName(String name) { 328 if (!internalMaster) { 329 masterName = name; 330 // if multiple clocks, this must be replaced by a loop over all hardware clocks to identify 331 // the one that is the hardware time source 332 hardwareTimeSource = InstanceManager.getDefault(ClockControl.class); 333 } else { 334 masterName = ""; 335 hardwareTimeSource = null; 336 } 337 } 338 339 /** 340 * {@inheritDoc} 341 */ 342 @Override 343 public String getMasterName() { 344 return masterName; 345 } 346 347 /** 348 * {@inheritDoc} 349 */ 350 @Override 351 public void setSynchronize(boolean synchronize, boolean update) { 352 if (synchronizeWithHardware != synchronize) { 353 synchronizeWithHardware = synchronize; 354 if (update) { 355 // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks 356 InstanceManager.getDefault(ClockControl.class).initializeHardwareClock( 357 userGetRate(), getTime(), false); 358 } 359 firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed 360 } 361 } 362 363 /** 364 * {@inheritDoc} 365 */ 366 @Override 367 public boolean getSynchronize() { 368 return synchronizeWithHardware; 369 } 370 371 /** 372 * If update true, calls initializeHardwareClock. 373 * {@inheritDoc} 374 */ 375 @Override 376 public void setCorrectHardware(boolean correct, boolean update) { 377 if (correctHardware != correct) { 378 correctHardware = correct; 379 if (update) { 380 // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks 381 InstanceManager.getDefault(ClockControl.class).initializeHardwareClock( 382 userGetRate(), getTime(), false); 383 } 384 firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed 385 } 386 } 387 388 /** 389 * {@inheritDoc} 390 */ 391 @Override 392 public boolean getCorrectHardware() { 393 return correctHardware; 394 } 395 396 /** 397 * {@inheritDoc} 398 */ 399 @Override 400 public void set12HourDisplay(boolean display, boolean update) { 401 if (display != display12HourClock) { 402 display12HourClock = display; 403 if (update) { 404 // Note if there are multiple hardware clocks, this should be a loop over all hardware clocks 405 InstanceManager.getDefault(ClockControl.class).initializeHardwareClock( 406 userGetRate(), getTime(), false); 407 } 408 } 409 } 410 411 /** 412 * {@inheritDoc} 413 */ 414 @Override 415 public boolean use12HourDisplay() { 416 return display12HourClock; 417 } 418 419 /** 420 * {@inheritDoc} 421 */ 422 @Override 423 public void setClockInitialRunState(ClockInitialRunState state) { 424 if (initialState != state) { 425 initialState = state; 426 firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed 427 } 428 } 429 430 /** 431 * {@inheritDoc} 432 */ 433 @Override 434 public ClockInitialRunState getClockInitialRunState() { 435 return initialState; 436 } 437 438 /** 439 * {@inheritDoc} 440 */ 441 @Override 442 public void setShowStopButton(boolean displayed) { 443 if (showStopButton != displayed) { 444 showStopButton = displayed; 445 firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed 446 } 447 } 448 449 /** 450 * {@inheritDoc} 451 */ 452 @Override 453 public boolean getShowStopButton() { 454 return showStopButton; 455 } 456 457 /** 458 * {@inheritDoc} 459 */ 460 @Override 461 public void setStartSetTime(boolean set, Date time) { 462 if (startSetTime!=set || startTime!=new Date(time.getTime())) { 463 startSetTime = set; 464 startTime = new Date(time.getTime()); 465 firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed 466 } 467 } 468 469 /** 470 * {@inheritDoc} 471 */ 472 @Override 473 public boolean getStartSetTime() { 474 return startSetTime; 475 } 476 477 /** 478 * {@inheritDoc} 479 */ 480 @Override 481 public void setStartRate(double factor) { 482 if (Math.abs(startupFactor - factor) > 0.0001) { //avoid possible float precision errors 483 startupFactor = factor; 484 haveStartupFactor = true; 485 firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed 486 } 487 } 488 489 /** 490 * {@inheritDoc} 491 */ 492 @Override 493 public double getStartRate() { 494 if (haveStartupFactor) { 495 return startupFactor; 496 } else { 497 return userGetRate(); 498 } 499 } 500 501 /** 502 * {@inheritDoc} 503 */ 504 @Override 505 public void setSetRateAtStart(boolean set) { 506 if (startSetRate != set) { 507 startSetRate = set; 508 firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed 509 } 510 } 511 512 /** 513 * {@inheritDoc} 514 */ 515 @Override 516 public boolean getSetRateAtStart() { 517 return startSetRate; 518 } 519 520 /** 521 * {@inheritDoc} 522 */ 523 @Override 524 public Date getStartTime() { 525 return new Date(startTime.getTime()); 526 } 527 528 /** 529 * {@inheritDoc} 530 */ 531 @Override 532 public void setStartClockOption(int option) { 533 if (startClockOption != option) { 534 startClockOption = option; 535 firePropertyChange("config", 0, 1); // inform listeners that the clock config has changed 536 } 537 } 538 539 /** 540 * {@inheritDoc} 541 */ 542 @Override 543 public int getStartClockOption() { 544 return startClockOption; 545 } 546 547 /** 548 * The following method should only be invoked at start up. 549 * {@inheritDoc} 550 */ 551 @Override 552 public void initializeClock() { 553 switch (startClockOption) { 554 case NIXIE_CLOCK: 555 jmri.jmrit.nixieclock.NixieClockFrame f = new jmri.jmrit.nixieclock.NixieClockFrame(); 556 f.setVisible(true); 557 break; 558 case ANALOG_CLOCK: 559 jmri.jmrit.analogclock.AnalogClockFrame g = new jmri.jmrit.analogclock.AnalogClockFrame(); 560 g.setVisible(true); 561 break; 562 case LCD_CLOCK: 563 jmri.jmrit.lcdclock.LcdClockFrame h = new jmri.jmrit.lcdclock.LcdClockFrame(); 564 h.setVisible(true); 565 break; 566 case PRAGOTRON_CLOCK: 567 jmri.jmrit.pragotronclock.PragotronClockFrame p = new jmri.jmrit.pragotronclock.PragotronClockFrame(); 568 p.setVisible(true); 569 break; 570 default: 571 log.debug("initializeClock() called with invalid startClockOption: {}", startClockOption); 572 } 573 } 574 575 /** 576 * {@inheritDoc} 577 */ 578 @Override 579 public void initializeHardwareClock() { 580 boolean startStopped = (initialState == ClockInitialRunState.DO_STOP); 581 if (synchronizeWithHardware || correctHardware) { 582 if (startStopped) { 583 InstanceManager.getList(ClockControl.class).forEach( cc -> 584 cc.initializeHardwareClock( 0, getTime(), (!internalMaster && !startSetTime)) ); 585 } else { 586 InstanceManager.getList(ClockControl.class).forEach( cc -> 587 cc.initializeHardwareClock( mFactor, getTime(), (!internalMaster && !startSetTime)) ); 588 } 589 } else if (!internalMaster) { 590 if (startStopped) { 591 hardwareTimeSource.initializeHardwareClock(0, getTime(), (!startSetTime)); 592 } else { 593 hardwareTimeSource.initializeHardwareClock(hardwareFactor, getTime(), (!startSetTime)); 594 } 595 } 596 notInitialized = false; 597 } 598 599 /** 600 * {@inheritDoc} 601 */ 602 @Override 603 public boolean getIsInitialized() { 604 return (!notInitialized); 605 } 606 607 /** 608 * Handle a change in the clock running sensor 609 */ 610 private void clockSensorChanged(java.beans.PropertyChangeEvent e) { 611 if (clockSensor.getKnownState() == Sensor.ACTIVE) { 612 // simply return if clock is already running 613 if (pauseTime == null) { 614 return; 615 } 616 setRun(true); 617 } else { 618 // simply return if clock is already stopped 619 if (pauseTime != null) { 620 return; 621 } 622 setRun(false); 623 } 624 } 625 626 /** 627 * Stops Timer. 628 * {@inheritDoc} 629 */ 630 @Override 631 public void dispose() { 632 if (timer != null) { 633 // end this timer 634 timer.setRepeats(false); // just in case 635 timer.stop(); 636 637 ActionListener[] listeners = timer.getListeners(ActionListener.class); 638 for (ActionListener listener : listeners) { 639 timer.removeActionListener(listener); 640 } 641 timer = null; 642 } 643 if ( clockSensor != null ) { 644 clockSensor.removePropertyChangeListener(this::clockSensorChanged); 645 } 646 super.dispose(); // remove standard property change listeners 647 } 648 649 /** 650 * InstanceManager.getDefault(jmri.Timebase.class) variables and options 651 */ 652 private double mFactor = 1.0; // this is the rate factor for the JMRI fast clock 653 private double hardwareFactor = 1.0; // this is the rate factor for the hardware clock 654 // The above is necessary to support hardware clock Time Sources that fiddle with mFactor to 655 // synchronize, instead of sending over a new time to synchronize. 656 private double startupFactor = 1.0; // this is the rate requested at startup 657 private boolean startSetRate = true; // if true, the hardware rate will be set to 658 private boolean haveStartupFactor = false; // true if startup factor was ever set. 659 // startupFactor at startup. 660 661 private Date startAtTime; 662 private Date setTimeValue; 663 private Date pauseTime; // null value indicates clock is running 664 private Sensor clockSensor = null; // active when clock is running, inactive when stopped 665 private Memory clockMemory = null; // contains current time on each tick 666 private Memory factorMemory = null; // contains the rate factor for the fast clock 667 668 private boolean internalMaster = true; // false indicates a hardware clock is the master 669 private String masterName = ""; // name of hardware time source, if not internal master 670 private ClockControl hardwareTimeSource = null; // ClockControl instance of hardware time source 671 private boolean synchronizeWithHardware = false; // true indicates need to synchronize 672 private boolean correctHardware = false; // true indicates hardware correction requested 673 private boolean display12HourClock = false; // true if 12-hour clock display is requested 674 private ClockInitialRunState initialState = ClockInitialRunState.DO_START; // what to do with the clock running state at startup 675 private boolean startSetTime = false; // true indicates set fast clock to specified time at 676 //start up requested 677 private Date startTime = new Date(); // specified time for setting fast clock at start up 678 private int startClockOption = NONE; // request start of a clock at start up 679 private boolean notInitialized = true; // true before initialization received from start up 680 private boolean showStopButton = false; // true indicates start up with start/stop button displayed 681 682 private java.text.SimpleDateFormat timeStorageFormat = null; 683 684 private javax.swing.Timer timer = null; 685 686 /** 687 * Start the minute alarm ticking, if it isnt already. 688 */ 689 void startAlarm() { 690 if (timer == null) { 691 handleAlarm(null); 692 } 693 } 694 695 private int oldHours = -1; 696 private int oldMinutes = -1; 697 private Date oldDate = null; 698 699 /** 700 * Handle an "alarm", which is used to count off minutes. 701 * <p> 702 * Listeners will be notified if the hours or minutes changed 703 * since the last time. 704 * @param e Event which triggered this 705 */ 706 void handleAlarm(ActionEvent e) { 707 // on first pass, set up the timer to call this routine 708 if (timer == null) { 709 timer = new javax.swing.Timer(60 * 1000, this::handleAlarm); 710 } 711 712 Calendar calendar = Calendar.getInstance(); 713 timer.stop(); 714 Date date = getTime(); 715 calendar.setTime(date); 716 int waitSeconds = 60 - calendar.get(Calendar.SECOND); 717 int delay = (int) (waitSeconds * 1000 / mFactor) + 100; // make sure you miss the time transition 718 timer.setInitialDelay(delay); 719 timer.setRepeats(true); // in case we run by 720 timer.start(); 721 722 // and notify the others 723 calendar.setTime(date); 724 int hours = calendar.get(Calendar.HOUR_OF_DAY); 725 int minutes = calendar.get(Calendar.MINUTE); 726 if (hours != oldHours || minutes != oldMinutes) { 727 // update memory 728 updateMemory(date); 729 // notify listeners 730 firePropertyChange(PROPERTY_CHANGE_MINUTES, Double.valueOf(oldMinutes), Double.valueOf(minutes)); 731 firePropertyChange(PROPERTY_CHANGE_TIME, oldDate != null ? new Date(oldDate.getTime()) : null, 732 new Date(date.getTime())); // to ensure not modified outside 733 } 734 oldDate = date; 735 oldHours = hours; 736 oldMinutes = minutes; 737 } 738 739 void updateMemory(Date date) { 740 if (timeStorageFormat == null) { 741 String pattern = java.util.ResourceBundle.getBundle("jmri.jmrit.simpleclock.SimpleClockBundle") 742 .getString("TimeStorageFormat"); 743 try { 744 timeStorageFormat = new java.text.SimpleDateFormat(pattern); 745 } catch (IllegalArgumentException e) { 746 log.info("Unable to parse date / time format: {}",pattern); 747 log.info("For supported formats see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/text/SimpleDateFormat.html"); 748 log.info("Dropping back to default time format (h:mm a) 4:56 PM, due to exception", e); 749 timeStorageFormat = new java.text.SimpleDateFormat("h:mm a"); 750 } 751 } 752 clockMemory.setValue(timeStorageFormat.format(date)); 753 } 754 755 void updateMemory(double factor) { 756 factorMemory.setValue(factor); 757 } 758 759 /** 760 * {@inheritDoc} 761 */ 762 @Override 763 public void addMinuteChangeListener(PropertyChangeListener l) { 764 addPropertyChangeListener(PROPERTY_CHANGE_MINUTES, l); 765 } 766 767 /** 768 * {@inheritDoc} 769 */ 770 @Override 771 public void removeMinuteChangeListener(PropertyChangeListener l) { 772 removePropertyChangeListener(PROPERTY_CHANGE_MINUTES, l); 773 } 774 775 /** 776 * {@inheritDoc} 777 */ 778 @Override 779 public PropertyChangeListener[] getMinuteChangeListeners() { 780 return getPropertyChangeListeners(PROPERTY_CHANGE_MINUTES); 781 } 782 783 @Override 784 public void addPropertyChangeListener(PropertyChangeListener listener) { 785 super.addPropertyChangeListener(listener); 786 startAlarm(); 787 } 788 789 790 @Override 791 public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { 792 super.addPropertyChangeListener(propertyName, listener); 793 if (propertyName != null && (propertyName.equals(PROPERTY_CHANGE_MINUTES) || propertyName.equals(PROPERTY_CHANGE_TIME))) { 794 startAlarm(); 795 } 796 } 797 798 /** 799 * Implementation does nothing. 800 * {@inheritDoc} 801 */ 802 @Override 803 public void setState(int s) throws jmri.JmriException { 804 } 805 806 /** 807 * Implementation returns 0 . 808 * {@inheritDoc} 809 */ 810 @Override 811 public int getState() { 812 return 0; 813 } 814 815 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SimpleTimebase.class); 816 817}