/********************************************************************** * This file is part of iDempiere ERP Open Source * * http://www.idempiere.org * * * * Copyright (C) Contributors * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * * Contributors: * * - Peter Takacs, Cloudempiere * **********************************************************************/ package org.adempiere.webui.window; import static org.compiere.model.SystemIDs.REFERENCE_DATESELECTIONMODE; import static org.compiere.model.SystemIDs.REFERENCE_MONTHS; import static org.compiere.model.SystemIDs.REFERENCE_QUARTERS; import static org.compiere.model.SystemIDs.REFERENCE_TIMEUNIT; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.concurrent.TimeUnit; import org.adempiere.exceptions.AdempiereException; import org.adempiere.webui.ClientInfo; import org.adempiere.webui.component.Button; import org.adempiere.webui.component.ComboItem; import org.adempiere.webui.component.Combobox; import org.adempiere.webui.component.Label; import org.adempiere.webui.component.ListItem; import org.adempiere.webui.component.Listbox; import org.adempiere.webui.component.Tab; import org.adempiere.webui.component.Tabbox; import org.adempiere.webui.component.Tabpanel; import org.adempiere.webui.component.Tabpanels; import org.adempiere.webui.component.Tabs; import org.adempiere.webui.component.Textbox; import org.adempiere.webui.component.ZkCssHelper; import org.adempiere.webui.editor.WEditor; import org.adempiere.webui.event.ValueChangeEvent; import org.adempiere.webui.event.ValueChangeListener; import org.adempiere.webui.factory.ButtonFactory; import org.compiere.model.MChart; import org.compiere.model.MRefList; import org.compiere.util.DisplayType; import org.compiere.util.Env; import org.compiere.util.Msg; import org.compiere.util.Util; import org.compiere.util.ValueNamePair; import org.zkoss.zhtml.Br; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.WrongValueException; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.EventListener; import org.zkoss.zk.ui.event.Events; import org.zkoss.zk.ui.event.InputEvent; import org.zkoss.zul.Comboitem; import org.zkoss.zul.Div; import org.zkoss.zul.Popup; import org.zkoss.zul.Spinner; /** * Dialog to set from and to value of range date field editor * @author Peter Takacs, Cloudempiere * */ public class DateRangePicker extends Popup implements EventListener, ValueChangeListener { /** * generated serial id */ private static final long serialVersionUID = -1674676283499219325L; // AD_Reference_ID private static final String DATESELECTIONMODE_PREVIOUS = "01"; private static final String DATESELECTIONMODE_NEXT = "02"; private static final String DATESELECTIONMODE_CURRENT = "03"; private static final String DATESELECTIONMODE_AGO = "04"; private static final String DATESELECTIONMODE_BEFORE = "05"; private static final String DATESELECTIONMODE_AFTER = "06"; private static final String DATESELECTIONMODE_ON = "07"; private static final String DATESELECTIONMODE_BETWEEN = "08"; private static final String DATESELECTIONMODE_QUICK = "09"; /** UI elements */ private Button okBtn; private Combobox modeCombobox; private Spinner numberBox; private Combobox unitCombobox; private Textbox dateTextBox; private org.zkoss.zul.Calendar cal; private org.zkoss.zul.Calendar cal2; private Div quickListBoxes; private Tabbox tabbox; private Tabs tabs; private Tabpanels tabpanels; private Tab fromTab; private Tab toTab; private Tabpanel fromTabPanel; private Tabpanel toTabPanel; private Div midDiv; /** date editor from **/ private WEditor editor; /** date editor to **/ private WEditor editor2; /** value of date from **/ private Date dateFrom; /** value of date to **/ private Date dateTo; /** current/new display value **/ private String displayValue; /** old display value **/ private String oldDisplayValue = ""; /** if true, react to value change event from {@link #editor} and {@link #editor2} **/ private boolean enableValueChange = true; /** is mobile version **/ private boolean isMobile = ClientInfo.isMobile() && ClientInfo.maxWidth(ClientInfo.SMALL_WIDTH-1); /** registered value change listeners **/ protected ArrayList listeners = new ArrayList(); /** rendered list boxes on 'Quick' mode **/ private ArrayList quickListBoxesArray = new ArrayList(); /** selected list item from the list boxes on 'Quick' mode **/ private ListItem selectedQuickListItem; /** * Constructor * @param editor * @param editor2 */ public DateRangePicker(WEditor editor, WEditor editor2) { super(); this.editor = editor; this.editor2 = editor2; init(); } /** * Initialize elements */ private void init() { editor.addValueChangeListener(this); editor2.addValueChangeListener(this); Div div = new Div(); okBtn = ButtonFactory.createNamedButton("ApplyFilter", true, false); modeCombobox = new Combobox(); modeCombobox.setSclass("date-picker-component"); modeCombobox.setWidth("120px"); modeCombobox.addEventListener(Events.ON_SELECT, this); numberBox = new Spinner(1); numberBox.setConstraint("no empty, min 1"); numberBox.setSclass("date-picker-component"); numberBox.addEventListener(Events.ON_CHANGING, this); unitCombobox = new Combobox(); unitCombobox.setSclass("date-picker-component"); unitCombobox.setWidth("90px"); unitCombobox.addEventListener(Events.ON_SELECT, this); // Tabs visible on "Between" mode, mobile screen tabbox = new Tabbox(); tabs = new Tabs(); tabbox.appendChild(tabs); tabpanels = new Tabpanels(); tabbox.appendChild(tabpanels); fromTab = new Tab(Msg.getMsg(Env.getCtx(), "From")); tabs.appendChild(fromTab); toTab = new Tab(Msg.getMsg(Env.getCtx(), "To")); tabs.appendChild(toTab); fromTabPanel = new Tabpanel(); toTabPanel = new Tabpanel(); tabpanels.appendChild(fromTabPanel); tabpanels.appendChild(toTabPanel); cal = new org.zkoss.zul.Calendar(); cal.setSclass("date-picker-component"); cal.addEventListener(Events.ON_CHANGE, this); cal2 = new org.zkoss.zul.Calendar(); cal2.setSclass("date-picker-component"); cal2.addEventListener(Events.ON_CHANGE, this); Label dateTextBoxLabel = new Label(Msg.getMsg(Env.getCtx(), "SelectedTimeRange")); dateTextBoxLabel.setSclass("date-picker-label"); dateTextBox = new Textbox(); dateTextBox.setReadonly(true); dateTextBox.setSclass("date-picker-component"); dateTextBox.setStyle("min-width: 170px; background: white !important"); dateTextBox.setValue(DisplayType.getDateFormat().format(cal.getValue())); dateTextBox.addEventListener(Events.ON_CHANGE, this); okBtn.setSclass("date-picker-component"); okBtn.addEventListener(Events.ON_CLICK, event -> { setDateTextBoxAndDisplayValue(); if(dateFrom != null && dateTo != null && dateTo.before(dateFrom)) throw new WrongValueException(dateTextBox, Msg.getMsg(Env.getCtx(), "EndDateAfterStartDate")); if(Util.isEmpty(dateTextBox.getValue())) { dateFrom = null; dateTo = null; } enableValueChange = false; editor.setValue(dateFrom); editor2.setValue(dateTo); fireValueChange(new ValueChangeEvent(this,"DateRangePicker",oldDisplayValue, displayValue)); enableValueChange = true; this.detach(); }); quickListBoxes = new Div(); quickListBoxes.appendChild(getQuickModeContent()); // Load Modes to ListBox ValueNamePair[] modes = MRefList.getList(Env.getCtx(), REFERENCE_DATESELECTIONMODE, false, "Value"); for(ValueNamePair mode : modes) { ComboItem item = new ComboItem(mode.getName(), mode.getValue()); modeCombobox.appendChild(item); } if(isMobile) { // disable "Quick" mode modeCombobox.removeItemAt(modeCombobox.getItemCount()-1); } modeCombobox.setSelectedIndex(0); // Load Units to ListBox ValueNamePair[] units = MRefList.getList(Env.getCtx(), REFERENCE_TIMEUNIT, false); for(ValueNamePair timeUnit : units) { ComboItem item = new ComboItem(timeUnit.getName(), timeUnit.getValue()); unitCombobox.appendChild(item); } unitCombobox.setSelectedIndex(0); div.setSclass("date-picker-container"); div.appendChild(modeCombobox); if (isMobile) { ZkCssHelper.appendStyle(modeCombobox, "margin: 0px 5px 10px 0px !important;"); div.appendChild(new Br()); } div.appendChild(numberBox); div.appendChild(unitCombobox); this.appendChild(div); midDiv = new Div(); midDiv.appendChild(tabbox); midDiv.setSclass("date-picker-container"); midDiv.setStyle("Margin-top: 10px;"); midDiv.appendChild(cal); midDiv.appendChild(cal2); midDiv.appendChild(quickListBoxes); this.appendChild(midDiv); div = new Div(); div.setSclass("date-picker-container"); div.setStyle("margin: 10px 0 10px 0;"); div.appendChild(dateTextBoxLabel); div.appendChild(dateTextBox); div.setSclass("date-picker-container"); if (isMobile) { div.appendChild(new Br()); ZkCssHelper.appendStyle(okBtn, "margin: 5px 5px 5px 0px !important;"); } else { ZkCssHelper.appendStyle(okBtn, "float: right;"); } div.appendChild(okBtn); this.appendChild(div); if(isMobile) this.setStyle("min-width: 350px;"); else this.setStyle("min-width: 320px;"); dateFrom = (Date) editor.getValue(); dateTo = (Date) editor2.getValue(); if(dateFrom != null || dateTo != null) { // Set the picker to defined Default Logic Date[] dates = setTimesOnDates(dateFrom, dateTo); dateFrom = dates[0]; dateTo = dates[1]; loadPickerSelection(); } else { // If no Default Logic defined, set Current Month as selected range setPickerSelection(DATESELECTIONMODE_CURRENT, MChart.TIMEUNIT_Month, 0); Date[] dates = setTimesOnDates(dateFrom, dateTo); dateFrom = dates[0]; dateTo = dates[1]; } updateUI(); } // init /** * Set picker selection * @param mode * @param unit * @param offset */ private void setPickerSelection(String mode, String unit, int offset) { for(Comboitem item : modeCombobox.getItems()) { if(item.getValue().equals(mode)) modeCombobox.setSelectedItem(item); } for(Comboitem item : unitCombobox.getItems()) { if(item.getValue().equals(unit)) unitCombobox.setSelectedItem(item); } int numBoxValue = Math.abs(offset) >= 1 ? Math.abs(offset) : 1; numberBox.setValue(numBoxValue); setDateTextBoxAndDisplayValue(); } // setPickerSelection /** * Update popup UI */ private void updateUI() { String selectedMode = modeCombobox.getSelectedItem().getValue().toString(); if(selectedMode.equalsIgnoreCase(DATESELECTIONMODE_BETWEEN) && isMobile) { updateCal1AndCal2(); cal.detach(); fromTabPanel.appendChild(cal); cal2.detach(); toTabPanel.appendChild(cal2); } else { cal.detach(); midDiv.appendChild(cal); cal2.detach(); midDiv.appendChild(cal2); } switch (selectedMode) { case DATESELECTIONMODE_AFTER: case DATESELECTIONMODE_BEFORE: case DATESELECTIONMODE_ON: tabbox.setVisible(false); numberBox.setVisible(false); unitCombobox.setVisible(false); cal.setVisible(true); cal2.setVisible(false); quickListBoxes.setVisible(false); break; case DATESELECTIONMODE_BETWEEN: tabbox.setVisible(isMobile); numberBox.setVisible(false); unitCombobox.setVisible(false); cal.setVisible(true); cal2.setVisible(true); quickListBoxes.setVisible(false); break; case DATESELECTIONMODE_CURRENT: tabbox.setVisible(false); numberBox.setVisible(false); unitCombobox.setVisible(true); cal.setVisible(false); cal2.setVisible(false); quickListBoxes.setVisible(false); break; case DATESELECTIONMODE_NEXT: case DATESELECTIONMODE_PREVIOUS: case DATESELECTIONMODE_AGO: tabbox.setVisible(false); numberBox.setVisible(true); unitCombobox.setVisible(true); cal.setVisible(false); cal2.setVisible(false); quickListBoxes.setVisible(false); break; case DATESELECTIONMODE_QUICK: tabbox.setVisible(false); numberBox.setVisible(false); unitCombobox.setVisible(false); cal.setVisible(false); cal2.setVisible(false); quickListBoxes.setVisible(true); break; default: break; } } // updateUI @Override public void onEvent(Event event) throws Exception { Component target = event.getTarget(); if(target.equals(modeCombobox)) updateUI(); if(target instanceof Listbox) { for(Listbox listBox : quickListBoxesArray) { if(!target.equals(listBox)) listBox.setSelectedItem(null); else selectedQuickListItem = listBox.getSelectedItem(); } } if(target instanceof Spinner) { String actValue = String.valueOf(((InputEvent)event).getValue()); if(!Util.isEmpty(actValue)) numberBox.setValue(Integer.valueOf(actValue)); } dateTextBox.clearErrorMessage(); if(!Util.isEmpty(dateTextBox.getValue()) || !event.getTarget().equals(dateTextBox)) { setDateTextBoxAndDisplayValue(); } } // onEvent /** * Calculate the values of the DateTextBox in the popup and the DisplayValue for Text and Range Picker */ private void setDateTextBoxAndDisplayValue() { displayValue = ""; String dateTextBoxValue = parseDateTextBoxValue(); dateTextBox.setValue(dateTextBoxValue); if(Util.isEmpty(displayValue)) displayValue = dateTextBoxValue; } // setDateTextBoxAndDisplayValue /** * @return date range text, for e.g "01/01/2023 - 01/31/2023" */ public String getDateRangeText() { return dateTextBox.getValue(); } // getDateRangeText /** * Parse the value shown in DateTextBox, set dateFrom and dateTo values * @return String value shown in DateTextBox */ private String parseDateTextBoxValue() { String returnVal = ""; Date[] dates; switch (modeCombobox.getSelectedItem().getValue().toString()) { case DATESELECTIONMODE_PREVIOUS: case DATESELECTIONMODE_NEXT: case DATESELECTIONMODE_CURRENT: case DATESELECTIONMODE_AGO: case DATESELECTIONMODE_BEFORE: case DATESELECTIONMODE_AFTER: case DATESELECTIONMODE_ON: returnVal = getIntervalAsString(); break; case DATESELECTIONMODE_BETWEEN: returnVal = DisplayType.getDateFormat().format(cal.getValue()) + " - " + DisplayType.getDateFormat().format(cal2.getValue()); dateFrom = new Timestamp(cal.getValue().getTime()); dateTo = new Timestamp(cal2.getValue().getTime()); dates = setTimesOnDates(dateFrom, dateTo); dateFrom = dates[0]; dateTo = dates[1]; break; case DATESELECTIONMODE_QUICK: if(selectedQuickListItem != null) { String unit = (String) selectedQuickListItem.getAttribute("TimeUnit"); int offset = (int) selectedQuickListItem.getAttribute("Offset"); Date dateFromAttr = (Date) selectedQuickListItem.getAttribute("DateFrom"); dates = getInterval(unit, unit, offset, false, false, dateFromAttr); this.dateFrom = new Timestamp(dates[0].getTime()); this.dateTo = new Timestamp(dates[1].getTime()); dates = setTimesOnDates(this.dateFrom, this.dateTo); this.dateFrom = dates[0]; this.dateTo = dates[1]; returnVal = DisplayType.getDateFormat().format(this.dateFrom) + " - " + DisplayType.getDateFormat().format(this.dateTo); } break; default: throw new AdempiereException("InvalidDateSelectionMode"); } return returnVal; } // parseDateTextBoxValue /** * Load the picker selection based on the default date values set with auto-detection */ private void loadPickerSelection() { String detectedMode = null; String detectedUnit = null; int detectedOffset = 0; Timestamp today = new Timestamp(System.currentTimeMillis()); today = new Timestamp(setTimesOnDates(today, null)[0].getTime()); detectedMode = autodetectMode(today); if(detectedMode != null) { String[] arr = autodetectUnitAndCorrectMode(today, detectedMode); if(arr != null) { detectedUnit = arr[0]; if(!Util.isEmpty(arr[1])) detectedMode = arr[1]; } } if(!Util.isEmpty(detectedMode) && !Util.isEmpty(detectedUnit)) { detectedOffset = autodetectOffset(detectedMode, detectedUnit); } for(Comboitem item : modeCombobox.getItems()) { if(item.getValue().equals(detectedMode)) { modeCombobox.setSelectedItem(item); break; } } for(Comboitem item : unitCombobox.getItems()) { if(item.getValue().equals(detectedUnit)) { unitCombobox.setSelectedItem(item); break; } } int numBoxValue = Math.abs(detectedOffset) >= 1 ? Math.abs(detectedOffset) : 1; numberBox.setValue(numBoxValue); updateCal1AndCal2(); setDateTextBoxAndDisplayValue(); } // loadPickerSelection /** * Update the picker calendars with current values */ private void updateCal1AndCal2() { if(dateFrom == null) { cal.setValue(dateTo); cal2.setValue(dateTo); } else if(dateTo == null) { cal.setValue(dateFrom); cal2.setValue(dateFrom); } else { cal.setValue(dateFrom); cal2.setValue(dateTo); } } // updateCal1AndCal2 /** * Auto-detect the date selection mode based on the default date values set * @param today * @return String date selection mode (value) */ private String autodetectMode(Timestamp today) { Date d1 = dateFrom; Date d2 = dateTo; d1 = setTimesOnDates(d1, null)[0]; d2 = setTimesOnDates(d2, null)[0]; if(d1 != null && d2 == null) return DATESELECTIONMODE_AFTER; else if(d1 == null && d2 != null) return DATESELECTIONMODE_BEFORE; else if(d1.compareTo(d2) == 0) return DATESELECTIONMODE_ON; if(d1.after(d2)) return null; if(d1.before(today)){ if(d2.before(today)) return DATESELECTIONMODE_PREVIOUS; else if(d2.after(today)) return DATESELECTIONMODE_CURRENT; else return DATESELECTIONMODE_BETWEEN; } else if(d1.after(today)){ return DATESELECTIONMODE_NEXT; } else return DATESELECTIONMODE_BETWEEN; } // autodetectMode /** * Auto-detect the time unit based on the default date values set, and correct the detected mode if necessary * @param today * @param predictedMode * @return [detectedUnit, correctedMode] */ private String[] autodetectUnitAndCorrectMode(Timestamp today, String predictedMode) { // use case: modes Before, After, On - unit is not needed Date d1 = dateFrom; Date d2 = dateTo; d1 = setTimesOnDates(d1, null)[0]; d2 = setTimesOnDates(d2, null)[0]; if(d1 == null || d2 == null || (d1.compareTo(d2) == 0)) return null; if(dateFrom.after(dateTo)) return null; String detectedUnit = ""; String correctedMode = predictedMode; Calendar calendar = Calendar.getInstance(Env.getLocale(Env.getCtx())); Calendar calendar2 = Calendar.getInstance(Env.getLocale(Env.getCtx())); Calendar calendarToday = Calendar.getInstance(Env.getLocale(Env.getCtx())); Calendar testCalendar = (Calendar) calendarToday.clone(); int timeUnit = 0; calendar.setTime(dateFrom); calendar2.setTime(dateTo); calendarToday.setTime(today); // check start and end day of periods if(calendar.get(Calendar.DAY_OF_YEAR) == 1 && calendar2.get(Calendar.DAY_OF_YEAR) == calendar2.getActualMaximum(Calendar.DAY_OF_YEAR)) { detectedUnit = MChart.TIMEUNIT_Year; timeUnit = Calendar.YEAR; } else if(calendar.get(Calendar.DAY_OF_MONTH) == 1 && calendar2.get(Calendar.DAY_OF_MONTH) == calendar2.getActualMaximum(Calendar.DAY_OF_MONTH)) { if((calendar.get(Calendar.MONTH) == Calendar.JANUARY && calendar2.get(Calendar.MONTH) == Calendar.MARCH ) || (calendar.get(Calendar.MONTH) == Calendar.APRIL && calendar2.get(Calendar.MONTH) == Calendar.JUNE) || (calendar.get(Calendar.MONTH) == Calendar.JULY && calendar2.get(Calendar.MONTH) == Calendar.SEPTEMBER) || (calendar.get(Calendar.MONTH) == Calendar.OCTOBER && calendar2.get(Calendar.MONTH) == Calendar.DECEMBER) && (calendar.get(Calendar.MONTH) != calendar2.get(Calendar.MONTH) && calendar.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR))) detectedUnit = MChart.TIMEUNIT_Quarter; else detectedUnit = MChart.TIMEUNIT_Month; timeUnit = Calendar.MONTH; } else if(calendar.get(Calendar.DAY_OF_WEEK) == calendar.getFirstDayOfWeek() && calendar2.get(Calendar.DAY_OF_WEEK) == calendar2.getActualMaximum(Calendar.DAY_OF_WEEK)) { detectedUnit = MChart.TIMEUNIT_Week; timeUnit = Calendar.WEEK_OF_YEAR; } else { if(predictedMode.equalsIgnoreCase(DATESELECTIONMODE_CURRENT)) correctedMode = DATESELECTIONMODE_BETWEEN; else { detectedUnit = MChart.TIMEUNIT_Day; timeUnit = Calendar.DAY_OF_YEAR; } } int testOffset = 1; if(detectedUnit.equalsIgnoreCase(MChart.TIMEUNIT_Quarter)) testOffset = 3; // three months = 1 quarter testCalendar.add(timeUnit, -testOffset); if(testCalendar.getTime().after(dateTo)) { detectedUnit = MChart.TIMEUNIT_Day; correctedMode = DATESELECTIONMODE_BETWEEN; } testCalendar = (Calendar) calendarToday.clone(); testCalendar.add(timeUnit, testOffset); if(testCalendar.getTime().before(dateFrom)) { detectedUnit = MChart.TIMEUNIT_Day; correctedMode = DATESELECTIONMODE_BETWEEN; } //re-validate current month detection if (detectedUnit == MChart.TIMEUNIT_Month ) { if (correctedMode.equalsIgnoreCase(DATESELECTIONMODE_CURRENT)) { if (calendar.get(Calendar.MONTH) != calendar2.get(Calendar.MONTH) || calendar.get(Calendar.YEAR) != calendar2.get(Calendar.YEAR)) { detectedUnit = MChart.TIMEUNIT_Day; correctedMode = DATESELECTIONMODE_BETWEEN; } else if (calendar.get(Calendar.MONTH) != calendarToday.get(Calendar.MONTH) || calendar.get(Calendar.YEAR) != calendarToday.get(Calendar.YEAR)) { detectedUnit = MChart.TIMEUNIT_Day; correctedMode = DATESELECTIONMODE_BETWEEN; } } } return new String[] {detectedUnit, correctedMode}; } // autodetectUnitAndCorrectMode /** * Auto-detect the time offset based on the default date values set * @param mode * @param unit Year (Y), Quarter (Q), Month (M) or Week (W) * @return difference between from and to date (in unit) */ private int autodetectOffset(String mode, String unit) { Date date = dateFrom; Date date2 = dateTo; long diffInMillies = Math.abs(date2.getTime() - date.getTime()); long diff = TimeUnit.DAYS.convert(diffInMillies+1, TimeUnit.MILLISECONDS); switch (unit) { case MChart.TIMEUNIT_Year: diff = diff / 365; break; case MChart.TIMEUNIT_Quarter: diff = diff / 89; break; case MChart.TIMEUNIT_Month: diff = diff / 28; break; case MChart.TIMEUNIT_Week: diff = diff / 7; break; } if(mode.equalsIgnoreCase(DATESELECTIONMODE_PREVIOUS)) diff = -diff; return (int) diff; } // autodetectOffset /** * Get the currently selected date interval * @return from and to dates separated by " - " */ private String getIntervalAsString() { Timestamp ts = new Timestamp(cal.getValue().getTime()); String mode = modeCombobox.getSelectedItem().getValue().toString(); String unit = unitCombobox.getSelectedItem().getValue().toString(); Integer numBoxValue = numberBox.getValue(); Date[] dates; if(numBoxValue == null) { numBoxValue = 1; numberBox.setValue(numBoxValue); } if(mode.equalsIgnoreCase(DATESELECTIONMODE_AFTER)) { displayValue = Msg.getMsg(Env.getCtx(), "AfterDate", new Object[] {DisplayType.getDateFormat().format(ts)}); dateFrom = ts; dateFrom = setTimesOnDates(dateFrom, null)[0]; dateTo = null; return displayValue; } else if(mode.equalsIgnoreCase(DATESELECTIONMODE_BEFORE)) { displayValue = Msg.getMsg(Env.getCtx(), "BeforeDate", new Object[] {DisplayType.getDateFormat().format(ts)}); dateFrom = null; dateTo = ts; dateTo = setTimesOnDates(null, dateTo)[1]; return displayValue; } else if(mode.equalsIgnoreCase(DATESELECTIONMODE_ON)) { displayValue = Msg.getMsg(Env.getCtx(), "OnDate", new Object[] {DisplayType.getDateFormat().format(ts)}); dateFrom = ts; dateTo = ts; dates = setTimesOnDates(dateFrom, dateTo); dateFrom = dates[0]; dateTo = dates[1]; return displayValue; } if(mode.equalsIgnoreCase(DATESELECTIONMODE_PREVIOUS) || mode.equalsIgnoreCase(DATESELECTIONMODE_AGO)) numBoxValue = -numBoxValue; if(mode.equalsIgnoreCase(DATESELECTIONMODE_CURRENT)) dates = getInterval(unit, 0); else if(mode.equalsIgnoreCase(DATESELECTIONMODE_AGO)) dates = getInterval(unit, unit, numBoxValue.intValue(), false, false, null); else dates = getInterval(unit, numBoxValue.intValue()); dateFrom = new Timestamp(dates[0].getTime()); dateTo = new Timestamp(dates[1].getTime()); dates = setTimesOnDates(dateFrom, dateTo); dateFrom = dates[0]; dateTo = dates[1]; displayValue = datesToHumanReadable(mode, unit, numBoxValue); return DisplayType.getDateFormat().format(dateFrom) + " - " + DisplayType.getDateFormat().format(dateTo); } // getIntervalAsString /** * Calls {@link #getInterval(String, String, int, boolean, boolean, Date) getInterval} method * @param timeUnit * @param offset * @return array of dates: {DateFrom, DateTo} */ private Date[] getInterval(String timeUnit, int offset) { return getInterval(timeUnit, null, offset, false, false, null); } // getInterval /** * Calculate time interval based on the selected parameters. * @param timeUnit - used with the offset parameter to set an exact date-time (e.g. current date-time 1 year ago) * @param timeUnitForRange - after an exact date-time is set with timeUnit and offset, timeUnitForRange defines what time * range should be defined around the exact date-time ((e.g. current day/week/month/year 1 year ago)) * @param offset - used with the timeUnit parameter to set an exact date-time (e.g. current date-time 1 year ago) * @param isToDate - if false, set date from and to to the beginning and end of the given time interval * @param includeThis - if true, it will include the current date (e.g. return from the beginning of last month until today/until end of last month) * @param dateFrom - define date from (default now) * @return array of dates: [DateFrom, DateTo] */ private Date[] getInterval(String timeUnit, String timeUnitForRange, int offset, boolean isToDate, boolean includeThis, Date dateFrom) { if(dateFrom == null) dateFrom = new Date(System.currentTimeMillis()); Calendar cal1 = Calendar.getInstance(Env.getLocale(Env.getCtx())); Calendar cal2 = Calendar.getInstance(Env.getLocale(Env.getCtx())); cal1.setTime(dateFrom); cal2.setTime(dateFrom); int iUnit; int iDayUnit; // Transform Time Unit switch (timeUnit) { case MChart.TIMEUNIT_Day: iUnit = Calendar.DAY_OF_YEAR; iDayUnit = Calendar.DAY_OF_YEAR; break; case MChart.TIMEUNIT_Month: iUnit = Calendar.MONTH; iDayUnit = Calendar.DAY_OF_MONTH; break; case MChart.TIMEUNIT_Quarter: iUnit = Calendar.MONTH; iDayUnit = Calendar.DAY_OF_MONTH; offset = offset * 3; break; case MChart.TIMEUNIT_Week: iUnit = Calendar.WEEK_OF_YEAR; iDayUnit = Calendar.DAY_OF_WEEK; break; case MChart.TIMEUNIT_Year: iUnit = Calendar.YEAR; iDayUnit = Calendar.DAY_OF_YEAR; break; default: throw new AdempiereException("TimeUnitNotSupported"); } if(!Util.isEmpty(timeUnitForRange) || !isToDate){ boolean hasTimeUnitForRange = true; if(Util.isEmpty(timeUnitForRange)) { timeUnitForRange = timeUnit; hasTimeUnitForRange = false; } // Add the offset if(hasTimeUnitForRange && offset != 0) { cal1.add(iUnit, offset); cal2.add(iUnit, offset); } else if(!hasTimeUnitForRange && !isToDate) { if(offset < 0) { cal1.add(iUnit, offset); if(!includeThis) { if(timeUnitForRange.equalsIgnoreCase(MChart.TIMEUNIT_Quarter)) cal2.add(iUnit, -3); else cal2.add(iUnit, -1); } } else if (offset > 0) { if(!includeThis) { if(timeUnitForRange.equalsIgnoreCase(MChart.TIMEUNIT_Quarter)) cal1.add(iUnit, 3); else cal1.add(iUnit, 1); } cal2.add(iUnit, offset); } } // Set first and last Day of the given time period if(timeUnitForRange.equalsIgnoreCase(MChart.TIMEUNIT_Week)) { cal1.set(iDayUnit, cal1.getFirstDayOfWeek()); cal2.set(iDayUnit, cal2.getFirstDayOfWeek()); cal2.add(iDayUnit, 6); } else if(timeUnitForRange.equalsIgnoreCase(MChart.TIMEUNIT_Quarter)){ if(cal1.after(new GregorianCalendar(cal1.get(Calendar.YEAR), Calendar.OCTOBER, 1))) { cal1.set(Calendar.MONTH, Calendar.OCTOBER); cal2.set(Calendar.MONTH, Calendar.DECEMBER); } else if(cal1.after(new GregorianCalendar(cal1.get(Calendar.YEAR), Calendar.JULY, 1))) { cal1.set(Calendar.MONTH, Calendar.JULY); cal2.set(Calendar.MONTH, Calendar.SEPTEMBER); } else if(cal1.after(new GregorianCalendar(cal1.get(Calendar.YEAR), Calendar.APRIL, 1))) { cal1.set(Calendar.MONTH, Calendar.APRIL); cal2.set(Calendar.MONTH, Calendar.JUNE); } else { cal1.set(Calendar.MONTH, Calendar.JANUARY); cal2.set(Calendar.MONTH, Calendar.MARCH); } cal1.set(Calendar.DAY_OF_MONTH, cal1.getActualMinimum(iDayUnit)); cal2.set(Calendar.DAY_OF_MONTH, cal2.getActualMaximum(Calendar.DAY_OF_MONTH)); } else if (!timeUnitForRange.equalsIgnoreCase(MChart.TIMEUNIT_Day)) { cal1.set(iDayUnit, cal1.getActualMinimum(iDayUnit)); cal2.set(iDayUnit, cal2.getActualMaximum(iDayUnit)); } } else if(isToDate) { if(offset < 0) cal1.add(iUnit, offset); else if (offset > 0) cal2.add(iUnit, offset); } Date date1 = cal1.getTime(); Date date2 = cal2.getTime(); return new Date[] {date1, date2}; } // getInterval /** * Convert dates to human readable form (e.g. Last Month) * @param mode * @param unit * @param offset * @return String */ private String datesToHumanReadable(String mode, String unit, Integer offset) { if(offset < 0) offset = -offset; if(mode.equalsIgnoreCase(DATESELECTIONMODE_CURRENT)) offset = -1; String unitVal = ""; for(Comboitem item : unitCombobox.getItems()) { if(item.getValue().equals(unit)) unitVal = item.getLabel(); } if(DATESELECTIONMODE_AGO.equals(mode)) return Msg.getMsg(Env.getCtx(), "DatePickerAgo", new Object[] {offset, unitVal}); String msgVal = ""; String modeVal = ""; switch (unit) { case MChart.TIMEUNIT_Day: msgVal = "DatePickerDay"; break; case MChart.TIMEUNIT_Month: msgVal = "DatePickerMonth"; break; case MChart.TIMEUNIT_Quarter: msgVal = "DatePickerQuarter"; break; case MChart.TIMEUNIT_Week: msgVal = "DatePickerWeek"; break; case MChart.TIMEUNIT_Year: msgVal = "DatePickerYear"; break; default: throw new AdempiereException("TimeUnitNotSupported"); } for(Comboitem item : modeCombobox.getItems()) { if(item.getValue().equals(mode)) modeVal = item.getLabel(); } return Msg.getMsg(Env.getCtx(), msgVal, new Object[]{modeVal, offset}); } // datesToHumanReadable /** * Set time on dateFrom: 00:00:00; on dateTo: 23:59:99 * @param dateFrom * @param dateTo * @return date array {dateFrom, dateTo} */ private Date[] setTimesOnDates(Date dateFrom, Date dateTo) { Calendar cal = Calendar.getInstance(Env.getLocale(Env.getCtx())); if(dateFrom != null) { cal.setTime(dateFrom); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); dateFrom = new Timestamp(cal.getTime().getTime()); } if(dateTo != null) { cal.setTime(dateTo); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); cal.add(Calendar.DAY_OF_MONTH, 1); cal.add(Calendar.MILLISECOND, -1); dateTo = new Timestamp(cal.getTime().getTime()); } return new Date[] {dateFrom, dateTo}; } // setTimesOnDates /** * Create a ListItem for Quick mode * @param value * @param timeUnit * @param offset * @param dateFrom * @return ListItem */ private ListItem createItem(String value, String timeUnit, int offset, Date dateFrom) { ListItem item = new ListItem(value, value); item.setAttribute("Offset", offset); item.setAttribute("TimeUnit", timeUnit); item.setAttribute("DateFrom", dateFrom); return item; } // createItem /** * Initialize Quick mode content * @return Div */ private Div getQuickModeContent() { Calendar cNow = Calendar.getInstance(Env.getLocale(Env.getCtx())); cNow.setTime(new Date(System.currentTimeMillis())); Calendar c = Calendar.getInstance(Env.getLocale(Env.getCtx())); c.setTime(new Date(System.currentTimeMillis())); Div wrapperDiv1 = new Div(); wrapperDiv1.setStyle("width: 420px; display: flex; border-top: 2px solid #CFCFCF;"); // Months Div wrapperDiv2 = new Div(); wrapperDiv2.setStyle("width: 420px; display: flex;"); Label label = new Label(Msg.getMsg(Env.getCtx(), "CurrentYearMonths")); label.setSclass("date-picker-label"); Div monthLabelDiv = new Div(); monthLabelDiv.setStyle("margin-top: 10px;"); monthLabelDiv.appendChild(label); c = cNow; int idx=0; int monthIdx = Calendar.APRIL; ListItem item; Listbox box; Div innerDiv; while (monthIdx <= Calendar.DECEMBER) { innerDiv = new Div(); innerDiv.setSclass("date-picker-component"); innerDiv.setStyle("margin: 5px 5px 16px 5px !important; width: 33%;"); box = new Listbox(); box.addEventListener(Events.ON_SELECT, this); box.setCheckmark(true); ValueNamePair[] months = MRefList.getList(Env.getCtx(), REFERENCE_MONTHS, false, "Value"); for(int i = idx; i <= monthIdx; i++) { c.set(Calendar.MONTH, i); item = createItem(months[i].getName(), MChart.TIMEUNIT_Month, 0, new Date(c.getTimeInMillis())); box.appendChild(item); idx=i; } innerDiv.appendChild(box); quickListBoxesArray.add(box); wrapperDiv1.appendChild(innerDiv); idx++; monthIdx += 4; } // Quarters c = cNow; innerDiv = new Div(); innerDiv.setSclass("date-picker-component"); innerDiv.setStyle("width: 50%;"); label = new Label(Msg.getMsg(Env.getCtx(), "CurrentYearQuarters")); label.setSclass("date-picker-label"); innerDiv.appendChild(label); box = new Listbox(); box.setStyle("border-top: 2px solid #CFCFCF;"); box.addEventListener(Events.ON_SELECT, this); box.setCheckmark(true); ValueNamePair[] quarters = MRefList.getList(Env.getCtx(), REFERENCE_QUARTERS, false, "Value"); idx = 0; for(ValueNamePair quarter : quarters) { c.set(Calendar.MONTH, idx); item = createItem(quarter.getName(), MChart.TIMEUNIT_Quarter, 0, new Date(c.getTimeInMillis())); box.appendChild(item); idx+=3; } innerDiv.appendChild(box); quickListBoxesArray.add(box); wrapperDiv2.appendChild(innerDiv); // Year innerDiv = new Div(); innerDiv.setSclass("date-picker-component"); innerDiv.setStyle("width: 50%;"); label = new Label(Msg.getMsg(Env.getCtx(), "Year")); label.setSclass("date-picker-label"); innerDiv.appendChild(label); box = new Listbox(); box.setStyle("border-top: 2px solid #CFCFCF;"); box.addEventListener(Events.ON_SELECT, this); box.setCheckmark(true); for(int i=0; i>=-3; i--) { item = createItem(String.valueOf(c.get(Calendar.YEAR)), MChart.TIMEUNIT_Year, i, null); box.appendChild(item); c.add(Calendar.YEAR, -1); } innerDiv.appendChild(box); quickListBoxesArray.add(box); wrapperDiv2.appendChild(innerDiv); Div div = new Div(); div.appendChild(monthLabelDiv); div.appendChild(wrapperDiv1); div.appendChild(wrapperDiv2); return div; } // getQuickModeContent /** * Get Display Value * @return String */ public String getDisplayValue() { return this.displayValue; } // getDisplayValue /** * Add value change listener * @param listener */ public void addValueChangeListener(ValueChangeListener listener) { if (listener == null) return; if (!listeners.contains(listener)) listeners.add(listener); } // addValueChangeListener /** * Remove value change listener * @param listener * @return true if the class had the specified listener */ public boolean removeValuechangeListener(ValueChangeListener listener) { return listeners.remove(listener); } // removeValuechangeListener /** * Fire value change event * @param event */ protected void fireValueChange(ValueChangeEvent event) { //copy to array to avoid concurrent modification exception ValueChangeListener[] vcl = new ValueChangeListener[listeners.size()]; listeners.toArray(vcl); for (ValueChangeListener listener : vcl) { listener.valueChange(event); } } // fireValueChange @Override public void valueChange(ValueChangeEvent evt) { if(enableValueChange) { dateFrom = (Date) editor.getValue(); dateTo = (Date) editor2.getValue(); if(dateFrom != null || dateTo != null) { // Set the picker to defined Default Logic Date[] dates = setTimesOnDates(dateFrom, dateTo); dateFrom = dates[0]; dateTo = dates[1]; loadPickerSelection(); } else { // If no Default Logic defined, set Current Month as selected range setPickerSelection(DATESELECTIONMODE_CURRENT, MChart.TIMEUNIT_Month, 0); Date[] dates = setTimesOnDates(dateFrom, dateTo); dateFrom = dates[0]; dateTo = dates[1]; } updateUI(); fireValueChange(new ValueChangeEvent(this,"DateRangePicker",oldDisplayValue, displayValue)); } } }