/* * 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. * * Modified by Sam Habiel to use in Scheduling GUI. Modified class licensed under LGPL. */ 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 string FMDate (this DateTime d) { return Create(d, FMDateTimePrecision.DateAndTime).FMDateString; }*/ 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 } }