/*
 * Copyright (C) 2004-2009  Medsphere Systems Corporation
 * All rights reserved.
 *
 * This source code contains the intellectual property
 * of its copyright holder(s), and is made available
 * under a license. If you do not know the terms of
 * the license, please stop and do not read further.
 *
 * Please read LICENSES for detailed information about 
 * the license this source code file is available under. 
 * Questions should be directed to legal@medsphere.com
 *
 *
 * Licensed under AGPL v3.
 * 
 * 

 Mods by Sam Habiel to use in Scheduling GUI.
 */


using System;
using System.Globalization;
using System.Text.RegularExpressions;

namespace IndianHealthService.ClinicalScheduling
{
	public delegate void FMDateTimeHandler (FMDateTime time);
	public delegate string FMDateStringHandler (string s);
	
	[Serializable]
	[System.Xml.Serialization.SoapType (Namespace="http://ws.medsphere.com")]
	public class FMDateTime : IComparable, ICloneable
	{
		/* public properties */
		[System.Xml.Serialization.SoapAttribute (Namespace="http://ws.medsphere.com")]
		public DateTime DateTime {
			get {
				switch (Precision) {
				case FMDateTimePrecision.YearOnly:
					return new DateTime (Year, 1, 1);
				case FMDateTimePrecision.YearMonthOnly:
					return new DateTime (Year, Month, 1);
				case FMDateTimePrecision.DateOnly:
					return new DateTime (Year, Month, Day);
				default:
					return new DateTime (Year, Month, Day, Hour, Minute, Second);
				}
			}
		}

		public int Year {
			get { return year; }
			set { year = value; }
		}

		public int Month {
			get { return month; }
			set { month = value; }
		}

		public int Day {
			get { return day; }
			set { day = value; }
		}

		public int Hour {
			get { return hour; }
			set {
				if (value > 24) {
					throw new ArgumentException ("Hour cannot be greater than 24");
				}

				hour = value % 24;
			}
		}

		public int Minute {
			get { return minute; }
			set {
				if (value > 59) {
					throw new ArgumentException ("Minute cannot be greater than 59");
				}

				minute = value;
			}
		}

		public int Second {
			get { return second; }
			set {
				if (value > 59) {
					throw new ArgumentException ("Second cannot be greater than 59");
				}

				second = value;
			}
		}

		public static FMDateTime ServerNow {
			get { return FMDateTime.Create (DateTime.Now - server_offset); }
		}

		public string RelativeFMDateString {
			get { return Raw; }
		}

		public string ShortHandString {
			get {
				if (Raw != null && date_format.IsMatch (Raw.Replace (" ", ""))) {
					return Raw;
				}
				return ToString ();
			}
		}

		public FMDateTime AtZero {
			get {
				FMDateTime d = (FMDateTime)this.Clone ();
				d.Precision = FMDateTimePrecision.DateAndTime;
				d.Hour = 0;
				d.Minute = 0;
				d.Second = 0;
				return d;
			}
		}

		public FMDateTime AtMidnight {
			get {
				FMDateTime d = (FMDateTime)this.Clone ();
				d.Precision = FMDateTimePrecision.DateAndTime;
				d.Hour = 23;
				d.Minute = 59;
				d.Second = 59;
				return d;
			}
		}

		public FMDateTime DateOnly {
			get {
				FMDateTime d = (FMDateTime)this.Clone ();
				if (Precision != FMDateTimePrecision.DateAndTime) {
					return d;
				}
				d.Precision = FMDateTimePrecision.DateOnly;
				d.Hour = d.Minute = d.Second = 0;
				return d;
			}
		}

		public string FMDateString {
			get {
				switch (Precision) {
				case FMDateTimePrecision.YearOnly:
					return String.Format ("{0:000}", year - 1700);
				case FMDateTimePrecision.YearMonthOnly:
					return String.Format ("{0:000}{1:00}", year - 1700, month);
				case FMDateTimePrecision.DateOnly:
					return String.Format ("{0:000}{1:00}{2:00}", year - 1700, month, day);
				case FMDateTimePrecision.DateAndTime:
				default:
					if (second > 0) {
						return String.Format ("{0:000}{1:00}{2:00}.{3:00}{4:00}{5:00}",
								      year - 1700, month, day, hour, minute, second);
					} else {
						return String.Format ("{0:000}{1:00}{2:00}.{3:00}{4:00}",
								      year - 1700, month, day, hour, minute);
					}
				}
			}
		}

