Click here to Skip to main content
15,886,673 members
Articles / Programming Languages / C#

A TimeZone Aware DateTime Implementation

Rate me:
Please Sign up or sign in to vote.
3.56/5 (10 votes)
24 Feb 2010CPOL6 min read 70.1K   466   37  
An implementation that wraps DateTime to allow for keeping track of TimeZone state
using System;
using System.Data.SqlTypes;

namespace DateTimeZone
{
	/// <summary>
	/// Simple class that represents a time zone.
	/// </summary>
	[Serializable]
	public struct TimeZone : INullable
	{
		private bool hasDST;
		
		/// <summary>
		/// Null-Handling
		/// </summary>
		public static TimeZone Null
		{
			// get a null instance
			get
			{
				TimeZone tz = new TimeZone();
				tz.isNull = true;
				return tz;
			}
		}

		/// <summary>
		/// Constructor to fully initialize the instance.
		/// </summary>
		/// <param name="offset"></param>
		/// <param name="canonicalID"></param>
		/// <param name="aliases"></param>
		/// <param name="defaultTimeZoneID"></param>
		/// <param name="summerOccurrence"></param>
		/// <param name="summerDay"></param>
		/// <param name="summerMonth"></param>
		/// <param name="summerHour"></param>
		/// <param name="summerMinutes"></param>
		/// <param name="winterOccurrence"></param>
		/// <param name="winterDay"></param>
		/// <param name="winterMonth"></param>
		/// <param name="winterHour"></param>
		/// <param name="winterMinutes"></param>
		/// <param name="dst"></param>
		public TimeZone(double offset, string canonicalID, string aliases, string defaultTimeZoneID,
			DayOccurrenceInMonth summerOccurrence, DayOfWeek summerDay, int summerMonth, int summerHour, int summerMinutes,
			DayOccurrenceInMonth winterOccurrence, DayOfWeek winterDay, int winterMonth, int winterHour, int winterMinutes, bool dst)
			: this()
		{
		// Daylight Saving Time currently starts on the second Sunday of March and ends 
			// on the first Sunday of November, with all time changes taking place at 2:00 a.m. local time.
			//int OccuranceOfWeekDayInMonth, DayOfWeek.Sunday, hour.
			hasDST = dst;

			Offset = offset;
			CanonicalID = canonicalID;
			Aliases = aliases;

			HourOffset = (int)offset;
			double rest = offset - (int)offset;
			if (rest == .25) MinuteOffset = 15;
			else if (rest == .50) MinuteOffset = 30;
			else if (rest == .75) MinuteOffset = 45;
			else MinuteOffset = 0;

			DefaultTimeZoneID = defaultTimeZoneID;

			SummerChange = new DaylightSavingTime(summerOccurrence, summerMonth, summerDay, summerHour, summerMinutes);
			WinterChange = new DaylightSavingTime(winterOccurrence, winterMonth, winterDay, winterHour, winterMinutes);
		}

		/// <summary>
		/// The CanonicalID of the default time zone.
		/// </summary>
		public string DefaultTimeZoneID { get; private set; }

		/// <summary>
		/// This time zone's offset from GMT.
		/// </summary>
		public double Offset { get; private set; }

		/// <summary>
		/// Calculated hour offset derived from Offset.
		/// </summary>
		public int HourOffset { get; private set; }

		/// <summary>
		/// Calculated minute offset derived from Offset.
		/// </summary>
		public int MinuteOffset { get; private set; }

		/// <summary>
		/// The Id.
		/// </summary>
		public string CanonicalID { get; private set; }

		private string aliases;
		private bool isNull;

		/// <summary>
		/// Location alias.
		/// </summary>
		public string Aliases
		{
			get { return aliases; }
			private set { aliases = value; }
		}

		/// <summary>
		/// A reference to the default time zone.
		/// </summary>
		public TimeZone DefaultTimeZone 
		{
			get { return TimeZoneUtil.GetTimeZone(DefaultTimeZoneID); }
		}

		/// <summary>
		/// The definition of when a summer time zone change occurs.
		/// </summary>
		public DaylightSavingTime SummerChange { get; private set; }

