001package jmri.jmrit.symbolicprog; 002 003import java.time.LocalDateTime; 004import java.time.format.DateTimeFormatter; 005import java.time.format.FormatStyle; 006import java.time.temporal.ChronoUnit; 007import java.time.temporal.UnsupportedTemporalTypeException; 008import java.util.HashMap; 009import javax.swing.JLabel; 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013/** 014 * Like {@link SplitVariableValue}, except that the string representation is 015 * a date and time relative to a given epoch start date. 016 * <ul> 017 * <li> 018 * A {@code base} attribute is required to indicate the epoch (zero) date and 019 * must be in XML dateTime format {@code "YYYY-MM-DDThh:mm:ss"}(all components 020 * are required). 021 * For example, the RailCom (S9.3.2) epoch is "2000-01-01T00:00:00", 022 * while the Java epoch is "1970-01-01T00:00:00" 023 * </li> 024 * <li> 025 * A {@code unit} attribute specifies the time unit of the value stored in the 026 * CVs. The default is {@code "Seconds"} and the available units are 027 * {@code "Nanos"}, {@code "Micros"}, {@code "Millis"}, {@code "Seconds"}, 028 * {@code "Minutes"}, {@code "Hours"}, {@code "HalfDays"}, {@code "Days"}, 029 * {@code "Weeks"}, {@code "Months"}, {@code "Years"}, {@code "Decades"}, 030 * {@code "Centuries"}, {@code "Millennia"} as per 031 * {@link java.time.temporal.ChronoUnit#values()} 032 * </li> 033 * <li> 034 * A {@code factor} attribute can be used to specify that the stored value is in 035 * multiples of a {@code unit}. For example, if the stored value is in tenths of 036 * a second, use {@code unit="Millis", factor="100"}. Large values of 037 * {@code factor} should be avoided, due to the possibility of multiplication 038 * overflow. 039 * </li> 040 * <li> 041 * A {@code display} attribute specifies the what is returned in the string representation 042 * The default is to return both date and time and the available displays are 043 * {@code "dateOnly"}, {@code "timeOnly"} and {@code "default"}. 044 * </li> 045 * </ul> 046 * Due to the difficulties in parsing date and time values, together with the 047 * loss of information in the display format and back conversion, the string 048 * representation will always be {@code readOnly}, even though the underlying CV 049 * values may not be {@code readOnly}. 050 * 051 * @author Bob Jacobsen Copyright (C) 2001, 2002, 2003, 2004, 2013, 2014 052 * @author Dave Heap Copyright (C) 2016 053 */ 054public class SplitDateTimeVariableValue extends SplitVariableValue { 055 056 public SplitDateTimeVariableValue(String name, String comment, String cvName, 057 boolean readOnly, boolean infoOnly, boolean writeOnly, boolean opsOnly, 058 String cvNum, String mask, int minVal, int maxVal, 059 HashMap<String, CvValue> v, JLabel status, String stdname, 060 String pSecondCV, int pFactor, int pOffset, String uppermask, String extra1, String extra2, String extra3, String extra4) { 061 super(name, comment, cvName, readOnly, infoOnly, writeOnly, opsOnly, cvNum, mask, minVal, maxVal, v, status, stdname, pSecondCV, pFactor, pOffset, uppermask, extra1, extra2, extra3, extra4); 062 } 063 064 LocalDateTime base; 065 long factor; 066 String unit; 067 String display; 068 069 @Override 070 public void stepOneActions(String name, String comment, String cvName, 071 boolean readOnly, boolean infoOnly, boolean writeOnly, boolean opsOnly, 072 String cvNum, String mask, int minVal, int maxVal, 073 HashMap<String, CvValue> v, JLabel status, String stdname, 074 String pSecondCV, int pFactor, int pOffset, String uppermask, String extra1, String extra2, String extra3, String extra4) { 075 base = LocalDateTime.parse(extra1); 076 factor = Long.parseLong(extra2); 077 unit = extra3; 078 display = extra4; 079 _minVal = 0; 080 _maxVal = ~0; 081 } 082 083 @Override 084 public void stepTwoActions() { 085 log.debug("{} SplitDateTimeVariableValue stepTwoActions", _name); 086 super.stepTwoActions(); // need to do base level checks 087 _columns = cvCount * 4; //new default column width 088 switch (display) { 089 case "dateOnly": 090 case "timeOnly": 091 _columns = cvCount * 2; //new column width 092 break; 093 default: 094 _columns = cvCount * 4; //new column width 095 } 096 } 097 098 /** 099 * Since we are not parsing text to value, we need to save the current value 100 * to return with {@link #getValueFromText getValueFromText}. 101 */ 102 long storedValue = 0; 103 104 @Override 105 long getValueFromText(String s) { 106 return storedValue; 107 } 108 109 @Override 110 String getTextFromValue(long v) { 111 storedValue = v; // save the current value 112 for (ChronoUnit theUnit : ChronoUnit.values()) { 113 if (theUnit.toString().equals(unit)) { 114 return getTextFromDateTime(base.plus((v * factor), ChronoUnit.valueOf(theUnit.name()))); 115 } 116 } 117 throw new UnsupportedTemporalTypeException("Invalid time unit '" + unit + "'."); 118 } 119 120 /** 121 * 122 * @param dateTime a {@link LocalDateTime} value. 123 * @return a string representation of {@code dateTime}. 124 */ 125 String getTextFromDateTime(LocalDateTime dateTime) { 126 switch (display) { 127 case "dateOnly": 128 return dateTime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)); 129 case "timeOnly": 130 return dateTime.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM)); 131 default: 132 return dateTime.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)); 133 } 134 } 135 136 /** 137 * Set value from a String value. 138 * <p> 139 * This does nothing since we can't reliably parse text to date/time value. 140 * 141 * @param value a string representing the date/time value to be set 142 */ 143 @Override 144 public void setValue(String value) { 145 log.debug("skipping set of date/time value \"{}\"", value); 146 } 147 148 // initialize logging 149 private final static Logger log = LoggerFactory.getLogger(SplitDateTimeVariableValue.class); 150 151}