Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[nebula-dev] Modification request to CalendarCombo project

Hi,

My name is Steeve. I work for Logiciels INFO-DATA company. For our application, we improved some classes of CalendarCombo project. Knowing liscencing, we want to share our improvement with community. I join our code in this mail hoping it will be added to CalendarCombo project. If our code does not exactly fit, please let us know what we should change so it fit.

In details,

we fixed what seemed a bug in org.eclipse.nebula.widgets.calendarcombo.DateHelper.class parse method. Parse method didnt parse with additionalDateFormats parameter. Now it is.

we added org.eclipse.nebula.widgets.calendarcombo.IDateFactory.cass interface that is used by CalendarComposit2 we added org.eclipse.nebula.widgets.calendarcombo.CalendarFactory.cass an IDateFactory implementation with Calendar

we added org.eclipse.nebula.widgets.calendarcombo.CalendarComposite2.class which is copy of CalendarComposite with less feature and that is really independant, as CalendarComposite is too much linked with CalendarCombo. CalendarComposite2 allow user to use its own date class via IDateFactory interface.

Thanks for this great products

Steeve
Logiciels INFO-DATA

Index: src/org/eclipse/nebula/widgets/calendarcombo/DateHelper.java
===================================================================
RCS file: /cvsroot/technology/org.eclipse.swt.nebula/org.eclipse.nebula.widgets.calendarcombo/src/org/eclipse/nebula/widgets/calendarcombo/DateHelper.java,v
retrieving revision 1.14
diff -u -r1.14 DateHelper.java
--- src/org/eclipse/nebula/widgets/calendarcombo/DateHelper.java	31 Aug 2009 10:40:38 -0000	1.14
+++ src/org/eclipse/nebula/widgets/calendarcombo/DateHelper.java	11 Nov 2010 18:32:56 -0000
@@ -149,15 +149,57 @@
 
 	public static Calendar parse(final String comboText, final Locale locale, final String dateFormat, final char[] acceptedSeparatorChars, final List additionalDateFormats)
 			throws CalendarDateParseException, Exception {
+
+		Calendar parsed = null;
+		Exception ex = null;
+		
 		boolean isNumeric = comboText.replaceAll("[^0-9]", "").length() == comboText.length();
 
 		if (isNumeric) {
-			return numericParse(comboText, locale, false);
+			try {
+				parsed = numericParse(comboText, locale, false);
+			} catch (Exception e) {
+				//maybe dont care
+				ex = ex==null?e:ex;
+			}
 		}
-		else {
-			return slashParse(comboText, dateFormat, acceptedSeparatorChars, locale);
+		
+		if (parsed == null){
+			try {
+				parsed = slashParse(comboText, dateFormat, acceptedSeparatorChars, locale);
+			} catch (Exception e) {
+				//maybe dont care
+				ex = ex==null?e:ex;
+			}
+		}
+		
+		if (parsed == null){
+			if (additionalDateFormats != null) {
+//				final Calendar today = Calendar.getInstance(locale);
+				for (int i = 0; i < additionalDateFormats.size(); i++) {
+					try {
+						String format = (String) additionalDateFormats.get(i);
+						Date date = DateHelper.getDate(comboText, format);
+						if (date != null){
+							parsed = calendarize(date, locale);
+							break;
+						}
+					}
+					catch (Exception e) {
+						//maybe dont care
+						ex = ex==null?e:ex;
+					}
+				}
+			}
+		}
+
+		if (parsed == null){
+			//no more try
+			throw ex;
 		}
 
+		return parsed;
+		
 		//return null;
 		//}
 
Index: src/org/eclipse/nebula/widgets/calendarcombo/IDateFactory.java
===================================================================
RCS file: src/org/eclipse/nebula/widgets/calendarcombo/IDateFactory.java
diff -N src/org/eclipse/nebula/widgets/calendarcombo/IDateFactory.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/org/eclipse/nebula/widgets/calendarcombo/IDateFactory.java	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright (c) Steeve St-Laurent - www.infodata.ca - sstlaurent@xxxxxxxxxxx
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    sstlaurent@xxxxxxxxxxx - initial API
+ *******************************************************************************/
+
+package org.eclipse.nebula.widgets.calendarcombo;
+
+import java.util.Locale;
+
+/**
+ * a factory used to create "date implementation independant" components
+ */
+public interface IDateFactory {
+
+	/**
+	 * @return never null
+	 */
+	Locale getLocale();
+
+	/**
+	 * create a new date instance
+	 * @param day start from 1 to month end
+	 * @param month start from 1 to 12
+	 * @param year
+	 * @return never null
+	 * @throws NullPointerException if date is invalid
+	 */
+	Object newDate(int day, int month, int year);
+	/**
+	 * clear date instance
+	 * @param date cant be null
+	 * @return cleared date, never null, may or may not return same instance as param date
+	 * @throws NullPointerException if date is null
+	 */
+	Object clear(Object date);
+	/**
+	 * create new today date instance
+	 * @return today date, never null
+	 */
+	Object today();
+	/**
+	 * add x day to date instance
+	 * @param date cant be null
+	 * @param day
+	 * @return modifed date, never null, may or may not return same instance as param date
+	 * @throws NullPointerException if date is null
+	 */
+	Object plusDay(Object date, int day);
+	/**
+	 * add x month to date instance
+	 * @param date cant be null
+	 * @param month
+	 * @return modifed date, never null, may or may not return same instance as param date
+	 * @throws NullPointerException if date is null
+	 */
+	Object plusMonth(Object date, int month);
+	/**
+	 * add x year to date instance
+	 * @param date cant be null
+	 * @param year
+	 * @return modifed date, never null, may or may not return same instance as param date
+	 * @throws NullPointerException if date is null
+	 */
+	Object plusYear(Object date, int year);
+	/**
+	 * set date instance day to x
+	 * @param date cant be null
+	 * @param day, start from 1 to monthEnd
+	 * @return modifed date, never null, may or may not return same instance as param date
+	 * @throws NullPointerException if date is null or day is invalid
+	 */
+	Object withDay(Object date, int day);
+	/**
+	 * set date instance year to x
+	 * @param date cant be null
+	 * @param year
+	 * @return modifed date, never null, may or may not return same instance as param date
+	 * @throws NullPointerException if date is null
+	 */
+	Object withYear(Object date, int year);
+	/**
+	 * set date instance month to x
+	 * @param date cant be null
+	 * @param month, start from 1 to 12
+	 * @return modifed date, never null, may or may not return same instance as param date
+	 * @throws NullPointerException if date is null or month is invalid
+	 */
+	Object withMonth(Object date, int month);
+
+	/**
+	 * set date instance dayOfWeek to x
+	 * @param date cant be null
+	 * @param dayOfWeek, start from 1 to 7, sunday to saturday
+	 * @return modifed date, never null, may or may not return same instance as param date
+	 * @throws NullPointerException if date is null or month is invalid
+	 */
+	Object withDayOfWeek(Object date, int dayOfWeek);
+
+	/**
+	 * @param date cant be null
+	 * @return date day, start from 1 to 31
+	 * @throws NullPointerException if date is null
+	 */
+	int getDay(Object date);
+	/**
+	 * @param date cant be null
+	 * @return date month, start from 1 to 12
+	 * @throws NullPointerException if date is null
+	 */
+	int getMonth(Object date);
+	/**
+	 * @param date cant be null
+	 * @return date year
+	 * @throws NullPointerException if date is null
+	 */
+	int getYear(Object date);
+	/**
+	 * @param date cant be null
+	 * @return date day of week, start from 1 to 7, sunday to saturday
+	 * @throws NullPointerException if date is null
+	 */
+	int getDayOfWeek(Object date);
+
+	/**
+	 * @param date1 can be null
+	 * @param date2 can be null
+	 * @return true if date1 equals date2
+	 */
+	boolean equals(Object date1, Object date2);
+
+	/**
+	 * @param date cant be null
+	 * @return a clone of date
+	 * @throws NullPointerException if date is null
+	 */
+	Object clone(Object date);
+
+	/**
+	 * clone date1 instance to date2 instance
+	 * @param date1 cant be null
+	 * @param date2 cant be null
+	 * @return date2
+	 * @throws NullPointerException if date is null
+	 */
+	Object clone(Object date1, Object date2);
+
+    /**
+     * Gets what the first day of the week is; e.g., <code>SUNDAY</code> in the U.S.,
+     * <code>MONDAY</code> in France.
+     *
+     * @return the first day of the week. 1 to 7, sunday to saturday
+     */
+	int getFirstDayOfWeek();
+
+}
Index: src/org/eclipse/nebula/widgets/calendarcombo/CalendarFactory.java
===================================================================
RCS file: src/org/eclipse/nebula/widgets/calendarcombo/CalendarFactory.java
diff -N src/org/eclipse/nebula/widgets/calendarcombo/CalendarFactory.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/org/eclipse/nebula/widgets/calendarcombo/CalendarFactory.java	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * Copyright (c) Steeve St-Laurent - www.infodata.ca - sstlaurent@xxxxxxxxxxx
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    sstlaurent@xxxxxxxxxxx - initial implementations
+ *******************************************************************************/
+
+package org.eclipse.nebula.widgets.calendarcombo;
+
+import java.util.Calendar;
+import java.util.Locale;
+
+/**
+ * a factory that work on calendar
+ */
+public class CalendarFactory implements IDateFactory {
+
+	private final Locale locale;
+	private final int firstDayOfWeek;
+
+	/**
+	 * @param new factory with locale set to {@link Locale#getDefault()}
+	 */
+	public CalendarFactory() {
+		this(null);
+	}
+
+	/**
+	 * @param locale can be null, default to {@link Locale#getDefault()}
+	 */
+	public CalendarFactory(Locale locale) {
+		if (locale == null) {
+			locale = Locale.getDefault();
+		}
+		this.locale = locale;
+		this.firstDayOfWeek = ((Calendar)today()).getFirstDayOfWeek();
+	}
+
+	public Object plusDay(Object date, int day) {
+		Calendar c = ((Calendar)date);
+		c.add(Calendar.DATE, day);
+		return c;
+	}
+
+	public Object plusMonth(Object date, int month) {
+		Calendar c = ((Calendar)date);
+		c.add(Calendar.MONTH, month);
+		return c;
+	}
+
+	public Object plusYear(Object date, int year) {
+		Calendar c = ((Calendar)date);
+		c.add(Calendar.YEAR, year);
+		return c;
+	}
+
+	public Object clear(Object date) {
+		Calendar c = ((Calendar)date);
+		c.clear();
+		return c;
+	}
+
+	public boolean equals(Object date1, Object date2) {
+		if (date1 == date2) {
+			return true;
+		}
+		if (date1 == null || date2 == null) {
+			return false;
+		}
+		return DateHelper.sameDate((Calendar)date1, (Calendar)date2);
+	}
+
+	public int getDay(Object date) {
+		Calendar c = ((Calendar)date);
+		return c.get(Calendar.DATE);
+	}
+
+	public int getDayOfWeek(Object date) {
+		Calendar c = ((Calendar)date);
+		return c.get(Calendar.DAY_OF_WEEK);
+	}
+
+	public int getMonth(Object date) {
+		Calendar c = ((Calendar)date);
+		return c.get(Calendar.MONTH) + 1;
+	}
+
+	public int getYear(Object date) {
+		Calendar c = ((Calendar)date);
+		return c.get(Calendar.YEAR);
+	}
+
+	public Object newDate(int day, int month, int year) {
+		Calendar c = Calendar.getInstance(this.locale);
+		c.set(Calendar.YEAR, year);
+		c.set(Calendar.MONTH, month-1);
+		c.set(Calendar.DATE, day);
+		return toMidnight(c);
+	}
+
+	private static Calendar toMidnight(Calendar c){
+		c.set(Calendar.HOUR, 0);
+		c.set(Calendar.HOUR_OF_DAY, 0);
+		c.set(Calendar.MINUTE, 0);
+		c.set(Calendar.SECOND, 0);
+		c.set(Calendar.MILLISECOND, 0);
+		return c;
+	}
+
+	public Object today() {
+		return toMidnight(Calendar.getInstance(this.locale));
+	}
+
+	public Object withDay(Object date, int day) {
+		Calendar c = ((Calendar)date);
+		c.set(Calendar.DAY_OF_MONTH, day);
+		return c;
+	}
+
+	public Object withMonth(Object date, int month) {
+		Calendar c = ((Calendar)date);
+		c.set(Calendar.MONTH, month-1);
+		return c;
+	}
+
+	public Object withYear(Object date, int year) {
+		Calendar c = ((Calendar)date);
+		c.set(Calendar.YEAR, year);
+		return c;
+	}
+
+	public Object withDayOfWeek(Object date, int dayOfWeek) {
+		Calendar c = ((Calendar)date);
+		c.set(Calendar.DAY_OF_WEEK, dayOfWeek);
+		return c;
+	}
+
+	public Object clone(Object date) {
+		Calendar c = ((Calendar)date);
+		return c.clone();
+	}
+
+	public int getFirstDayOfWeek() {
+		return this.firstDayOfWeek;
+	}
+
+	public Object clone(Object date1, Object date2) {
+		Calendar c1 = ((Calendar)date1);
+		Calendar c2 = ((Calendar)date2);
+		c2.set(Calendar.YEAR, c1.get(Calendar.YEAR));
+		c2.set(Calendar.MONTH, c1.get(Calendar.MONTH));
+		c2.set(Calendar.DATE, c1.get(Calendar.DATE));
+		return toMidnight(c2);
+	}
+
+	public Locale getLocale() {
+		return this.locale;
+	}
+}
Index: src/org/eclipse/nebula/widgets/calendarcombo/CalendarComposite2.java
===================================================================
RCS file: src/org/eclipse/nebula/widgets/calendarcombo/CalendarComposite2.java
diff -N src/org/eclipse/nebula/widgets/calendarcombo/CalendarComposite2.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/org/eclipse/nebula/widgets/calendarcombo/CalendarComposite2.java	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,829 @@
+/*******************************************************************************
+ * Copyright (c) Emil Crumhorn - Hexapixel.com - emil.crumhorn@xxxxxxxxx
+ * Copyright (c) Steeve St-Laurent - www.infodata.ca - sstlaurent@xxxxxxxxxxx
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    emil.crumhorn@xxxxxxxxx - initial API and implementation of CalendarComposite
+ *    sstlaurent@xxxxxxxxxxx - copy and epuration of CalendarComposite
+ *******************************************************************************/
+
+package org.eclipse.nebula.widgets.calendarcombo;
+
+
+import java.text.DateFormatSymbols;
+import java.util.ArrayList;
+import java.util.Locale;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Layout;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * this class does not support {@link ISettings#showMonthPickerOnMonthNameMousePress()} because if does not support month picker<br>
+ * this class does not support {@link IColorManager#getDisabledDayForegroundColor()} because it does not support disallowed dates<br>
+ * this class is really independant from {@link CalendarCombo}<br>
+ * this class allow user to use its own date class via {@link IDateFactory} interface
+ */
+public class CalendarComposite2 extends Canvas implements MouseListener, MouseMoveListener {
+
+	public static interface IDateListener {
+		/**
+		 * @see IDateFactory for date type
+		 * @param date
+		 */
+		void dateChanged(Object date);
+	}
+
+    private static final boolean OS_CARBON        = "carbon".equals(SWT.getPlatform());
+	private static final boolean OS_GTK           = "gtk".equals(SWT.getPlatform());
+	private static final boolean OS_WINDOWS       = "win32".equals(SWT.getPlatform());
+
+	private final int					ARROW_LEFT					= 1;
+
+	private final int					ARROW_RIGHT					= 2;
+
+	private Button						mButtonToday;
+
+	private Button						mButtonNone;
+
+	private Rectangle					mLeftArrowBounds;
+
+	private Rectangle					mRightArrowBounds;
+
+	private Object					mCalendar;
+
+	private Object					mToday;
+
+	private int							mDatesTopY					= 0;
+
+	private int							mDayXs[]					= new int[7];
+
+	private CalDay[]					mDays						= new CalDay[7 * 6];
+
+	private Object					mSelectedDay;
+
+	private static DateFormatSymbols	mDFS;
+
+	private String						mMonths[];
+
+	private static String[]				mDayTitles					= null;
+
+	private ArrayList mListeners;
+
+	private boolean						mNoDayClicked;
+
+	// this thread deals with holding down the mouse over the arrows to jump
+	// months quickly
+
+	// milliseconds
+	private static final int			ARROW_SLOW_TIME				= 300;
+
+	private static final int			ARROW_FAST_TIME				= 120;
+
+	// after how many iterations do we switch speed? (after how many months
+	// flipped-by)
+	private static final int			ARROW_SPEED_SWITCH_COUNT	= 10;
+
+	// default speed
+	private int							mArrowSleepTime				= ARROW_SLOW_TIME;
+
+	// various variables used to keep track
+	private int							mArrowIterations			= 0;
+
+	private Thread						mArrowThread;
+
+	private boolean						mArrowRun;
+
+	private boolean						mArrowPause;
+
+	private int							mArrowThreadDirection		= 0;
+
+	private final IColorManager				mColorManager;
+
+	private final ISettings					mSettings;
+
+	private final IDateFactory				mDateFactory;
+
+	public CalendarComposite2(Composite parent, IColorManager colorManager, ISettings settings, IDateFactory dateFactory) {
+		super(parent, SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED);
+		this.mColorManager = colorManager;
+		this.mSettings = settings;
+		this.mDateFactory = dateFactory == null ? new CalendarFactory(this.mSettings == null ? null : this.mSettings.getLocale()) : dateFactory;
+
+		init();
+
+		build();
+	}
+
+	private void init() {
+		mDFS = new DateFormatSymbols(this.mDateFactory.getLocale());
+		this.mMonths = mDFS.getMonths();
+
+		if (this.mCalendar == null) {
+			this.mCalendar = this.mDateFactory.today();
+		}
+		if (this.mToday == null) {
+			this.mToday = this.mDateFactory.today();
+		}
+
+		String[] weekdays = mDFS.getWeekdays();
+		mDayTitles = new String[weekdays.length];
+		for (int i = 0; i < weekdays.length; i++) {
+			String weekday = weekdays[i];
+			if (weekday.length() > 0) {
+				mDayTitles[i] = weekday.substring(0, 1).toUpperCase();
+			}
+		}
+
+		//will be reused in draw days methods
+		for (int i = 0; i < 42; i++) {
+			this.mDays[i] = new CalDay(i, this.mDateFactory.today(), new Rectangle(0,0,0,0));
+		}
+
+	}
+
+	private void build() {
+		// button height & width and spacers
+		int bheight = this.mSettings.getButtonHeight();
+		int bwidth = this.mSettings.getButtonWidth();
+		int buttonStyle = SWT.PUSH;
+		this.mListeners = new ArrayList();
+
+		// Mac buttons need different flag to look normal
+		if (OS_CARBON) {
+			bheight = this.mSettings.getCarbonButtonHeight();
+			bwidth = this.mSettings.getButtonWidthCarbon();
+			buttonStyle = SWT.FLAT;
+		}
+
+		setLayout(new ButtonSectionLayout());
+
+		addPaintListener(new PaintListener() {
+			public void paintControl(PaintEvent event) {
+				paint(event);
+			}
+		});
+
+		addMouseListener(this);
+		addMouseMoveListener(this);
+		addKeyListener(new KeyListener() {
+			public void keyReleased(KeyEvent e) {
+			}
+			public void keyPressed(KeyEvent e) {
+				CalendarComposite2.this.keyPressed(e.keyCode, e.stateMask);
+			}
+		});
+
+		this.mButtonToday = new Button(this, buttonStyle | SWT.NO_FOCUS);
+		this.mButtonToday.setText(this.mSettings.getTodayText());
+		this.mButtonToday.setLayoutData(new GridData(bwidth, bheight));
+		this.mButtonToday.addListener(SWT.Selection, new Listener() {
+			public void handleEvent(Event event) {
+				clickedTodayButton();
+			}
+		});
+
+		this.mButtonNone = new Button(this, buttonStyle | SWT.NO_FOCUS);
+		this.mButtonNone.setText(this.mSettings.getNoneText());
+		this.mButtonNone.setLayoutData(new GridData(bwidth, bheight));
+		this.mButtonNone.addListener(SWT.Selection, new Listener() {
+			public void handleEvent(Event event) {
+				clickedNoneButton();
+			}
+		});
+	}
+
+	private void clickedTodayButton() {
+		setReferenceDate(this.mDateFactory.today());
+		this.mSelectedDay = this.mDateFactory.today();
+		notifyListeners();
+	}
+
+	private void clickedNoneButton() {
+		selectDate(null);
+		notifyListeners();
+	}
+
+	private void paint(PaintEvent event) {
+		GC gc = event.gc;
+		drawChartOntoGC(gc);
+	}
+
+	private void drawChartOntoGC(GC gc) {
+		Rectangle bounds = super.getBounds();
+		gc.setBackground(this.mColorManager.getCalendarBackgroundColor());
+		gc.fillRectangle(bounds);
+
+		Font used = null;
+		if (OS_CARBON) {
+			used = this.mSettings.getCarbonDrawFont();
+			if (used != null) {
+				gc.setFont(used);
+			}
+		}
+
+		// header
+		drawHeader(gc);
+		// day titles
+		drawTitleDays(gc);
+		// days
+		drawDays(gc);
+		// 1 pixel border
+		drawBorder(gc);
+
+		gc.dispose();
+		if (used != null) {
+			used.dispose();
+		}
+	}
+
+	public void setReferenceDate(Object date) {
+		this.mCalendar = date == null ? this.mDateFactory.today() : this.mDateFactory.clone(date);
+		redraw();
+	}
+
+	public void selectDate(Object date){
+		this.mSelectedDay = date == null ? null : this.mDateFactory.clone(date);
+		redraw();
+	}
+
+	public void nextMonth() {
+		this.mSelectedDay = null;
+		this.mCalendar = this.mDateFactory.plusMonth(this.mCalendar, 1);
+		this.mCalendar = this.mDateFactory.withDay(this.mCalendar, 1);
+		redraw();
+	}
+
+	public void prevMonth() {
+		this.mSelectedDay = null;
+		this.mCalendar = this.mDateFactory.plusMonth(this.mCalendar, -1);
+		this.mCalendar = this.mDateFactory.withDay(this.mCalendar, 1);
+		redraw();
+	}
+
+	public void goToToday() {
+		this.mSelectedDay = null;
+		this.mCalendar = this.mDateFactory.today();
+		redraw();
+	}
+
+	// draws 1 pixel border around entire calendar
+	private void drawBorder(GC gc) {
+		Rectangle bounds = super.getBounds();
+		gc.setForeground(this.mColorManager.getCalendarBorderColor());
+		gc.drawRectangle(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1);
+	}
+
+	private void drawHeader(GC gc) {
+		Rectangle bounds = super.getBounds();
+		Rectangle bgRect = new Rectangle(bounds.x + this.mSettings.getHeaderLeftMargin(), bounds.y + this.mSettings.getHeaderTopMargin(), this.mSettings.getCalendarWidth() - 13, bounds.y
+				+ this.mSettings.getHeaderHeight());
+		gc.setBackground(this.mColorManager.getCalendarHeaderColor());
+		gc.fillRectangle(bgRect);
+		drawArrow(gc, bounds.x + this.mSettings.getHeaderLeftMargin() + this.mSettings.getArrowLeftSpacing() + 1, bounds.y + this.mSettings.getHeaderTopMargin() + this.mSettings.getArrowTopSpacing()
+				+ 4, this.ARROW_LEFT);
+		drawArrow(gc, bounds.x + this.mSettings.getCalendarWidth() - 13 - this.mSettings.getHeaderRightMargin(), bounds.y + this.mSettings.getHeaderTopMargin() + this.mSettings.getArrowTopSpacing()
+				+ 4, this.ARROW_RIGHT);
+
+		String toDraw = this.mMonths[this.mDateFactory.getMonth(this.mCalendar)-1] + " " + this.mDateFactory.getYear(this.mCalendar);
+		Point strWidth = gc.stringExtent(toDraw);
+
+		int avail = this.mSettings.getCalendarWidth() - 13 - strWidth.x;
+		avail /= 2;
+
+		gc.drawString(toDraw, bounds.x + this.mSettings.getHeaderLeftMargin() + avail, bounds.y + this.mSettings.getHeaderTopMargin() + 1, true);
+	}
+
+	private void drawTitleDays(GC gc) {
+		// fetch the first day of the week, and draw starting on that day
+		int fdow = this.mDateFactory.getFirstDayOfWeek();
+
+		Rectangle bounds = super.getBounds();
+		int xStart = this.mSettings.getDatesLeftMargin() + 5;
+		int yStart = bounds.y + this.mSettings.getHeaderTopMargin() + this.mSettings.getHeaderHeight() + 1;
+
+		int spacer = 0;
+		int letterHeight = 0;
+
+		for (int i = 0; i < 7; i++) {
+			Point strWidth = gc.stringExtent(mDayTitles[fdow]);
+
+			int x = xStart + this.mSettings.getOneDateBoxSize() + spacer - strWidth.x;
+			// don't add the string width, as our string width later when
+			// drawing days will differ
+			this.mDayXs[i] = xStart + this.mSettings.getOneDateBoxSize() + spacer;
+
+			gc.drawString(mDayTitles[fdow], x, yStart, true);
+
+			letterHeight = strWidth.y;
+			spacer += this.mSettings.getOneDateBoxSize() + this.mSettings.getBoxSpacer();
+
+			fdow++;
+			if (fdow > 7) {
+				fdow = 1;
+			}
+		}
+
+		int lineStart = yStart + 1 + letterHeight;
+		gc.setForeground(this.mColorManager.getLineColor());
+		gc.drawLine(this.mSettings.getDatesLeftMargin() + 1, lineStart, bounds.width - this.mSettings.getDatesRightMargin() - 3, lineStart);
+
+		this.mDatesTopY = lineStart + 3;
+	}
+
+	private void drawDays(GC gc) {
+		gc.setBackground(this.mColorManager.getCalendarBackgroundColor());
+		gc.setForeground(this.mColorManager.getTextColor());
+
+		Rectangle bounds = super.getBounds();
+		int spacer = 0;
+
+//		debug("mCalendar", this.mCalendar);
+		Object start = this.mDateFactory.clone(this.mCalendar);
+//		debug("start", start);
+		start = this.mDateFactory.withDay(start, 1);
+//		debug("start", start);
+		start = this.mDateFactory.withDayOfWeek(start, 1);
+//		debug("start", start);
+
+		Object date = start;
+
+		final int monthToShow = this.mDateFactory.getMonth(this.mCalendar);
+
+		int col = 0;
+		int colCount = 0;
+
+		String typicalLetter = "8";
+		int strHeight = gc.stringExtent(typicalLetter).y;
+
+		gc.setForeground(this.mColorManager.getLineColor());
+
+		int lastY = 0;
+
+		for (int y = 0; y < 42; y++) {
+
+//			debug(date);
+
+			// new row
+			if (y % 7 == 0 && y != 0) {
+				spacer += strHeight + 2;
+			}
+
+			if (colCount > 6) {
+				colCount = 0;
+				col = 0;
+			}
+
+			if (this.mDateFactory.getMonth(date) == monthToShow) {
+				gc.setForeground(this.mColorManager.getTextColor());
+			} else {
+				gc.setForeground(this.mColorManager.getPreviousAndNextMonthForegroundColor());
+			}
+
+			String dateStr = String.valueOf(this.mDateFactory.getDay(date));
+			Point width = gc.stringExtent(dateStr);
+
+			if (this.mDateFactory.equals(this.mSelectedDay, date)) {
+				gc.setBackground(this.mColorManager.getSelectedDayColor());
+				gc.fillRectangle(this.mDayXs[col] - this.mSettings.getOneDateBoxSize() - 4, this.mDatesTopY + spacer - 1, this.mSettings.getOneDateBoxSize() + 5, 14);
+				gc.setBackground(this.mColorManager.getCalendarBackgroundColor());
+			}
+
+			this.mDays[y].bounds.x = this.mDayXs[col] - this.mSettings.getOneDateBoxSize() - 4;
+			this.mDays[y].bounds.y = this.mDatesTopY + spacer - 1;
+			this.mDays[y].bounds.width = this.mSettings.getOneDateBoxSize() + 5;
+			this.mDays[y].bounds.height = 14;
+			this.mDays[y].number = y;
+			this.mDays[y].date = this.mDateFactory.clone(date, this.mDays[y].date);
+
+			gc.drawString(dateStr, this.mDayXs[col] - width.x, this.mDatesTopY + spacer, true);
+
+			if (this.mDateFactory.equals(this.mToday, date)) {
+				Color old = gc.getForeground();
+				gc.setForeground(this.mColorManager.getSelectedDayBorderColor());
+				gc.drawRectangle(this.mDayXs[col] - this.mSettings.getOneDateBoxSize() - 4, this.mDatesTopY + spacer - 1, this.mSettings.getOneDateBoxSize() + 5, 14);
+				gc.setForeground(old);
+			}
+
+			date = this.mDateFactory.plusDay(date, 1);
+			col++;
+			colCount++;
+			lastY = this.mDatesTopY + spacer;
+		}
+
+		lastY += strHeight + 1;
+
+		gc.setForeground(this.mColorManager.getLineColor());
+		gc.drawLine(this.mSettings.getDatesLeftMargin() + 1, lastY, bounds.width - this.mSettings.getDatesRightMargin() - 3, lastY);
+	}
+
+	private void debug(Object date) {
+		System.out.println(this.mDateFactory.getDay(date) + "-"+this.mDateFactory.getMonth(date) +"-"+this.mDateFactory.getYear(date));
+	}
+	private void debug(String text, Object date) {
+		System.out.println(text + " " + this.mDateFactory.getDay(date) + "-"+this.mDateFactory.getMonth(date) +"-"+this.mDateFactory.getYear(date));
+	}
+
+	private void drawArrow(GC gc, int x, int y, int style) {
+		gc.setForeground(this.mColorManager.getArrowColor());
+		switch (style) {
+		case ARROW_RIGHT:
+			gc.drawLine(x, y - 4, x, y + 4);
+			gc.drawLine(x + 1, y - 3, x + 1, y + 3);
+			gc.drawLine(x + 2, y - 2, x + 2, y + 2);
+			gc.drawLine(x + 3, y - 1, x + 3, y + 1);
+			gc.drawLine(x + 4, y, x + 4, y);
+			this.mRightArrowBounds = new Rectangle(x - 4, y - 4, x + 8, y);
+			break;
+		case ARROW_LEFT:
+			gc.drawLine(x - 1, y, x - 1, y);
+			gc.drawLine(x, y - 1, x, y + 1);
+			gc.drawLine(x + 1, y - 2, x + 1, y + 2);
+			gc.drawLine(x + 2, y - 3, x + 2, y + 3);
+			gc.drawLine(x + 3, y - 4, x + 3, y + 4);
+			this.mLeftArrowBounds = new Rectangle(x - 4, y - 4, x + 8, y);
+			break;
+		}
+	}
+
+	private static boolean isInside(int x, int y, Rectangle rect) {
+		if (rect == null) {
+			return false;
+		}
+
+		if (x >= rect.x && y >= rect.y && x <= (rect.x + rect.width) && y <= (rect.y + rect.height)) {
+			return true;
+		}
+
+		return false;
+	}
+
+	public void mouseMove(MouseEvent e) {
+
+		// "dragging" the day, just paint it
+		if (e.stateMask != 0) {
+			doDaySelection(e.x, e.y);
+		}
+
+		if (this.mArrowRun && this.mArrowThread != null) {
+			if (!isInside(e.x, e.y, this.mLeftArrowBounds) && !isInside(e.x, e.y, this.mRightArrowBounds)) {
+				this.mArrowPause = true;
+				// also pause the speed
+				this.mArrowIterations = 0;
+				this.mArrowSleepTime = ARROW_SLOW_TIME;
+			} else {
+				if (isInside(e.x, e.y, this.mLeftArrowBounds)) {
+					this.mArrowThreadDirection = this.ARROW_LEFT;
+				} else {
+					this.mArrowThreadDirection = this.ARROW_RIGHT;
+				}
+
+				this.mArrowPause = false;
+			}
+		}
+	}
+
+	public void mouseDoubleClick(MouseEvent event) {
+	}
+
+	// draw the date selection on mouse down to give that flicker of "response"
+	// to the user
+	public void mouseDown(MouseEvent event) {
+
+		if (isInside(event.x, event.y, this.mLeftArrowBounds)) {
+			prevMonth();
+
+			runArrowThread(this.ARROW_LEFT);
+
+			return;
+		}
+
+		if (isInside(event.x, event.y, this.mRightArrowBounds)) {
+			nextMonth();
+
+			runArrowThread(this.ARROW_RIGHT);
+
+			return;
+		}
+
+		doDaySelection(event.x, event.y);
+
+	}
+
+	private void killArrowThread() {
+		if (this.mArrowThread != null) {
+			this.mArrowPause = true;
+			this.mArrowThreadDirection = 0;
+			this.mArrowRun = false;
+			this.mArrowThread = null;
+		}
+	}
+
+	private void runArrowThread(int direction) {
+		this.mArrowThreadDirection = direction;
+		this.mArrowIterations = 0;
+		this.mArrowSleepTime = ARROW_SLOW_TIME;
+		this.mArrowRun = false;
+		this.mArrowPause = false;
+		this.mArrowThread = new Thread() {
+			public void run() {
+				while (CalendarComposite2.this.mArrowRun) {
+					try {
+						sleep(CalendarComposite2.this.mArrowSleepTime);
+
+						if (!CalendarComposite2.this.mArrowPause) {
+							CalendarComposite2.this.mArrowIterations++;
+						}
+
+						if (CalendarComposite2.this.mArrowIterations > ARROW_SPEED_SWITCH_COUNT && CalendarComposite2.this.mArrowSleepTime != ARROW_FAST_TIME) {
+							CalendarComposite2.this.mArrowSleepTime = ARROW_FAST_TIME;
+						}
+					} catch (InterruptedException e) {
+						e.printStackTrace();
+					}
+					if (!CalendarComposite2.this.mArrowPause) {
+						Display.getDefault().syncExec(new Runnable() {
+							public void run() {
+								if (isDisposed()) {
+									CalendarComposite2.this.mArrowRun = false;
+									CalendarComposite2.this.mArrowThread = null;
+								} else {
+									if (CalendarComposite2.this.mArrowThreadDirection == CalendarComposite2.this.ARROW_LEFT) {
+										prevMonth();
+									} else if (CalendarComposite2.this.mArrowThreadDirection == CalendarComposite2.this.ARROW_RIGHT) {
+										nextMonth();
+									}
+								}
+							}
+						});
+					}
+				}
+			}
+		};
+		this.mArrowPause = false;
+		this.mArrowRun = true;
+		this.mArrowThread.start();
+	}
+
+	// selects a day at x, y if there is a day there to select
+	private void doDaySelection(int x, int y) {
+		int curYear = this.mDateFactory.getYear(this.mCalendar);
+		int curMonth = this.mDateFactory.getMonth(this.mCalendar);
+		this.mNoDayClicked = false;
+
+		for (int i = 0; i < this.mDays.length; i++) {
+			if (isInside(x, y, this.mDays[i].bounds)) {
+
+				int year = this.mDateFactory.getYear(this.mDays[i].date);
+				int month = this.mDateFactory.getMonth(this.mDays[i].date);
+
+				if (year == curYear) {
+					if (month < curMonth) {
+						prevMonth();
+						return;
+					} else if (month > curMonth) {
+						nextMonth();
+						return;
+					}
+				} else {
+					if (year < curYear) {
+						prevMonth();
+						return;
+					} else if (year > curYear) {
+						nextMonth();
+						return;
+					}
+				}
+
+				if (this.mSelectedDay == null) {
+					this.mSelectedDay = this.mDateFactory.clone(this.mDays[i].date);
+				} else {
+					this.mSelectedDay = this.mDateFactory.clone(this.mDays[i].date, this.mSelectedDay);
+				}
+
+				redraw();
+				return;
+			}
+		}
+
+		this.mNoDayClicked = true;
+	}
+
+	public void mouseUp(MouseEvent event) {
+		killArrowThread();
+
+		if (this.mNoDayClicked) {
+			this.mNoDayClicked = false;
+			return;
+		}
+
+		if (this.mSelectedDay != null) {
+			notifyListeners();
+		}
+	}
+
+	private void notifyListeners() {
+		final Object date = this.mSelectedDay == null ? null : this.mDateFactory.clone(this.mSelectedDay);
+		for (int i = 0; i < this.mListeners.size(); i++) {
+			IDateListener listener = (IDateListener) this.mListeners.get(i);
+			listener.dateChanged(date);
+		}
+	}
+
+	public void addDateListener(IDateListener listener) {
+		if (!this.mListeners.contains(listener)) {
+			this.mListeners.add(listener);
+		}
+	}
+
+	public void removeCalendarListener(IDateListener listener) {
+		this.mListeners.remove(listener);
+	}
+
+	private void keyPressed(int keyCode, int stateMask) {
+		if (this.mSelectedDay == null) {
+			this.mSelectedDay = this.mDateFactory.clone(this.mCalendar);
+			this.mSelectedDay = this.mDateFactory.withDay(this.mSelectedDay, 1);
+			redraw();
+		} else {
+			if (keyCode == SWT.ARROW_RIGHT) {
+				int monthNow = this.mDateFactory.getMonth(this.mSelectedDay);
+				this.mSelectedDay = this.mDateFactory.plusDay(this.mSelectedDay, 1);
+				int monthAfter = this.mDateFactory.getMonth(this.mSelectedDay);
+				if (monthAfter != monthNow) {
+					this.mCalendar = this.mDateFactory.plusMonth(this.mCalendar, 1);
+				}
+				redraw();
+			} else if (keyCode == SWT.ARROW_LEFT) {
+				int monthNow = this.mDateFactory.getMonth(this.mSelectedDay);
+				this.mSelectedDay = this.mDateFactory.plusDay(this.mSelectedDay, -1);
+				int monthAfter = this.mDateFactory.getMonth(this.mSelectedDay);
+				if (monthAfter != monthNow) {
+					this.mCalendar = this.mDateFactory.plusMonth(this.mCalendar, -1);
+				}
+				redraw();
+			} else if (keyCode == SWT.ARROW_UP) {
+				int monthNow = this.mDateFactory.getMonth(this.mSelectedDay);
+				this.mSelectedDay = this.mDateFactory.plusDay(this.mSelectedDay, -7);
+				int monthAfter = this.mDateFactory.getMonth(this.mSelectedDay);
+				if (monthAfter != monthNow) {
+					this.mCalendar = this.mDateFactory.plusMonth(this.mCalendar, -1);
+				}
+				redraw();
+			} else if (keyCode == SWT.ARROW_DOWN) {
+				int monthNow = this.mDateFactory.getMonth(this.mSelectedDay);
+				this.mSelectedDay = this.mDateFactory.plusDay(this.mSelectedDay, 7);
+				int monthAfter = this.mDateFactory.getMonth(this.mSelectedDay);
+				if (monthAfter != monthNow) {
+					this.mCalendar = this.mDateFactory.plusMonth(this.mCalendar, 1);
+				}
+				redraw();
+			} else if (keyCode == SWT.CR || keyCode == SWT.LF) {
+				notifyListeners();
+				return;
+			}
+		}
+	}
+
+	private class ButtonSectionLayout extends Layout {
+
+		protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
+			return new Point(153, 160);
+		}
+
+		protected void layout(Composite composite, boolean flushCache) {
+			int bheight = CalendarComposite2.this.mSettings.getButtonHeight();
+			int bwidth = CalendarComposite2.this.mSettings.getButtonWidth();
+			int vspacer = CalendarComposite2.this.mSettings.getButtonVerticalSpace();
+			int bspacer = CalendarComposite2.this.mSettings.getButtonsHorizontalSpace();
+			if (OS_CARBON) {
+				bwidth = CalendarComposite2.this.mSettings.getButtonWidthCarbon();
+				bheight = CalendarComposite2.this.mSettings.getCarbonButtonHeight();
+				vspacer = CalendarComposite2.this.mSettings.getCarbonButtonVerticalSpace();
+				bspacer = CalendarComposite2.this.mSettings.getCarbonButtonsHorizontalSpace();
+			}
+
+			// see how much space we put on the left and right sides of the
+			// buttons
+			int width = CalendarComposite2.this.mSettings.getCalendarWidth() - (bwidth * 2) - bspacer;
+			width /= 2;
+
+			int button1Left = width;
+			int button2Left = CalendarComposite2.this.mSettings.getCalendarWidth() - width - bwidth;
+
+			Control[] children = composite.getChildren();
+			for (int i = 0; i < children.length; i++) {
+				switch (i) {
+				case 0:
+					children[i].setBounds(button1Left, vspacer, bwidth, bheight);
+
+					break;
+				case 1:
+					children[i].setBounds(button2Left, vspacer, bwidth, bheight);
+					break;
+				}
+
+			}
+		}
+
+	}
+
+	private final class CalDay {
+		public Object date;
+		public int number;
+		public Rectangle bounds;
+		public CalDay(int number, Object date, Rectangle bounds) {
+			this.date = date;
+			this.bounds = bounds;
+			this.number = number;
+		}
+	}
+
+	public static void main(String[] args) {
+
+		Display display = new Display();
+		Shell shell = new Shell(display);
+		shell.setLayout(new FillLayout());
+
+		CalendarComposite2 calendar = new CalendarComposite2(shell, new MyColorManager(), new MySettings(), null);
+		calendar.addDateListener(new IDateListener(){
+			public void dateChanged(Object date) {
+				System.out.println(date);
+			}
+		});
+
+		shell.pack();
+		shell.open();
+		while (!shell.isDisposed()) {
+			if (!display.readAndDispatch()) {
+				display.sleep();
+			}
+		}
+		display.dispose();
+
+	}
+
+	private static class MyColorManager extends AbstractColorManager {
+		private MyColorManager() {
+			super(SKIN_BLUE);
+		}
+	}
+
+	private static class MySettings extends AbstractSettings {
+
+		private Font macFont;
+
+		MySettings() {
+			super();
+		}
+
+		public Locale getLocale() {
+			return Locale.getDefault();
+		}
+
+		public String getNoneText() {
+			return "Aucun";
+		}
+
+		public String getTodayText() {
+			return "Auj.";
+		}
+
+		public boolean showCalendarInRightCorner() {
+			return true;
+		}
+
+		public Font getCarbonDrawFont() {
+			if (OS_CARBON && (this.macFont == null || this.macFont.isDisposed())) {
+				this.macFont = new Font(Display.getDefault(), "Lucida Grande", 11, SWT.NORMAL);
+			}
+			return this.macFont;
+		}
+
+	}
+}

/*******************************************************************************
 * Copyright (c) Steeve St-Laurent - www.infodata.ca - sstlaurent@xxxxxxxxxxx
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    sstlaurent@xxxxxxxxxxx - initial API
 *******************************************************************************/

package org.eclipse.nebula.widgets.calendarcombo;

import java.util.Locale;

/**
 * a factory used to create "date implementation independant" components
 */
public interface IDateFactory {

	/**
	 * @return never null
	 */
	Locale getLocale();

	/**
	 * create a new date instance
	 * @param day start from 1 to month end
	 * @param month start from 1 to 12
	 * @param year
	 * @return never null
	 * @throws NullPointerException if date is invalid
	 */
	Object newDate(int day, int month, int year);
	/**
	 * clear date instance
	 * @param date cant be null
	 * @return cleared date, never null, may or may not return same instance as param date
	 * @throws NullPointerException if date is null
	 */
	Object clear(Object date);
	/**
	 * create new today date instance
	 * @return today date, never null
	 */
	Object today();
	/**
	 * add x day to date instance
	 * @param date cant be null
	 * @param day
	 * @return modifed date, never null, may or may not return same instance as param date
	 * @throws NullPointerException if date is null
	 */
	Object plusDay(Object date, int day);
	/**
	 * add x month to date instance
	 * @param date cant be null
	 * @param month
	 * @return modifed date, never null, may or may not return same instance as param date
	 * @throws NullPointerException if date is null
	 */
	Object plusMonth(Object date, int month);
	/**
	 * add x year to date instance
	 * @param date cant be null
	 * @param year
	 * @return modifed date, never null, may or may not return same instance as param date
	 * @throws NullPointerException if date is null
	 */
	Object plusYear(Object date, int year);
	/**
	 * set date instance day to x
	 * @param date cant be null
	 * @param day, start from 1 to monthEnd
	 * @return modifed date, never null, may or may not return same instance as param date
	 * @throws NullPointerException if date is null or day is invalid
	 */
	Object withDay(Object date, int day);
	/**
	 * set date instance year to x
	 * @param date cant be null
	 * @param year
	 * @return modifed date, never null, may or may not return same instance as param date
	 * @throws NullPointerException if date is null
	 */
	Object withYear(Object date, int year);
	/**
	 * set date instance month to x
	 * @param date cant be null
	 * @param month, start from 1 to 12
	 * @return modifed date, never null, may or may not return same instance as param date
	 * @throws NullPointerException if date is null or month is invalid
	 */
	Object withMonth(Object date, int month);

	/**
	 * set date instance dayOfWeek to x
	 * @param date cant be null
	 * @param dayOfWeek, start from 1 to 7, sunday to saturday
	 * @return modifed date, never null, may or may not return same instance as param date
	 * @throws NullPointerException if date is null or month is invalid
	 */
	Object withDayOfWeek(Object date, int dayOfWeek);

	/**
	 * @param date cant be null
	 * @return date day, start from 1 to 31
	 * @throws NullPointerException if date is null
	 */
	int getDay(Object date);
	/**
	 * @param date cant be null
	 * @return date month, start from 1 to 12
	 * @throws NullPointerException if date is null
	 */
	int getMonth(Object date);
	/**
	 * @param date cant be null
	 * @return date year
	 * @throws NullPointerException if date is null
	 */
	int getYear(Object date);
	/**
	 * @param date cant be null
	 * @return date day of week, start from 1 to 7, sunday to saturday
	 * @throws NullPointerException if date is null
	 */
	int getDayOfWeek(Object date);

	/**
	 * @param date1 can be null
	 * @param date2 can be null
	 * @return true if date1 equals date2
	 */
	boolean equals(Object date1, Object date2);

	/**
	 * @param date cant be null
	 * @return a clone of date
	 * @throws NullPointerException if date is null
	 */
	Object clone(Object date);

	/**
	 * clone date1 instance to date2 instance
	 * @param date1 cant be null
	 * @param date2 cant be null
	 * @return date2
	 * @throws NullPointerException if date is null
	 */
	Object clone(Object date1, Object date2);

    /**
     * Gets what the first day of the week is; e.g., <code>SUNDAY</code> in the U.S.,
     * <code>MONDAY</code> in France.
     *
     * @return the first day of the week. 1 to 7, sunday to saturday
     */
	int getFirstDayOfWeek();

}

/*******************************************************************************
 * Copyright (c) Emil Crumhorn - Hexapixel.com - emil.crumhorn@xxxxxxxxx
 * Copyright (c) Steeve St-Laurent - www.infodata.ca - sstlaurent@xxxxxxxxxxx
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    emil.crumhorn@xxxxxxxxx - initial API and implementation of CalendarComposite
 *    sstlaurent@xxxxxxxxxxx - copy and epuration of CalendarComposite
 *******************************************************************************/

package org.eclipse.nebula.widgets.calendarcombo;


import java.text.DateFormatSymbols;
import java.util.ArrayList;
import java.util.Locale;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;

/**
 * this class does not support {@link ISettings#showMonthPickerOnMonthNameMousePress()} because if does not support month picker<br>
 * this class does not support {@link IColorManager#getDisabledDayForegroundColor()} because it does not support disallowed dates<br>
 * this class is really independant from {@link CalendarCombo}<br>
 * this class allow user to use its own date class via {@link IDateFactory} interface
 */
public class CalendarComposite2 extends Canvas implements MouseListener, MouseMoveListener {

	public static interface IDateListener {
		/**
		 * @see IDateFactory for date type
		 * @param date
		 */
		void dateChanged(Object date);
	}

    private static final boolean OS_CARBON        = "carbon".equals(SWT.getPlatform());
	private static final boolean OS_GTK           = "gtk".equals(SWT.getPlatform());
	private static final boolean OS_WINDOWS       = "win32".equals(SWT.getPlatform());

	private final int					ARROW_LEFT					= 1;

	private final int					ARROW_RIGHT					= 2;

	private Button						mButtonToday;

	private Button						mButtonNone;

	private Rectangle					mLeftArrowBounds;

	private Rectangle					mRightArrowBounds;

	private Object					mCalendar;

	private Object					mToday;

	private int							mDatesTopY					= 0;

	private int							mDayXs[]					= new int[7];

	private CalDay[]					mDays						= new CalDay[7 * 6];

	private Object					mSelectedDay;

	private static DateFormatSymbols	mDFS;

	private String						mMonths[];

	private static String[]				mDayTitles					= null;

	private ArrayList mListeners;

	private boolean						mNoDayClicked;

	// this thread deals with holding down the mouse over the arrows to jump
	// months quickly

	// milliseconds
	private static final int			ARROW_SLOW_TIME				= 300;

	private static final int			ARROW_FAST_TIME				= 120;

	// after how many iterations do we switch speed? (after how many months
	// flipped-by)
	private static final int			ARROW_SPEED_SWITCH_COUNT	= 10;

	// default speed
	private int							mArrowSleepTime				= ARROW_SLOW_TIME;

	// various variables used to keep track
	private int							mArrowIterations			= 0;

	private Thread						mArrowThread;

	private boolean						mArrowRun;

	private boolean						mArrowPause;

	private int							mArrowThreadDirection		= 0;

	private final IColorManager				mColorManager;

	private final ISettings					mSettings;

	private final IDateFactory				mDateFactory;

	public CalendarComposite2(Composite parent, IColorManager colorManager, ISettings settings, IDateFactory dateFactory) {
		super(parent, SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED);
		this.mColorManager = colorManager;
		this.mSettings = settings;
		this.mDateFactory = dateFactory == null ? new CalendarFactory(this.mSettings == null ? null : this.mSettings.getLocale()) : dateFactory;

		init();

		build();
	}

	private void init() {
		mDFS = new DateFormatSymbols(this.mDateFactory.getLocale());
		this.mMonths = mDFS.getMonths();

		if (this.mCalendar == null) {
			this.mCalendar = this.mDateFactory.today();
		}
		if (this.mToday == null) {
			this.mToday = this.mDateFactory.today();
		}

		String[] weekdays = mDFS.getWeekdays();
		mDayTitles = new String[weekdays.length];
		for (int i = 0; i < weekdays.length; i++) {
			String weekday = weekdays[i];
			if (weekday.length() > 0) {
				mDayTitles[i] = weekday.substring(0, 1).toUpperCase();
			}
		}

		//will be reused in draw days methods
		for (int i = 0; i < 42; i++) {
			this.mDays[i] = new CalDay(i, this.mDateFactory.today(), new Rectangle(0,0,0,0));
		}

	}

	private void build() {
		// button height & width and spacers
		int bheight = this.mSettings.getButtonHeight();
		int bwidth = this.mSettings.getButtonWidth();
		int buttonStyle = SWT.PUSH;
		this.mListeners = new ArrayList();

		// Mac buttons need different flag to look normal
		if (OS_CARBON) {
			bheight = this.mSettings.getCarbonButtonHeight();
			bwidth = this.mSettings.getButtonWidthCarbon();
			buttonStyle = SWT.FLAT;
		}

		setLayout(new ButtonSectionLayout());

		addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent event) {
				paint(event);
			}
		});

		addMouseListener(this);
		addMouseMoveListener(this);
		addKeyListener(new KeyListener() {
			public void keyReleased(KeyEvent e) {
			}
			public void keyPressed(KeyEvent e) {
				CalendarComposite2.this.keyPressed(e.keyCode, e.stateMask);
			}
		});

		this.mButtonToday = new Button(this, buttonStyle | SWT.NO_FOCUS);
		this.mButtonToday.setText(this.mSettings.getTodayText());
		this.mButtonToday.setLayoutData(new GridData(bwidth, bheight));
		this.mButtonToday.addListener(SWT.Selection, new Listener() {
			public void handleEvent(Event event) {
				clickedTodayButton();
			}
		});

		this.mButtonNone = new Button(this, buttonStyle | SWT.NO_FOCUS);
		this.mButtonNone.setText(this.mSettings.getNoneText());
		this.mButtonNone.setLayoutData(new GridData(bwidth, bheight));
		this.mButtonNone.addListener(SWT.Selection, new Listener() {
			public void handleEvent(Event event) {
				clickedNoneButton();
			}
		});
	}

	private void clickedTodayButton() {
		setReferenceDate(this.mDateFactory.today());
		this.mSelectedDay = this.mDateFactory.today();
		notifyListeners();
	}

	private void clickedNoneButton() {
		selectDate(null);
		notifyListeners();
	}

	private void paint(PaintEvent event) {
		GC gc = event.gc;
		drawChartOntoGC(gc);
	}

	private void drawChartOntoGC(GC gc) {
		Rectangle bounds = super.getBounds();
		gc.setBackground(this.mColorManager.getCalendarBackgroundColor());
		gc.fillRectangle(bounds);

		Font used = null;
		if (OS_CARBON) {
			used = this.mSettings.getCarbonDrawFont();
			if (used != null) {
				gc.setFont(used);
			}
		}

		// header
		drawHeader(gc);
		// day titles
		drawTitleDays(gc);
		// days
		drawDays(gc);
		// 1 pixel border
		drawBorder(gc);

		gc.dispose();
		if (used != null) {
			used.dispose();
		}
	}

	public void setReferenceDate(Object date) {
		this.mCalendar = date == null ? this.mDateFactory.today() : this.mDateFactory.clone(date);
		redraw();
	}

	public void selectDate(Object date){
		this.mSelectedDay = date == null ? null : this.mDateFactory.clone(date);
		redraw();
	}

	public void nextMonth() {
		this.mSelectedDay = null;
		this.mCalendar = this.mDateFactory.plusMonth(this.mCalendar, 1);
		this.mCalendar = this.mDateFactory.withDay(this.mCalendar, 1);
		redraw();
	}

	public void prevMonth() {
		this.mSelectedDay = null;
		this.mCalendar = this.mDateFactory.plusMonth(this.mCalendar, -1);
		this.mCalendar = this.mDateFactory.withDay(this.mCalendar, 1);
		redraw();
	}

	public void goToToday() {
		this.mSelectedDay = null;
		this.mCalendar = this.mDateFactory.today();
		redraw();
	}

	// draws 1 pixel border around entire calendar
	private void drawBorder(GC gc) {
		Rectangle bounds = super.getBounds();
		gc.setForeground(this.mColorManager.getCalendarBorderColor());
		gc.drawRectangle(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1);
	}

	private void drawHeader(GC gc) {
		Rectangle bounds = super.getBounds();
		Rectangle bgRect = new Rectangle(bounds.x + this.mSettings.getHeaderLeftMargin(), bounds.y + this.mSettings.getHeaderTopMargin(), this.mSettings.getCalendarWidth() - 13, bounds.y
				+ this.mSettings.getHeaderHeight());
		gc.setBackground(this.mColorManager.getCalendarHeaderColor());
		gc.fillRectangle(bgRect);
		drawArrow(gc, bounds.x + this.mSettings.getHeaderLeftMargin() + this.mSettings.getArrowLeftSpacing() + 1, bounds.y + this.mSettings.getHeaderTopMargin() + this.mSettings.getArrowTopSpacing()
				+ 4, this.ARROW_LEFT);
		drawArrow(gc, bounds.x + this.mSettings.getCalendarWidth() - 13 - this.mSettings.getHeaderRightMargin(), bounds.y + this.mSettings.getHeaderTopMargin() + this.mSettings.getArrowTopSpacing()
				+ 4, this.ARROW_RIGHT);

		String toDraw = this.mMonths[this.mDateFactory.getMonth(this.mCalendar)-1] + " " + this.mDateFactory.getYear(this.mCalendar);
		Point strWidth = gc.stringExtent(toDraw);

		int avail = this.mSettings.getCalendarWidth() - 13 - strWidth.x;
		avail /= 2;

		gc.drawString(toDraw, bounds.x + this.mSettings.getHeaderLeftMargin() + avail, bounds.y + this.mSettings.getHeaderTopMargin() + 1, true);
	}

	private void drawTitleDays(GC gc) {
		// fetch the first day of the week, and draw starting on that day
		int fdow = this.mDateFactory.getFirstDayOfWeek();

		Rectangle bounds = super.getBounds();
		int xStart = this.mSettings.getDatesLeftMargin() + 5;
		int yStart = bounds.y + this.mSettings.getHeaderTopMargin() + this.mSettings.getHeaderHeight() + 1;

		int spacer = 0;
		int letterHeight = 0;

		for (int i = 0; i < 7; i++) {
			Point strWidth = gc.stringExtent(mDayTitles[fdow]);

			int x = xStart + this.mSettings.getOneDateBoxSize() + spacer - strWidth.x;
			// don't add the string width, as our string width later when
			// drawing days will differ
			this.mDayXs[i] = xStart + this.mSettings.getOneDateBoxSize() + spacer;

			gc.drawString(mDayTitles[fdow], x, yStart, true);

			letterHeight = strWidth.y;
			spacer += this.mSettings.getOneDateBoxSize() + this.mSettings.getBoxSpacer();

			fdow++;
			if (fdow > 7) {
				fdow = 1;
			}
		}

		int lineStart = yStart + 1 + letterHeight;
		gc.setForeground(this.mColorManager.getLineColor());
		gc.drawLine(this.mSettings.getDatesLeftMargin() + 1, lineStart, bounds.width - this.mSettings.getDatesRightMargin() - 3, lineStart);

		this.mDatesTopY = lineStart + 3;
	}

	private void drawDays(GC gc) {
		gc.setBackground(this.mColorManager.getCalendarBackgroundColor());
		gc.setForeground(this.mColorManager.getTextColor());

		Rectangle bounds = super.getBounds();
		int spacer = 0;

//		debug("mCalendar", this.mCalendar);
		Object start = this.mDateFactory.clone(this.mCalendar);
//		debug("start", start);
		start = this.mDateFactory.withDay(start, 1);
//		debug("start", start);
		start = this.mDateFactory.withDayOfWeek(start, 1);
//		debug("start", start);

		Object date = start;

		final int monthToShow = this.mDateFactory.getMonth(this.mCalendar);

		int col = 0;
		int colCount = 0;

		String typicalLetter = "8";
		int strHeight = gc.stringExtent(typicalLetter).y;

		gc.setForeground(this.mColorManager.getLineColor());

		int lastY = 0;

		for (int y = 0; y < 42; y++) {

//			debug(date);

			// new row
			if (y % 7 == 0 && y != 0) {
				spacer += strHeight + 2;
			}

			if (colCount > 6) {
				colCount = 0;
				col = 0;
			}

			if (this.mDateFactory.getMonth(date) == monthToShow) {
				gc.setForeground(this.mColorManager.getTextColor());
			} else {
				gc.setForeground(this.mColorManager.getPreviousAndNextMonthForegroundColor());
			}

			String dateStr = String.valueOf(this.mDateFactory.getDay(date));
			Point width = gc.stringExtent(dateStr);

			if (this.mDateFactory.equals(this.mSelectedDay, date)) {
				gc.setBackground(this.mColorManager.getSelectedDayColor());
				gc.fillRectangle(this.mDayXs[col] - this.mSettings.getOneDateBoxSize() - 4, this.mDatesTopY + spacer - 1, this.mSettings.getOneDateBoxSize() + 5, 14);
				gc.setBackground(this.mColorManager.getCalendarBackgroundColor());
			}

			this.mDays[y].bounds.x = this.mDayXs[col] - this.mSettings.getOneDateBoxSize() - 4;
			this.mDays[y].bounds.y = this.mDatesTopY + spacer - 1;
			this.mDays[y].bounds.width = this.mSettings.getOneDateBoxSize() + 5;
			this.mDays[y].bounds.height = 14;
			this.mDays[y].number = y;
			this.mDays[y].date = this.mDateFactory.clone(date, this.mDays[y].date);

			gc.drawString(dateStr, this.mDayXs[col] - width.x, this.mDatesTopY + spacer, true);

			if (this.mDateFactory.equals(this.mToday, date)) {
				Color old = gc.getForeground();
				gc.setForeground(this.mColorManager.getSelectedDayBorderColor());
				gc.drawRectangle(this.mDayXs[col] - this.mSettings.getOneDateBoxSize() - 4, this.mDatesTopY + spacer - 1, this.mSettings.getOneDateBoxSize() + 5, 14);
				gc.setForeground(old);
			}

			date = this.mDateFactory.plusDay(date, 1);
			col++;
			colCount++;
			lastY = this.mDatesTopY + spacer;
		}

		lastY += strHeight + 1;

		gc.setForeground(this.mColorManager.getLineColor());
		gc.drawLine(this.mSettings.getDatesLeftMargin() + 1, lastY, bounds.width - this.mSettings.getDatesRightMargin() - 3, lastY);
	}

	private void debug(Object date) {
		System.out.println(this.mDateFactory.getDay(date) + "-"+this.mDateFactory.getMonth(date) +"-"+this.mDateFactory.getYear(date));
	}
	private void debug(String text, Object date) {
		System.out.println(text + " " + this.mDateFactory.getDay(date) + "-"+this.mDateFactory.getMonth(date) +"-"+this.mDateFactory.getYear(date));
	}

	private void drawArrow(GC gc, int x, int y, int style) {
		gc.setForeground(this.mColorManager.getArrowColor());
		switch (style) {
		case ARROW_RIGHT:
			gc.drawLine(x, y - 4, x, y + 4);
			gc.drawLine(x + 1, y - 3, x + 1, y + 3);
			gc.drawLine(x + 2, y - 2, x + 2, y + 2);
			gc.drawLine(x + 3, y - 1, x + 3, y + 1);
			gc.drawLine(x + 4, y, x + 4, y);
			this.mRightArrowBounds = new Rectangle(x - 4, y - 4, x + 8, y);
			break;
		case ARROW_LEFT:
			gc.drawLine(x - 1, y, x - 1, y);
			gc.drawLine(x, y - 1, x, y + 1);
			gc.drawLine(x + 1, y - 2, x + 1, y + 2);
			gc.drawLine(x + 2, y - 3, x + 2, y + 3);
			gc.drawLine(x + 3, y - 4, x + 3, y + 4);
			this.mLeftArrowBounds = new Rectangle(x - 4, y - 4, x + 8, y);
			break;
		}
	}

	private static boolean isInside(int x, int y, Rectangle rect) {
		if (rect == null) {
			return false;
		}

		if (x >= rect.x && y >= rect.y && x <= (rect.x + rect.width) && y <= (rect.y + rect.height)) {
			return true;
		}

		return false;
	}

	public void mouseMove(MouseEvent e) {

		// "dragging" the day, just paint it
		if (e.stateMask != 0) {
			doDaySelection(e.x, e.y);
		}

		if (this.mArrowRun && this.mArrowThread != null) {
			if (!isInside(e.x, e.y, this.mLeftArrowBounds) && !isInside(e.x, e.y, this.mRightArrowBounds)) {
				this.mArrowPause = true;
				// also pause the speed
				this.mArrowIterations = 0;
				this.mArrowSleepTime = ARROW_SLOW_TIME;
			} else {
				if (isInside(e.x, e.y, this.mLeftArrowBounds)) {
					this.mArrowThreadDirection = this.ARROW_LEFT;
				} else {
					this.mArrowThreadDirection = this.ARROW_RIGHT;
				}

				this.mArrowPause = false;
			}
		}
	}

	public void mouseDoubleClick(MouseEvent event) {
	}

	// draw the date selection on mouse down to give that flicker of "response"
	// to the user
	public void mouseDown(MouseEvent event) {

		if (isInside(event.x, event.y, this.mLeftArrowBounds)) {
			prevMonth();

			runArrowThread(this.ARROW_LEFT);

			return;
		}

		if (isInside(event.x, event.y, this.mRightArrowBounds)) {
			nextMonth();

			runArrowThread(this.ARROW_RIGHT);

			return;
		}

		doDaySelection(event.x, event.y);

	}

	private void killArrowThread() {
		if (this.mArrowThread != null) {
			this.mArrowPause = true;
			this.mArrowThreadDirection = 0;
			this.mArrowRun = false;
			this.mArrowThread = null;
		}
	}

	private void runArrowThread(int direction) {
		this.mArrowThreadDirection = direction;
		this.mArrowIterations = 0;
		this.mArrowSleepTime = ARROW_SLOW_TIME;
		this.mArrowRun = false;
		this.mArrowPause = false;
		this.mArrowThread = new Thread() {
			public void run() {
				while (CalendarComposite2.this.mArrowRun) {
					try {
						sleep(CalendarComposite2.this.mArrowSleepTime);

						if (!CalendarComposite2.this.mArrowPause) {
							CalendarComposite2.this.mArrowIterations++;
						}

						if (CalendarComposite2.this.mArrowIterations > ARROW_SPEED_SWITCH_COUNT && CalendarComposite2.this.mArrowSleepTime != ARROW_FAST_TIME) {
							CalendarComposite2.this.mArrowSleepTime = ARROW_FAST_TIME;
						}
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					if (!CalendarComposite2.this.mArrowPause) {
						Display.getDefault().syncExec(new Runnable() {
							public void run() {
								if (isDisposed()) {
									CalendarComposite2.this.mArrowRun = false;
									CalendarComposite2.this.mArrowThread = null;
								} else {
									if (CalendarComposite2.this.mArrowThreadDirection == CalendarComposite2.this.ARROW_LEFT) {
										prevMonth();
									} else if (CalendarComposite2.this.mArrowThreadDirection == CalendarComposite2.this.ARROW_RIGHT) {
										nextMonth();
									}
								}
							}
						});
					}
				}
			}
		};
		this.mArrowPause = false;
		this.mArrowRun = true;
		this.mArrowThread.start();
	}

	// selects a day at x, y if there is a day there to select
	private void doDaySelection(int x, int y) {
		int curYear = this.mDateFactory.getYear(this.mCalendar);
		int curMonth = this.mDateFactory.getMonth(this.mCalendar);
		this.mNoDayClicked = false;

		for (int i = 0; i < this.mDays.length; i++) {
			if (isInside(x, y, this.mDays[i].bounds)) {

				int year = this.mDateFactory.getYear(this.mDays[i].date);
				int month = this.mDateFactory.getMonth(this.mDays[i].date);

				if (year == curYear) {
					if (month < curMonth) {
						prevMonth();
						return;
					} else if (month > curMonth) {
						nextMonth();
						return;
					}
				} else {
					if (year < curYear) {
						prevMonth();
						return;
					} else if (year > curYear) {
						nextMonth();
						return;
					}
				}

				if (this.mSelectedDay == null) {
					this.mSelectedDay = this.mDateFactory.clone(this.mDays[i].date);
				} else {
					this.mSelectedDay = this.mDateFactory.clone(this.mDays[i].date, this.mSelectedDay);
				}

				redraw();
				return;
			}
		}

		this.mNoDayClicked = true;
	}

	public void mouseUp(MouseEvent event) {
		killArrowThread();

		if (this.mNoDayClicked) {
			this.mNoDayClicked = false;
			return;
		}

		if (this.mSelectedDay != null) {
			notifyListeners();
		}
	}

	private void notifyListeners() {
		final Object date = this.mSelectedDay == null ? null : this.mDateFactory.clone(this.mSelectedDay);
		for (int i = 0; i < this.mListeners.size(); i++) {
			IDateListener listener = (IDateListener) this.mListeners.get(i);
			listener.dateChanged(date);
		}
	}

	public void addDateListener(IDateListener listener) {
		if (!this.mListeners.contains(listener)) {
			this.mListeners.add(listener);
		}
	}

	public void removeCalendarListener(IDateListener listener) {
		this.mListeners.remove(listener);
	}

	private void keyPressed(int keyCode, int stateMask) {
		if (this.mSelectedDay == null) {
			this.mSelectedDay = this.mDateFactory.clone(this.mCalendar);
			this.mSelectedDay = this.mDateFactory.withDay(this.mSelectedDay, 1);
			redraw();
		} else {
			if (keyCode == SWT.ARROW_RIGHT) {
				int monthNow = this.mDateFactory.getMonth(this.mSelectedDay);
				this.mSelectedDay = this.mDateFactory.plusDay(this.mSelectedDay, 1);
				int monthAfter = this.mDateFactory.getMonth(this.mSelectedDay);
				if (monthAfter != monthNow) {
					this.mCalendar = this.mDateFactory.plusMonth(this.mCalendar, 1);
				}
				redraw();
			} else if (keyCode == SWT.ARROW_LEFT) {
				int monthNow = this.mDateFactory.getMonth(this.mSelectedDay);
				this.mSelectedDay = this.mDateFactory.plusDay(this.mSelectedDay, -1);
				int monthAfter = this.mDateFactory.getMonth(this.mSelectedDay);
				if (monthAfter != monthNow) {
					this.mCalendar = this.mDateFactory.plusMonth(this.mCalendar, -1);
				}
				redraw();
			} else if (keyCode == SWT.ARROW_UP) {
				int monthNow = this.mDateFactory.getMonth(this.mSelectedDay);
				this.mSelectedDay = this.mDateFactory.plusDay(this.mSelectedDay, -7);
				int monthAfter = this.mDateFactory.getMonth(this.mSelectedDay);
				if (monthAfter != monthNow) {
					this.mCalendar = this.mDateFactory.plusMonth(this.mCalendar, -1);
				}
				redraw();
			} else if (keyCode == SWT.ARROW_DOWN) {
				int monthNow = this.mDateFactory.getMonth(this.mSelectedDay);
				this.mSelectedDay = this.mDateFactory.plusDay(this.mSelectedDay, 7);
				int monthAfter = this.mDateFactory.getMonth(this.mSelectedDay);
				if (monthAfter != monthNow) {
					this.mCalendar = this.mDateFactory.plusMonth(this.mCalendar, 1);
				}
				redraw();
			} else if (keyCode == SWT.CR || keyCode == SWT.LF) {
				notifyListeners();
				return;
			}
		}
	}

	private class ButtonSectionLayout extends Layout {

		protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
			return new Point(153, 160);
		}

		protected void layout(Composite composite, boolean flushCache) {
			int bheight = CalendarComposite2.this.mSettings.getButtonHeight();
			int bwidth = CalendarComposite2.this.mSettings.getButtonWidth();
			int vspacer = CalendarComposite2.this.mSettings.getButtonVerticalSpace();
			int bspacer = CalendarComposite2.this.mSettings.getButtonsHorizontalSpace();
			if (OS_CARBON) {
				bwidth = CalendarComposite2.this.mSettings.getButtonWidthCarbon();
				bheight = CalendarComposite2.this.mSettings.getCarbonButtonHeight();
				vspacer = CalendarComposite2.this.mSettings.getCarbonButtonVerticalSpace();
				bspacer = CalendarComposite2.this.mSettings.getCarbonButtonsHorizontalSpace();
			}

			// see how much space we put on the left and right sides of the
			// buttons
			int width = CalendarComposite2.this.mSettings.getCalendarWidth() - (bwidth * 2) - bspacer;
			width /= 2;

			int button1Left = width;
			int button2Left = CalendarComposite2.this.mSettings.getCalendarWidth() - width - bwidth;

			Control[] children = composite.getChildren();
			for (int i = 0; i < children.length; i++) {
				switch (i) {
				case 0:
					children[i].setBounds(button1Left, vspacer, bwidth, bheight);

					break;
				case 1:
					children[i].setBounds(button2Left, vspacer, bwidth, bheight);
					break;
				}

			}
		}

	}

	private final class CalDay {
		public Object date;
		public int number;
		public Rectangle bounds;
		public CalDay(int number, Object date, Rectangle bounds) {
			this.date = date;
			this.bounds = bounds;
			this.number = number;
		}
	}

	public static void main(String[] args) {

		Display display = new Display();
		Shell shell = new Shell(display);
		shell.setLayout(new FillLayout());

		CalendarComposite2 calendar = new CalendarComposite2(shell, new MyColorManager(), new MySettings(), null);
		calendar.addDateListener(new IDateListener(){
			public void dateChanged(Object date) {
				System.out.println(date);
			}
		});

		shell.pack();
		shell.open();
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch()) {
				display.sleep();
			}
		}
		display.dispose();

	}

	private static class MyColorManager extends AbstractColorManager {
		private MyColorManager() {
			super(SKIN_BLUE);
		}
	}

	private static class MySettings extends AbstractSettings {

		private Font macFont;

		MySettings() {
			super();
		}

		public Locale getLocale() {
			return Locale.getDefault();
		}

		public String getNoneText() {
			return "Aucun";
		}

		public String getTodayText() {
			return "Auj.";
		}

		public boolean showCalendarInRightCorner() {
			return true;
		}

		public Font getCarbonDrawFont() {
			if (OS_CARBON && (this.macFont == null || this.macFont.isDisposed())) {
				this.macFont = new Font(Display.getDefault(), "Lucida Grande", 11, SWT.NORMAL);
			}
			return this.macFont;
		}

	}
}

