Index: /trunk/src/org/openstreetmap/josm/data/osm/DateFormatter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/DateFormatter.java	(revision 374)
+++ /trunk/src/org/openstreetmap/josm/data/osm/DateFormatter.java	(revision 374)
@@ -0,0 +1,84 @@
+package org.openstreetmap.josm.data.osm;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+
+/**
+ * Outputs a date in a format suitable for an OSM XML file.
+ * 
+ * @author Brett Henderson
+ */
+public class DateFormatter {
+	
+	private GregorianCalendar calendar;
+	
+	
+	/**
+	 * Creates a new instance.
+	 */
+	public DateFormatter() {
+		calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+	}
+	
+	
+	/**
+	 * Formats a date in XML format.
+	 * 
+	 * @param date
+	 *            The date to be formatted.
+	 * @return The string representing the date.
+	 */
+	public String format(Date date) {
+		StringBuilder result;
+		int year;
+		int month;
+		int day;
+		int hour;
+		int minute;
+		int second;
+		
+		calendar.setTime(date);
+		
+		result = new StringBuilder(20);
+		
+		year = calendar.get(Calendar.YEAR);
+		month = calendar.get(Calendar.MONTH) + 1;
+		day = calendar.get(Calendar.DATE);
+		hour = calendar.get(Calendar.HOUR_OF_DAY);
+		minute = calendar.get(Calendar.MINUTE);
+		second = calendar.get(Calendar.SECOND);
+		
+		result.append(year);
+		result.append('-');
+		if (month < 10) {
+			result.append('0');
+		}
+		result.append(month);
+		result.append('-');
+		if (day < 10) {
+			result.append('0');
+		}
+		result.append(day);
+		result.append('T');
+		if (hour < 10) {
+			result.append('0');
+		}
+		result.append(hour);
+		result.append(':');
+		if (minute < 10) {
+			result.append('0');
+		}
+		result.append(minute);
+		result.append(':');
+		if (second < 10) {
+			result.append('0');
+		}
+		result.append(second);
+		result.append('Z');
+		
+		return result.toString();
+	}
+}
Index: /trunk/src/org/openstreetmap/josm/tools/DateParser.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/DateParser.java	(revision 373)
+++ /trunk/src/org/openstreetmap/josm/tools/DateParser.java	(revision 374)
@@ -3,8 +3,6 @@
 
 import java.text.ParseException;
-import java.text.SimpleDateFormat;
 import java.util.Date;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+
 
 /**
@@ -14,32 +12,6 @@
  */
 public class DateParser {
-
-	private static final String[] formats = {
-		"yyyy-MM-dd'T'HH:mm:ss'Z'",
-		"yyyy-MM-dd'T'HH:mm:ssZ",
-		"yyyy-MM-dd'T'HH:mm:ss",
-		"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
-		"yyyy-MM-dd'T'HH:mm:ss.SSSZ",
-		"yyyy-MM-dd HH:mm:ss",
-		"MM/dd/yyyy HH:mm:ss",
-		"MM/dd/yyyy'T'HH:mm:ss.SSS'Z'",
-		"MM/dd/yyyy'T'HH:mm:ss.SSSZ",
-		"MM/dd/yyyy'T'HH:mm:ss.SSS",
-		"MM/dd/yyyy'T'HH:mm:ssZ",
-		"MM/dd/yyyy'T'HH:mm:ss",
-		"yyyy:MM:dd HH:mm:ss", // unfcklvble, but I have seen this...
-	};
-	
 	public static Date parse(String d) throws ParseException {
-		// first try to fix ruby's broken xmlschema - format
-		Matcher m = Pattern.compile("(....-..-..T..:..:..[+-]..):(..)").matcher(d);
-		if (m.matches())
-			d = m.group(1) + m.group(2);
-
-		for (String parse : formats) {
-			SimpleDateFormat sdf = new SimpleDateFormat(parse);
-			try {return sdf.parse(d);} catch (ParseException pe) {}
-		}
-		throw new ParseException("", 0);
+		return new PrimaryDateParser().parse(d);
 	}
 }
