Merge pull request #321 from RAOF/aot-cpu-safety
[mono.git] / mcs / class / corlib / System / TimeZone.cs
index 16907de1b5f7508b81cbd4e2d632ceda4719d332..e0203f9a65ced062d2302178254a0db488672f3c 100644 (file)
@@ -7,10 +7,8 @@
 //   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 ()
@@ -52,18 +68,22 @@ namespace System
                // 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;
                }
@@ -108,19 +128,22 @@ namespace System
 
                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
@@ -129,44 +152,107 @@ namespace System
 
                        // 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
                {
@@ -193,43 +279,65 @@ namespace System
                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;
@@ -237,10 +345,8 @@ namespace System
                                        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;
                        }
@@ -248,10 +354,44 @@ namespace System
 
                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]));
+               }
+
        }
 }