/*******************************************************************************
 * Copyright (c) Steeve St-Laurent - www.infodata.ca - sstlaurent@xxxxxxxxxxx
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    sstlaurent@xxxxxxxxxxx - initial implementations
 *******************************************************************************/

package org.eclipse.nebula.widgets.calendarcombo;

import java.util.Calendar;
import java.util.Locale;

/**
 * a factory that work on calendar
 */
public class CalendarFactory implements IDateFactory {

	private final Locale locale;
	private final int firstDayOfWeek;

	/**
	 * @param new factory with locale set to {@link Locale#getDefault()}
	 */
	public CalendarFactory() {
		this(null);
	}

	/**
	 * @param locale can be null, default to {@link Locale#getDefault()}
	 */
	public CalendarFactory(Locale locale) {
		if (locale == null) {
			locale = Locale.getDefault();
		}
		this.locale = locale;
		this.firstDayOfWeek = ((Calendar)today()).getFirstDayOfWeek();
	}

	public Object plusDay(Object date, int day) {
		Calendar c = ((Calendar)date);
		c.add(Calendar.DATE, day);
		return c;
	}

	public Object plusMonth(Object date, int month) {
		Calendar c = ((Calendar)date);
		c.add(Calendar.MONTH, month);
		return c;
	}

	public Object plusYear(Object date, int year) {
		Calendar c = ((Calendar)date);
		c.add(Calendar.YEAR, year);
		return c;
	}

