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}