// Martin Baulig (martin@gnome.org)
//
// (C) Ximian, Inc.
-//
-
-//
-// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+// Copyright (C) 2004-2006 Novell, Inc (http://www.novell.com)
+// Copyright 2011 Xamarin Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
-
+//
+// TODO:
+//
+// Rewrite ToLocalTime to use GetLocalTimeDiff(DateTime,TimeSpan),
+// this should only leave the validation at the beginning (for MaxValue)
+// and then call the helper function. This would remove all the
+// ifdefs in that code, and replace it with only one, for the construction
+// of the object.
+//
+// Rewrite ToUniversalTime to use a similar setup to that
+//
using System.Collections;
using System.Globalization;
using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+using System.Runtime.InteropServices;
namespace System
{
[Serializable]
+ [ComVisible (true)]
public abstract class TimeZone
{
// Fields
- private static TimeZone currentTimeZone;
+ static TimeZone currentTimeZone;
+
+ [NonSerialized]
+ static object tz_lock = new object ();
+ [NonSerialized]
+ static long timezone_check;
// Constructor
protected TimeZone ()
// Properties
public static TimeZone CurrentTimeZone {
get {
- if (currentTimeZone == null)
- currentTimeZone = new CurrentTimeZone ();
-
- return currentTimeZone;
+ long now = DateTime.GetNow ();
+ TimeZone tz;
+
+ lock (tz_lock) {
+ if (currentTimeZone == null || (now - timezone_check) > TimeSpan.TicksPerMinute) {
+ currentTimeZone = new CurrentSystemTimeZone (now);
+ timezone_check = now;
+ }
+
+ tz = currentTimeZone;
+ }
+
+ return tz;
}
}
- internal static void ClearCurrentTimeZone ()
- {
- currentTimeZone = null;
- }
-
public abstract string DaylightName {
get;
}
public virtual DateTime ToLocalTime (DateTime time)
{
- DaylightTime dlt = GetDaylightChanges (time.Year);
- TimeSpan utcOffset = GetUtcOffset (time);
+ if (time.Kind == DateTimeKind.Local)
+ return time;
+
+ TimeSpan utcOffset = GetUtcOffset (new DateTime (time.Ticks));
if (utcOffset.Ticks > 0) {
if (DateTime.MaxValue - utcOffset < time)
- return DateTime.MaxValue;
- //} else if (utcOffset.Ticks < 0) {
- // LAMESPEC: MS.NET fails to check validity here
- // it may throw ArgumentOutOfRangeException.
+ return DateTime.SpecifyKind (DateTime.MaxValue, DateTimeKind.Local);
+ } else if (utcOffset.Ticks < 0) {
+ if (time.Ticks + utcOffset.Ticks < DateTime.MinValue.Ticks)
+ return DateTime.SpecifyKind (DateTime.MinValue, DateTimeKind.Local);
}
- DateTime local = time.Add (utcOffset);
+ DateTime local = DateTime.SpecifyKind (time.Add (utcOffset), DateTimeKind.Local);
+ DaylightTime dlt = GetDaylightChanges (time.Year);
if (dlt.Delta.Ticks == 0)
- return local;
+ return DateTime.SpecifyKind (local, DateTimeKind.Local);
// FIXME: check all of the combination of
// - basis: local-based or UTC-based
// PST should work fine here.
if (local < dlt.End && dlt.End.Subtract (dlt.Delta) <= local)
- return local;
- if (local >= dlt.Start && dlt.Start.Add (dlt.Delta) > local)
- return local.Subtract (dlt.Delta);
+ return DateTime.SpecifyKind (local, DateTimeKind.Local);
TimeSpan localOffset = GetUtcOffset (local);
- return time.Add (localOffset);
+ return DateTime.SpecifyKind (time.Add (localOffset), DateTimeKind.Local);
}
public virtual DateTime ToUniversalTime (DateTime time)
{
+ if (time.Kind == DateTimeKind.Utc)
+ return time;
+
TimeSpan offset = GetUtcOffset (time);
if (offset.Ticks < 0) {
if (DateTime.MaxValue + offset < time)
- return DateTime.MaxValue;
+ return DateTime.SpecifyKind (DateTime.MaxValue, DateTimeKind.Utc);
} else if (offset.Ticks > 0) {
if (DateTime.MinValue + offset > time)
- return DateTime.MinValue;
+ return DateTime.SpecifyKind (DateTime.MinValue, DateTimeKind.Utc);
}
- return new DateTime (time.Ticks - offset.Ticks);
+ return DateTime.SpecifyKind (new DateTime (time.Ticks - offset.Ticks), DateTimeKind.Utc);
+ }
+
+ //
+ // This routine returns the TimeDiff that would have to be
+ // added to "time" to turn it into a local time. This would
+ // be equivalent to call ToLocalTime.
+ //
+ // There is one important consideration:
+ //
+ // This information is only valid during the minute it
+ // was called.
+ //
+ // This only works with a real time, not one of the boundary
+ // cases like DateTime.MaxValue, so validation must be done
+ // before.
+ //
+ // This is intended to be used by DateTime.Now
+ //
+ // We use a minute, just to be conservative and cope with
+ // any potential time zones that might be defined in the future
+ // that might not nicely fit in hour or half-hour steps.
+ //
+ internal TimeSpan GetLocalTimeDiff (DateTime time)
+ {
+ return GetLocalTimeDiff (time, GetUtcOffset (time));
+ }
+
+ //
+ // This routine is intended to be called by GetLocalTimeDiff(DatetTime)
+ // or by ToLocalTime after validation has been performed
+ //
+ // time is the time to map, utc_offset is the utc_offset that
+ // has been computed for calling GetUtcOffset on time.
+ //
+ // When called by GetLocalTime, utc_offset is assumed to come
+ // from a time constructed by new DateTime (DateTime.GetNow ()), that
+ // is a valid time.
+ //
+ // When called by ToLocalTime ranges are checked before this is
+ // called.
+ //
+ internal TimeSpan GetLocalTimeDiff (DateTime time, TimeSpan utc_offset)
+ {
+ DaylightTime dlt = GetDaylightChanges (time.Year);
+
+ if (dlt.Delta.Ticks == 0)
+ return utc_offset;
+
+ DateTime local = time.Add (utc_offset);
+ if (local < dlt.End && dlt.End.Subtract (dlt.Delta) <= local)
+ return utc_offset;
+
+ if (local >= dlt.Start && dlt.Start.Add (dlt.Delta) > local)
+ return utc_offset - dlt.Delta;
+
+ return GetUtcOffset (local);
}
}
- internal class CurrentTimeZone : TimeZone
- {
+ [Serializable]
+ internal class CurrentSystemTimeZone : TimeZone, IDeserializationCallback {
+
// Fields
- private static string daylightName;
- private static string standardName;
+ private string m_standardName;
+ private string m_daylightName;
// A yearwise cache of DaylightTime.
- private static Hashtable daylightCache = new Hashtable (1);
+ private Hashtable m_CachedDaylightChanges = new Hashtable (1);
- // the offset when daylightsaving is not on.
- private static TimeSpan utcOffsetWithOutDLS;
+ // the offset when daylightsaving is not on (in ticks)
+ private long m_ticksOffset;
+ // the offset when daylightsaving is not on.
+ [NonSerialized]
+ private TimeSpan utcOffsetWithOutDLS;
+
// the offset when daylightsaving is on.
- private static TimeSpan utcOffsetWithDLS;
+ [NonSerialized]
+ private TimeSpan utcOffsetWithDLS;
internal enum TimeZoneData
{
private static extern bool GetTimeZoneData (int year, out Int64[] data, out string[] names);
// Constructor
- internal CurrentTimeZone ()
- : base ()
+ internal CurrentSystemTimeZone ()
+ {
+ }
+
+ //
+ // Initialized by the constructor
+ //
+ static int this_year;
+ static DaylightTime this_year_dlt;
+
+ //
+ // The "lnow" parameter must be the current time, I could have moved
+ // the code here, but I do not want to interfere with serialization
+ // which is why I kept the other constructor around
+ //
+ internal CurrentSystemTimeZone (long lnow)
{
Int64[] data;
string[] names;
- DateTime now = new DateTime(DateTime.GetNow ());
+ DateTime now = new DateTime (lnow);
if (!GetTimeZoneData (now.Year, out data, out names))
throw new NotSupportedException (Locale.GetText ("Can't get timezone name."));
- standardName = Locale.GetText (names[(int)TimeZoneNames.StandardNameIdx]);
- daylightName = Locale.GetText (names[(int)TimeZoneNames.DaylightNameIdx]);
+ m_standardName = Locale.GetText (names[(int)TimeZoneNames.StandardNameIdx]);
+ m_daylightName = Locale.GetText (names[(int)TimeZoneNames.DaylightNameIdx]);
+
+ m_ticksOffset = data[(int)TimeZoneData.UtcOffsetIdx];
- utcOffsetWithOutDLS = new TimeSpan (data[(int)TimeZoneData.UtcOffsetIdx]);
- utcOffsetWithDLS = new TimeSpan (data[(int)TimeZoneData.UtcOffsetIdx]
- + data[(int)TimeZoneData.AdditionalDaylightOffsetIdx]);
+ DaylightTime dlt = GetDaylightTimeFromData (data);
+ m_CachedDaylightChanges.Add (now.Year, dlt);
+ OnDeserialization (dlt);
}
// Properties
public override string DaylightName {
- get { return daylightName; }
+ get { return m_daylightName; }
}
public override string StandardName {
- get { return standardName; }
+ get { return m_standardName; }
}
// Methods
- [MonoTODO]
public override DaylightTime GetDaylightChanges (int year)
{
if (year < 1 || year > 9999)
throw new ArgumentOutOfRangeException ("year", year +
Locale.GetText (" is not in a range between 1 and 9999."));
- lock (daylightCache) {
- DaylightTime dlt = (DaylightTime) daylightCache [year];
+ //
+ // First we try the case for this year, very common, and is used
+ // by DateTime.Now (a popular call) indirectly.
+ //
+ if (year == this_year)
+ return this_year_dlt;
+
+ lock (m_CachedDaylightChanges) {
+ DaylightTime dlt = (DaylightTime) m_CachedDaylightChanges [year];
if (dlt == null) {
Int64[] data;
string[] names;
if (!GetTimeZoneData (year, out data, out names))
throw new ArgumentException (Locale.GetText ("Can't get timezone data for " + year));
- dlt = new DaylightTime (new DateTime (data[(int)TimeZoneData.DaylightSavingStartIdx]),
- new DateTime (data[(int)TimeZoneData.DaylightSavingEndIdx]),
- new TimeSpan (data[(int)TimeZoneData.AdditionalDaylightOffsetIdx]));
- daylightCache.Add (year, dlt);
+ dlt = GetDaylightTimeFromData (data);
+ m_CachedDaylightChanges.Add (year, dlt);
}
return dlt;
}
public override TimeSpan GetUtcOffset (DateTime time)
{
+ if (time.Kind == DateTimeKind.Utc)
+ return TimeSpan.Zero;
+
if (IsDaylightSavingTime (time))
return utcOffsetWithDLS;
return utcOffsetWithOutDLS;
}
+
+ void IDeserializationCallback.OnDeserialization (object sender)
+ {
+ OnDeserialization (null);
+ }
+
+ private void OnDeserialization (DaylightTime dlt)
+ {
+ if (dlt == null) {
+ Int64[] data;
+ string[] names;
+
+ this_year = DateTime.Now.Year;
+ if (!GetTimeZoneData (this_year, out data, out names))
+ throw new ArgumentException (Locale.GetText ("Can't get timezone data for " + this_year));
+ dlt = GetDaylightTimeFromData (data);
+ } else
+ this_year = dlt.Start.Year;
+
+ utcOffsetWithOutDLS = new TimeSpan (m_ticksOffset);
+ utcOffsetWithDLS = new TimeSpan (m_ticksOffset + dlt.Delta.Ticks);
+ this_year_dlt = dlt;
+ }
+
+ private DaylightTime GetDaylightTimeFromData (long[] data)
+ {
+ return new DaylightTime (new DateTime (data[(int)TimeZoneData.DaylightSavingStartIdx]),
+ new DateTime (data[(int)TimeZoneData.DaylightSavingEndIdx]),
+ new TimeSpan (data[(int)TimeZoneData.AdditionalDaylightOffsetIdx]));
+ }
+
}
}