	public Object clear(Object date) {
		Calendar c = ((Calendar)date);
		c.clear();
		return c;
	}

	public boolean equals(Object date1, Object date2) {
		if (date1 == date2) {
			return true;
		}
		if (date1 == null || date2 == null) {
			return false;
		}
		return DateHelper.sameDate((Calendar)date1, (Calendar)date2);
	}

	public int getDay(Object date) {
		Calendar c = ((Calendar)date);
		return c.get(Calendar.DATE);
	}

	public int getDayOfWeek(Object date) {
		Calendar c = ((Calendar)date);
		return c.get(Calendar.DAY_OF_WEEK);
	}

	public int getMonth(Object date) {
		Calendar c = ((Calendar)date);
		return c.get(Calendar.MONTH) + 1;
	}

	public int getYear(Object date) {
		Calendar c = ((Calendar)date);
		return c.get(Calendar.YEAR);
	}

	public Object newDate(int day, int month, int year) {
		Calendar c = Calendar.getInstance(this.locale);
		c.set(Calendar.YEAR, year);
		c.set(Calendar.MONTH, month-1);
		c.set(Calendar.DATE, day);
		return toMidnight(c);
	}

	private static Calendar toMidnight(Calendar c){
		c.set(Calendar.HOUR, 0);
		c.set(Calendar.HOUR_OF_DAY, 0);
		c.set(Calendar.MINUTE, 0);
		c.set(Calendar.SECOND, 0);
		c.set(Calendar.MILLISECOND, 0);
		return c;
	}

