using System;
using System.Collections.Generic;
using System.Text;
namespace CSharpVitamins
{
///
///
///
public struct Age : IFormattable, IComparable, IComparable, IComparable
{
public static readonly Age Empty = new Age(DateTime.MinValue, DateTime.MinValue);
#region Private Variables
DateTime _advent;
DateTime _terminus;
int _years;
int _months;
int _weeks;
int _days;
#endregion
#region Constructors
///
/// Creates a new instance of Age starting from the given date until now
///
///
public Age(DateTime advent)
: this(advent, getNowFromKind(advent))
{
}
///
/// Creates a new instance of Age starting from the given date until now
///
///
///
public Age(DateTime advent, DateTime terminus)
{
_advent = advent;
_terminus = terminus;
_years = 0;
_months = 0;
_weeks = 0;
_days = 0;
updateInternal();
}
#endregion
#region Properties
///
/// Gets the number of years old
///
public int Years
{
get { return _years; }
}
///
/// Gets the number of months old
///
public int Months
{
get { return _months; }
}
///
/// Gets the number of weeks old
///
public int Weeks
{
get { return _weeks; }
}
///
/// Gets the number of days old
///
public int Days
{
get { return _days; }
}
///
/// Gets/Sets the start of the age
///
public DateTime Advent
{
get { return _advent; }
set
{
_advent = value;
updateInternal();
}
}
///
/// Gets/Sets the end of the age
///
public DateTime Terminus
{
get { return _terminus; }
set
{
_terminus = value;
updateInternal();
}
}
///
/// Gets the elapsed time
///
public TimeSpan Elapsed
{
get { return new TimeSpan(_terminus.Ticks - _advent.Ticks); }
}
///
/// Gets a value indicating that the age is empty (both advent and terminus dates are MinValue)
///
///
public bool IsEmpty
{
get
{
return _advent == DateTime.MinValue
&& _terminus == DateTime.MinValue;
}
}
#endregion
#region Update
///
/// Updates the age to now
///
public Age Update()
{
return Update(getNowFromKind(_advent));
}
///
/// Updates the age to the given terminus
///
///
public Age Update(DateTime terminus)
{
return new Age(_advent, terminus);
}
///
/// Updates the values for years, months, weeks and days
///
void updateInternal()
{
Extract(_advent, _terminus, out _years, out _months, out _weeks, out _days);
}
#endregion
#region Extract
///
/// Calculates the years, months, weeks and days within the two given dates
///
///
/// Implementation based on http://tommycarlier.blogspot.com/2006/02/years-months-and-days-between-2-dates.html#links
///
///
///
///
///
///
///
public static void Extract(DateTime advent, DateTime terminus, out int years, out int months, out int weeks, out int days)
{
/// only extracts down to 'days', so the TimeOfDay needs to be
/// normalised for this calculation
TimeSpan time = advent.TimeOfDay;
if (time > TimeSpan.Zero)
{
advent = advent.Subtract(time);
terminus = terminus.Subtract(time);
}
years = terminus.Year - advent.Year;
months = terminus.Month - advent.Month;
days = terminus.Day - advent.Day;
weeks = 0;
if (days < 0) months -= 1;
while (months < 0)
{
months += 12;
years -= 1;
}
TimeSpan span = terminus - advent.AddYears(years).AddMonths(months);
days = (int)Math.Floor(span.TotalDays);
if (days > 0)
{
weeks = days / 7;
days = days % 7;
}
}
#endregion
#region ToString
///
///
///
///
public override string ToString()
{
return ToString(0, true);
}
///
///
///
///
///
public string ToString(int significantPlaces)
{
return ToString(significantPlaces, true);
}
///
///
///
///
///
///
public string ToString(int significantPlaces, bool includeTime)
{
if (IsEmpty)
{
return string.Empty;
}
int max = significantPlaces < 1 ? 10 : significantPlaces;
int parts = 0;
StringBuilder result = new StringBuilder();
if (_years > 0 && parts < max)
{
result.AppendFormat(" {0} year{1}", _years, plural(_years));
++parts;
}
if (_months > 0 && parts < max)
{
result.AppendFormat(" {0} month{1}", _months, plural(_months));
++parts;
}
if (_weeks > 0 && parts < max)
{
result.AppendFormat(" {0} week{1}", _weeks, plural(_weeks));
++parts;
}
if (_days > 0 && parts < max)
{
result.AppendFormat(" {0} day{1}", _days, plural(_days));
++parts;
}
if (includeTime)
{
TimeSpan time = Elapsed;
if (time.Hours != 0 && parts < max)
{
result.AppendFormat(" {0} hour{1}", time.Hours, plural(time.Hours));
++parts;
}
if (time.Minutes != 0 && parts < max)
{
result.AppendFormat(" {0} minute{1}", time.Minutes, plural(time.Minutes));
++parts;
}
if (time.Seconds != 0 && parts < max)
{
result.AppendFormat(" {0} second{1}", time.Seconds, plural(time.Seconds));
++parts;
}
}
return result.Length == 0
? includeTime ? "less than a second" : "less than a day"
: result.ToString().Trim();
}
///
///
///
///
///
public string ToString(string format)
{
return ToString(format, System.Globalization.CultureInfo.CurrentCulture);
}
#region IFormattable Members
///
///
///
///
///
///
public string ToString(string format, IFormatProvider provider)
{
/// TODO: Finish implmenting the provider
if (string.IsNullOrEmpty(format)) format = "g";
char first = format[0];
if (char.ToLower(first) == 'g')
{
int parts = 0;
if (format.Length > 1 && char.IsDigit(format[1]))
parts = int.Parse(format[1].ToString());
return ToString(parts);
}
else if (char.IsDigit(first))
{
int parts = int.Parse(first.ToString());
return ToString(parts);
}
throw new FormatException("Could not parse the Age format: " + format);
}
#endregion
#endregion
#region getNowFromKind
///
/// Gets the current date/time for universal vrs local time
///
///
///
static DateTime getNowFromKind(DateTime time)
{
return time.Kind == DateTimeKind.Utc ? DateTime.UtcNow : DateTime.Now;
}
#endregion
#region plural
///
/// Returns a "s" for numbers other than 0 otherwise an empty string.
///
///
///
string plural(int number)
{
return number == 1 ? string.Empty : "s";
}
#endregion
#region Format
///
///
///
///
///
public static string Format(DateTime advent)
{
return Format(advent, getNowFromKind(advent), 0);
}
///
///
///
///
///
///
public static string Format(DateTime advent, DateTime terminus)
{
return Format(advent, terminus, 0);
}
///
///
///
///
///
///
public static string Format(DateTime advent, int parts)
{
return Format(advent, getNowFromKind(advent), parts);
}
///
///
///
///
///
///
///
public static string Format(DateTime advent, DateTime terminus, int parts)
{
return new Age(advent, terminus).ToString(parts);
}
#endregion
#region IComparable Members
///
///
///
///
///
public int CompareTo(object obj)
{
if (obj is Age)
{
return CompareTo((Age)obj);
}
else if (obj is TimeSpan)
{
return CompareTo((TimeSpan)obj);
}
throw new ArgumentException(string.Format("The object '{0}' is of the wrong type for comparison.", obj.GetType(), "obj"));
}
///
///
///
///
///
public int CompareTo(Age other)
{
return CompareTo(other.Elapsed);
}
///
///
///
///
///
public int CompareTo(TimeSpan other)
{
return Elapsed.CompareTo(other);
}
#endregion
#region GetHashCode (Commented Out)
/////
/////
/////
/////
//public override int GetHashCode()
//{
// return _advent.GetHashCode() ^ _terminus.GetHashCode();
//}
#endregion
#region Equals (Commented Out)
/////
///// Tests weather the object is of an equal age by comparing their elapsed timespans.
/////
/////
/////
//public override bool Equals( object obj )
//{
// if( obj is Age )
// {
// return this == (Age)obj;
// }
// else if( obj is TimeSpan )
// {
// return this.Elapsed == (TimeSpan)obj;
// }
// return false;
//}
#endregion
#region Operators
#region "==" and "!=" (Commented Out)
/////
/////
/////
/////
///// There is some controversy on overriding the Equals operator.
/////
/////
/////
/////
//public static bool operator ==( Age x, Age y )
//{
// if( (object)x == null ) return (object)y == null;
// return x.Elapsed == y.Elapsed;
//}
/////
/////
/////
/////
/////
/////
//public static bool operator !=( Age x, Age y )
//{
// return !( x == y );
//}
#endregion
///
///
///
///
///
///
public static bool operator >(Age x, Age y)
{
if ((object)y == null) return (object)x != null;
return x.Elapsed > y.Elapsed;
}
///
///
///
///
///
///
public static bool operator <(Age x, Age y)
{
if ((object)x == null) return (object)y != null;
return x.Elapsed < y.Elapsed;
}
///
///
///
///
///
///
public static bool operator >=(Age x, Age y)
{
if ((object)y == null) return (object)x != null;
return x.Elapsed >= y.Elapsed;
}
///
///
///
///
///
///
public static bool operator <=(Age x, Age y)
{
if ((object)x == null) return (object)y != null;
return x.Elapsed <= y.Elapsed;
}
///
///
///
///
///
public static implicit operator TimeSpan(Age age)
{
return age.Elapsed;
}
#endregion
}
}