		/// <summary>
		/// The definition of when a revertion to a standard time zone change occurs.
		/// </summary>
		public DaylightSavingTime WinterChange { get; private set; }

		///<summary>
		///Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
		///</summary>
		///
		///<returns>
		///A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
		///</returns>
		///<filterpriority>2</filterpriority>
		public override string ToString()
		{
			return CanonicalID;
		}

		// TimeZone(1,Europe/Madrid,,Unknown,DayOccurrenceInMonth.Fourth,DayOfWeekSunday,10,3,0,,DayOccurrenceInMonth.Last,DayOfWeekSunday,3,2,0,True);
		/// <summary>
		/// Printing the content of this
		/// </summary>
		/// <returns>A string formatted to print the c# code needed to define the TimeZone based on the values in this class</returns>
		public string PrintValues()
		{
			string alias = "\"" + Aliases + "\"";

			//TimeZone(5, "Etc/GMT-5", "", UTC, DayOccurrenceInMonth.Last, DayOfWeek.Sunday, 0, 0, 0, DayOccurrenceInMonth.Last, DayOfWeek.Sunday, 0, 0, 0, false);
			return "public static readonly TimeZone " + GetFieldName() + " = new TimeZone(" +
				Offset + "," + "\"" + CanonicalID + "\"" + "," + alias + ",\"" + DefaultTimeZoneID +
				"\",DayOccurrenceInMonth." + SummerChange.DayOccurrenceInMonth + ",DayOfWeek." + SummerChange.DayOfWeek + "," + SummerChange.Month + "," + SummerChange.Hour + "," + SummerChange.Minutes + "," +
				"DayOccurrenceInMonth." + WinterChange.DayOccurrenceInMonth + ",DayOfWeek." + WinterChange.DayOfWeek + "," + WinterChange.Month + "," + WinterChange.Hour + "," + WinterChange.Minutes + "," + GetHasDST() + ");";
		}

		private string GetFieldName()
		{
			string minus = "___";
			string plus = "__";
			bool isPlus = true;
			int index = CanonicalID.IndexOf('+');
			if (index == -1)
			{
				index = CanonicalID.IndexOf('-');
				isPlus = false;
			}

			string fieldName = CanonicalID;
			if (index != -1)
			{
				string side = (isPlus ? plus : minus);
				fieldName = CanonicalID.Insert(index, side);		
			}
			fieldName = fieldName.Replace("/", plus);
			return fieldName;
		}

		private string GetHasDST()
		{
			string trueFalse = HasDST.ToString();
			return trueFalse.ToLower();
		}

		/// <summary>
		/// Convenience getter for checking if a time zone has DST.
		/// </summary>
		public bool HasDST { get { return hasDST; } }

		/// <summary>
		/// A convenience method that checks if the newly converted date for a time zone is within that
		/// time zone's definted DST rules.
		/// </summary>
		/// <param name="time"></param>
		/// <returns></returns>
		public bool IsDST(DateTime time)
		{
			if (hasDST)
			{
				DateTime summer = GetDSTStart(time.Year);
				DateTime winter = GetDSTEnd(time.Year);

				if (summer < winter)
				{
					if (time <= summer) return false;
					return time > summer && time <= winter;
				}
				winter = GetDSTEnd(time.Year + 1);
				if (time <= summer) return false;
				return time > summer && time <= winter;
			}
			return false;
		}

        /// <summary>
        /// Convenience method for calculating the point in time when the winter time starts.
        /// </summary>
        /// <param name="year">For which year to calculate the winter change.</param>
        /// <returns>A point in time representing the winter change.</returns>
		public DateTime GetDSTEnd(int year)
		{
			DayOfWeek w_dayOfWeek = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), WinterChange.DayOfWeek);
			DayOccurrenceInMonth w_occurrance = (DayOccurrenceInMonth)Enum.Parse(typeof(DayOccurrenceInMonth), WinterChange.DayOccurrenceInMonth);
			int winterDay;