	public Object today() {
		return toMidnight(Calendar.getInstance(this.locale));
	}

	public Object withDay(Object date, int day) {
		Calendar c = ((Calendar)date);
		c.set(Calendar.DAY_OF_MONTH, day);
		return c;
	}

	public Object withMonth(Object date, int month) {
		Calendar c = ((Calendar)date);
		c.set(Calendar.MONTH, month-1);
		return c;
	}

	public Object withYear(Object date, int year) {
		Calendar c = ((Calendar)date);
		c.set(Calendar.YEAR, year);
		return c;
	}

	public Object withDayOfWeek(Object date, int dayOfWeek) {
		Calendar c = ((Calendar)date);
		c.set(Calendar.DAY_OF_WEEK, dayOfWeek);
		return c;
	}

	public Object clone(Object date) {
		Calendar c = ((Calendar)date);
		return c.clone();
	}

	public int getFirstDayOfWeek() {
		return this.firstDayOfWeek;
	}

	public Object clone(Object date1, Object date2) {
		Calendar c1 = ((Calendar)date1);
		Calendar c2 = ((Calendar)date2);
		c2.set(Calendar.YEAR, c1.get(Calendar.YEAR));
		c2.set(Calendar.MONTH, c1.get(Calendar.MONTH));
		c2.set(Calendar.DATE, c1.get(Calendar.DATE));
		return toMidnight(c2);
	}

