001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas/
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv.data;
030
031import static javax.swing.GroupLayout.Alignment.BASELINE;
032import static javax.swing.GroupLayout.Alignment.LEADING;
033import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
034import static javax.swing.LayoutStyle.ComponentPlacement.UNRELATED;
035
036import java.awt.Dimension;
037import java.rmi.RemoteException;
038import java.time.ZonedDateTime;
039import java.time.format.DateTimeFormatter;
040import java.util.Calendar;
041import java.util.Date;
042import java.util.Hashtable;
043
044import javax.swing.GroupLayout;
045import javax.swing.JComponent;
046import javax.swing.JLabel;
047import javax.swing.JPanel;
048import javax.swing.JTextField;
049
050import name.gano.astro.time.Time;
051
052import org.slf4j.Logger;
053import org.slf4j.LoggerFactory;
054
055import ucar.unidata.data.DataSelection;
056import ucar.unidata.data.DataSelectionComponent;
057import ucar.unidata.data.DataSourceImpl;
058
059import visad.VisADException;
060
061import edu.wisc.ssec.mcidasv.Constants;
062import edu.wisc.ssec.mcidasv.data.dateChooser.JCalendar;
063import edu.wisc.ssec.mcidasv.data.dateChooser.JDateChooser;
064import edu.wisc.ssec.mcidasv.data.dateChooser.JDayChooser;
065import edu.wisc.ssec.mcidasv.data.dateChooser.JMonthChooser;
066import edu.wisc.ssec.mcidasv.data.dateChooser.JYearChooser;
067
068public class TimeRangeSelection extends DataSelectionComponent implements Constants {
069
070      private static final Logger logger = LoggerFactory.getLogger(TimeRangeSelection.class);
071
072      public static final String DEFAULT_BEGIN_TIME = "00:00:00";
073      public static final String DEFAUULT_END_TIME = "23:59:59";
074
075      private static Calendar cal = Calendar.getInstance();
076
077      public static final Date DEFAULT_BEGIN_DATE = cal.getTime();
078      public static final Date DEFAULT_END_DATE = cal.getTime();
079
080      private JTextField beginTimeFld;
081      private JTextField endTimeFld;
082      
083      private String selectedBeginTime = DEFAULT_BEGIN_TIME;
084      private String selectedEndTime = DEFAUULT_END_TIME;
085      
086      private Date selectedBeginDate = DEFAULT_BEGIN_DATE;
087      private Date selectedEndDate = DEFAULT_END_DATE;
088
089      private JDateChooser begDay = null;
090      private JDateChooser endDay = null;
091
092      public static final String PROP_BEGTIME = "BeginTime";
093      public static final String PROP_ENDTIME = "EndTime";
094      public static final String PROP_BEGDATE = "BeginDate";
095      public static final String PROP_ENDDATE = "EndDate";
096      protected static final String PROP_BTIME = "BTime";
097      protected static final String PROP_ETIME = "ETime";
098      protected static final String PROP_YEAR = "Year";
099      protected static final String PROP_MONTH = "Month";
100      protected static final String PROP_DAY = "Day";
101      protected static final String PROP_HOURS = "Hours";
102      protected static final String PROP_MINS = "Mins";
103      protected static final String PROP_SECS = "Secs";
104
105      private JPanel timeRangeComp = new JPanel();
106
107      public TimeRangeSelection(DataSourceImpl dataSource) 
108              throws VisADException, RemoteException {
109          super("Time Range");
110      }
111      
112    protected JComponent doMakeContents() {
113
114        logger.debug("creating the TimeRangeSelection panel...");
115        JLabel begLab = new JLabel("  Begin");
116        JLabel endLab = new JLabel("  End");
117        JLabel begTimeLab = new JLabel("      Time:");
118        JLabel endTimeLab = new JLabel("      Time:");
119        JLabel begDateLab = new JLabel("      Date:");
120        JLabel endDateLab = new JLabel("      Date:");
121        beginTimeFld = new JTextField(selectedBeginTime, 8);
122        beginTimeFld.setMaximumSize(new Dimension(80, 40));
123        endTimeFld = new JTextField(selectedEndTime, 8);
124        endTimeFld.setMaximumSize(new Dimension(80, 40));
125        begDay = new JDateChooser(selectedBeginDate);
126        begDay.setMaximumSize(new Dimension(140, 20));
127        endDay = new JDateChooser(selectedEndDate);
128        endDay.setMaximumSize(new Dimension(140, 20));
129        
130        // make them listen to each other to maintain range validity (beg <= end) and vice versa
131        begDay.getJCalendar().getDayChooser().setName(JDayChooser.BEG_DAY);
132        endDay.getJCalendar().getDayChooser().setName(JDayChooser.END_DAY);
133        begDay.getJCalendar().getDayChooser().addPropertyChangeListener("day", endDay);
134        endDay.getJCalendar().getDayChooser().addPropertyChangeListener("day", begDay);
135
136        GroupLayout layout = new GroupLayout(timeRangeComp);
137        timeRangeComp.setLayout(layout);
138        layout.setHorizontalGroup(
139            layout.createParallelGroup(LEADING)
140                .addComponent(begLab)
141                .addGroup(layout.createSequentialGroup()
142                    .addComponent(begTimeLab)
143                    .addGap(GAP_RELATED)
144                    .addComponent(beginTimeFld))
145                .addGroup(layout.createSequentialGroup()
146                    .addComponent(begDateLab)
147                    .addGap(GAP_RELATED)
148                    .addComponent(begDay))
149                .addComponent(endLab)
150                .addGroup(layout.createSequentialGroup()
151                    .addComponent(endTimeLab)
152                    .addGap(GAP_RELATED)
153                    .addComponent(endTimeFld))
154                .addGroup(layout.createSequentialGroup()
155                    .addComponent(endDateLab)
156                    .addGap(GAP_RELATED)
157                    .addComponent(endDay))
158         );
159
160        layout.setVerticalGroup(
161            layout.createParallelGroup(LEADING)
162            .addGroup(layout.createSequentialGroup()
163                .addComponent(begLab)
164                .addGroup(layout.createParallelGroup(BASELINE)
165                    .addComponent(begTimeLab)
166                    .addComponent(beginTimeFld))
167                .addPreferredGap(RELATED)
168                .addGroup(layout.createParallelGroup(BASELINE)
169                    .addComponent(begDateLab)
170                    .addComponent(begDay))
171                .addPreferredGap(UNRELATED)
172                .addComponent(endLab)
173                .addGroup(layout.createParallelGroup(BASELINE)
174                    .addComponent(endTimeLab)
175                    .addComponent(endTimeFld))
176                .addPreferredGap(RELATED)
177                .addGroup(layout.createParallelGroup(BASELINE)
178                    .addComponent(endDateLab)
179                    .addComponent(endDay)))
180         );
181
182         return timeRangeComp;
183    }
184
185    protected JComponent getTimeRangeComponent() {
186        return timeRangeComp;
187    }
188
189    public boolean begTimeOk() {
190        String begTime = beginTimeFld.getText();
191        String[] timeStrings = begTime.split(":");
192        int num = timeStrings.length;
193        if (num != 3) return false;
194        int hours = -1;
195        int mins = -1;
196        int secs = -1; 
197        try {
198                hours = Integer.parseInt(timeStrings[0]);
199                mins = Integer.parseInt(timeStrings[1]);
200                secs = Integer.parseInt(timeStrings[2]);
201        } catch (NumberFormatException nfe) {
202                return false;
203        }
204        if ((hours < 0) || (hours > 23)) return false;
205        if ((mins < 0) || (mins > 59)) return false;
206        if ((secs < 0) || (secs > 59)) return false;
207        return true;
208    }
209    
210    public boolean endTimeOk() {
211        String endTime = endTimeFld.getText();
212        String[] timeStrings = endTime.split(":");
213        int num = timeStrings.length;
214        if (num != 3) return false;
215        int hours = -1;
216        int mins = -1;
217        int secs = -1;
218        try {
219                hours = Integer.parseInt(timeStrings[0]);
220                mins = Integer.parseInt(timeStrings[1]);
221                secs = Integer.parseInt(timeStrings[2]);
222        } catch (NumberFormatException nfe) {
223                return false;
224        }
225        if ((hours < 0) || (hours > 23)) return false;
226        if ((mins < 0) || (mins > 59)) return false;
227        if ((secs < 0) || (secs > 59)) return false;
228        return true;
229    }
230    
231    /**
232     * TJJ Jun 2015
233     * Return the difference in seconds between the Calendar objects only!
234     * NOTE: this will only give you the number of days difference,
235     * the HH, MM, and SS data comes from a different UI widget.
236     * 
237     * We compute seconds simply because that is easiest way to get an
238     * absolute time from Date object
239     */
240    
241    public long getTimeRangeInSeconds() {
242        
243        // make sure our widgets are set with a valid end time greater
244        // than begin time
245        if (! timeRangeOk()) {
246                return -1;
247        }
248        
249        long endTimeMs = endDay.getDate().getTime();
250        long begTimeMs = begDay.getDate().getTime();
251        return (endTimeMs - begTimeMs) / 1000;
252        
253    }
254    
255    /**
256     * Make sure the end date/time exceeds the beginning date/time.
257     * 
258     * @return true if condition met, false otherwise
259     * 
260     */
261    
262    public boolean timeRangeOk() {
263        
264        if (! begTimeOk()) return false;
265        if (! endTimeOk()) return false;
266        
267        JCalendar cal = begDay.getJCalendar();
268        JDayChooser dayChooser = cal.getDayChooser();
269        int day = dayChooser.getDay();
270        JMonthChooser monthChooser = cal.getMonthChooser();
271        int month = monthChooser.getMonth() + 1;
272        JYearChooser yearChooser = cal.getYearChooser();
273        int year = yearChooser.getYear();
274
275        int hours = 0;
276        int mins = 0;
277        double secs = 0.0;
278        String begTime = beginTimeFld.getText();
279        String[] timeStrings = begTime.split(":");
280        int num = timeStrings.length;
281        if (num > 0)
282            hours = (new Integer(timeStrings[0])).intValue();
283        if (num > 1)
284            mins = (new Integer(timeStrings[1])).intValue();
285        if (num > 2)
286            secs = (new Integer(timeStrings[2])).intValue();
287
288        Time bTime = new Time(year, month, day, hours, mins, secs);
289
290        cal = endDay.getJCalendar();
291        dayChooser = cal.getDayChooser();
292        day = dayChooser.getDay();
293        monthChooser = cal.getMonthChooser();
294        month = monthChooser.getMonth() + 1;
295        yearChooser = cal.getYearChooser();
296        year = yearChooser.getYear();
297
298        String endTime = endTimeFld.getText();
299        timeStrings = endTime.split(":");
300        num = timeStrings.length;
301        if (num > 0)
302            hours = (new Integer(timeStrings[0])).intValue();
303        if (num > 1)
304            mins = (new Integer(timeStrings[1])).intValue();
305        if (num > 2)
306            secs = (new Integer(timeStrings[2])).intValue();
307
308        Time eTime = new Time(year, month, day, hours, mins, secs);
309        
310        if (eTime.getJulianDate() < bTime.getJulianDate()) {
311                return false;
312        }
313        return true;
314    }
315    
316    @Override public void applyToDataSelection(DataSelection dataSelection) {
317        
318        if (! begTimeOk()) return;
319        if (! endTimeOk()) return;
320        
321        if (dataSelection == null) {
322            dataSelection = new DataSelection(true);
323        }
324
325        JCalendar cal = begDay.getJCalendar();
326        JDayChooser dayChooser = cal.getDayChooser();
327        int day = dayChooser.getDay();
328        JMonthChooser monthChooser = cal.getMonthChooser();
329        int month = monthChooser.getMonth() + 1;
330        JYearChooser yearChooser = cal.getYearChooser();
331        int year = yearChooser.getYear();
332
333        int hours = 0;
334        int mins = 0;
335        double secs = 0.0;
336        String begTime = beginTimeFld.getText();
337        String[] timeStrings = begTime.split(":");
338        int num = timeStrings.length;
339        if (num > 0)
340            hours = (new Integer(timeStrings[0])).intValue();
341        if (num > 1)
342            mins = (new Integer(timeStrings[1])).intValue();
343        if (num > 2)
344            secs = (new Integer(timeStrings[2])).intValue();
345        if ((hours < 0) || (hours > 23)) hours = 0;
346        if ((mins < 0) || (mins > 59)) mins = 0;
347        if ((secs < 0.0) || (secs > 59.0)) secs = 0.0;
348
349        Time bTime = new Time(year, month, day, hours, mins, secs);
350        double dVal = bTime.getJulianDate();
351        Double bigD = new Double(dVal);
352        String begTimeStr = bigD.toString();
353        dataSelection.putProperty(PROP_BEGTIME, bTime.getDateTimeStr());
354
355        Integer intVal = new Integer(year);
356        dataSelection.putProperty(PROP_YEAR, intVal.toString());
357        intVal = new Integer(month);
358        dataSelection.putProperty(PROP_MONTH, intVal.toString());
359        intVal = new Integer(day);
360        dataSelection.putProperty(PROP_DAY, intVal.toString());
361        intVal = new Integer(hours);
362        dataSelection.putProperty(PROP_HOURS, intVal.toString());
363        intVal = new Integer(mins);
364        dataSelection.putProperty(PROP_MINS, intVal.toString());
365        Double doubleVal = new Double(secs);
366        dataSelection.putProperty(PROP_SECS, doubleVal.toString());
367
368        cal = endDay.getJCalendar();
369        dayChooser = cal.getDayChooser();
370        day = dayChooser.getDay();
371        monthChooser = cal.getMonthChooser();
372        month = monthChooser.getMonth() + 1;
373        yearChooser = cal.getYearChooser();
374        year = yearChooser.getYear();
375
376        String endTime = endTimeFld.getText();
377        timeStrings = endTime.split(":");
378        num = timeStrings.length;
379        if (num > 0)
380            hours = (new Integer(timeStrings[0])).intValue();
381        if (num > 1)
382            mins = (new Integer(timeStrings[1])).intValue();
383        if (num > 2)
384            secs = (new Integer(timeStrings[2])).intValue();
385        if ((hours < 0) || (hours > 23)) hours = 0;
386        if ((mins < 0) || (mins > 59)) mins = 0;
387        if ((secs < 0.0) || (secs > 59.0)) secs = 0.0;
388
389        Time eTime = new Time(year, month, day, hours, mins, secs);
390        dVal = eTime.getJulianDate();
391        bigD = new Double(dVal);
392        String endTimeStr = bigD.toString();
393
394        dataSelection.putProperty(PROP_ENDTIME, eTime.getDateTimeStr());
395        dataSelection.putProperty(PROP_BTIME, begTimeStr);
396        dataSelection.putProperty(PROP_ETIME, endTimeStr);
397        dataSelection.putProperty(PROP_BEGDATE, begDay.getDate());
398        dataSelection.putProperty(PROP_ENDDATE, endDay.getDate());
399    }
400    
401    /**
402     * Applies {@link DataSelection} properties to this instance.
403     * 
404     * @param props Properties from a {@code DataSelection}.
405     *              Cannot be {@code null}.
406     */
407
408    public void applyFromDataSelectionProperties(Hashtable<String, Object> props) {
409
410        if (props.containsKey(PROP_BEGTIME)) {
411            selectedBeginTime = extractTime((String)props.get(PROP_BEGTIME));
412            if (beginTimeFld != null) {
413                beginTimeFld.setText(selectedBeginTime);
414            }
415        }
416        if (props.containsKey(PROP_ENDTIME)) {
417            selectedEndTime = extractTime((String)props.get(PROP_ENDTIME));
418            if (endTimeFld != null) {
419                endTimeFld.setText(selectedEndTime);
420            }
421        }
422        if (props.containsKey(PROP_BEGDATE)) {
423            selectedBeginDate = ((Date) props.get(PROP_BEGDATE));
424            if (begDay != null) {
425                begDay.setDate(selectedBeginDate);
426            }
427        }
428        if (props.containsKey(PROP_ENDDATE)) {
429            selectedEndDate = ((Date) props.get(PROP_ENDDATE));
430            if (endDay != null) {
431                endDay.setDate(selectedEndDate);
432            }
433        }
434
435    }
436    
437    /**
438     * Extract time of day from a string resembling 
439     * {@literal "2019-03-18 13:59:59 UTC"}.
440     * 
441     * @param dateTime String to extract from. Cannot be {@code null}.
442     * 
443     * @return Time of day (UTC).
444     */
445
446    private static String extractTime(String dateTime) {
447        DateTimeFormatter parser =
448            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
449    
450        DateTimeFormatter formatter =
451            DateTimeFormatter.ofPattern("HH:mm:ss");
452    
453        ZonedDateTime zdt = parser.parse(dateTime, ZonedDateTime::from);
454        
455        return formatter.format(zdt);
456    }
457    
458}