		/* public fields */
		public FMDateTimePrecision Precision;

		[NonSerialized]
		public static FMDateStringHandler ValidationMethod;

		public static FMDateTime MinValue; // 1/1/1700
		public static FMDateTime MaxValue; // 12/31/2699 12:59 PM

		/* public methods */
		static FMDateTime ()
		{
			// This is the equivalent of the FMDateTime string '0000101'
			// We do this manually to avoid parsing overhead here.
			MinValue = new FMDateTime (); // 1/1/1700
			MinValue.Hour = MinValue.Minute = MinValue.Second = 0;
			MinValue.Day = MinValue.Month = 1;
			MinValue.Year = 1700;
			MinValue.Precision = FMDateTimePrecision.DateOnly;

			// This is the equivalent of the FMDateTime string '9991231.235959'
			// We do this manually to avoid parsing overhead here.
			MaxValue = new FMDateTime (); // 12/31/2699 12:59 PM
			MaxValue.Hour = 23;
			MaxValue.Minute = 59;
			MaxValue.Second = 59;
			MaxValue.Day = 31;
			MaxValue.Month = 12;
			MaxValue.Year = 2699;
			MaxValue.Precision = FMDateTimePrecision.DateAndTime;
		}

		public FMDateTime ()
		{
		}

        public FMDateTime(DateTime d)
            : this(d, FMDateTimePrecision.DateAndTime)
        {
        }

        public FMDateTime(DateTime d, FMDateTimePrecision precision)
        {
            if (d > MaxValue.DateTime || d < MinValue.DateTime)
                return;

            this.Precision = precision;
            this.Year = d.Year;
            this.Month = d.Month;
            this.Day = d.Day;
            this.Hour = d.Hour;
            this.Minute = d.Minute;
            this.Second = d.Second;
        }
		
		public static FMDateTime Create (DateTime d, FMDateTimePrecision precision)
		{
			if (d > MaxValue.DateTime || d < MinValue.DateTime) {
				return null;
			}
			
			FMDateTime f = new FMDateTime ();
			f.Precision = precision;
			f.Year = d.Year;
			f.Month = d.Month;
			f.Day = d.Day;
			f.Hour = d.Hour;
			f.Minute = d.Minute;
			f.Second = d.Second;

			return f;
		}

		public static FMDateTime Create (DateTime d)
		{
			return Create (d, FMDateTimePrecision.DateAndTime);
		}

		public static FMDateTime Parse (string str)
		{
			return Parse (str, FMDateTime.ValidationMethod);
		}

		public static FMDateTime Parse (string str,
						FMDateStringHandler validation_method)
		{
			if (validation_method == null) {
				throw new ArgumentNullException ("You must pass in a valid validation_method");
			}

			if (str == null) {
				return null;
			}

			FMDateTime date = null;

			str = str.Trim ();
			if (str == "0" || str == String.Empty) {
				return null;
			}

			if (str.IndexOf ("@") != -1) {
				date = new FMDateTime ();

				// string has a date and time part
				string[] tokens = str.Split (new char[] {'@'}, 2);
				if (ParseDatePart (tokens[0], ref date)
 				    || ParseUsingDateTime (tokens[0], ref date)
				    || (validation_method != null
				        && ParseInternalFormat (validation_method (tokens[0]), ref date))) {
					// Its been decided that if you have an
					// invalid time part, that the entire
					// string is invalid
					if (!ParseTimePart (tokens[1], true, ref date)) {
						return null;
					}

					date.Raw = str;
					return date;
				} else {
					// Account for @0600
					date = FMDateTime.ServerNow;
					if (!ParseTimePart (tokens[1], true, ref date)) {
						return null;
					}
					return date;
				}
			}
			
			if (ParseDatePart (str, ref date)) {
				date.Raw = str;
				return date;
			}

			if (ParseTimePart (str, false, ref date)) {
				FMDateTime now = ServerNow;
				date.Year = now.Year;
				date.Month = now.Month;
				date.Day = now.Day;
				return date;
			}

			if (ParseUsingDateTime (str, ref date)) {
				return date;
			}

			if (ParseInternalFormat (str, ref date)) {
				return date;
			}

			if (validation_method != null) {
				if (ParseInternalFormat (validation_method (str), ref date)) {
					return date;
				}
				return null;
			}

			if (date == null) {
				Console.WriteLine ("WARNING: FMDateTime failed parsing '{0}'", str);
			}

			return date;
		}