	public Locale getLocale() {
		return this.locale;
	}
}

/*******************************************************************************
 * Copyright (c) Emil Crumhorn - Hexapixel.com - emil.crumhorn@xxxxxxxxx
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    emil.crumhorn@xxxxxxxxx - initial API and implementation
 *******************************************************************************/

package org.eclipse.nebula.widgets.calendarcombo;

import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;

public class DateHelper {

	private static final long	MILLISECONDS_IN_DAY	= 24 * 60 * 60 * 1000;

	public static long daysBetween(Calendar start, Calendar end, Locale locale) {
		// create copies
		GregorianCalendar startDate = new GregorianCalendar(locale);
		GregorianCalendar endDate = new GregorianCalendar(locale);

		// switch calendars to pure Julian mode for correct day-between
		// calculation, from the Java API:
		// - To obtain a pure Julian calendar, set the change date to
		// Date(Long.MAX_VALUE).
		startDate.setGregorianChange(new Date(Long.MAX_VALUE));
		endDate.setGregorianChange(new Date(Long.MAX_VALUE));

		// set them
		startDate.setTime(start.getTime());
		endDate.setTime(end.getTime());

		// force times to be exactly the same
		startDate.set(Calendar.HOUR_OF_DAY, 12);
		endDate.set(Calendar.HOUR_OF_DAY, 12);
		startDate.set(Calendar.MINUTE, 0);
		endDate.set(Calendar.MINUTE, 0);
		startDate.set(Calendar.SECOND, 0);
		endDate.set(Calendar.SECOND, 0);
		startDate.set(Calendar.MILLISECOND, 0);
		endDate.set(Calendar.MILLISECOND, 0);

		// now we should be able to do a "safe" millisecond/day caluclation to
		// get the number of days
		long endMilli = endDate.getTimeInMillis();
		long startMilli = startDate.getTimeInMillis();

		// calculate # of days, finally
		long diff = (endMilli - startMilli) / MILLISECONDS_IN_DAY;

		return diff;
	}

