package com.applang.components; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.regex.MatchResult; import java.util.regex.Pattern; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.text.ParseException; import java.text.SimpleDateFormat; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFormattedTextField; import javax.swing.JLabel; import javax.swing.JPanel; import android.util.Log; import static com.applang.Util.*; import static com.applang.Util2.*; import static com.applang.SwingUtil.*; public class DatePicker { private static final String TAG = DatePicker.class.getSimpleName(); public static int modality = Behavior.MODAL; public static void main(String[] args) { modality |= Behavior.EXIT_ON_CLOSE; long time = now(); println(pickADate(time, "", "", timeLine(time, 1))); } class PickButton extends JButton { public PickButton() { this(null, null, 0,0,0,0); } public PickButton(String text) { this(text, null, 6,0,6,0); } public PickButton(String text, Icon icon, int...tlbr) { super(text, icon); adjustButtonSize(this, tlbr); } } int monthOfYear, year; int rows = 7, cols = 8; String date; PickButton month = new PickButton(""); PickButton[] weeks, days; SimpleDateFormat sdf = new SimpleDateFormat(); Long[] timeLine = null; Font font = monoSpaced(Font.BOLD); public DatePicker(Component relative, Object... params) { date = param_String("", 0, params); timeLine = param(null, 1, params); String title = param_String("Date Picker", 2, params); final boolean cancelable = param_Boolean(false, 3, params); sdf.setCalendar(getCalendar()); showDialog(null, relative, title, new UIFunction() { public Component[] apply(Component comp, Object[] parms) { JDialog dialog = (JDialog) comp; JPanel[] panels = panels(dialog); dialog.add(panels[1], BorderLayout.NORTH); if (cancelable) dialog.add(panels[2], BorderLayout.SOUTH); dialog.add(panels[0], BorderLayout.CENTER); return null; } }, new UIFunction() { public Component[] apply(Component comp, Object[] parms) { setDateString(date); month.requestFocusInWindow(); return null; } }, new UIFunction() { public Component[] apply(Component comp, Object[] parms) { if (cancelable && !dateChanged) setDate(""); return null; } }, modality); } public JPanel[] panels(final JDialog dialog) { JPanel p1 = new JPanel(new GridLayout(rows, cols)); p1.setPreferredSize(new Dimension(400, 200)); final PickButton[] buttons = new PickButton[rows*cols]; String[] header = { "", "S", "M", "T", "W", "T", "F", "S" }; ArrayList<PickButton> w = alist(); ArrayList<PickButton> d = alist(); for (int x = 0; x < buttons.length; x++) { final int selection = x; buttons[x] = new PickButton(); buttons[x].setFocusPainted(false); buttons[x].setBorderPainted(false); buttons[x].setBackground(Color.white); if (x < cols) { buttons[x].setText(header[x]); buttons[x].setForeground(Color.red); } else { buttons[x].addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { setDate(buttons[selection].getActionCommand()); dialog.setVisible(false); } }); if (x % cols == 0) w.add(buttons[x]); else d.add(buttons[x]); } p1.add(buttons[x]); } weeks = w.toArray(new PickButton[]{}); days = d.toArray(new PickButton[]{}); JPanel p2 = new JPanel(new GridLayout(1, 5)); move[0] = new PickButton(moves[0]); move[0].addActionListener(moveMonth); move[1] = new PickButton(moves[1]); move[1].addActionListener(moveMonth); move[2] = new PickButton(moves[2]); move[2].addActionListener(moveMonth); move[3] = new PickButton(moves[3]); move[3].addActionListener(moveMonth); p2.add(move[0]); p2.add(move[1]); month.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { setDate(month.getActionCommand()); dialog.setVisible(false); } }); p2.add(month); p2.add(move[2]); p2.add(move[3]); JPanel p3 = new JPanel(new GridLayout(1, 2)); p3.add(new JLabel("number of days (into the past)")); final JFormattedTextField tf = new JFormattedTextField("" + ndays); tf.setFont(font); tf.setForeground(Color.BLUE); tf.setHorizontalAlignment(JFormattedTextField.CENTER); tf.addPropertyChangeListener("value", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { ndays = toInt(ndays, tf.getText()); } }); p3.add(tf); return new JPanel[] {p1,p2,p3}; } private boolean dateChanged = false; public void setDate(String date) { this.date = date; dateChanged = true; } String[] moves = strings("<<", "<", ">", ">>"); PickButton[] move = new PickButton[moves.length]; ActionListener moveMonth = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { getCalendar().set(year, monthOfYear, 1); switch (asList(moves).indexOf(e.getActionCommand())) { case 0: getCalendar().add(Calendar.YEAR, -1); break; case 1: getCalendar().add(Calendar.MONTH, -1); break; case 2: getCalendar().add(Calendar.MONTH, 1); break; case 3: getCalendar().add(Calendar.YEAR, 1); break; } monthOfYear = getCalendar().get(Calendar.MONTH); year = getCalendar().get(Calendar.YEAR); displayDate(); } }; void displayDate() { getCalendar().set(year, monthOfYear, 1); weekOfMonthOfFirstDayOfMonth = getCalendar().get(Calendar.WEEK_OF_MONTH); month.setForeground(Color.BLUE); Date time = getCalendar().getTime(); sdf.applyPattern("MMM yyyy"); month.setText(sdf.format(time)); sdf.applyPattern(monthFormat); month.setToolTipText(sdf.format(time)); clearDisplay(); int dayOfWeek = getCalendar().get(Calendar.DAY_OF_WEEK); int daysInMonth = getCalendar().getActualMaximum(Calendar.DAY_OF_MONTH); int weekInYear = 0; sdf.applyPattern(weekFormat); for (int x = dayOfWeek - 1, day = 1; day <= daysInMonth; x++, day++) { setDay(x, day); weekInYear = setWeek(weekInYear); getCalendar().add(Calendar.DATE, 1); } } private void clearDisplay() { for (int x = 0; x < days.length; x++) days[x].setText(""); for (int x = 0; x < weeks.length; x++) weeks[x].setText(""); } int weekOfMonthOfFirstDayOfMonth = 1; private int setWeek(int weekInYear) { int week = weekInYear; weekInYear = getCalendar().get(Calendar.WEEK_OF_YEAR); if (week != weekInYear) { int weekOfMonth = getCalendar().get(Calendar.WEEK_OF_MONTH); long time; if (week > weekInYear && weekOfMonth > 1) time = timeInMillis(year + 1, Calendar.JANUARY, 1); else time = getCalendar().getTimeInMillis(); String format = sdf.format(time); PickButton btn = weeks[weekOfMonth - weekOfMonthOfFirstDayOfMonth]; btn.setForeground(Color.BLUE); btn.setText(format); } return weekInYear; } private void setDay(int x, int day) { days[x].setText("" + day); boolean flag = false; int crit = -1; if (timeLine != null) { long time = timeInMillis(year, -monthOfYear, day); crit = Arrays.binarySearch(timeLine, time); if (crit < 0) { crit = -crit - 2; flag = crit > -1 && crit < timeLine.length - 1; } else flag = true; } days[x].setForeground(flag ? Color.GREEN : Color.BLACK); } public void setDateString(String text) { setCalendar(text); monthOfYear = getCalendar().get(Calendar.MONTH); year = getCalendar().get(Calendar.YEAR); displayDate(); } private void setCalendar(String text) { try { switch (kindOfDate(text)) { case 1: sdf.applyPattern(calendarFormat); break; case 2: sdf.applyPattern(weekFormat); break; case 3: sdf.applyPattern(monthFormat); break; default: int day = Integer.parseInt(text); getCalendar().set(year, monthOfYear, day); sdf.applyPattern(calendarFormat); return; } Date dt = sdf.parse(text); getCalendar().setTime(dt); } catch (Exception e) {} } public String getDateString() { if (date.length() < 1 || kindOfDate(date) > 0) return date; else return getDateString(calendarFormat); } private String getDateString(String format) { int kind = kindOfDate(date); if (weekFormat.equals(format) && kind == 2) return date; if (monthFormat.equals(format) && kind == 3) return date; if (calendarFormat.equals(format) && kind == 1) return date; if (nullOrEmpty(format)) if (kind > 0 || date.length() < 1) return date; else format = calendarFormat; setCalendar(date); sdf.applyPattern(format); return sdf.format(getCalendar().getTime()); } public static String pickADate(long time, String format, String title, Long...timeLine) { try { String dateString = formatDate(time, calendarFormat, Locale.getDefault()); return new DatePicker(null, dateString, timeLine, title) .getDateString(format); } catch (Exception e) { Log.e(TAG, "pickADate", e); } return ""; } public static Long[] timeLine(long time, int days) { ValList list = vlist(); for (int i = days; i > 0; i--) { list.add(time - getMillis(i) + 1); } list.add(time); return list.toArray(new Long[0]); } public static int[] pickAPeriod(int[] period, String title) { try { long time = dateInMillis(period[0], period[1] - 1, period[2]); ndays = Math.max(1, period[3]); String dateString = formatDate(time, calendarFormat, Locale.getDefault()); dateString = new DatePicker(null, dateString, timeLine(time, ndays), title, true) .getDateString(""); if (dateString.length() > 0) return parsePeriod(dateString); } catch (Exception e) { Log.e(TAG, "pickAPeriod", e); } return null; } public static final String monthFormat = "MMMM yyyy"; public static final String weekFormat = "w/yy"; public static final String calendarFormat = "EEEEE, dd.MMM.yyyy"; public static int kindOfDate(String dateString) { if (isWeekDate(dateString)) return 2; else if (isCalendarDate(dateString)) return 1; else if (isMonthDate(dateString)) return 3; else return 0; } public static boolean isCalendarDate(String dateString) { int slash = dateString.indexOf(","); return slash > -1 && slash < dateString.length() - 2 && slash == dateString.lastIndexOf(","); } public static boolean isWeekDate(String dateString) { int slash = dateString.indexOf("/"); return slash > -1 && slash < dateString.length() - 1 && slash == dateString.lastIndexOf("/"); } public static boolean isMonthDate(String dateString) { int slash = dateString.indexOf(" "); return slash > -1 && slash < dateString.length() - 1 && slash == dateString.lastIndexOf(" "); } public static int[] parseCalendarDate(String dateString) { return getCalendarDate(toTime(dateString, calendarFormat, Locale.getDefault())); } public static int[] parseWeekDate(String dateString) { int slash = dateString.indexOf("/"); if (slash < 0) return null; int[] parts = new int[2]; parts[0] = Integer.parseInt(dateString.substring(0, slash)); parts[1] = Integer.parseInt(dateString.substring(1 + slash)); if (parts[1] < 100) { int year = getCalendarDate(now())[2] % 100; parts[1] = parts[1] > year ? parts[1] + 1900 : parts[1] + 2000; } return parts; } public static String weekDate(long[] interval) { int[] val0 = parseWeekDate(formatDate(interval[0], weekFormat)); int[] val1 = parseWeekDate(formatDate(interval[1], weekFormat)); if (val1[0] - val0[0] == 1 && val0[1] != val1[1]) return val0[0] + "/" + val1[1] % 100; else return val0[0] + "/" + val0[1] % 100; } public static int[] parseMonthDate(String dateString) { int[] parts = new int[2]; int year = getCalendar().get(Calendar.YEAR) % 100; try { Date date = new SimpleDateFormat(monthFormat, Locale.getDefault()).parse(dateString); getCalendar().setTime(date); parts[0] = getCalendar().get(Calendar.MONTH); parts[1] = getCalendar().get(Calendar.YEAR); } catch (ParseException e) { return null; } if (parts[1] < 100) { parts[1] = parts[1] > year ? parts[1] + 1900 : parts[1] + 2000; } return parts; } public static String monthDate(long[] interval) { return formatDate(interval[0], monthFormat); } private static int ndays = 1; /** * @param dateString like from {@link #DatePicker} * @return an int array [year, month, day, number of days] or null if dateString is not recognized */ public static int[] parsePeriod(String dateString) { int[] parts = null; int kind = DatePicker.kindOfDate(dateString); int days; switch(kind) { case 1: parts = DatePicker.parseCalendarDate(dateString); days = ndays; break; case 2: long[] interval = weekInterval(dateString, 1); parts = getCalendarDate(interval[1] - getMillis(1)); days = 7; break; case 3: parts = DatePicker.parseMonthDate(dateString); days = -1; break; default: return parts; } arrayreverse(parts); List<Integer> list = fromIntArray(parts); switch(kind) { case 3: list.add(1); break; } list.set(1, list.get(1) + 1); list.add(days); return toIntArray(list); } public static int[] extendPeriod(int days, int...period) { dateInMillis(period[0], period[1] - 1, period[2], days); period[0] = getCalendar().get(Calendar.YEAR); period[1] = getCalendar().get(Calendar.MONTH) + 1; period[2] = getCalendar().get(Calendar.DAY_OF_MONTH); period[3] += days; return period; } public static long[] dayInterval(String dateString, int days) { Long date = toTime(dateString, calendarFormat); return com.applang.Util.dayInterval(date, days); } public static long[] weekInterval(String dateString, int weeks) { Date date = toDate(dateString, weekFormat); return weekInterval(date, weeks); } private static long[] weekInterval(Date date, int weeks) { if (date == null) return null; else return com.applang.Util.weekInterval(date, weeks); } public static long[] monthInterval(String dateString, int months) { Date date = toDate(dateString, monthFormat); long[] interval = monthInterval(date, months); return new long[] {interval[0], interval[1], interval[1]}; } private static long[] monthInterval(Date date, int months) { if (date == null) return null; else return com.applang.Util.monthInterval(date, months); } public static long[] toInterval(String dateString, int size) { switch (kindOfDate(dateString)) { case 3: return monthInterval(dateString, size); case 2: return weekInterval(dateString, size); case 1: return dayInterval(dateString, size); default: return com.applang.Util.dayInterval(toTime(dateString), size); } } public static long[] nextWeekInterval(String dateString) { long[] week = weekInterval(dateString, 2); return com.applang.Util.weekInterval(new Date(week[1]), -1); } public static long[] previousWeekInterval(String dateString) { return weekInterval(dateString, -1); } public static class Period { public static String[] periods = strings("weather.period","berichtsheft.period"); public static int year, month, day, length; public static final int MONTH = -1; public static int[] getParts() { return ints(year, month, day, length); } public static void setParts(int...parts) { if (parts != null) { for (int i = 0; i < Math.min(4, parts.length); i++) switch (i) { case 0: year = parts[i]; break; case 1: month = parts[i]; break; case 2: day = parts[i]; break; case 3: length = parts[i]; break; } datesInMillis.removeAll(); for (int i = 0; i < length; i++) { long time = dateInMillis(year, month - 1, Period.day, -i); int d = getCalendar().get(Calendar.DAY_OF_MONTH); datesInMillis.add(d, time); } } } private static BidiMultiMap datesInMillis = new BidiMultiMap(); public static void save(int no) { putSetting(periods[no], Arrays.toString(getParts())); Settings.save(); } public static void load(int no) { String string = getSetting(periods[no], ""); MatchResult[] matches = findAllIn(string, Pattern.compile("\\d+")); if (matches.length > 0) { int[] parts = new int[matches.length]; for (int i = 0; i < parts.length; i++) { parts[i] = toInt(-1, matches[i].group()); } setParts(parts); } else { Calendar cal = getCalendar(); cal.setTimeInMillis(now()); setParts(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH)); } } public static int[] loadParts(int no) { load(no); return getParts(); } public static void saveParts(int no, int[] parts) { Period.setParts(parts); Period.save(no); } public static String weekDate() { if (notAvailable(0, datesInMillis.getKeys())) return ""; Long time = (Long) datesInMillis.getValues().get(-1); long[] interval = weekInterval(new Date(time), 1); return DatePicker.weekDate(interval); } // NOTE used in scripts public static Long getMillis(int day) { if (length < 1) return dateInMillis(year, month - 1, day); else return datesInMillis.getValue(day); } public static long getMillisByHour(int day, int hour) { long epoch = dateInMillis(year, month - 1, day); return hoursFromDate(epoch, hour); } public static long[] getInterval() { if (isAvailable(0, datesInMillis.getKeys())) return new long[] { (Long)datesInMillis.getValues().get(-1), (Long)datesInMillis.getValues().get(0) }; return null; } // NOTE used in scripts public static String description() { switch (length) { case MONTH: return formatDate(getMillis(day), monthFormat); default: if (isAvailable(0, datesInMillis.getKeys())) { Long time = (Long) datesInMillis.getValues().get(-1); return String.format("%d day(s) starting %s", length, formatDate(time, calendarFormat)); } else return formatDate(getMillis(day), calendarFormat); } } // NOTE used in scripts public static String getDescription(int no) { Period.load(no); return description(); } // NOTE used in scripts public static boolean pick(int no) { Period.load(no); int[] period = pickAPeriod(Period.getParts(), "Pick day, week or month"); if (period == null) return false; Period.saveParts(no, period); return true; } } }