		public static FMDateTime Parse (string str, FMDateTimePrecision precision)
		{
			FMDateTime date = FMDateTime.Parse (str);
			if (date != null) {
				date.Precision = precision;
			}

			return date;
		}

		public void PopulateFrom12HrTime (int hour, int minute, int second, bool is_pm)
		{
			if (hour < 12 && is_pm) {
				hour += 12;
			} else if (hour == 12 && !is_pm) {
				hour = 0;
			}

			Hour = hour;
			Minute = minute;
			Second = second;
		}

		public bool IsFutureDate
		{
			get {
				return (CompareTo (Precision, FMDateTime.ServerNow, FMDateTime.ServerNow.Precision) > 0);
			}
		}

		public bool IsPastDate
		{
			get {
				return (CompareTo (Precision, FMDateTime.ServerNow, FMDateTime.ServerNow.Precision) < 0);
			}
		}

		public static void UpdateServerNow (FMDateTime server_now)
		{
			if (server_now != null) {
				server_offset = (DateTime.Now - server_now.DateTime);
			}
		}

		public override string ToString ()
		{
			switch (Precision) {
			case FMDateTimePrecision.YearOnly:
				return DateTime.ToString ("yyyy");
			case FMDateTimePrecision.YearMonthOnly:
				return DateTime.ToString ("Y");
			case FMDateTimePrecision.DateOnly:
				return DateTime.ToString ("d");
			default:
				return DateTime.ToString ("G");
			}
		}

		public static string ToString (FMDateTime date)
		{
			if (date != null) {
				return date.ToString ();
			}
			return String.Empty;
		}

		public string ToString (string format)
		{
			return DateTime.ToString (format);
		}

		public static string ToString (FMDateTime date, string format)
		{
			if (date != null) {
				return date.ToString (format);
			}
			return String.Empty;
		}

		public string ToDateString ()
		{
			return DateTime.ToString ("d");
		}

		public static string ToDateString (FMDateTime date)
		{
			if (date != null) {
				return date.ToDateString ();
			}
			return String.Empty;
		}

		public string ToTimeString ()
		{
			return DateTime.ToString ("t");
		}

		public static string ToTimeString (FMDateTime date)
		{
			if (date != null) {
				return date.ToTimeString ();
			}
			return String.Empty;
		}

		public static string ToFMDateString (FMDateTime date)
		{
			if (date != null) {
				return date.FMDateString;
			}
			return String.Empty;
		}
				
		/**
		 * Compares this FMDateTime instance with given FMDateTimePrecision this_p to dt
		 * using the given FMDateTimePrecision p.
		 */ 
		public int CompareTo (FMDateTimePrecision this_p, FMDateTime dt, FMDateTimePrecision dt_p)
		{
			int r;
			FMDateTimePrecision save_this_p = Precision;
			FMDateTimePrecision save_dt_p = dt.Precision;
			Precision = this_p;
			dt.Precision = dt_p;
			r = DateTime.CompareTo (dt.DateTime);
			Precision = save_this_p;
			dt.Precision = save_dt_p;
			return r;
		}

		/**
		 * Implementation of IComparable interface.
		 */
		public int CompareTo (object o)
		{
			if (o == null) {
				return 1;
			} else if (o is FMDateTime) {
				FMDateTime f = (FMDateTime)o;
				int r = DateTime.CompareTo (f.DateTime);
				if (r == 0) {
					// special cases of DateTime comparison:
					//     1900 and January,1900 and 01/01/1900 are all 
					//         represented as 01/01/1900
					//     TODAY@0 and TODAY are both represented as TODAY@0
					// these are the cases where precision has the last word
					if (Precision < f.Precision) {
						r = -1;
					} else if (Precision > f.Precision) {
						r = 1;
					}
				}
				return r;
			} else if (o is DateTime) {
				DateTime d = (DateTime)o;
				return DateTime.CompareTo (d);
			}
			
			throw new ArgumentException ("Value is not a DateTime or FMDateTime.");
		}

		public static int Compare (FMDateTime a, FMDateTime b)
		{
			if (a == null && b == null) {
				return 0;
			} else if (a != null && b != null) {
				return a.CompareTo (b);
			/* We sort the non-null item before the null one for the mixed case */
			} else if (a == null) {
				return -1;
			} else if (b == null) {
				return 1;
			}

			// This code path really has no way of being hit.
			return 0;
		}

