5 * Stephane Delcroix <stephane@delcroix.org>
7 * Permission is hereby granted, free of charge, to any person obtaining
8 * a copy of this software and associated documentation files (the
9 * "Software"), to deal in the Software without restriction, including
10 * without limitation the rights to use, copy, modify, merge, publish,
11 * distribute, sublicense, and/or sell copies of the Software, and to
12 * permit persons to whom the Software is furnished to do so, subject to
13 * the following conditions:
15 * The above copyright notice and this permission notice shall be
16 * included in all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 using System
.Runtime
.CompilerServices
;
30 #if !INSIDE_CORLIB && (NET_4_0 || BOOTSTRAP_NET_4_0 || MOONLIGHT)
32 [assembly
:TypeForwardedTo (typeof(TimeZoneInfo
))]
34 #elif NET_3_5 || (MONOTOUCH && !INSIDE_CORLIB) || (MOONLIGHT && INSIDE_CORLIB)
36 using System
.Collections
.Generic
;
37 using System
.Collections
.ObjectModel
;
38 using System
.Runtime
.Serialization
;
46 using Microsoft
.Win32
;
50 #if NET_4_0 || BOOTSTRAP_NET_4_0
51 [TypeForwardedFrom (Consts
.AssemblySystemCore_3_5
)]
53 [TypeForwardedFrom (Consts
.AssemblySystem_Core
)]
55 [SerializableAttribute
]
56 public sealed partial class TimeZoneInfo
: IEquatable
<TimeZoneInfo
>, ISerializable
, IDeserializationCallback
58 TimeSpan baseUtcOffset
;
59 public TimeSpan BaseUtcOffset
{
60 get { return baseUtcOffset; }
63 string daylightDisplayName
;
64 public string DaylightName
{
66 if (disableDaylightSavingTime
)
68 return daylightDisplayName
;
73 public string DisplayName
{
74 get { return displayName; }
82 static TimeZoneInfo local
;
83 public static TimeZoneInfo Local
{
88 local
= FindSystemTimeZoneByFileName ("Local", "/etc/localtime");
91 local
= FindSystemTimeZoneByFileName ("Local", Path
.Combine (TimeZoneDirectory
, "localtime"));
93 throw new TimeZoneNotFoundException ();
97 throw new TimeZoneNotFoundException ();
104 string standardDisplayName
;
105 public string StandardName
{
106 get { return standardDisplayName; }
109 bool disableDaylightSavingTime
;
110 public bool SupportsDaylightSavingTime
{
111 get { return !disableDaylightSavingTime; }
114 static TimeZoneInfo utc
;
115 public static TimeZoneInfo Utc
{
118 utc
= CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
123 static string timeZoneDirectory
= null;
124 static string TimeZoneDirectory
{
126 if (timeZoneDirectory
== null)
127 timeZoneDirectory
= "/usr/share/zoneinfo";
128 return timeZoneDirectory
;
132 timeZoneDirectory
= value;
136 private AdjustmentRule
[] adjustmentRules
;
139 static RegistryKey timeZoneKey
= null;
140 static bool timeZoneKeySet
= false;
141 static RegistryKey TimeZoneKey
{
143 if (!timeZoneKeySet
) {
144 int p
= (int) Environment
.OSVersion
.Platform
;
145 /* Only use the registry on non-Unix platforms. */
146 if ((p
!= 4) && (p
!= 6) && (p
!= 128))
147 timeZoneKey
= Registry
.LocalMachine
.OpenSubKey (
148 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
150 timeZoneKeySet
= true;
157 public static void ClearCachedData ()
161 systemTimeZones
= null;
164 public static DateTime
ConvertTime (DateTime dateTime
, TimeZoneInfo destinationTimeZone
)
166 return ConvertTime (dateTime
, TimeZoneInfo
.Local
, destinationTimeZone
);
169 public static DateTime
ConvertTime (DateTime dateTime
, TimeZoneInfo sourceTimeZone
, TimeZoneInfo destinationTimeZone
)
171 if (dateTime
.Kind
== DateTimeKind
.Local
&& sourceTimeZone
!= TimeZoneInfo
.Local
)
172 throw new ArgumentException ("Kind propery of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
174 if (dateTime
.Kind
== DateTimeKind
.Utc
&& sourceTimeZone
!= TimeZoneInfo
.Utc
)
175 throw new ArgumentException ("Kind propery of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
177 if (sourceTimeZone
.IsInvalidTime (dateTime
))
178 throw new ArgumentException ("dateTime parameter is an invalid time");
180 if (sourceTimeZone
== null)
181 throw new ArgumentNullException ("sourceTimeZone");
183 if (destinationTimeZone
== null)
184 throw new ArgumentNullException ("destinationTimeZone");
186 if (dateTime
.Kind
== DateTimeKind
.Local
&& sourceTimeZone
== TimeZoneInfo
.Local
&& destinationTimeZone
== TimeZoneInfo
.Local
)
189 DateTime utc
= ConvertTimeToUtc (dateTime
);
191 if (destinationTimeZone
== TimeZoneInfo
.Utc
)
194 return ConvertTimeFromUtc (utc
, destinationTimeZone
);
198 public static DateTimeOffset
ConvertTime (DateTimeOffset dateTimeOffset
, TimeZoneInfo destinationTimeZone
)
200 throw new NotImplementedException ();
203 public static DateTime
ConvertTimeBySystemTimeZoneId (DateTime dateTime
, string destinationTimeZoneId
)
205 return ConvertTime (dateTime
, FindSystemTimeZoneById (destinationTimeZoneId
));
208 public static DateTime
ConvertTimeBySystemTimeZoneId (DateTime dateTime
, string sourceTimeZoneId
, string destinationTimeZoneId
)
210 return ConvertTime (dateTime
, FindSystemTimeZoneById (sourceTimeZoneId
), FindSystemTimeZoneById (destinationTimeZoneId
));
213 public static DateTimeOffset
ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset
, string destinationTimeZoneId
)
215 return ConvertTime (dateTimeOffset
, FindSystemTimeZoneById (destinationTimeZoneId
));
218 private DateTime
ConvertTimeFromUtc (DateTime dateTime
)
220 if (dateTime
.Kind
== DateTimeKind
.Local
)
221 throw new ArgumentException ("Kind property of dateTime is Local");
223 if (this == TimeZoneInfo
.Utc
)
224 return DateTime
.SpecifyKind (dateTime
, DateTimeKind
.Utc
);
226 //FIXME: do not rely on DateTime implementation !
227 if (this == TimeZoneInfo
.Local
)
228 return DateTime
.SpecifyKind (dateTime
.ToLocalTime (), DateTimeKind
.Unspecified
);
230 AdjustmentRule rule
= GetApplicableRule (dateTime
);
232 if (IsDaylightSavingTime (DateTime
.SpecifyKind (dateTime
, DateTimeKind
.Utc
)))
233 return DateTime
.SpecifyKind (dateTime
+ BaseUtcOffset
+ rule
.DaylightDelta
, DateTimeKind
.Unspecified
);
235 return DateTime
.SpecifyKind (dateTime
+ BaseUtcOffset
, DateTimeKind
.Unspecified
);
238 public static DateTime
ConvertTimeFromUtc (DateTime dateTime
, TimeZoneInfo destinationTimeZone
)
240 if (destinationTimeZone
== null)
241 throw new ArgumentNullException ("destinationTimeZone");
243 return destinationTimeZone
.ConvertTimeFromUtc (dateTime
);
246 public static DateTime
ConvertTimeToUtc (DateTime dateTime
)
248 if (dateTime
.Kind
== DateTimeKind
.Utc
)
251 //FIXME: do not rely on DateTime implementation !
252 return DateTime
.SpecifyKind (dateTime
.ToUniversalTime (), DateTimeKind
.Utc
);
255 public static DateTime
ConvertTimeToUtc (DateTime dateTime
, TimeZoneInfo sourceTimeZone
)
257 if (sourceTimeZone
== null)
258 throw new ArgumentNullException ("sourceTimeZone");
260 if (dateTime
.Kind
== DateTimeKind
.Utc
&& sourceTimeZone
!= TimeZoneInfo
.Utc
)
261 throw new ArgumentException ("Kind propery of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
263 if (dateTime
.Kind
== DateTimeKind
.Local
&& sourceTimeZone
!= TimeZoneInfo
.Local
)
264 throw new ArgumentException ("Kind propery of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
266 if (sourceTimeZone
.IsInvalidTime (dateTime
))
267 throw new ArgumentException ("dateTime parameter is an invalid time");
269 if (dateTime
.Kind
== DateTimeKind
.Utc
&& sourceTimeZone
== TimeZoneInfo
.Utc
)
272 if (dateTime
.Kind
== DateTimeKind
.Utc
)
275 if (dateTime
.Kind
== DateTimeKind
.Local
)
276 return ConvertTimeToUtc (dateTime
);
278 if (sourceTimeZone
.IsAmbiguousTime (dateTime
) || !sourceTimeZone
.IsDaylightSavingTime (dateTime
))
279 return DateTime
.SpecifyKind (dateTime
- sourceTimeZone
.BaseUtcOffset
, DateTimeKind
.Utc
);
281 AdjustmentRule rule
= sourceTimeZone
.GetApplicableRule (dateTime
);
282 return DateTime
.SpecifyKind (dateTime
- sourceTimeZone
.BaseUtcOffset
- rule
.DaylightDelta
, DateTimeKind
.Utc
);
286 public static TimeZoneInfo
CreateCustomTimeZone (string id
, TimeSpan baseUtcOffset
, string displayName
, string standardDisplayName
)
288 return CreateCustomTimeZone (id
, baseUtcOffset
, displayName
, standardDisplayName
, null, null, true);
291 public static TimeZoneInfo
CreateCustomTimeZone (string id
, TimeSpan baseUtcOffset
, string displayName
, string standardDisplayName
, string daylightDisplayName
, TimeZoneInfo
.AdjustmentRule
[] adjustmentRules
)
293 return CreateCustomTimeZone (id
, baseUtcOffset
, displayName
, standardDisplayName
, daylightDisplayName
, adjustmentRules
, false);
296 public static TimeZoneInfo
CreateCustomTimeZone ( string id
, TimeSpan baseUtcOffset
, string displayName
, string standardDisplayName
, string daylightDisplayName
, TimeZoneInfo
.AdjustmentRule
[] adjustmentRules
, bool disableDaylightSavingTime
)
298 return new TimeZoneInfo (id
, baseUtcOffset
, displayName
, standardDisplayName
, daylightDisplayName
, adjustmentRules
, disableDaylightSavingTime
);
301 public bool Equals (TimeZoneInfo other
)
306 return other
.Id
== this.Id
&& HasSameRules (other
);
309 public static TimeZoneInfo
FindSystemTimeZoneById (string id
)
311 //FIXME: this method should check for cached values in systemTimeZones
313 throw new ArgumentNullException ("id");
315 if (TimeZoneKey
!= null)
317 RegistryKey key
= TimeZoneKey
.OpenSubKey (id
, false);
319 throw new TimeZoneNotFoundException ();
320 return FromRegistryKey(id
, key
);
324 string filepath
= Path
.Combine (TimeZoneDirectory
, id
);
325 return FindSystemTimeZoneByFileName (id
, filepath
);
327 throw new NotImplementedException ();
332 const int BUFFER_SIZE
= 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
333 private static TimeZoneInfo
FindSystemTimeZoneByFileName (string id
, string filepath
)
335 if (!File
.Exists (filepath
))
336 throw new TimeZoneNotFoundException ();
338 byte [] buffer
= new byte [BUFFER_SIZE
];
340 using (FileStream stream
= File
.OpenRead (filepath
)) {
341 length
= stream
.Read (buffer
, 0, BUFFER_SIZE
);
344 if (!ValidTZFile (buffer
, length
))
345 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
348 return ParseTZBuffer (id
, buffer
, length
);
349 } catch (Exception e
) {
350 throw new InvalidTimeZoneException (e
.Message
);
356 private static TimeZoneInfo
FromRegistryKey (string id
, RegistryKey key
)
358 byte [] reg_tzi
= (byte []) key
.GetValue ("TZI");
361 throw new InvalidTimeZoneException ();
363 int bias
= BitConverter
.ToInt32 (reg_tzi
, 0);
364 TimeSpan baseUtcOffset
= new TimeSpan (0, -bias
, 0);
366 string display_name
= (string) key
.GetValue ("Display");
367 string standard_name
= (string) key
.GetValue ("Std");
368 string daylight_name
= (string) key
.GetValue ("Dlt");
370 List
<AdjustmentRule
> adjustmentRules
= new List
<AdjustmentRule
> ();
372 RegistryKey dst_key
= key
.OpenSubKey ("Dynamic DST", false);
373 if (dst_key
!= null) {
374 int first_year
= (int) dst_key
.GetValue ("FirstEntry");
375 int last_year
= (int) dst_key
.GetValue ("LastEntry");
378 for (year
=first_year
; year
<=last_year
; year
++) {
379 byte [] dst_tzi
= (byte []) dst_key
.GetValue (year
.ToString ());
380 if (dst_tzi
!= null) {
381 int start_year
= year
== first_year
? 1 : year
;
382 int end_year
= year
== last_year
? 9999 : year
;
383 ParseRegTzi(adjustmentRules
, start_year
, end_year
, dst_tzi
);
388 ParseRegTzi(adjustmentRules
, 1, 9999, reg_tzi
);
390 return CreateCustomTimeZone (id
, baseUtcOffset
, display_name
, standard_name
, daylight_name
, ValidateRules (adjustmentRules
).ToArray ());
393 private static void ParseRegTzi (List
<AdjustmentRule
> adjustmentRules
, int start_year
, int end_year
, byte [] buffer
)
395 //int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
396 int daylight_bias
= BitConverter
.ToInt32 (buffer
, 8);
398 int standard_year
= BitConverter
.ToInt16 (buffer
, 12);
399 int standard_month
= BitConverter
.ToInt16 (buffer
, 14);
400 int standard_dayofweek
= BitConverter
.ToInt16 (buffer
, 16);
401 int standard_day
= BitConverter
.ToInt16 (buffer
, 18);
402 int standard_hour
= BitConverter
.ToInt16 (buffer
, 20);
403 int standard_minute
= BitConverter
.ToInt16 (buffer
, 22);
404 int standard_second
= BitConverter
.ToInt16 (buffer
, 24);
405 int standard_millisecond
= BitConverter
.ToInt16 (buffer
, 26);
407 int daylight_year
= BitConverter
.ToInt16 (buffer
, 28);
408 int daylight_month
= BitConverter
.ToInt16 (buffer
, 30);
409 int daylight_dayofweek
= BitConverter
.ToInt16 (buffer
, 32);
410 int daylight_day
= BitConverter
.ToInt16 (buffer
, 34);
411 int daylight_hour
= BitConverter
.ToInt16 (buffer
, 36);
412 int daylight_minute
= BitConverter
.ToInt16 (buffer
, 38);
413 int daylight_second
= BitConverter
.ToInt16 (buffer
, 40);
414 int daylight_millisecond
= BitConverter
.ToInt16 (buffer
, 42);
416 if (standard_month
== 0 || daylight_month
== 0)
420 DateTime start_timeofday
= new DateTime (1, 1, 1, daylight_hour
, daylight_minute
, daylight_second
, daylight_millisecond
);
421 TransitionTime start_transition_time
;
423 if (daylight_year
== 0) {
424 start_date
= new DateTime (start_year
, 1, 1);
425 start_transition_time
= TransitionTime
.CreateFloatingDateRule (
426 start_timeofday
, daylight_month
, daylight_day
,
427 (DayOfWeek
) daylight_dayofweek
);
430 start_date
= new DateTime (daylight_year
, daylight_month
, daylight_day
,
431 daylight_hour
, daylight_minute
, daylight_second
, daylight_millisecond
);
432 start_transition_time
= TransitionTime
.CreateFixedDateRule (
433 start_timeofday
, daylight_month
, daylight_day
);
437 DateTime end_timeofday
= new DateTime (1, 1, 1, standard_hour
, standard_minute
, standard_second
, standard_millisecond
);
438 TransitionTime end_transition_time
;
440 if (standard_year
== 0) {
441 end_date
= new DateTime (end_year
, 12, 31);
442 end_transition_time
= TransitionTime
.CreateFloatingDateRule (
443 end_timeofday
, standard_month
, standard_day
,
444 (DayOfWeek
) standard_dayofweek
);
447 end_date
= new DateTime (standard_year
, standard_month
, standard_day
,
448 standard_hour
, standard_minute
, standard_second
, standard_millisecond
);
449 end_transition_time
= TransitionTime
.CreateFixedDateRule (
450 end_timeofday
, standard_month
, standard_day
);
453 TimeSpan daylight_delta
= new TimeSpan(0, -daylight_bias
, 0);
455 adjustmentRules
.Add (AdjustmentRule
.CreateAdjustmentRule (
456 start_date
, end_date
, daylight_delta
,
457 start_transition_time
, end_transition_time
));
461 public static TimeZoneInfo
FromSerializedString (string source
)
463 throw new NotImplementedException ();
466 public AdjustmentRule
[] GetAdjustmentRules ()
468 if (disableDaylightSavingTime
)
469 return new AdjustmentRule
[0];
471 return (AdjustmentRule
[]) adjustmentRules
.Clone ();
474 public TimeSpan
[] GetAmbiguousTimeOffsets (DateTime dateTime
)
476 if (!IsAmbiguousTime (dateTime
))
477 throw new ArgumentException ("dateTime is not an ambiguous time");
479 AdjustmentRule rule
= GetApplicableRule (dateTime
);
480 return new TimeSpan
[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta}
;
483 public TimeSpan
[] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset
)
485 if (!IsAmbiguousTime (dateTimeOffset
))
486 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
488 throw new NotImplementedException ();
491 public override int GetHashCode ()
493 int hash_code
= Id
.GetHashCode ();
494 foreach (AdjustmentRule rule
in GetAdjustmentRules ())
495 hash_code ^
= rule
.GetHashCode ();
500 void ISerializable
.GetObjectData (SerializationInfo info
, StreamingContext context
)
502 public void GetObjectData (SerializationInfo info
, StreamingContext context
)
505 throw new NotImplementedException ();
508 //FIXME: change this to a generic Dictionary and allow caching for FindSystemTimeZoneById
509 private static List
<TimeZoneInfo
> systemTimeZones
= null;
510 public static ReadOnlyCollection
<TimeZoneInfo
> GetSystemTimeZones ()
512 if (systemTimeZones
== null) {
513 systemTimeZones
= new List
<TimeZoneInfo
> ();
515 if (TimeZoneKey
!= null) {
516 foreach (string id
in TimeZoneKey
.GetSubKeyNames ()) {
518 systemTimeZones
.Add (FindSystemTimeZoneById (id
));
522 return new ReadOnlyCollection
<TimeZoneInfo
> (systemTimeZones
);
526 string[] continents
= new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"}
;
527 foreach (string continent
in continents
) {
529 foreach (string zonepath
in Directory
.GetFiles (Path
.Combine (TimeZoneDirectory
, continent
))) {
531 string id
= String
.Format ("{0}/{1}", continent
, Path
.GetFileName (zonepath
));
532 systemTimeZones
.Add (FindSystemTimeZoneById (id
));
533 } catch (ArgumentNullException
) {
534 } catch (TimeZoneNotFoundException
) {
535 } catch (InvalidTimeZoneException
) {
536 } catch (Exception
) {
543 throw new NotImplementedException ("This method is not implemented for this platform");
546 return new ReadOnlyCollection
<TimeZoneInfo
> (systemTimeZones
);
549 public TimeSpan
GetUtcOffset (DateTime dateTime
)
551 if (IsDaylightSavingTime (dateTime
)) {
552 AdjustmentRule rule
= GetApplicableRule (dateTime
);
553 return BaseUtcOffset
+ rule
.DaylightDelta
;
556 return BaseUtcOffset
;
559 public TimeSpan
GetUtcOffset (DateTimeOffset dateTimeOffset
)
561 throw new NotImplementedException ();
564 public bool HasSameRules (TimeZoneInfo other
)
567 throw new ArgumentNullException ("other");
569 if ((this.adjustmentRules
== null) != (other
.adjustmentRules
== null))
572 if (this.adjustmentRules
== null)
575 if (this.BaseUtcOffset
!= other
.BaseUtcOffset
)
578 if (this.adjustmentRules
.Length
!= other
.adjustmentRules
.Length
)
581 for (int i
= 0; i
< adjustmentRules
.Length
; i
++) {
582 if (! (this.adjustmentRules
[i
]).Equals (other
.adjustmentRules
[i
]))
589 public bool IsAmbiguousTime (DateTime dateTime
)
591 if (dateTime
.Kind
== DateTimeKind
.Local
&& IsInvalidTime (dateTime
))
592 throw new ArgumentException ("Kind is Local and time is Invalid");
594 if (this == TimeZoneInfo
.Utc
)
597 if (dateTime
.Kind
== DateTimeKind
.Utc
)
598 dateTime
= ConvertTimeFromUtc (dateTime
);
600 if (dateTime
.Kind
== DateTimeKind
.Local
&& this != TimeZoneInfo
.Local
)
601 dateTime
= ConvertTime (dateTime
, TimeZoneInfo
.Local
, this);
603 AdjustmentRule rule
= GetApplicableRule (dateTime
);
604 DateTime tpoint
= TransitionPoint (rule
.DaylightTransitionEnd
, dateTime
.Year
);
605 if (dateTime
> tpoint
- rule
.DaylightDelta
&& dateTime
<= tpoint
)
611 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset
)
613 throw new NotImplementedException ();
616 public bool IsDaylightSavingTime (DateTime dateTime
)
618 if (dateTime
.Kind
== DateTimeKind
.Local
&& IsInvalidTime (dateTime
))
619 throw new ArgumentException ("dateTime is invalid and Kind is Local");
621 if (this == TimeZoneInfo
.Utc
)
624 if (!SupportsDaylightSavingTime
)
626 //FIXME: do not rely on DateTime implementation !
627 if ((dateTime
.Kind
== DateTimeKind
.Local
|| dateTime
.Kind
== DateTimeKind
.Unspecified
) && this == TimeZoneInfo
.Local
)
628 return dateTime
.IsDaylightSavingTime ();
630 //FIXME: do not rely on DateTime implementation !
631 if (dateTime
.Kind
== DateTimeKind
.Local
&& this != TimeZoneInfo
.Utc
)
632 return IsDaylightSavingTime (DateTime
.SpecifyKind (dateTime
.ToUniversalTime (), DateTimeKind
.Utc
));
634 AdjustmentRule rule
= GetApplicableRule (dateTime
.Date
);
638 DateTime DST_start
= TransitionPoint (rule
.DaylightTransitionStart
, dateTime
.Year
);
639 DateTime DST_end
= TransitionPoint (rule
.DaylightTransitionEnd
, dateTime
.Year
+ ((rule
.DaylightTransitionStart
.Month
< rule
.DaylightTransitionEnd
.Month
) ? 0 : 1));
640 if (dateTime
.Kind
== DateTimeKind
.Utc
) {
641 DST_start
-= BaseUtcOffset
;
642 DST_end
-= (BaseUtcOffset
+ rule
.DaylightDelta
);
645 return (dateTime
>= DST_start
&& dateTime
< DST_end
);
648 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset
)
650 throw new NotImplementedException ();
653 public bool IsInvalidTime (DateTime dateTime
)
655 if (dateTime
.Kind
== DateTimeKind
.Utc
)
657 if (dateTime
.Kind
== DateTimeKind
.Local
&& this != Local
)
660 AdjustmentRule rule
= GetApplicableRule (dateTime
);
661 DateTime tpoint
= TransitionPoint (rule
.DaylightTransitionStart
, dateTime
.Year
);
662 if (dateTime
>= tpoint
&& dateTime
< tpoint
+ rule
.DaylightDelta
)
669 void IDeserializationCallback
.OnDeserialization (object sender
)
671 public void OnDeserialization (object sender
)
674 throw new NotImplementedException ();
677 public string ToSerializedString ()
679 throw new NotImplementedException ();
682 public override string ToString ()
687 private TimeZoneInfo (string id
, TimeSpan baseUtcOffset
, string displayName
, string standardDisplayName
, string daylightDisplayName
, TimeZoneInfo
.AdjustmentRule
[] adjustmentRules
, bool disableDaylightSavingTime
)
690 throw new ArgumentNullException ("id");
692 if (id
== String
.Empty
)
693 throw new ArgumentException ("id parameter is an empty string");
695 if (baseUtcOffset
.Ticks
% TimeSpan
.TicksPerMinute
!= 0)
696 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
698 if (baseUtcOffset
> new TimeSpan (14, 0, 0) || baseUtcOffset
< new TimeSpan (-14, 0, 0))
699 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
703 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
706 if (adjustmentRules
!= null && adjustmentRules
.Length
!= 0) {
707 AdjustmentRule prev
= null;
708 foreach (AdjustmentRule current
in adjustmentRules
) {
710 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
712 if ((baseUtcOffset
+ current
.DaylightDelta
< new TimeSpan (-14, 0, 0)) ||
713 (baseUtcOffset
+ current
.DaylightDelta
> new TimeSpan (14, 0, 0)))
714 throw new InvalidTimeZoneException ("Sum of baseUtcOffset and DaylightDelta of one or more object in adjustmentRules array is greater than 14 or less than -14 hours;");
716 if (prev
!= null && prev
.DateStart
> current
.DateStart
)
717 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
719 if (prev
!= null && prev
.DateEnd
> current
.DateStart
)
720 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
722 if (prev
!= null && prev
.DateEnd
== current
.DateStart
)
723 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
730 this.baseUtcOffset
= baseUtcOffset
;
731 this.displayName
= displayName
?? id
;
732 this.standardDisplayName
= standardDisplayName
?? id
;
733 this.daylightDisplayName
= daylightDisplayName
;
734 this.disableDaylightSavingTime
= disableDaylightSavingTime
;
735 this.adjustmentRules
= adjustmentRules
;
738 private AdjustmentRule
GetApplicableRule (DateTime dateTime
)
740 //Transitions are always in standard time
741 DateTime date
= dateTime
;
743 if (dateTime
.Kind
== DateTimeKind
.Local
&& this != TimeZoneInfo
.Local
)
744 date
= date
.ToUniversalTime () + BaseUtcOffset
;
746 if (dateTime
.Kind
== DateTimeKind
.Utc
&& this != TimeZoneInfo
.Utc
)
747 date
= date
+ BaseUtcOffset
;
749 foreach (AdjustmentRule rule
in adjustmentRules
) {
750 if (rule
.DateStart
> date
.Date
)
752 if (rule
.DateEnd
< date
.Date
)
759 private static DateTime
TransitionPoint (TransitionTime transition
, int year
)
761 if (transition
.IsFixedDateRule
)
762 return new DateTime (year
, transition
.Month
, transition
.Day
) + transition
.TimeOfDay
.TimeOfDay
;
764 DayOfWeek first
= (new DateTime (year
, transition
.Month
, 1)).DayOfWeek
;
765 int day
= 1 + (transition
.Week
- 1) * 7 + (transition
.DayOfWeek
- first
) % 7;
766 if (day
> DateTime
.DaysInMonth (year
, transition
.Month
))
768 return new DateTime (year
, transition
.Month
, day
) + transition
.TimeOfDay
.TimeOfDay
;
772 private static bool ValidTZFile (byte [] buffer
, int length
)
774 StringBuilder magic
= new StringBuilder ();
776 for (int i
= 0; i
< 4; i
++)
777 magic
.Append ((char)buffer
[i
]);
779 if (magic
.ToString () != "TZif")
782 if (length
>= BUFFER_SIZE
)
790 public readonly int Offset
;
791 public readonly bool IsDst
;
794 public TimeType (int offset
, bool is_dst
, string abbrev
)
796 this.Offset
= offset
;
801 public override string ToString ()
803 return "offset: " + Offset
+ "s, is_dst: " + IsDst
+ ", zone name: " + Name
;
807 static int SwapInt32 (int i
)
809 return (((i
>> 24) & 0xff)
810 | ((i
>> 8) & 0xff00)
811 | ((i
<< 8) & 0xff0000)
815 static int ReadBigEndianInt32 (byte [] buffer
, int start
)
817 int i
= BitConverter
.ToInt32 (buffer
, start
);
818 if (!BitConverter
.IsLittleEndian
)
821 return SwapInt32 (i
);
824 private static TimeZoneInfo
ParseTZBuffer (string id
, byte [] buffer
, int length
)
826 //Reading the header. 4 bytes for magic, 16 are reserved
827 int ttisgmtcnt
= ReadBigEndianInt32 (buffer
, 20);
828 int ttisstdcnt
= ReadBigEndianInt32 (buffer
, 24);
829 int leapcnt
= ReadBigEndianInt32 (buffer
, 28);
830 int timecnt
= ReadBigEndianInt32 (buffer
, 32);
831 int typecnt
= ReadBigEndianInt32 (buffer
, 36);
832 int charcnt
= ReadBigEndianInt32 (buffer
, 40);
834 if (length
< 44 + timecnt
* 5 + typecnt
* 6 + charcnt
+ leapcnt
* 8 + ttisstdcnt
+ ttisgmtcnt
)
835 throw new InvalidTimeZoneException ();
837 Dictionary
<int, string> abbreviations
= ParseAbbreviations (buffer
, 44 + 4 * timecnt
+ timecnt
+ 6 * typecnt
, charcnt
);
838 Dictionary
<int, TimeType
> time_types
= ParseTimesTypes (buffer
, 44 + 4 * timecnt
+ timecnt
, typecnt
, abbreviations
);
839 List
<KeyValuePair
<DateTime
, TimeType
>> transitions
= ParseTransitions (buffer
, 44, timecnt
, time_types
);
841 if (time_types
.Count
== 0)
842 throw new InvalidTimeZoneException ();
844 if (time_types
.Count
== 1 && ((TimeType
)time_types
[0]).IsDst
)
845 throw new InvalidTimeZoneException ();
847 TimeSpan baseUtcOffset
= new TimeSpan (0);
848 TimeSpan dstDelta
= new TimeSpan (0);
849 string standardDisplayName
= null;
850 string daylightDisplayName
= null;
851 bool dst_observed
= false;
852 DateTime dst_start
= DateTime
.MinValue
;
853 List
<AdjustmentRule
> adjustmentRules
= new List
<AdjustmentRule
> ();
855 for (int i
= 0; i
< transitions
.Count
; i
++) {
856 var pair
= transitions
[i
];
857 DateTime ttime
= pair
.Key
;
858 TimeType ttype
= pair
.Value
;
860 if (standardDisplayName
!= ttype
.Name
|| baseUtcOffset
.TotalSeconds
!= ttype
.Offset
) {
861 standardDisplayName
= ttype
.Name
;
862 daylightDisplayName
= null;
863 baseUtcOffset
= new TimeSpan (0, 0, ttype
.Offset
);
864 adjustmentRules
= new List
<AdjustmentRule
> ();
865 dst_observed
= false;
868 //FIXME: check additional fields for this:
869 //most of the transitions are expressed in GMT
870 dst_start
+= baseUtcOffset
;
871 DateTime dst_end
= ttime
+ baseUtcOffset
+ dstDelta
;
873 //some weird timezone (America/Phoenix) have end dates on Jan 1st
874 if (dst_end
.Date
== new DateTime (dst_end
.Year
, 1, 1) && dst_end
.Year
> dst_start
.Year
)
875 dst_end
-= new TimeSpan (24, 0, 0);
877 DateTime dateStart
, dateEnd
;
878 if (dst_start
.Month
< 7)
879 dateStart
= new DateTime (dst_start
.Year
, 1, 1);
881 dateStart
= new DateTime (dst_start
.Year
, 7, 1);
883 if (dst_end
.Month
>= 7)
884 dateEnd
= new DateTime (dst_end
.Year
, 12, 31);
886 dateEnd
= new DateTime (dst_end
.Year
, 6, 30);
889 TransitionTime transition_start
= TransitionTime
.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start
.TimeOfDay
, dst_start
.Month
, dst_start
.Day
);
890 TransitionTime transition_end
= TransitionTime
.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end
.TimeOfDay
, dst_end
.Month
, dst_end
.Day
);
891 if (transition_start
!= transition_end
) //y, that happened in Argentina in 1943-1946
892 adjustmentRules
.Add (AdjustmentRule
.CreateAdjustmentRule (dateStart
, dateEnd
, dstDelta
, transition_start
, transition_end
));
894 dst_observed
= false;
896 if (daylightDisplayName
!= ttype
.Name
|| dstDelta
.TotalSeconds
!= ttype
.Offset
- baseUtcOffset
.TotalSeconds
) {
897 daylightDisplayName
= ttype
.Name
;
898 dstDelta
= new TimeSpan(0, 0, ttype
.Offset
) - baseUtcOffset
;
905 if (adjustmentRules
.Count
== 0) {
906 TimeType t
= (TimeType
)time_types
[0];
907 if (standardDisplayName
== null) {
908 standardDisplayName
= t
.Name
;
909 baseUtcOffset
= new TimeSpan (0, 0, t
.Offset
);
911 return CreateCustomTimeZone (id
, baseUtcOffset
, id
, standardDisplayName
);
913 return CreateCustomTimeZone (id
, baseUtcOffset
, id
, standardDisplayName
, daylightDisplayName
, ValidateRules (adjustmentRules
).ToArray ());
917 static List
<AdjustmentRule
> ValidateRules (List
<AdjustmentRule
> adjustmentRules
)
919 AdjustmentRule prev
= null;
920 foreach (AdjustmentRule current
in adjustmentRules
.ToArray ()) {
921 if (prev
!= null && prev
.DateEnd
> current
.DateStart
) {
922 adjustmentRules
.Remove (current
);
926 return adjustmentRules
;
929 static Dictionary
<int, string> ParseAbbreviations (byte [] buffer
, int index
, int count
)
931 var abbrevs
= new Dictionary
<int, string> ();
932 int abbrev_index
= 0;
933 var sb
= new StringBuilder ();
934 for (int i
= 0; i
< count
; i
++) {
935 char c
= (char) buffer
[index
+ i
];
939 abbrevs
.Add (abbrev_index
, sb
.ToString ());
940 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
941 for (int j
= 1; j
< sb
.Length
; j
++)
942 abbrevs
.Add (abbrev_index
+ j
, sb
.ToString (j
, sb
.Length
- j
));
943 abbrev_index
= i
+ 1;
944 sb
= new StringBuilder ();
950 static Dictionary
<int, TimeType
> ParseTimesTypes (byte [] buffer
, int index
, int count
, Dictionary
<int, string> abbreviations
)
952 var types
= new Dictionary
<int, TimeType
> (count
);
953 for (int i
= 0; i
< count
; i
++) {
954 int offset
= ReadBigEndianInt32 (buffer
, index
+ 6 * i
);
955 byte is_dst
= buffer
[index
+ 6 * i
+ 4];
956 byte abbrev
= buffer
[index
+ 6 * i
+ 5];
957 types
.Add (i
, new TimeType (offset
, (is_dst
!= 0), abbreviations
[(int)abbrev
]));
962 static List
<KeyValuePair
<DateTime
, TimeType
>> ParseTransitions (byte [] buffer
, int index
, int count
, Dictionary
<int, TimeType
> time_types
)
964 var list
= new List
<KeyValuePair
<DateTime
, TimeType
>> (count
);
965 for (int i
= 0; i
< count
; i
++) {
966 int unixtime
= ReadBigEndianInt32 (buffer
, index
+ 4 * i
);
967 DateTime ttime
= DateTimeFromUnixTime (unixtime
);
968 byte ttype
= buffer
[index
+ 4 * count
+ i
];
969 list
.Add (new KeyValuePair
<DateTime
, TimeType
> (ttime
, time_types
[(int)ttype
]));
974 static DateTime
DateTimeFromUnixTime (long unix_time
)
976 DateTime date_time
= new DateTime (1970, 1, 1);
977 return date_time
.AddSeconds (unix_time
);