	public static long daysBetween(Date start, Date end, Locale locale) {
		Calendar dEnd = Calendar.getInstance(locale);
		Calendar dStart = Calendar.getInstance(locale);
		dEnd.setTime(end);
		dStart.setTime(start);
		return daysBetween(dStart, dEnd, locale);
	}

	public static boolean isToday(Date date, Locale locale) {
		Calendar cal = Calendar.getInstance(locale);
		cal.setTime(date);

		return isToday(cal, locale);
	}

	public static boolean isToday(Calendar cal, Locale locale) {
		Calendar today = Calendar.getInstance(locale);

		if (today.get(Calendar.YEAR) == cal.get(Calendar.YEAR)) {
			if (today.get(Calendar.DAY_OF_YEAR) == cal.get(Calendar.DAY_OF_YEAR)) {
				return true;
			}
		}

		return false;
	}

	public static String getDate(Calendar cal, String dateFormat) {
		Calendar toUse = (Calendar) cal.clone();
		toUse.add(Calendar.MONTH, -1);

		SimpleDateFormat df = new SimpleDateFormat(dateFormat);
		df.setLenient(true);
		return df.format(cal.getTime());
	}

	public static boolean sameDate(Date date1, Date date2, Locale locale) {
		Calendar cal1 = Calendar.getInstance(locale);
		Calendar cal2 = Calendar.getInstance(locale);

		cal1.setTime(date1);
		cal2.setTime(date2);

		return sameDate(cal1, cal2);
	}