		public override bool Equals (object o)
		{
			if (o == null) {
				return false;
			} else if (o is FMDateTime) {
				FMDateTime f = (FMDateTime)o;

				if (f.Precision != Precision) {
					return false;
				}

				switch (Precision) {
				case FMDateTimePrecision.YearOnly:
					return Year == f.Year;
				case FMDateTimePrecision.YearMonthOnly:
					return Year == f.Year && Month == f.Month;
				case FMDateTimePrecision.DateOnly:
					return Year == f.Year && Month == f.Month && Day == f.Day;
				case FMDateTimePrecision.DateAndTime:
				default:
					return Year == f.Year && Month == f.Month && Day == f.Day
					       && Hour == f.Hour && Minute == f.Minute && Second == f.Second;
				}
			}

			throw new ArgumentException ("Value is not a FMDateTime.");
		}

		public override int GetHashCode ()
		{
			return (int)Precision + year + month + day + hour + minute + second;
		}

		/**
		 * This gets a hash code based upon the FMDateTime precision, so that
		 * an object can be stored based on DateOnly, for example, and if you
		 * try to look it up later using a different FMDateTime object that
		 * has the same date, but may have different time.  Seconds are
		 * intentionally never factored into this hash code, even for DateAndTime
		 * cases.  If you want to factor in seconds as  well, just use GetHashCode().
		 *
		 * @return   An integer hash code.
		 */
		public int GetPrecisionAwareHashCode ()
		{
			int hash_code = (int)Precision;

			switch (Precision)
			{
			case FMDateTimePrecision.YearOnly:
				hash_code += year;
				break;
			case FMDateTimePrecision.YearMonthOnly:
				hash_code += year;
				hash_code += month;
				break;
			case FMDateTimePrecision.DateOnly:
				hash_code += year;
				hash_code += month;
				hash_code += day;
				break;
			case FMDateTimePrecision.DateAndTime:
			default:
				hash_code += year;
				hash_code += month;
				hash_code += day;
				hash_code += hour;
				hash_code += minute;
				break;
			}

			return hash_code;
		}

		public object Clone ()
		{
			FMDateTime d = new FMDateTime ();
			d.Precision = Precision;
			d.Year = year;
			d.Month = month;
			d.Day = day;
			d.Hour = hour;
			d.Minute = minute;
			d.Second = second;
			return d;
		}
	
		public static bool operator == (FMDateTime a, FMDateTime b)
		{
			object obj_a = (object)a;
			object obj_b = (object)b;

			if (obj_a == null && obj_b == null) {
				return true;
			} else if (obj_a != null && obj_b != null) {
				return a.Equals (b);
			} else {
				return false;
			}
		}

		public static bool operator != (FMDateTime a, FMDateTime b)
		{
			return !(a == b);
		}

		public static bool operator > (FMDateTime a, FMDateTime b)
		{
			if (a == null) {
				throw new ArgumentException ("Left hand argument to comparison cannot be null.");
			}

			return (a.CompareTo (b) > 0);
		}

		public static bool operator >= (FMDateTime a, FMDateTime b)
		{
			if (a == null) {
				throw new ArgumentException ("Left hand argument to comparison cannot be null.");
			}

			return (a.CompareTo (b) >= 0);
		}

		public static bool operator < (FMDateTime a, FMDateTime b)
		{
			if (a == null) {
				throw new ArgumentException ("Left hand argument to comparison cannot be null.");
			}

			return (a.CompareTo (b) < 0);
		}

		public static bool operator <= (FMDateTime a, FMDateTime b)
		{
			if (a == null) {
				throw new ArgumentException ("Left hand argument to comparison cannot be null.");
			}

			return (a.CompareTo (b) <= 0);
		}

		public static implicit operator FMDateTime (DateTime d)
		{
			return FMDateTime.Create (d);
		}

		/* protected properties */
		protected string Raw;
		
		/* private properties */
		private static Calendar CurrentCalendar {
			get { return CultureInfo.CurrentCulture.Calendar; }
		}

		/* private fields */
		private int year, month, day, hour, minute, second;