Index: /trunk/src/org/openstreetmap/josm/tools/FallbackDateParser.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/FallbackDateParser.java	(revision 374)
+++ /trunk/src/org/openstreetmap/josm/tools/FallbackDateParser.java	(revision 374)
@@ -0,0 +1,111 @@
+package org.openstreetmap.josm.tools;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+
+/**
+ * Handles a number of different date formats encountered in OSM. This is built
+ * based on similar code in JOSM. This class is not threadsafe, a separate
+ * instance must be created per thread.
+ * 
+ * @author Brett Henderson
+ */
+public class FallbackDateParser {
+	
+	private static final String[] formats = {
+	    "yyyy-MM-dd'T'HH:mm:ss'Z'",
+		"yyyy-MM-dd'T'HH:mm:ssZ",
+		"yyyy-MM-dd'T'HH:mm:ss",
+		"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
+		"yyyy-MM-dd'T'HH:mm:ss.SSSZ",
+		"yyyy-MM-dd HH:mm:ss",
+		"MM/dd/yyyy HH:mm:ss",
+		"MM/dd/yyyy'T'HH:mm:ss.SSS'Z'",
+		"MM/dd/yyyy'T'HH:mm:ss.SSSZ",
+		"MM/dd/yyyy'T'HH:mm:ss.SSS",
+		"MM/dd/yyyy'T'HH:mm:ssZ",
+		"MM/dd/yyyy'T'HH:mm:ss",
+		"yyyy:MM:dd HH:mm:ss"
+	};
+	
+	
+	private List<DateFormat> dateParsers;
+	private int activeDateParser;
+	
+	
+	/**
+	 * Creates a new instance.
+	 */
+	public FallbackDateParser() {
+		// Build a list of candidate date parsers.
+		dateParsers = new ArrayList<DateFormat>(formats.length);
+		for (int i = 0; i < formats.length; i++) {
+			dateParsers.add(new SimpleDateFormat(formats[i]));
+		}
+		
+		// We haven't selected a date parser yet.
+		activeDateParser = -1;
+	}
+	
+	
+	/**
+	 * Attempts to parse the specified date.
+	 * 
+	 * @param date
+	 *            The date to parse.
+	 * @return The date.
+	 * @throws ParseException
+	 *             Occurs if the date does not match any of the supported date
+	 *             formats.
+	 */
+	public Date parse(String date) throws ParseException {
+		String correctedDate;
+		
+		// Try to fix ruby's broken xmlschema - format
+		// Replace this:
+		// 2007-02-12T18:43:01+00:00
+		// With this:
+		// 2007-02-12T18:43:01+0000
+		if (date.length() == 25 && date.charAt(22) == ':') {
+			correctedDate = date.substring(0, 22) + date.substring(23, 25);
+		} else {
+			correctedDate = date;
+		}
+		
+		// If we have previously successfully used a date parser, we'll try it
+		// first.
+		if (activeDateParser >= 0) {
+			try {
+				return dateParsers.get(activeDateParser).parse(correctedDate);
+			} catch (ParseException e) {
+				// The currently active parser didn't work, so we must clear it
+				// and find a new appropriate parser.
+				activeDateParser = -1;
+			}
+		}
+		
+		// Try the date parsers one by one until a suitable format is found.
+		for (int i = 0; i < dateParsers.size(); i++) {
+			try {
+				Date result;
+				
+				// Attempt to parse with the current parser, if successful we
+				// store its index for next time.
+				result = dateParsers.get(i).parse(correctedDate);
+				activeDateParser = i;
+				
+				return result;
+				
+			} catch (ParseException pe) {
+				// Ignore parsing errors and try the next pattern.
+			}
+		}
+		
+		throw new ParseException("The date string (" + date + ") could not be parsed.", 0);
+	}
+}
Index: /trunk/src/org/openstreetmap/josm/tools/PrimaryDateParser.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/PrimaryDateParser.java	(revision 374)
+++ /trunk/src/org/openstreetmap/josm/tools/PrimaryDateParser.java	(revision 374)
@@ -0,0 +1,269 @@
+package org.openstreetmap.josm.tools;
+
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import javax.xml.datatype.DatatypeConfigurationException;
+import javax.xml.datatype.DatatypeFactory;
+
+
+/**
+ * Handles a number of different date formats encountered in OSM. This is built
+ * based on similar code in JOSM. This class is not threadsafe, a separate
+ * instance must be created per thread.
+ * 
+ * @author Brett Henderson
+ */
+public class PrimaryDateParser {
+	private DatatypeFactory datatypeFactory;
+	private FallbackDateParser fallbackDateParser;
+	private Calendar calendar;
+	
+	
+	/**
+	 * Creates a new instance.
+	 */
+	public PrimaryDateParser() {
+		// Build an xml data type factory.
+		try {
+			datatypeFactory = DatatypeFactory.newInstance();
+			
+		} catch (DatatypeConfigurationException e) {
+			throw new RuntimeException("Unable to instantiate xml datatype factory.", e);
+		}
+		
+		fallbackDateParser = new FallbackDateParser();
+		
+		calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+	}
+	
+	
+	private boolean isDateInShortStandardFormat(String date) {
+		char[] dateChars;
+		// We can only parse the date if it is in a very specific format.
+		// eg. 2007-09-23T08:25:43Z
+		
+		if (date.length() != 20) {
+			return false;
+		}
+		
+		dateChars = date.toCharArray();
+		
+		// Make sure any fixed characters are in the correct place.
+		if (dateChars[4] != '-') {
+			return false;
+		}
+		if (dateChars[7] != '-') {
+			return false;
+		}
+		if (dateChars[10] != 'T') {
+			return false;
+		}
+		if (dateChars[13] != ':') {
+			return false;
+		}
+		if (dateChars[16] != ':') {
+			return false;
+		}
+		if (dateChars[19] != 'Z') {
+			return false;
+		}
+		
+		// Ensure all remaining characters are numbers.
+		for (int i = 0; i < 4; i++) {
+			if (dateChars[i] < '0' || dateChars[i] > '9') {
+				return false;
+			}
+		}
+		for (int i = 5; i < 7; i++) {
+			if (dateChars[i] < '0' || dateChars[i] > '9') {
+				return false;
+			}
+		}
+		for (int i = 8; i < 10; i++) {
+			if (dateChars[i] < '0' || dateChars[i] > '9') {
+				return false;
+			}
+		}
+		for (int i = 11; i < 13; i++) {
+			if (dateChars[i] < '0' || dateChars[i] > '9') {
+				return false;
+			}
+		}
+		for (int i = 14; i < 16; i++) {
+			if (dateChars[i] < '0' || dateChars[i] > '9') {
+				return false;
+			}
+		}
+		for (int i = 17; i < 19; i++) {
+			if (dateChars[i] < '0' || dateChars[i] > '9') {
+				return false;
+			}
+		}
+		
+		// No problems found so it is in the special case format.
+		return true;
+	}
+	
+	
+	private boolean isDateInLongStandardFormat(String date) {
+		char[] dateChars;
+		// We can only parse the date if it is in a very specific format.
+		// eg. 2007-09-23T08:25:43.000Z
+		
+		if (date.length() != 24) {
+			return false;
+		}
+		
+		dateChars = date.toCharArray();
+		
+		// Make sure any fixed characters are in the correct place.
+		if (dateChars[4] != '-') {
+			return false;
+		}
+		if (dateChars[7] != '-') {
+			return false;
+		}
+		if (dateChars[10] != 'T') {
+			return false;
+		}
+		if (dateChars[13] != ':') {
+			return false;
+		}
+		if (dateChars[16] != ':') {
+			return false;
+		}
+		if (dateChars[19] != '.') {
+			return false;
+		}
+		if (dateChars[23] != 'Z') {
+			return false;
+		}
+		
+		// Ensure all remaining characters are numbers.
+		for (int i = 0; i < 4; i++) {
+			if (dateChars[i] < '0' || dateChars[i] > '9') {
+				return false;
+			}
+		}
+		for (int i = 5; i < 7; i++) {
+			if (dateChars[i] < '0' || dateChars[i] > '9') {
+				return false;
+			}
+		}
+		for (int i = 8; i < 10; i++) {
+			if (dateChars[i] < '0' || dateChars[i] > '9') {
+				return false;
+			}
+		}
+		for (int i = 11; i < 13; i++) {
+			if (dateChars[i] < '0' || dateChars[i] > '9') {
+				return false;
+			}
+		}
+		for (int i = 14; i < 16; i++) {
+			if (dateChars[i] < '0' || dateChars[i] > '9') {
+				return false;
+			}
+		}
+		for (int i = 17; i < 19; i++) {
+			if (dateChars[i] < '0' || dateChars[i] > '9') {
+				return false;
+			}
+		}
+		for (int i = 20; i < 23; i++) {
+			if (dateChars[i] < '0' || dateChars[i] > '9') {
+				return false;
+			}
+		}
+		
+		// No problems found so it is in the special case format.
+		return true;
+	}
+	
+	
+	private Date parseShortStandardDate(String date) {
+		int year;
+		int month;
+		int day;
+		int hour;
+		int minute;
+		int second;
+		
+		year = Integer.parseInt(date.substring(0, 4));
+		month = Integer.parseInt(date.substring(5, 7));
+		day = Integer.parseInt(date.substring(8, 10));
+		hour = Integer.parseInt(date.substring(11, 13));
+		minute = Integer.parseInt(date.substring(14, 16));
+		second = Integer.parseInt(date.substring(17, 19));
+		
+		calendar.clear();
+		calendar.set(Calendar.YEAR, year);
+		calendar.set(Calendar.MONTH, month - 1);
+		calendar.set(Calendar.DAY_OF_MONTH, day);
+		calendar.set(Calendar.HOUR_OF_DAY, hour);
+		calendar.set(Calendar.MINUTE, minute);
+		calendar.set(Calendar.SECOND, second);
+		
+		return calendar.getTime();
+	}
+	
+	
+	private Date parseLongStandardDate(String date) {
+		int year;
+		int month;
+		int day;
+		int hour;
+		int minute;
+		int second;
+		int millisecond;
+		
+		year = Integer.parseInt(date.substring(0, 4));
+		month = Integer.parseInt(date.substring(5, 7));
+		day = Integer.parseInt(date.substring(8, 10));
+		hour = Integer.parseInt(date.substring(11, 13));
+		minute = Integer.parseInt(date.substring(14, 16));
+		second = Integer.parseInt(date.substring(17, 19));
+		millisecond = Integer.parseInt(date.substring(20, 23));
+		
+		calendar.clear();
+		calendar.set(Calendar.YEAR, year);
+		calendar.set(Calendar.MONTH, month - 1);
+		calendar.set(Calendar.DAY_OF_MONTH, day);
+		calendar.set(Calendar.HOUR_OF_DAY, hour);
+		calendar.set(Calendar.MINUTE, minute);
+		calendar.set(Calendar.SECOND, second);
+		calendar.set(Calendar.MILLISECOND, millisecond);
+		
+		return calendar.getTime();
+	}
+	
+	
+	/**
+	 * Attempts to parse the specified date.
+	 * 
+	 * @param date
+	 *            The date to parse.
+	 * @return The date.
+	 * @throws ParseException
+	 *             Occurs if the date does not match any of the supported date
+	 *             formats.
+	 */
+	public Date parse(String date) throws ParseException {
+		try {
+			if (isDateInShortStandardFormat(date)) {
+				return parseShortStandardDate(date);
+			} else if (isDateInLongStandardFormat(date)) {
+				return parseLongStandardDate(date);
+			} else {
+				return datatypeFactory.newXMLGregorianCalendar(date).toGregorianCalendar().getTime();
+			}
+			
+		} catch (IllegalArgumentException e) {
+			return fallbackDateParser.parse(date);
+		}
+	}
+}