	public static boolean sameDate(Calendar cal1, Calendar cal2) {
		if (cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)) {
			if (cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR)) {
				return true;
			}
		}

		return false;
	}

	public static Date getDate(String str, String dateFormat) throws Exception {
		SimpleDateFormat df = new SimpleDateFormat(dateFormat);
		df.setLenient(false);

		return df.parse(str);
	}

	public static Calendar getDate(String str, String dateFormat, Locale locale) throws Exception {
		SimpleDateFormat df = new SimpleDateFormat(dateFormat, locale);
		Date d = df.parse(str);
		Calendar cal = Calendar.getInstance(locale);
		cal.setTime(d);
		return cal;
	}

	public static Calendar parseDate(String str, Locale locale) throws Exception {
		Date foo = DateFormat.getDateInstance(DateFormat.SHORT, locale).parse(str);
		Calendar cal = Calendar.getInstance(locale);
		cal.setTime(foo);
		return cal;
	}

	private static Calendar calendarize(Date date, Locale locale) {
		Calendar cal = Calendar.getInstance(locale);
		cal.setTime(date);
		return cal;
	}

	public static Calendar parse(final String comboText, final Locale locale, final String dateFormat, final char[] acceptedSeparatorChars, final List additionalDateFormats)
			throws CalendarDateParseException, Exception {

		Calendar parsed = null;
		Exception ex = null;
		
		boolean isNumeric = comboText.replaceAll("[^0-9]", "").length() == comboText.length();

		if (isNumeric) {
			try {
				parsed = numericParse(comboText, locale, false);
			} catch (Exception e) {
				//maybe dont care
				ex = ex==null?e:ex;
			}
		}
		
		if (parsed == null){
			try {
				parsed = slashParse(comboText, dateFormat, acceptedSeparatorChars, locale);
			} catch (Exception e) {
				//maybe dont care
				ex = ex==null?e:ex;
			}
		}
		
		if (parsed == null){
			if (additionalDateFormats != null) {
//				final Calendar today = Calendar.getInstance(locale);
				for (int i = 0; i < additionalDateFormats.size(); i++) {
					try {
						String format = (String) additionalDateFormats.get(i);
						Date date = DateHelper.getDate(comboText, format);
						if (date != null){
							parsed = calendarize(date, locale);
							break;
						}
					}
					catch (Exception e) {
						//maybe dont care
						ex = ex==null?e:ex;
					}
				}
			}
		}

		if (parsed == null){
			//no more try
			throw ex;
		}

		return parsed;
		
		//return null;
		//}

		/*if (comboText.length() == 0) {
			return null;
		}

		try {
			// start with a hard parse as date format parses can return
			// false positives on various locales.
			// false positives may sound good, but they're bad, as they can
			// cause a year to end up 2000 years off...
			try {
				mStartDate = DateHelper.parseDateHard(comboText, locale);
				return mStartDate;
			}
			catch (Exception err) {

			}
			
			// try true date format parse
			mStartDate = DateHelper.getDate(comboText, dateFormat, locale);
			return mStartDate;
			// System.err.println("Got here 2 - Settings parse " +
			// mStartDate.getTime());
		}
		catch (Exception err) {
			// try the locale (this is error prone due to how java parses
			// dates)
			try {
				mStartDate = DateHelper.parseDate(comboText, locale);
				return mStartDate;
				// System.err.println("Got here 3 - Locale parse " +
				// mStartDate.getTime());
			}
			catch (Exception deeper) {
				try {
					mStartDate = DateHelper.slashParse(comboText, dateFormat, acceptedSeparatorChars, locale);
					return mStartDate;
				}
				catch (Exception ohwell) {
					// System.err.println("Failed parse, trying additional formats");
					if (additionalDateFormats != null) {
						try {
							for (int i = 0; i < additionalDateFormats.size(); i++) {
								try {
									String format = (String) additionalDateFormats.get(i);
									Date date = DateHelper.getDate(comboText, format);
									return mStartDate;
								}
								catch (Exception failed) {
									// keep trying
								}
							}
						}
						catch (Exception err2) {
							// don't care
						}
					}
				}

				return mStartDate;
			}
		}
		}
		catch (Exception err) {
		err.printStackTrace();
		}*/

		//return null;
	}

	/**
	 * This method will try its best to parse a date based on the current
	 * Locale.
	 * 
	 * @param str
	 *            String to parse
	 * @param locale
	 *            Current Locale
	 * @return Calendar or null on failure
	 * @throws CalendarDateParseException
	 *             If date could not be parsed
	 * @throws Exception
	 *             on any unforseen issues or bad parse errors
	 */
	public static Calendar parseDateHard(final String str, final Locale locale) throws CalendarDateParseException, Exception {

		try {
			DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
			String actualLocalePattern = ((SimpleDateFormat) df).toPattern();

			try {
				Calendar foo = slashParse(str, actualLocalePattern, new char[] {
						'/', '-', '.'
				}, locale);
				return foo;
			}
			catch (Exception err) {

			}

			try {
				Date foo = df.parse(str);
				return calendarize(foo, locale);
			}
			catch (Exception err) {
				// some locales already have 4 y's
				if (actualLocalePattern.indexOf("yyyy") == -1)
					actualLocalePattern = actualLocalePattern.replaceAll("yy", "yyyy");

				try {
					Date foo = df.parse(str);
					return calendarize(foo, locale);
				}
				catch (Exception err2) {
					// fall through
				}
			}
		}
		catch (Exception err) {
			// fall through
		}

		try {
			Date foo = DateFormat.getDateInstance().parse(str);
			return calendarize(foo, locale);
		}
		catch (Exception err) {
			try {
				Integer.parseInt(str);

				try {
					DateFormat df = DateFormat.getDateInstance();
					df.setLenient(false);
					Date foo = df.parse(str);
					return calendarize(foo, locale);
				}
				catch (Exception err2) {
					return numericParse(str, locale, true);
				}
			}
			catch (Exception failedInt) {
				// clear the bad chars and try again
				StringBuffer buf = new StringBuffer();
				for (int i = 0; i < str.length(); i++) {
					if (str.charAt(i) >= '0' && str.charAt(i) <= '9')
						buf.append(str.charAt(i));
				}

				String fixed = buf.toString();
				try {
					Integer.parseInt(fixed);
					return numericParse(fixed, locale, true);
				}
				catch (Exception forgetit) {
					throw new CalendarDateParseException(forgetit, CalendarDateParseException.TYPE_EXCEPTION);
				}
			}
		}
	}

	// date formats with a single M d y etc are highly problematic (US dates),
	// so replace them with their proper format so that we can parse
	// or else we'll be parsing years like "03" as "0003"
	public static String dateFormatFix(String str) {
		if (str.indexOf("M") != -1 && str.indexOf("MM") == -1 && str.indexOf("MMM") == -1) {
			str = str.replaceAll("M", "MM");
		}
		if (str.indexOf("d") != -1 && str.indexOf("dd") == -1 && str.indexOf("ddd") == -1) {
			str = str.replaceAll("d", "dd");
		}
		if (str.indexOf("y") != -1 && str.indexOf("yy") == -1 && str.indexOf("yyy") == -1) {
			str = str.replaceAll("y", "yy");
		}

		return str;

	}

	public static Calendar numericParse(String str, Locale locale, boolean doUsEuParse) throws Exception {
		// we always start with the locale and try to parse that numerically, if
		// that fails we'll try another few possibilities before we give up
		DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
		String actualLocalePattern = ((SimpleDateFormat) df).toPattern();

		// remove all non letters which will leave us with a clean date pattern
		actualLocalePattern = dateFormatFix(actualLocalePattern.replaceAll("[^a-zA-Z]", ""));
		actualLocalePattern = actualLocalePattern.replaceAll("G", "");

		// parse it into long / short versions where the year is 4 or 2 digits
		String actualLocaleLong = "";
		String actualLocaleShort = "";
		if (actualLocalePattern.indexOf("yyyy") == -1) {
			actualLocaleShort = actualLocalePattern;
			actualLocaleLong = actualLocalePattern.replaceAll("yy", "yyyy");
		}
		else {
			actualLocaleLong = actualLocalePattern;
			actualLocaleShort = actualLocalePattern.replaceAll("yyyy", "yy");
		}

		Date parsed = null;

		// now parse it according to locale if we can
		try {
			if (str.length() == 6) {
				SimpleDateFormat sdf = new SimpleDateFormat(actualLocaleShort);
				parsed = sdf.parse(str);
			}
			else if (str.length() == 5) {
				// if a user enters a 5-digit date we assume they were clever enough to get the day and month in 2 digit formats, and the last being the year in a 1 digit format.
				// so we need to 2-digitize the year and re-parse. As any 1900-year would be 2 digit except for early 1900's (we don't care) we assume it's 2000+. As I said, if we
				// get here the user is really pushing their luck on parsing anyway and we're doing them a favor to begin with
				StringBuffer buf = new StringBuffer();
				buf.append(str.substring(0, 4));
				buf.append("0");
				buf.append(str.substring(4, 5));
				return numericParse(buf.toString(), locale, doUsEuParse);
			}
			else {
				SimpleDateFormat sdf = new SimpleDateFormat(actualLocaleLong);
				parsed = sdf.parse(str);
			}

			if (parsed != null)
				return calendarize(parsed, locale);
		}
		catch (ParseException pe) {
			// ignore, try more
		}

		if (doUsEuParse) {
			// try a couple of pre-defined formats, it's highly likely it's
			// either
			// US or European
			String usFormat6 = "MMddyy";
			String usFormat8 = "MMddyyyy";
			String euFormat6 = "ddMMyy";
			String euFormat8 = "ddMMyyyy";

			if (locale.equals(Locale.US)) {
				if (str.length() == 6) {
					SimpleDateFormat sdf = new SimpleDateFormat(usFormat6);
					parsed = sdf.parse(str);
				}
				else {
					SimpleDateFormat sdf = new SimpleDateFormat(usFormat8);
					parsed = sdf.parse(str);
				}
			}
			else {
				if (str.length() == 6) {
					SimpleDateFormat sdf = new SimpleDateFormat(euFormat6);
					parsed = sdf.parse(str);
				}
				else {
					SimpleDateFormat sdf = new SimpleDateFormat(euFormat8);
					parsed = sdf.parse(str);
				}
			}
		}

		if (parsed != null) {
			return calendarize(parsed, locale);
		}

		return null;
	}

	public static int getCalendarTypeForString(String oneChar) {

		int calType = -1;

		switch (oneChar.charAt(0)) {
			case 'G':
				calType = Calendar.ERA;
				break;
			case 'y':
				calType = Calendar.YEAR;
				break;
			case 'M':
				calType = Calendar.MONTH;
				break;
			case 'd':
				calType = Calendar.DAY_OF_MONTH;
				break;
			case 'E':
				calType = Calendar.DAY_OF_WEEK;
				break;
			case 'D':
				calType = Calendar.DAY_OF_YEAR;
				break;
			case 'F':
				calType = Calendar.DATE;
				break;
			case 'h':
				calType = Calendar.HOUR;
				break;
			case 'm':
				calType = Calendar.MINUTE;
				break;
			case 's':
				calType = Calendar.SECOND;
				break;
			case 'S':
				calType = Calendar.MILLISECOND;
				break;
			case 'w':
				calType = Calendar.WEEK_OF_YEAR;
				break;
			case 'W':
				calType = Calendar.WEEK_OF_MONTH;
				break;
			case 'a':
				calType = Calendar.AM_PM;
				break;
			case 'k':
				calType = Calendar.HOUR_OF_DAY;
				break;
			case 'K':
				// ?
				break;
			case 'z':
				calType = Calendar.ZONE_OFFSET;
				break;
		}

		return calType;
	}

	/**
	 * This method assumes the dateFormat has a separator char in it, and that
	 * we can use that to determine what the user entered by using that
	 * separator to split up the user entered date, and then do some logic on
	 * it. This is by no means a foolproof method and should not be relied upon
	 * returning 100% correct dates all the time.
	 * 
	 * @param str
	 *            String to parse
	 * @param dateFormat
	 *            DateFormat to use
	 * @param separators
	 *            Separator chars that can be encountered
	 * @param locale
	 *            Locale
	 * @return Calendar
	 * @throws CalendarDateParseException
	 *             If date could not be parsed
	 * @throws Exception
	 *             If any step of the parsing failed
	 */
	public static Calendar slashParse(final String str, final String dateFormat, final char[] separators, final Locale locale) throws CalendarDateParseException, Exception {
		int start = -1;
		String splitter = null;
		String dateFormatToUse = dateFormat;
		for (int i = 0; i < separators.length; i++) {
			start = str.indexOf(separators[i]);
			if (start != -1) {
				splitter = String.valueOf(separators[i]);
				break;
			}
		}
		if (start == -1)
			throw new CalendarDateParseException("Failed to find splitter char", CalendarDateParseException.TYPE_NO_SLPITTER_CHAR);

		// replace dateFormat until we have same splitter
		for (int i = 0; i < separators.length; i++) {
			if (String.valueOf(separators[i]).equals(splitter))
				continue;

			dateFormatToUse = dateFormatToUse.replaceAll("\\" + String.valueOf(separators[i]), splitter);
		}

		Calendar toReturn = Calendar.getInstance(locale);
		StringTokenizer st = new StringTokenizer(str, splitter);
		StringTokenizer st2 = new StringTokenizer(dateFormatToUse, splitter);

		if (st.countTokens() != st2.countTokens())
			throw new CalendarDateParseException("Date format does not match date string in terms of splitter character numbers", CalendarDateParseException.TYPE_INSUFFICIENT_SPLITTERS);

		// variables we'll be extracting
		int monthToSet = -1;
		int dayToSet = -1;
		int yearToSet = -1;
	
		// reset, skipping month this time
		st = new StringTokenizer(str, splitter);
		st2 = new StringTokenizer(dateFormatToUse, splitter);

		while (st.hasMoreTokens()) {
			String dateValue = st.nextToken();
			String dateType = st2.nextToken();

			dateValue = dateValue.replaceAll(" ", "");
			dateType = dateType.replaceAll(" ", "");

			int calType = getCalendarTypeForString(dateType);
			// we already did month
			if (calType == Calendar.MONTH) {
			    monthToSet = Integer.parseInt(dateValue);
				continue;
			}
			if (calType == Calendar.YEAR) {
			    yearToSet = Integer.parseInt(dateValue);
			    continue;
			}
            if (calType == Calendar.DATE) {
                dayToSet = Integer.parseInt(dateValue);
                continue;
            }

			toReturn.set(calType, Integer.parseInt(dateValue));
		}

		// set all date parameters at the same time, or else we'll get month-skipping due to setting a value later (such as a date that is too high
		// for the current month). (-1 for month as Calendar class is month-zero-based).
		if (monthToSet != -1 && dayToSet != -1 && yearToSet != -1) {
		    toReturn.set(yearToSet, monthToSet-1, dayToSet);
		}
		else {
		    // set what we know
		    if (yearToSet != -1) {
		        toReturn.set(Calendar.YEAR, yearToSet);
		    }
		    if (monthToSet != -1) {
		        toReturn.set(Calendar.MONTH, monthToSet-1);
		    }
		    if (dayToSet != -1) {
		        toReturn.set(Calendar.DATE, dayToSet);
		    }
		}
		
		if (toReturn.get(Calendar.YEAR) < 100)
			toReturn.set(Calendar.YEAR, toReturn.get(Calendar.YEAR) + 2000);

		toReturn.set(Calendar.HOUR_OF_DAY, 0);
		toReturn.set(Calendar.MINUTE, 0);
		toReturn.set(Calendar.SECOND, 0);
		toReturn.set(Calendar.MILLISECOND, 0);

		return toReturn;
	}

	/**
	 * Parses a string (representing a month) and returns it's corresponding
	 * value as a Calendar month. This is used to parse MMM month dates
	 * 
	 * @param monthStr
	 *            String to parse
	 * @param locale
	 *            Locale to use
	 * @return Month value or -1 if not found
	 */
	private static int getMonthForString(String monthStr, Locale locale) {
		DateFormatSymbols dfs = new DateFormatSymbols(locale);
		String[] months = dfs.getMonths();
		for (int i = 0; i < months.length; i++) {
			if (months[i].toLowerCase(locale).startsWith(monthStr.toLowerCase(locale))) {
				return i + 1;
			}
		}

		return -1;
	}

	/*public Calendar smartParse(String dateStr, Locale locale) {
		
		// Samples:
		// 080101 20080101
		// 08/01/01 2008/01/01
		return null;
	}*/
}


Back to the top