		// We do this here so we can lazy load the compiled regexes.
		private static Regex internal_format {
			get {
				if (priv_internal_format == null) {
					priv_internal_format = new Regex (@"^(\d{3})(\d{2})?(\d{2})?(\.(\d{1,2})?(\d{1,2})?(\d{1,2})?)?$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
				}
				
				return priv_internal_format;
			}
		}
		private static Regex priv_internal_format;

		private static Regex date_format {
			get {
				if (priv_date_format == null) {
					priv_date_format = new Regex (@"^(t(oday)?|n(ow)?)(([+-])(\d+)(w)?)?$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
				}
				
				return priv_date_format;
			}
		}
		private static Regex priv_date_format;

		private static Regex time_format {
			get {
				if (priv_time_format == null) {
					priv_time_format = new Regex (@"^(\d{1,2})(:(\d{2}))?(:(\d{2}))?(\s*)(AM|PM)?$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
				}
				
				return priv_time_format;
			}
		}
		private static Regex priv_time_format;

		private static TimeSpan server_offset = new TimeSpan ();

		/* private methods */
		private static bool ParseDatePart (string str, ref FMDateTime date)
		{
			FMDateTime orig_date = date;
		
			string clean = str.Replace (" ", "");

			// see if it matches (t|today|now) +/- some number
			if (!date_format.IsMatch (clean)) {
				return false;
			}

			Match m = date_format.Match (clean);
			if (m != null) {
				if (date == null) {
					date = new FMDateTime ();
				}

				// be safe about dates like T-99999999
				try {
					int modifier = 0;
					if (m.Groups[5].Value != String.Empty) {
						int sign_bit = 1;
						if (m.Groups[5].Value == "-") {
							sign_bit = -1;
						}
						
						// Convert will bomb if the modifier
						// won't fit into an int (it's invalid
						// anyway)
						modifier = sign_bit * Convert.ToInt32 (m.Groups[6].Value);
					}

					DateTime dt = ServerNow.DateTime;
					if (m.Groups[7].Value.ToLower () == "w") {
						dt = CurrentCalendar.AddWeeks (dt, modifier);
					} else {
						dt = CurrentCalendar.AddDays (dt, modifier);
					}

					date.Day = dt.Day;
					date.Month = dt.Month;
					date.Year = dt.Year;

					if (m.Groups[1].Value.ToLower ().StartsWith ("n")) {
						date.Precision = FMDateTimePrecision.DateAndTime;
						date.Hour = dt.Hour;
						date.Minute = dt.Minute;
						date.Second = dt.Second;
					} else {
						date.Precision = FMDateTimePrecision.DateOnly;
						date.Hour = date.Minute = date.Second = 0;
					}
				} catch {
					date = orig_date;
					return false;
				}

				return true;
			}

			date = orig_date;
			return false;
		}

		private static bool ParseTimePart (string str, bool try_int_parse, ref FMDateTime date)
		{
            int time;
			str = str.ToUpper ();
			if (str == "NOON") {
				if (date == null) {
					date = new FMDateTime ();
				}
			
				date.Hour = 12;
				date.Minute = date.Second = 0;
				
				date.Precision = FMDateTimePrecision.DateAndTime;

				return true;
			} else if (str == "MID" || str == "MIDNIGHT") {
				if (date == null) {
					date = new FMDateTime ();
				}
			
				date.Hour = 23;
				date.Minute = 59;
				date.Second = 59;

				date.Precision = FMDateTimePrecision.DateAndTime;

				return true;
			} else if (time_format.IsMatch (str)) {
				Match m = time_format.Match (str);
				if (m == null) {
					return false;
				}

				int hour, minute, second;
                int.TryParse(m.Groups[1].Value, out hour);
				int.TryParse(m.Groups[3].Value, out minute);
				int.TryParse(m.Groups[5].Value, out second);

				if (m.Groups[7].Value == "PM") {
					hour += 12;
				}

				if (hour == 24 && minute == 0 && second == 0) {
					hour = 23;
					minute = second = 59;
				}

				if (!ValidateTime (hour, minute, second)) {
					return false;
				}

				if (date == null) {
					date = new FMDateTime ();
				}

				date.Hour = hour;
				date.Minute = minute;
				date.Second = second;
				date.Precision = FMDateTimePrecision.DateAndTime;

				return true;
			} else if (try_int_parse && int.TryParse(str, out time)) {

				int hour, minute, second;
				if (time <= 2359) {
					hour = time / 100;
					minute = time - (hour * 100);
					second = 0;
				} else if (time <= 235959) {
					hour = time / 10000;
					minute = (time - (hour * 10000)) / 100;
					second = time - ((time / 100) * 100);
				} else {
					return false;
				}
				
				if (hour == 24 && minute == 0 && second == 0) {
					hour = 23;
					minute = second = 59;
				}

				if (!ValidateTime (hour, minute, second)) {
					return false;
				}

				if (date == null) {
					date = new FMDateTime ();
				}
				
				date.Hour = hour;
				date.Minute = minute;
				date.Second = second;
				date.Precision = FMDateTimePrecision.DateAndTime;

				return true;
			}
			
			return false;
		}

		private static bool ParseUsingDateTime (string str, ref FMDateTime date)
		{
			// we need to use DateTime.Parse to allow
			// roundtripping of our ToString () methods

			// LAMESPEC: There isn't any way to find out whether a
			// DateTime contains a time part, or just a date part
			// after calling Parse, so we have to specifically call
			// ParseExact on a few known formats
			try {
				string[] formats = new string[] {
					"yyyy"
				};
				
				DateTime d = DateTime.ParseExact (str, formats, null,
				                                  DateTimeStyles.AllowWhiteSpaces);
				if (date == null) {
					date = new FMDateTime ();
				}
				                                  
				date.Year = d.Year;
				date.Precision = FMDateTimePrecision.YearOnly;
				return true;
			} catch (FormatException) {
			}

			try {
				string[] formats = new string[] {
					"Y"
				};
				
				DateTime d = DateTime.ParseExact (str, formats, null,
				                                  DateTimeStyles.AllowWhiteSpaces);
				if (date == null) {
					date = new FMDateTime ();
				}
				
				date.Year = d.Year;
				date.Month = d.Month;
				date.Precision = FMDateTimePrecision.YearMonthOnly;
				return true;
			} catch (FormatException) {
			}

			try {
				string[] formats = new string[] {
					"d", "MM/dd/yy"
				};
				
				DateTime d = DateTime.ParseExact (str, formats, null,
				                                  DateTimeStyles.AllowWhiteSpaces);
				if (date == null) {
					date = new FMDateTime ();
				}
				
				date.Year = d.Year;
				date.Month = d.Month;
				date.Day = d.Day;
				date.Precision = FMDateTimePrecision.DateOnly;
				return true;
			} catch (FormatException) {
			}

			try {
				string[] formats = new string[] {
					"g", "G", "MM/dd/yy hh:mm tt",
					"MM/dd/yy h:mm tt"
				};
				
				DateTime d = DateTime.ParseExact (str, formats, null,
				                                  DateTimeStyles.AllowWhiteSpaces);
				if (date == null) {
					date = new FMDateTime ();
				}
				
				date.Year = d.Year;
				date.Month = d.Month;
				date.Day = d.Day;

				date.Hour = d.Hour;
				date.Minute = d.Minute;
				date.Second = d.Second;

				date.Precision = FMDateTimePrecision.DateAndTime;
				return true;
			} catch (FormatException) {
			}
		
			/* XXX: Disabiling this for now, since it sucks incredibly
			// first try parsing date & time
			try {
				string[] date_time_formats = new string[] {
					"dddd*, MMMM* dd, yyyy HH*:mm* tt*", "f",
					"dddd*, MMMM* dd, yyyy HH*:mm*:ss* tt*", "F",
					"MM/dd/yyyy HH*:mm* tt*", "g",
					"MM/dd/yyyy HH*:mm*:ss* tt*", "G",
					"dddd*, MMMM* dd, yyyy HH*:mm*:ss* tt*", "U"
				};

				DateTime d = DateTime.ParseExact (str, date_time_formats, null,
				                                  DateTimeStyles.AllowWhiteSpaces);
				date.Year = d.Year;
				date.Month = d.Month;
				date.Day = d.Day;
				date.Hour = d.Hour;
				date.Minute = d.Minute;
				date.Second = d.Second;
				date.Precision = FMDateTimePrecision.DateAndTime;
				return true;
			} catch { }
			
			// fall back on just parsing a date
			try {
				string[] date_formats = new string[] {
					"d", "D", "m", "M", "y", "Y"
				};

				DateTime d = DateTime.ParseExact (str, date_formats, null,
				                                  DateTimeStyles.AllowWhiteSpaces);
				date.Year = d.Year;
				date.Month = d.Month;
				date.Day = d.Day;
				date.Precision = FMDateTimePrecision.DateOnly;
				return true;
			} catch { }
			
			// if nothing else, try a couple of time formats
			try {
				string[] time_formats = new string[] {
					"HH*:mm* tt*", "t",
					"HH*:mm*:ss* tt*", "T"
				};

				DateTime d = DateTime.ParseExact (str, time_formats, null,
				                                  DateTimeStyles.AllowWhiteSpaces);
				date = FMDateTime.ServerNow;
				date.Hour = d.Hour;
				date.Minute = d.Minute;
				date.Second = d.Second;
				date.Precision = FMDateTimePrecision.DateAndTime;
				return true;
			} catch { }
			*/

			return false;
		}

		private static bool ParseInternalFormat (string str, ref FMDateTime date)
		{
			FMDateTime orig_date = date;
		
			if (internal_format.IsMatch (str)) {
				Match m = internal_format.Match (str);
				if (m != null && m.Groups.Count == 8) {
					int year, month, day, hour, minute, second;

					int.TryParse(m.Groups[1].Value, out year);
					year += 1700;

					int.TryParse(m.Groups[2].Value, out month);
					int.TryParse(m.Groups[3].Value, out day);
					int.TryParse(m.Groups[5].Value, out hour);
                    int.TryParse(m.Groups[6].Value, out minute);
                    int.TryParse(m.Groups[7].Value, out second);

					// 1 digit hours apparently have just
					// had the trailing 0 dropped.  Go figure.
					if (m.Groups[5].Value.Length == 1) {
						hour *= 10;
					}

					// 1 digit minutes do too
					if (m.Groups[6].Value.Length == 1) {
						minute *= 10;
					}

					// 1 digit seconds aren't to be left out
					if (m.Groups[7].Value.Length == 1) {
						second *= 10;
					}

					if (!ValidateYear (year)) {
						return false;
					}

					if (date == null) {
						date = new FMDateTime ();
					}
					
					date.Year = year;

					date.Precision = FMDateTimePrecision.YearOnly;
					if (m.Groups[5].Value != String.Empty
					    && month > 0 && day > 0 && hour > 0) {
						if (!ValidateDate (year, month, day)
						    || !ValidateTime (hour, minute, second)) {
							date = orig_date;
							return false;
						}

						date.Month = month;
						date.Day = day;
						date.Hour = hour;
						date.Minute = minute;
						date.Second = second;

						date.Precision = FMDateTimePrecision.DateAndTime;
					} else if (m.Groups[3].Value != String.Empty
					           && month > 0 && day > 0) {
						if (!ValidateDate (year, month, day)) {
							date = orig_date;
							return false;
						}

						date.Month = month;
						date.Day = day;

						date.Precision = FMDateTimePrecision.DateOnly;
					} else if (m.Groups[2].Value != String.Empty
					           && month > 0) {
						if (!ValidateYearMonth (year, month)) {
							date = orig_date;
							return false;
						}

						date.Month = month;

						date.Precision = FMDateTimePrecision.YearMonthOnly;
					}

					return true;
				}
			}

			return false;
		}

		private static bool ValidateYear (int year)
		{
			// Sadly, we can't use MaxValue and MinValue due to
			// this function being used in the
			// parsing and initialization of those values
			if (year < 1700 || year > 2699) {
				return false;
			}
			
			return true;
		}

		private static bool ValidateYearMonth (int year, int month)
		{
			if (!ValidateYear (year)) {
				return false;
			}

			int num_months = CurrentCalendar.GetMonthsInYear (year);
			if (month < 1 || month > num_months) {
				return false;
			}
			
			return true;
		}

		private static bool ValidateDate (int year, int month, int day)
		{
			if (!ValidateYearMonth (year, month)) {
				return false;
			}

			int num_days = CurrentCalendar.GetDaysInMonth (year, month);
			if (day < 1 || day > num_days) {
				return false;
			}

			return true;
		}

		private static bool ValidateTime (int hour, int minute, int second)
		{
			if (hour < 0 || hour > 24) {
				return false;
			}

			if (minute < 0 || minute > 59) {
				return false;
			}

			if (second < 0 || second > 59) {
				return false;
			}

			if (hour == 24 && (minute > 0 || second > 0)) {
				return false;
			}

			return true;
		}
	}

	public enum FMDateTimePrecision {
		YearOnly,
		YearMonthOnly,
		DateOnly,
		DateAndTime
	}
}