			if (DayOccurrenceInMonth.First == w_occurrance) winterDay = DateUtil.FindDayInMonth(year, WinterChange.Month, w_dayOfWeek, 1);
			else if (DayOccurrenceInMonth.Second == w_occurrance) winterDay = DateUtil.FindDayInMonth(year, WinterChange.Month, w_dayOfWeek, 2);
			else if (DayOccurrenceInMonth.Third == w_occurrance) winterDay = DateUtil.FindDayInMonth(year, WinterChange.Month, w_dayOfWeek, 3);
			else if (DayOccurrenceInMonth.Fourth == w_occurrance) winterDay = DateUtil.FindDayInMonth(year, WinterChange.Month, w_dayOfWeek, 4);
			else winterDay = DateUtil.FindLastOccurrenceOfDayInMonth(year, WinterChange.Month, w_dayOfWeek);

			return new DateTime(year, WinterChange.Month, winterDay, WinterChange.Hour, WinterChange.Minutes, 0);
		}

        /// <summary>
        /// Convenience method for calculating the point in time when the summer time starts.
        /// </summary>
        /// <param name="year">For which year to calculate the summer change.</param>
        /// <returns>A point in time representing the summer change.</returns>
        public DateTime GetDSTStart(int year)
		{
			DayOfWeek s_dayOfWeek = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), SummerChange.DayOfWeek);
			DayOccurrenceInMonth s_occurrance = (DayOccurrenceInMonth)Enum.Parse(typeof(DayOccurrenceInMonth), SummerChange.DayOccurrenceInMonth);
			int summerDay;

			if (DayOccurrenceInMonth.First == s_occurrance) summerDay = DateUtil.FindDayInMonth(year, SummerChange.Month, s_dayOfWeek, 1);
			else if (DayOccurrenceInMonth.Second == s_occurrance) summerDay = DateUtil.FindDayInMonth(year, SummerChange.Month, s_dayOfWeek, 2);
			else if (DayOccurrenceInMonth.Third == s_occurrance) summerDay = DateUtil.FindDayInMonth(year, SummerChange.Month, s_dayOfWeek, 3);
			else if (DayOccurrenceInMonth.Fourth == s_occurrance) summerDay = DateUtil.FindDayInMonth(year, SummerChange.Month, s_dayOfWeek, 4);
			else summerDay = DateUtil.FindLastOccurrenceOfDayInMonth(year, SummerChange.Month, s_dayOfWeek);

			return new DateTime(year, SummerChange.Month, summerDay, SummerChange.Hour, SummerChange.Minutes, 0);
		}

		/// <summary>
		/// Indicates whether a structure is null. This property is read-only.
		/// </summary>
		/// <returns>true if the value of this object is null. Otherwise, false.</returns>
		public bool IsNull
		{
			get { return isNull; }
		}

		/// <summary>
		/// Equals comparison
		/// </summary>
		/// <param name="tz1">The time zone instance should be equal to tz2.</param>
		/// <param name="tz2">The time zone instance should be equal to tz1.</param>
		/// <returns>True if time1 is equal to time2</returns>
		public static bool operator ==(TimeZone tz1, TimeZone tz2)
		{
			return tz1.CanonicalID.Equals(tz2.CanonicalID);
		}

		/// <summary>
		/// Unequal (not equal) comparison
		/// </summary>		
		/// <param name="tz1">The time zone instance should not be equal to tz2.</param>
		/// <param name="tz2">The time zone instance should not be equal to tz1.</param>
		/// <returns>True if time1 is not equal to time2</returns>
		public static bool operator !=(TimeZone tz1, TimeZone tz2)
		{
			return !tz1.CanonicalID.Equals(tz2.CanonicalID);
		}

		/// <summary>
		/// Overriding equals method so that instances of TimeZones representing the same locale
		/// will evaluate true (equal) even if they are located in different memory locations.
		/// </summary>
		/// <param name="obj">The object to compare.</param>
		/// <returns>True if the provided object is a TimeZone representing the same locale as current instance.</returns>
		public override bool Equals(object obj)
		{
			if(obj is TimeZone) return CanonicalID.Equals(((TimeZone) obj).CanonicalID);
			return false;
		}

		/// <summary>
		/// Overriding object hash code to use the hash code of the canonical id.
		/// </summary>
		/// <returns>The hash code of the canonical id.</returns>
		public override int GetHashCode()
		{
			return CanonicalID.GetHashCode();
		}

		
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Norway Norway
http://www.linkedin.com/in/steinardragsnes

Comments and Discussions