New test.
[mono.git] / mcs / class / corlib / System / TimeZone.cs
1 //
2 // System.TimeZone.cs
3 //
4 // Authors:
5 //   Duncan Mak (duncan@ximian.com)
6 //   Ajay Kumar Dwivedi (adwiv@yahoo.com)
7 //   Martin Baulig (martin@gnome.org)
8 //
9 // (C) Ximian, Inc.
10 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System.Collections;
33 using System.Globalization;
34 using System.Runtime.CompilerServices;
35 using System.Runtime.Serialization;
36
37 namespace System
38 {
39         [Serializable]
40         public abstract class TimeZone
41         {
42                 // Fields
43                 private static TimeZone currentTimeZone;
44
45                 // Constructor
46                 protected TimeZone ()
47                 {
48                 }
49
50                 // Properties
51                 public static TimeZone CurrentTimeZone {
52                         get {
53                                 if (currentTimeZone == null) {
54                                         DateTime now = new DateTime (DateTime.GetNow ());
55                                         currentTimeZone = new CurrentSystemTimeZone (now);
56                                 }
57                                 return currentTimeZone;
58                         }
59                 }
60
61                 internal static void ClearCurrentTimeZone ()
62                 {
63                         currentTimeZone = null;
64                 }
65
66                 public abstract string DaylightName {
67                         get;
68                 }
69
70                 public abstract string StandardName {
71                         get;
72                 }
73
74                 // Methods
75                 public abstract DaylightTime GetDaylightChanges (int year);
76
77                 public abstract TimeSpan GetUtcOffset (DateTime time);
78
79                 public virtual bool IsDaylightSavingTime (DateTime time)
80                 {
81                         return IsDaylightSavingTime (time, GetDaylightChanges (time.Year));
82                 }
83
84                 public static bool IsDaylightSavingTime (DateTime time, DaylightTime daylightTimes)
85                 {
86                         if (daylightTimes == null)
87                                 throw new ArgumentNullException ("daylightTimes");
88
89                         // If Start == End, then DST is off
90                         if (daylightTimes.Start.Ticks == daylightTimes.End.Ticks)
91                                 return false;
92
93                         //We are in the northern hemisphere.
94                         if (daylightTimes.Start.Ticks < daylightTimes.End.Ticks) {
95                                 if (daylightTimes.Start.Ticks < time.Ticks && daylightTimes.End.Ticks > time.Ticks)
96                                         return true; // time lies between Start and End
97
98                         }
99                         else {  // We are in the southern hemisphere.
100                                 if (time.Year == daylightTimes.Start.Year && time.Year == daylightTimes.End.Year)
101                                         if (time.Ticks < daylightTimes.End.Ticks || time.Ticks > daylightTimes.Start.Ticks)
102                                                 return true; // time is less than End OR more than Start 
103                         }
104
105                         return false;
106                 }
107
108                 public virtual DateTime ToLocalTime (DateTime time)
109                 {
110 #if NET_2_0
111                         if (time.Kind == DateTimeKind.Local)
112                                 return time;
113 #endif
114
115                         DaylightTime dlt = GetDaylightChanges (time.Year);
116                         TimeSpan utcOffset = GetUtcOffset (time);
117                         if (utcOffset.Ticks > 0) {
118                                 if (DateTime.MaxValue - utcOffset < time)
119 #if NET_2_0
120                                         return DateTime.SpecifyKind (DateTime.MaxValue, DateTimeKind.Local);
121 #else
122                                         return DateTime.MaxValue;
123 #endif
124                         //} else if (utcOffset.Ticks < 0) {
125                         //      LAMESPEC: MS.NET fails to check validity here
126                         //      it may throw ArgumentOutOfRangeException.
127                         }
128
129                         DateTime local = time.Add (utcOffset);
130                         if (dlt.Delta.Ticks == 0)
131 #if NET_2_0
132                                 return DateTime.SpecifyKind (local, DateTimeKind.Local);
133 #else
134                                 return local;
135 #endif
136
137                         // FIXME: check all of the combination of
138                         //      - basis: local-based or UTC-based
139                         //      - hemisphere: Northern or Southern
140                         //      - offset: positive or negative
141
142                         // PST should work fine here.
143                         if (local < dlt.End && dlt.End.Subtract (dlt.Delta) <= local)
144 #if NET_2_0
145                                 return DateTime.SpecifyKind (local, DateTimeKind.Local);
146 #else
147                                 return local;
148 #endif
149                         if (local >= dlt.Start && dlt.Start.Add (dlt.Delta) > local)
150 #if NET_2_0
151                                 return DateTime.SpecifyKind (local.Subtract (dlt.Delta), DateTimeKind.Local);
152 #else
153                                 return local.Subtract (dlt.Delta);
154 #endif
155
156                         TimeSpan localOffset = GetUtcOffset (local);
157 #if NET_2_0
158                         return DateTime.SpecifyKind (time.Add (localOffset), DateTimeKind.Local);
159 #else
160                         return time.Add (localOffset);
161 #endif
162                 }
163
164                 public virtual DateTime ToUniversalTime (DateTime time)
165                 {
166 #if NET_2_0
167                         if (time.Kind == DateTimeKind.Utc)
168                                 return time;
169 #endif
170
171                         TimeSpan offset = GetUtcOffset (time);
172
173                         if (offset.Ticks < 0) {
174                                 if (DateTime.MaxValue + offset < time)
175 #if NET_2_0
176                                         return DateTime.SpecifyKind (DateTime.MaxValue, DateTimeKind.Utc);
177 #else
178                                         return DateTime.MaxValue;
179 #endif
180                         } else if (offset.Ticks > 0) {
181                                 if (DateTime.MinValue + offset > time)
182 #if NET_2_0
183                                         return DateTime.SpecifyKind (DateTime.MinValue, DateTimeKind.Utc);
184 #else
185                                         return DateTime.MinValue;
186 #endif
187                         }
188
189 #if NET_2_0
190                         return DateTime.SpecifyKind (new DateTime (time.Ticks - offset.Ticks), DateTimeKind.Utc);
191 #else
192                         return new DateTime (time.Ticks - offset.Ticks);
193 #endif          
194                 }
195         }
196
197         [Serializable]
198         internal class CurrentSystemTimeZone : TimeZone, IDeserializationCallback {
199
200                 // Fields
201                 private string m_standardName;
202                 private string m_daylightName;
203
204                 // A yearwise cache of DaylightTime.
205                 private Hashtable m_CachedDaylightChanges = new Hashtable (1);
206
207                 // the offset when daylightsaving is not on (in ticks)
208                 private long m_ticksOffset;
209
210                 // the offset when daylightsaving is not on.
211                 [NonSerialized]
212                 private TimeSpan utcOffsetWithOutDLS;
213   
214                 // the offset when daylightsaving is on.
215                 [NonSerialized]
216                 private TimeSpan utcOffsetWithDLS;
217
218                 internal enum TimeZoneData
219                 {
220                         DaylightSavingStartIdx,
221                         DaylightSavingEndIdx,
222                         UtcOffsetIdx,
223                         AdditionalDaylightOffsetIdx
224                 };
225
226                 internal enum TimeZoneNames
227                 {
228                         StandardNameIdx,
229                         DaylightNameIdx
230                 };
231
232                 // Internal method to get timezone data.
233                 //    data[0]:  start of daylight saving time (in DateTime ticks).
234                 //    data[1]:  end of daylight saving time (in DateTime ticks).
235                 //    data[2]:  utcoffset (in TimeSpan ticks).
236                 //    data[3]:  additional offset when daylight saving (in TimeSpan ticks).
237                 //    name[0]:  name of this timezone when not daylight saving.
238                 //    name[1]:  name of this timezone when daylight saving.
239                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
240                 private static extern bool GetTimeZoneData (int year, out Int64[] data, out string[] names);
241
242                 // Constructor
243                 internal CurrentSystemTimeZone ()
244                 {
245                 }
246
247                 internal CurrentSystemTimeZone (DateTime now)
248                 {
249                         Int64[] data;
250                         string[] names;
251
252                         if (!GetTimeZoneData (now.Year, out data, out names))
253                                 throw new NotSupportedException (Locale.GetText ("Can't get timezone name."));
254
255                         m_standardName = Locale.GetText (names[(int)TimeZoneNames.StandardNameIdx]);
256                         m_daylightName = Locale.GetText (names[(int)TimeZoneNames.DaylightNameIdx]);
257
258                         m_ticksOffset = data[(int)TimeZoneData.UtcOffsetIdx];
259
260                         DaylightTime dlt = GetDaylightTimeFromData (data);
261                         m_CachedDaylightChanges.Add (now.Year, dlt);
262                         OnDeserialization (dlt);
263                 }
264
265                 // Properties
266                 public override string DaylightName {
267                         get { return m_daylightName; }
268                 }
269
270                 public override string StandardName {
271                         get { return m_standardName; }
272                 }
273
274                 // Methods
275                 [MonoTODO]
276                 public override DaylightTime GetDaylightChanges (int year)
277                 {
278                         if (year < 1 || year > 9999)
279                                 throw new ArgumentOutOfRangeException ("year", year +
280                                         Locale.GetText (" is not in a range between 1 and 9999."));
281
282                         lock (m_CachedDaylightChanges) {
283                                 DaylightTime dlt = (DaylightTime) m_CachedDaylightChanges [year];
284                                 if (dlt == null) {
285                                         Int64[] data;
286                                         string[] names;
287
288                                         if (!GetTimeZoneData (year, out data, out names))
289                                                 throw new ArgumentException (Locale.GetText ("Can't get timezone data for " + year));
290
291                                         dlt = GetDaylightTimeFromData (data);
292                                         m_CachedDaylightChanges.Add (year, dlt);
293                                 }
294                                 return dlt;
295                         }
296                 }
297
298                 public override TimeSpan GetUtcOffset (DateTime time)
299                 {
300                         if (IsDaylightSavingTime (time))
301                                 return utcOffsetWithDLS;
302
303                         return utcOffsetWithOutDLS;
304                 }
305
306                 void IDeserializationCallback.OnDeserialization (object sender)
307                 {
308                         OnDeserialization (null);
309                 }
310
311                 private void OnDeserialization (DaylightTime dlt)
312                 {
313                         if (dlt == null) {
314                                 Int64[] data;
315                                 string[] names;
316
317                                 int year = DateTime.Now.Year;
318                                 if (!GetTimeZoneData (year, out data, out names))
319                                         throw new ArgumentException (Locale.GetText ("Can't get timezone data for " + year));
320                                 dlt = GetDaylightTimeFromData (data);
321                         }
322                         utcOffsetWithOutDLS = new TimeSpan (m_ticksOffset);
323                         utcOffsetWithDLS = new TimeSpan (m_ticksOffset + dlt.Delta.Ticks);
324                 }
325
326                 private DaylightTime GetDaylightTimeFromData (long[] data)
327                 {
328                         return new DaylightTime (new DateTime (data[(int)TimeZoneData.DaylightSavingStartIdx]),
329                                 new DateTime (data[(int)TimeZoneData.DaylightSavingEndIdx]),
330                                 new TimeSpan (data[(int)TimeZoneData.AdditionalDaylightOffsetIdx]));
331                 }
332         }
333 }