6 * Stephane Delcroix <stephane@delcroix.org>
8 * Copyright 2011 Xamarin Inc.
10 * Permission is hereby granted, free of charge, to any person obtaining
11 * a copy of this software and associated documentation files (the
12 * "Software"), to deal in the Software without restriction, including
13 * without limitation the rights to use, copy, modify, merge, publish,
14 * distribute, sublicense, and/or sell copies of the Software, and to
15 * permit persons to whom the Software is furnished to do so, subject to
16 * the following conditions:
18 * The above copyright notice and this permission notice shall be
19 * included in all copies or substantial portions of the Software.
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System
.Runtime
.CompilerServices
;
32 using System
.Threading
;
33 using System
.Collections
.Generic
;
34 using System
.Collections
.ObjectModel
;
35 using System
.Runtime
.Serialization
;
36 using System
.Runtime
.InteropServices
;
38 using System
.Globalization
;
41 using Microsoft
.Win32
;
45 partial class TimeZoneInfo
47 TimeSpan baseUtcOffset
;
48 public TimeSpan BaseUtcOffset
{
49 get { return baseUtcOffset; }
52 string daylightDisplayName
;
53 public string DaylightName
{
55 return supportsDaylightSavingTime
62 public string DisplayName
{
63 get { return displayName; }
71 static TimeZoneInfo local
;
72 public static TimeZoneInfo Local
{
78 throw new TimeZoneNotFoundException ();
80 if (Interlocked
.CompareExchange (ref local
, l
, null) != null)
89 TimeZone transitions are stored when there is a change on the base offset.
91 private List
<KeyValuePair
<DateTime
, TimeType
>> transitions
;
93 private static bool readlinkNotFound
;
96 private static extern int readlink (string path
, byte[] buffer
, int buflen
);
98 private static string readlink (string path
)
100 if (readlinkNotFound
)
103 byte[] buf
= new byte [512];
107 ret
= readlink (path
, buf
, buf
.Length
);
108 } catch (DllNotFoundException
) {
109 readlinkNotFound
= true;
111 } catch (EntryPointNotFoundException
) {
112 readlinkNotFound
= true;
116 if (ret
== -1) return null;
117 char[] cbuf
= new char [512];
118 int chars
= System
.Text
.Encoding
.Default
.GetChars (buf
, 0, ret
, cbuf
, 0);
119 return new String (cbuf
, 0, chars
);
122 private static bool TryGetNameFromPath (string path
, out string name
)
125 var linkPath
= readlink (path
);
126 if (linkPath
!= null) {
127 if (Path
.IsPathRooted(linkPath
))
130 path
= Path
.Combine(Path
.GetDirectoryName(path
), linkPath
);
133 path
= Path
.GetFullPath (path
);
135 if (string.IsNullOrEmpty (TimeZoneDirectory
))
138 var baseDir
= TimeZoneDirectory
;
139 if (baseDir
[baseDir
.Length
-1] != Path
.DirectorySeparatorChar
)
140 baseDir
+= Path
.DirectorySeparatorChar
;
142 if (!path
.StartsWith (baseDir
, StringComparison
.InvariantCulture
))
145 name
= path
.Substring (baseDir
.Length
);
146 if (name
== "localtime")
152 #if (!MONODROID && !MONOTOUCH && !XAMMAC && !WASM) || MOBILE_DESKTOP_HOST
153 static TimeZoneInfo
CreateLocal ()
156 if (IsWindows
&& LocalZoneKey
!= null) {
157 string name
= (string)LocalZoneKey
.GetValue ("TimeZoneKeyName");
159 name
= (string)LocalZoneKey
.GetValue ("StandardName"); // windows xp
160 name
= TrimSpecial (name
);
162 return TimeZoneInfo
.FindSystemTimeZoneById (name
);
163 } else if (IsWindows
) {
164 return GetLocalTimeZoneInfoWinRTFallback ();
168 var tz
= Environment
.GetEnvironmentVariable ("TZ");
170 if (tz
== String
.Empty
)
173 return FindSystemTimeZoneByFileName (tz
, Path
.Combine (TimeZoneDirectory
, tz
));
179 var tzFilePaths
= new string [] {
181 Path
.Combine (TimeZoneDirectory
, "localtime")};
183 foreach (var tzFilePath
in tzFilePaths
) {
185 string tzName
= null;
186 if (!TryGetNameFromPath (tzFilePath
, out tzName
))
188 return FindSystemTimeZoneByFileName (tzName
, tzFilePath
);
189 } catch (TimeZoneNotFoundException
) {
197 static TimeZoneInfo
FindSystemTimeZoneByIdCore (string id
)
200 string filepath
= Path
.Combine (TimeZoneDirectory
, id
);
201 return FindSystemTimeZoneByFileName (id
, filepath
);
203 throw new NotImplementedException ();
207 static void GetSystemTimeZonesCore (List
<TimeZoneInfo
> systemTimeZones
)
210 if (TimeZoneKey
!= null) {
211 foreach (string id
in TimeZoneKey
.GetSubKeyNames ()) {
212 using (RegistryKey subkey
= TimeZoneKey
.OpenSubKey (id
))
214 if (subkey
== null || subkey
.GetValue ("TZI") == null)
217 systemTimeZones
.Add (FindSystemTimeZoneById (id
));
221 } else if (IsWindows
) {
222 systemTimeZones
.AddRange (GetSystemTimeZonesWinRTFallback ());
228 string[] continents
= new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Australia", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"}
;
229 foreach (string continent
in continents
) {
231 foreach (string zonepath
in Directory
.GetFiles (Path
.Combine (TimeZoneDirectory
, continent
))) {
233 string id
= String
.Format ("{0}/{1}", continent
, Path
.GetFileName (zonepath
));
234 systemTimeZones
.Add (FindSystemTimeZoneById (id
));
235 } catch (ArgumentNullException
) {
236 } catch (TimeZoneNotFoundException
) {
237 } catch (InvalidTimeZoneException
) {
238 } catch (Exception
) {
245 throw new NotImplementedException ("This method is not implemented for this platform");
248 #endif // !MONODROID && !MONOTOUCH && !XAMMAC && !WASM
250 string standardDisplayName
;
251 public string StandardName
{
252 get { return standardDisplayName; }
255 bool supportsDaylightSavingTime
;
256 public bool SupportsDaylightSavingTime
{
257 get { return supportsDaylightSavingTime; }
260 static TimeZoneInfo utc
;
261 public static TimeZoneInfo Utc
{
264 utc
= CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
269 const string DefaultTimeZoneDirectory
= "/usr/share/zoneinfo";
270 static string timeZoneDirectory
;
271 static string TimeZoneDirectory
{
273 if (timeZoneDirectory
== null)
274 timeZoneDirectory
= readlink (DefaultTimeZoneDirectory
) ?? DefaultTimeZoneDirectory
;
275 return timeZoneDirectory
;
279 timeZoneDirectory
= value;
283 private AdjustmentRule
[] adjustmentRules
;
285 #if (!MOBILE || !FULL_AOT_DESKTOP || WIN_PLATFORM) && !XAMMAC_4_5
287 /// Determine whether windows of not (taken Stephane Delcroix's code)
289 private static bool IsWindows
292 int platform
= (int) Environment
.OSVersion
.Platform
;
293 return ((platform
!= 4) && (platform
!= 6) && (platform
!= 128));
298 /// Needed to trim misc garbage in MS registry keys
300 private static string TrimSpecial (string str
)
305 while (Istart
< str
.Length
&& !char.IsLetterOrDigit(str
[Istart
])) Istart
++;
306 var Iend
= str
.Length
- 1;
307 while (Iend
> Istart
&& !char.IsLetterOrDigit(str
[Iend
]) && str
[Iend
] != ')') // zone name can include parentheses like "Central Standard Time (Mexico)"
310 return str
.Substring (Istart
, Iend
-Istart
+1);
313 #if !FULL_AOT_DESKTOP || WIN_PLATFORM
314 static RegistryKey timeZoneKey
;
315 static RegistryKey TimeZoneKey
{
317 if (timeZoneKey
!= null)
323 return timeZoneKey
= Registry
.LocalMachine
.OpenSubKey (
324 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
332 static RegistryKey localZoneKey
;
333 static RegistryKey LocalZoneKey
{
335 if (localZoneKey
!= null)
342 return localZoneKey
= Registry
.LocalMachine
.OpenSubKey (
343 "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false);
350 #endif // !MOBILE || !FULL_AOT_DESKTOP || WIN_PLATFORM
352 private static bool TryAddTicks (DateTime date
, long ticks
, out DateTime result
, DateTimeKind kind
= DateTimeKind
.Unspecified
)
354 var resultTicks
= date
.Ticks
+ ticks
;
355 if (resultTicks
< DateTime
.MinValue
.Ticks
) {
356 result
= DateTime
.SpecifyKind (DateTime
.MinValue
, kind
);
360 if (resultTicks
> DateTime
.MaxValue
.Ticks
) {
361 result
= DateTime
.SpecifyKind (DateTime
.MaxValue
, kind
);
365 result
= new DateTime (resultTicks
, kind
);
369 public static void ClearCachedData ()
373 systemTimeZones
= null;
376 public static DateTime
ConvertTime (DateTime dateTime
, TimeZoneInfo destinationTimeZone
)
378 return ConvertTime (dateTime
, dateTime
.Kind
== DateTimeKind
.Utc
? TimeZoneInfo
.Utc
: TimeZoneInfo
.Local
, destinationTimeZone
);
381 public static DateTime
ConvertTime (DateTime dateTime
, TimeZoneInfo sourceTimeZone
, TimeZoneInfo destinationTimeZone
)
383 if (sourceTimeZone
== null)
384 throw new ArgumentNullException ("sourceTimeZone");
386 if (destinationTimeZone
== null)
387 throw new ArgumentNullException ("destinationTimeZone");
389 if (dateTime
.Kind
== DateTimeKind
.Local
&& sourceTimeZone
!= TimeZoneInfo
.Local
)
390 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
392 if (dateTime
.Kind
== DateTimeKind
.Utc
&& sourceTimeZone
!= TimeZoneInfo
.Utc
)
393 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
395 if (sourceTimeZone
.IsInvalidTime (dateTime
))
396 throw new ArgumentException ("dateTime parameter is an invalid time");
398 if (dateTime
.Kind
== DateTimeKind
.Local
&& sourceTimeZone
== TimeZoneInfo
.Local
&& destinationTimeZone
== TimeZoneInfo
.Local
)
401 DateTime utc
= ConvertTimeToUtc (dateTime
, sourceTimeZone
);
403 if (destinationTimeZone
!= TimeZoneInfo
.Utc
) {
404 utc
= ConvertTimeFromUtc (utc
, destinationTimeZone
);
405 if (dateTime
.Kind
== DateTimeKind
.Unspecified
)
406 return DateTime
.SpecifyKind (utc
, DateTimeKind
.Unspecified
);
412 public static DateTimeOffset
ConvertTime(DateTimeOffset dateTimeOffset
, TimeZoneInfo destinationTimeZone
)
414 if (destinationTimeZone
== null)
415 throw new ArgumentNullException("destinationTimeZone");
417 var utcDateTime
= dateTimeOffset
.UtcDateTime
;
420 var utcOffset
= destinationTimeZone
.GetUtcOffset(utcDateTime
, out isDst
);
422 return new DateTimeOffset(DateTime
.SpecifyKind(utcDateTime
, DateTimeKind
.Unspecified
) + utcOffset
, utcOffset
);
425 public static DateTime
ConvertTimeBySystemTimeZoneId (DateTime dateTime
, string destinationTimeZoneId
)
427 return ConvertTime (dateTime
, FindSystemTimeZoneById (destinationTimeZoneId
));
430 public static DateTime
ConvertTimeBySystemTimeZoneId (DateTime dateTime
, string sourceTimeZoneId
, string destinationTimeZoneId
)
432 TimeZoneInfo source_tz
;
433 if (dateTime
.Kind
== DateTimeKind
.Utc
&& sourceTimeZoneId
== TimeZoneInfo
.Utc
.Id
) {
436 source_tz
= FindSystemTimeZoneById (sourceTimeZoneId
);
439 return ConvertTime (dateTime
, source_tz
, FindSystemTimeZoneById (destinationTimeZoneId
));
442 public static DateTimeOffset
ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset
, string destinationTimeZoneId
)
444 return ConvertTime (dateTimeOffset
, FindSystemTimeZoneById (destinationTimeZoneId
));
447 private DateTime
ConvertTimeFromUtc (DateTime dateTime
)
449 if (dateTime
.Kind
== DateTimeKind
.Local
)
450 throw new ArgumentException ("Kind property of dateTime is Local");
452 if (this == TimeZoneInfo
.Utc
)
453 return DateTime
.SpecifyKind (dateTime
, DateTimeKind
.Utc
);
455 var utcOffset
= GetUtcOffset (dateTime
);
457 var kind
= (this == TimeZoneInfo
.Local
)? DateTimeKind
.Local
: DateTimeKind
.Unspecified
;
460 if (!TryAddTicks (dateTime
, utcOffset
.Ticks
, out result
, kind
))
461 return DateTime
.SpecifyKind (DateTime
.MaxValue
, kind
);
466 public static DateTime
ConvertTimeFromUtc (DateTime dateTime
, TimeZoneInfo destinationTimeZone
)
468 if (destinationTimeZone
== null)
469 throw new ArgumentNullException ("destinationTimeZone");
471 return destinationTimeZone
.ConvertTimeFromUtc (dateTime
);
474 public static DateTime
ConvertTimeToUtc (DateTime dateTime
)
476 if (dateTime
.Kind
== DateTimeKind
.Utc
)
479 return ConvertTimeToUtc (dateTime
, TimeZoneInfo
.Local
);
482 static internal DateTime
ConvertTimeToUtc(DateTime dateTime
, TimeZoneInfoOptions flags
)
484 return ConvertTimeToUtc (dateTime
, TimeZoneInfo
.Local
, flags
);
487 public static DateTime
ConvertTimeToUtc (DateTime dateTime
, TimeZoneInfo sourceTimeZone
)
489 return ConvertTimeToUtc (dateTime
, sourceTimeZone
, TimeZoneInfoOptions
.None
);
492 static DateTime
ConvertTimeToUtc (DateTime dateTime
, TimeZoneInfo sourceTimeZone
, TimeZoneInfoOptions flags
)
494 if ((flags
& TimeZoneInfoOptions
.NoThrowOnInvalidTime
) == 0) {
495 if (sourceTimeZone
== null)
496 throw new ArgumentNullException ("sourceTimeZone");
498 if (dateTime
.Kind
== DateTimeKind
.Utc
&& sourceTimeZone
!= TimeZoneInfo
.Utc
)
499 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
501 if (dateTime
.Kind
== DateTimeKind
.Local
&& sourceTimeZone
!= TimeZoneInfo
.Local
)
502 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
504 if (sourceTimeZone
.IsInvalidTime (dateTime
))
505 throw new ArgumentException ("dateTime parameter is an invalid time");
508 if (dateTime
.Kind
== DateTimeKind
.Utc
)
512 var utcOffset
= sourceTimeZone
.GetUtcOffset (dateTime
, out isDst
);
514 DateTime utcDateTime
;
515 TryAddTicks (dateTime
, -utcOffset
.Ticks
, out utcDateTime
, DateTimeKind
.Utc
);
519 static internal TimeSpan
GetDateTimeNowUtcOffsetFromUtc(DateTime time
, out Boolean isAmbiguousLocalDst
)
521 bool isDaylightSavings
;
522 return GetUtcOffsetFromUtc(time
, TimeZoneInfo
.Local
, out isDaylightSavings
, out isAmbiguousLocalDst
);
525 public static TimeZoneInfo
CreateCustomTimeZone (string id
, TimeSpan baseUtcOffset
, string displayName
, string standardDisplayName
)
527 return CreateCustomTimeZone (id
, baseUtcOffset
, displayName
, standardDisplayName
, null, null, true);
530 public static TimeZoneInfo
CreateCustomTimeZone (string id
, TimeSpan baseUtcOffset
, string displayName
, string standardDisplayName
, string daylightDisplayName
, TimeZoneInfo
.AdjustmentRule
[] adjustmentRules
)
532 return CreateCustomTimeZone (id
, baseUtcOffset
, displayName
, standardDisplayName
, daylightDisplayName
, adjustmentRules
, false);
535 public static TimeZoneInfo
CreateCustomTimeZone ( string id
, TimeSpan baseUtcOffset
, string displayName
, string standardDisplayName
, string daylightDisplayName
, TimeZoneInfo
.AdjustmentRule
[] adjustmentRules
, bool disableDaylightSavingTime
)
537 return new TimeZoneInfo (id
, baseUtcOffset
, displayName
, standardDisplayName
, daylightDisplayName
, adjustmentRules
, disableDaylightSavingTime
);
540 public override bool Equals (object obj
)
542 return Equals (obj
as TimeZoneInfo
);
545 public bool Equals (TimeZoneInfo other
)
550 return other
.Id
== this.Id
&& HasSameRules (other
);
553 public static TimeZoneInfo
FindSystemTimeZoneById (string id
)
555 //FIXME: this method should check for cached values in systemTimeZones
557 throw new ArgumentNullException ("id");
559 if (TimeZoneKey
!= null)
561 if (id
== "Coordinated Universal Time")
562 id
= "UTC"; //windows xp exception for "StandardName" property
563 RegistryKey key
= TimeZoneKey
.OpenSubKey (id
, false);
565 throw new TimeZoneNotFoundException ();
566 return FromRegistryKey(id
, key
);
567 } else if (IsWindows
) {
568 return FindSystemTimeZoneByIdWinRTFallback (id
);
571 // Local requires special logic that already exists in the Local property (bug #326)
575 return FindSystemTimeZoneByIdCore (id
);
579 private static TimeZoneInfo
FindSystemTimeZoneByFileName (string id
, string filepath
)
581 FileStream stream
= null;
583 stream
= File
.OpenRead (filepath
);
584 } catch (Exception ex
) {
585 throw new TimeZoneNotFoundException ("Couldn't read time zone file " + filepath
, ex
);
588 return BuildFromStream (id
, stream
);
597 private static TimeZoneInfo
FromRegistryKey (string id
, RegistryKey key
)
599 byte [] reg_tzi
= (byte []) key
.GetValue ("TZI");
602 throw new InvalidTimeZoneException ();
604 int bias
= BitConverter
.ToInt32 (reg_tzi
, 0);
605 TimeSpan baseUtcOffset
= new TimeSpan (0, -bias
, 0);
607 string display_name
= (string) key
.GetValue ("Display");
608 string standard_name
= (string) key
.GetValue ("Std");
609 string daylight_name
= (string) key
.GetValue ("Dlt");
611 List
<AdjustmentRule
> adjustmentRules
= new List
<AdjustmentRule
> ();
613 RegistryKey dst_key
= key
.OpenSubKey ("Dynamic DST", false);
614 if (dst_key
!= null) {
615 int first_year
= (int) dst_key
.GetValue ("FirstEntry");
616 int last_year
= (int) dst_key
.GetValue ("LastEntry");
619 for (year
=first_year
; year
<=last_year
; year
++) {
620 byte [] dst_tzi
= (byte []) dst_key
.GetValue (year
.ToString ());
621 if (dst_tzi
!= null) {
622 int start_year
= year
== first_year
? 1 : year
;
623 int end_year
= year
== last_year
? 9999 : year
;
624 ParseRegTzi(adjustmentRules
, start_year
, end_year
, dst_tzi
);
629 ParseRegTzi(adjustmentRules
, 1, 9999, reg_tzi
);
631 return CreateCustomTimeZone (id
, baseUtcOffset
, display_name
, standard_name
, daylight_name
, ValidateRules (adjustmentRules
));
634 private static void ParseRegTzi (List
<AdjustmentRule
> adjustmentRules
, int start_year
, int end_year
, byte [] buffer
)
636 //int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
637 int daylight_bias
= BitConverter
.ToInt32 (buffer
, 8);
639 int standard_year
= BitConverter
.ToInt16 (buffer
, 12);
640 int standard_month
= BitConverter
.ToInt16 (buffer
, 14);
641 int standard_dayofweek
= BitConverter
.ToInt16 (buffer
, 16);
642 int standard_day
= BitConverter
.ToInt16 (buffer
, 18);
643 int standard_hour
= BitConverter
.ToInt16 (buffer
, 20);
644 int standard_minute
= BitConverter
.ToInt16 (buffer
, 22);
645 int standard_second
= BitConverter
.ToInt16 (buffer
, 24);
646 int standard_millisecond
= BitConverter
.ToInt16 (buffer
, 26);
648 int daylight_year
= BitConverter
.ToInt16 (buffer
, 28);
649 int daylight_month
= BitConverter
.ToInt16 (buffer
, 30);
650 int daylight_dayofweek
= BitConverter
.ToInt16 (buffer
, 32);
651 int daylight_day
= BitConverter
.ToInt16 (buffer
, 34);
652 int daylight_hour
= BitConverter
.ToInt16 (buffer
, 36);
653 int daylight_minute
= BitConverter
.ToInt16 (buffer
, 38);
654 int daylight_second
= BitConverter
.ToInt16 (buffer
, 40);
655 int daylight_millisecond
= BitConverter
.ToInt16 (buffer
, 42);
657 if (standard_month
== 0 || daylight_month
== 0)
661 DateTime start_timeofday
= new DateTime (1, 1, 1, daylight_hour
, daylight_minute
, daylight_second
, daylight_millisecond
);
662 TransitionTime start_transition_time
;
664 start_date
= new DateTime (start_year
, 1, 1);
665 if (daylight_year
== 0) {
666 start_transition_time
= TransitionTime
.CreateFloatingDateRule (
667 start_timeofday
, daylight_month
, daylight_day
,
668 (DayOfWeek
) daylight_dayofweek
);
671 start_transition_time
= TransitionTime
.CreateFixedDateRule (
672 start_timeofday
, daylight_month
, daylight_day
);
676 DateTime end_timeofday
= new DateTime (1, 1, 1, standard_hour
, standard_minute
, standard_second
, standard_millisecond
);
677 TransitionTime end_transition_time
;
679 end_date
= new DateTime (end_year
, 12, 31);
680 if (standard_year
== 0) {
681 end_transition_time
= TransitionTime
.CreateFloatingDateRule (
682 end_timeofday
, standard_month
, standard_day
,
683 (DayOfWeek
) standard_dayofweek
);
686 end_transition_time
= TransitionTime
.CreateFixedDateRule (
687 end_timeofday
, standard_month
, standard_day
);
690 TimeSpan daylight_delta
= new TimeSpan(0, -daylight_bias
, 0);
692 adjustmentRules
.Add (AdjustmentRule
.CreateAdjustmentRule (
693 start_date
, end_date
, daylight_delta
,
694 start_transition_time
, end_transition_time
));
698 public AdjustmentRule
[] GetAdjustmentRules ()
700 if (!supportsDaylightSavingTime
|| adjustmentRules
== null)
701 return new AdjustmentRule
[0];
703 return (AdjustmentRule
[]) adjustmentRules
.Clone ();
706 public TimeSpan
[] GetAmbiguousTimeOffsets (DateTime dateTime
)
708 if (!IsAmbiguousTime (dateTime
))
709 throw new ArgumentException ("dateTime is not an ambiguous time");
711 AdjustmentRule rule
= GetApplicableRule (dateTime
);
713 return new TimeSpan
[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta}
;
715 return new TimeSpan
[] {baseUtcOffset, baseUtcOffset}
;
718 public TimeSpan
[] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset
)
720 if (!IsAmbiguousTime (dateTimeOffset
))
721 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
723 throw new NotImplementedException ();
726 public override int GetHashCode ()
728 int hash_code
= Id
.GetHashCode ();
729 foreach (AdjustmentRule rule
in GetAdjustmentRules ())
730 hash_code ^
= rule
.GetHashCode ();
734 void ISerializable
.GetObjectData (SerializationInfo info
, StreamingContext context
)
737 throw new ArgumentNullException ("info");
738 info
.AddValue ("Id", id
);
739 info
.AddValue ("DisplayName", displayName
);
740 info
.AddValue ("StandardName", standardDisplayName
);
741 info
.AddValue ("DaylightName", daylightDisplayName
);
742 info
.AddValue ("BaseUtcOffset", baseUtcOffset
);
743 info
.AddValue ("AdjustmentRules", adjustmentRules
);
744 info
.AddValue ("SupportsDaylightSavingTime", SupportsDaylightSavingTime
);
747 static ReadOnlyCollection
<TimeZoneInfo
> systemTimeZones
;
749 public static ReadOnlyCollection
<TimeZoneInfo
> GetSystemTimeZones ()
751 if (systemTimeZones
== null) {
752 var tz
= new List
<TimeZoneInfo
> ();
753 GetSystemTimeZonesCore (tz
);
754 Interlocked
.CompareExchange (ref systemTimeZones
, new ReadOnlyCollection
<TimeZoneInfo
> (tz
), null);
757 return systemTimeZones
;
760 public TimeSpan
GetUtcOffset (DateTime dateTime
)
763 return GetUtcOffset (dateTime
, out isDST
);
766 public TimeSpan
GetUtcOffset (DateTimeOffset dateTimeOffset
)
769 return GetUtcOffset (dateTimeOffset
.UtcDateTime
, out isDST
);
772 private TimeSpan
GetUtcOffset (DateTime dateTime
, out bool isDST
, bool forOffset
= false)
776 TimeZoneInfo tz
= this;
777 if (dateTime
.Kind
== DateTimeKind
.Utc
)
778 tz
= TimeZoneInfo
.Utc
;
780 if (dateTime
.Kind
== DateTimeKind
.Local
)
781 tz
= TimeZoneInfo
.Local
;
784 var tzOffset
= GetUtcOffsetHelper (dateTime
, tz
, out isTzDst
, forOffset
);
791 DateTime utcDateTime
;
792 if (!TryAddTicks (dateTime
, -tzOffset
.Ticks
, out utcDateTime
, DateTimeKind
.Utc
))
793 return BaseUtcOffset
;
795 return GetUtcOffsetHelper (utcDateTime
, this, out isDST
, forOffset
);
798 // This is an helper method used by the method above, do not use this on its own.
799 private static TimeSpan
GetUtcOffsetHelper (DateTime dateTime
, TimeZoneInfo tz
, out bool isDST
, bool forOffset
= false)
801 if (dateTime
.Kind
== DateTimeKind
.Local
&& tz
!= TimeZoneInfo
.Local
)
802 throw new Exception ();
806 if (tz
== TimeZoneInfo
.Utc
)
807 return TimeSpan
.Zero
;
810 if (tz
.TryGetTransitionOffset(dateTime
, out offset
, out isDST
, forOffset
))
813 if (dateTime
.Kind
== DateTimeKind
.Utc
) {
814 var utcRule
= tz
.GetApplicableRule (dateTime
);
815 if (utcRule
!= null && tz
.IsInDST (utcRule
, dateTime
)) {
817 return tz
.BaseUtcOffset
+ utcRule
.DaylightDelta
;
820 return tz
.BaseUtcOffset
;
823 DateTime stdUtcDateTime
;
824 if (!TryAddTicks (dateTime
, -tz
.BaseUtcOffset
.Ticks
, out stdUtcDateTime
, DateTimeKind
.Utc
))
825 return tz
.BaseUtcOffset
;
827 var tzRule
= tz
.GetApplicableRule (stdUtcDateTime
);
829 DateTime dstUtcDateTime
= DateTime
.MinValue
;
830 if (tzRule
!= null) {
831 if (!TryAddTicks (stdUtcDateTime
, -tzRule
.DaylightDelta
.Ticks
, out dstUtcDateTime
, DateTimeKind
.Utc
))
832 return tz
.BaseUtcOffset
;
835 if (tzRule
!= null && tz
.IsInDST (tzRule
, dateTime
)) {
836 // Replicate what .NET does when given a time which falls into the hour which is lost when
837 // DST starts. isDST should be false and the offset should be BaseUtcOffset without the
838 // DST delta while in that hour.
841 if (tz
.IsInDST (tzRule
, dstUtcDateTime
)) {
843 return tz
.BaseUtcOffset
+ tzRule
.DaylightDelta
;
845 return tz
.BaseUtcOffset
;
849 return tz
.BaseUtcOffset
;
852 public bool HasSameRules (TimeZoneInfo other
)
855 throw new ArgumentNullException ("other");
857 if ((this.adjustmentRules
== null) != (other
.adjustmentRules
== null))
860 if (this.adjustmentRules
== null)
863 if (this.BaseUtcOffset
!= other
.BaseUtcOffset
)
866 if (this.adjustmentRules
.Length
!= other
.adjustmentRules
.Length
)
869 for (int i
= 0; i
< adjustmentRules
.Length
; i
++) {
870 if (! (this.adjustmentRules
[i
]).Equals (other
.adjustmentRules
[i
]))
877 public bool IsAmbiguousTime (DateTime dateTime
)
879 if (dateTime
.Kind
== DateTimeKind
.Local
&& IsInvalidTime (dateTime
))
880 throw new ArgumentException ("Kind is Local and time is Invalid");
882 if (this == TimeZoneInfo
.Utc
)
885 if (dateTime
.Kind
== DateTimeKind
.Utc
)
886 dateTime
= ConvertTimeFromUtc (dateTime
);
888 if (dateTime
.Kind
== DateTimeKind
.Local
&& this != TimeZoneInfo
.Local
)
889 dateTime
= ConvertTime (dateTime
, TimeZoneInfo
.Local
, this);
891 AdjustmentRule rule
= GetApplicableRule (dateTime
);
893 DateTime tpoint
= TransitionPoint (rule
.DaylightTransitionEnd
, dateTime
.Year
);
894 if (dateTime
> tpoint
- rule
.DaylightDelta
&& dateTime
<= tpoint
)
901 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset
)
903 throw new NotImplementedException ();
906 private bool IsInDST (AdjustmentRule rule
, DateTime dateTime
)
908 // Check whether we're in the dateTime year's DST period
909 if (IsInDSTForYear (rule
, dateTime
, dateTime
.Year
))
912 // We might be in the dateTime previous year's DST period
913 return dateTime
.Year
> 1 && IsInDSTForYear (rule
, dateTime
, dateTime
.Year
- 1);
916 bool IsInDSTForYear (AdjustmentRule rule
, DateTime dateTime
, int year
)
918 DateTime DST_start
= TransitionPoint (rule
.DaylightTransitionStart
, year
);
919 DateTime DST_end
= TransitionPoint (rule
.DaylightTransitionEnd
, year
+ ((rule
.DaylightTransitionStart
.Month
< rule
.DaylightTransitionEnd
.Month
) ? 0 : 1));
920 if (dateTime
.Kind
== DateTimeKind
.Utc
) {
921 DST_start
-= BaseUtcOffset
;
922 DST_end
-= BaseUtcOffset
;
924 DST_end
-= rule
.DaylightDelta
;
925 return (dateTime
>= DST_start
&& dateTime
< DST_end
);
928 public bool IsDaylightSavingTime (DateTime dateTime
)
930 if (dateTime
.Kind
== DateTimeKind
.Local
&& IsInvalidTime (dateTime
))
931 throw new ArgumentException ("dateTime is invalid and Kind is Local");
933 if (this == TimeZoneInfo
.Utc
)
936 if (!SupportsDaylightSavingTime
)
940 GetUtcOffset (dateTime
, out isDst
);
945 internal bool IsDaylightSavingTime (DateTime dateTime
, TimeZoneInfoOptions flags
)
947 return IsDaylightSavingTime (dateTime
);
950 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset
)
952 var dateTime
= dateTimeOffset
.DateTime
;
954 if (dateTime
.Kind
== DateTimeKind
.Local
&& IsInvalidTime (dateTime
))
955 throw new ArgumentException ("dateTime is invalid and Kind is Local");
957 if (this == TimeZoneInfo
.Utc
)
960 if (!SupportsDaylightSavingTime
)
964 GetUtcOffset (dateTime
, out isDst
, true);
969 internal DaylightTime
GetDaylightChanges (int year
)
971 DateTime start
= DateTime
.MinValue
, end
= DateTime
.MinValue
;
972 TimeSpan delta
= new TimeSpan ();
974 if (transitions
!= null) {
975 end
= DateTime
.MaxValue
;
976 for (var i
= transitions
.Count
- 1; i
>= 0; i
--) {
977 var pair
= transitions
[i
];
978 DateTime ttime
= pair
.Key
;
979 TimeType ttype
= pair
.Value
;
981 if (ttime
.Year
> year
)
983 if (ttime
.Year
< year
)
987 // DaylightTime.Delta is relative to the current BaseUtcOffset.
988 delta
= new TimeSpan (0, 0, ttype
.Offset
) - BaseUtcOffset
;
995 // DaylightTime.Start is relative to the Standard time.
996 if (!TryAddTicks (start
, BaseUtcOffset
.Ticks
, out start
))
997 start
= DateTime
.MinValue
;
999 // DaylightTime.End is relative to the DST time.
1000 if (!TryAddTicks (end
, BaseUtcOffset
.Ticks
+ delta
.Ticks
, out end
))
1001 end
= DateTime
.MinValue
;
1003 AdjustmentRule first
= null, last
= null;
1005 // Rule start/end dates are either very specific or very broad depending on the platform
1006 // 2015-10-04..2016-04-03 - Rule for a time zone in southern hemisphere on non-Windows platforms
1007 // 2016-03-27..2016-10-03 - Rule for a time zone in northern hemisphere on non-Windows platforms
1008 // 0001-01-01..9999-12-31 - Rule for a time zone on Windows
1010 foreach (var rule
in GetAdjustmentRules ()) {
1011 if (rule
.DateStart
.Year
> year
|| rule
.DateEnd
.Year
< year
)
1013 if (rule
.DateStart
.Year
<= year
&& (first
== null || rule
.DateStart
.Year
> first
.DateStart
.Year
))
1015 if (rule
.DateEnd
.Year
>= year
&& (last
== null || rule
.DateEnd
.Year
< last
.DateEnd
.Year
))
1019 if (first
== null || last
== null)
1020 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
1022 start
= TransitionPoint (first
.DaylightTransitionStart
, year
);
1023 end
= TransitionPoint (last
.DaylightTransitionEnd
, year
);
1024 delta
= first
.DaylightDelta
;
1027 if (start
== DateTime
.MinValue
|| end
== DateTime
.MinValue
)
1028 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
1030 return new DaylightTime (start
, end
, delta
);
1033 public bool IsInvalidTime (DateTime dateTime
)
1035 if (dateTime
.Kind
== DateTimeKind
.Utc
)
1037 if (dateTime
.Kind
== DateTimeKind
.Local
&& this != Local
)
1040 AdjustmentRule rule
= GetApplicableRule (dateTime
);
1042 DateTime tpoint
= TransitionPoint (rule
.DaylightTransitionStart
, dateTime
.Year
);
1043 if (dateTime
>= tpoint
&& dateTime
< tpoint
+ rule
.DaylightDelta
)
1050 void IDeserializationCallback
.OnDeserialization (object sender
)
1053 TimeZoneInfo
.Validate (id
, baseUtcOffset
, adjustmentRules
);
1054 } catch (ArgumentException ex
) {
1055 throw new SerializationException ("invalid serialization data", ex
);
1059 private static void Validate (string id
, TimeSpan baseUtcOffset
, AdjustmentRule
[] adjustmentRules
)
1062 throw new ArgumentNullException ("id");
1064 if (id
== String
.Empty
)
1065 throw new ArgumentException ("id parameter is an empty string");
1067 if (baseUtcOffset
.Ticks
% TimeSpan
.TicksPerMinute
!= 0)
1068 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1070 if (baseUtcOffset
> new TimeSpan (14, 0, 0) || baseUtcOffset
< new TimeSpan (-14, 0, 0))
1071 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1075 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1078 if (adjustmentRules
!= null && adjustmentRules
.Length
!= 0) {
1079 AdjustmentRule prev
= null;
1080 foreach (AdjustmentRule current
in adjustmentRules
) {
1081 if (current
== null)
1082 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1084 if ((baseUtcOffset
+ current
.DaylightDelta
< new TimeSpan (-14, 0, 0)) ||
1085 (baseUtcOffset
+ current
.DaylightDelta
> new TimeSpan (14, 0, 0)))
1086 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;");
1088 if (prev
!= null && prev
.DateStart
> current
.DateStart
)
1089 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1091 if (prev
!= null && prev
.DateEnd
> current
.DateStart
)
1092 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1094 if (prev
!= null && prev
.DateEnd
== current
.DateStart
)
1095 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1102 public override string ToString ()
1107 private TimeZoneInfo (SerializationInfo info
, StreamingContext context
)
1110 throw new ArgumentNullException ("info");
1111 id
= (string) info
.GetValue ("Id", typeof (string));
1112 displayName
= (string) info
.GetValue ("DisplayName", typeof (string));
1113 standardDisplayName
= (string) info
.GetValue ("StandardName", typeof (string));
1114 daylightDisplayName
= (string) info
.GetValue ("DaylightName", typeof (string));
1115 baseUtcOffset
= (TimeSpan
) info
.GetValue ("BaseUtcOffset", typeof (TimeSpan
));
1116 adjustmentRules
= (TimeZoneInfo
.AdjustmentRule
[]) info
.GetValue ("AdjustmentRules", typeof (TimeZoneInfo
.AdjustmentRule
[]));
1117 supportsDaylightSavingTime
= (bool) info
.GetValue ("SupportsDaylightSavingTime", typeof (bool));
1120 private TimeZoneInfo (string id
, TimeSpan baseUtcOffset
, string displayName
, string standardDisplayName
, string daylightDisplayName
, TimeZoneInfo
.AdjustmentRule
[] adjustmentRules
, bool disableDaylightSavingTime
)
1123 throw new ArgumentNullException ("id");
1125 if (id
== String
.Empty
)
1126 throw new ArgumentException ("id parameter is an empty string");
1128 if (baseUtcOffset
.Ticks
% TimeSpan
.TicksPerMinute
!= 0)
1129 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1131 if (baseUtcOffset
> new TimeSpan (14, 0, 0) || baseUtcOffset
< new TimeSpan (-14, 0, 0))
1132 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1136 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1139 bool supportsDaylightSavingTime
= !disableDaylightSavingTime
;
1141 if (adjustmentRules
!= null && adjustmentRules
.Length
!= 0) {
1142 AdjustmentRule prev
= null;
1143 foreach (AdjustmentRule current
in adjustmentRules
) {
1144 if (current
== null)
1145 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1147 if ((baseUtcOffset
+ current
.DaylightDelta
< new TimeSpan (-14, 0, 0)) ||
1148 (baseUtcOffset
+ current
.DaylightDelta
> new TimeSpan (14, 0, 0)))
1149 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;");
1151 if (prev
!= null && prev
.DateStart
> current
.DateStart
)
1152 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1154 if (prev
!= null && prev
.DateEnd
> current
.DateStart
)
1155 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1157 if (prev
!= null && prev
.DateEnd
== current
.DateStart
)
1158 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1163 supportsDaylightSavingTime
= false;
1167 this.baseUtcOffset
= baseUtcOffset
;
1168 this.displayName
= displayName
?? id
;
1169 this.standardDisplayName
= standardDisplayName
?? id
;
1170 this.daylightDisplayName
= daylightDisplayName
;
1171 this.supportsDaylightSavingTime
= supportsDaylightSavingTime
;
1172 this.adjustmentRules
= adjustmentRules
;
1175 private AdjustmentRule
GetApplicableRule (DateTime dateTime
)
1177 //Applicable rules are in standard time
1178 DateTime date
= dateTime
;
1180 if (dateTime
.Kind
== DateTimeKind
.Local
&& this != TimeZoneInfo
.Local
) {
1181 if (!TryAddTicks (date
.ToUniversalTime (), BaseUtcOffset
.Ticks
, out date
))
1183 } else if (dateTime
.Kind
== DateTimeKind
.Utc
&& this != TimeZoneInfo
.Utc
) {
1184 if (!TryAddTicks (date
, BaseUtcOffset
.Ticks
, out date
))
1188 // get the date component of the datetime
1191 if (adjustmentRules
!= null) {
1192 foreach (AdjustmentRule rule
in adjustmentRules
) {
1193 if (rule
.DateStart
> date
)
1195 if (rule
.DateEnd
< date
)
1203 private bool TryGetTransitionOffset (DateTime dateTime
, out TimeSpan offset
, out bool isDst
, bool forOffset
= false)
1205 offset
= BaseUtcOffset
;
1208 if (transitions
== null)
1211 //Transitions are in UTC
1212 DateTime date
= dateTime
;
1214 if (dateTime
.Kind
== DateTimeKind
.Local
&& this != TimeZoneInfo
.Local
) {
1215 if (!TryAddTicks (date
.ToUniversalTime (), BaseUtcOffset
.Ticks
, out date
, DateTimeKind
.Utc
))
1219 if (dateTime
.Kind
!= DateTimeKind
.Utc
) {
1220 if (!TryAddTicks (date
, -BaseUtcOffset
.Ticks
, out date
, DateTimeKind
.Utc
))
1224 AdjustmentRule current
= GetApplicableRule (date
);
1225 if (current
!= null) {
1226 DateTime tStart
= TransitionPoint (current
.DaylightTransitionStart
, date
.Year
);
1227 DateTime tEnd
= TransitionPoint (current
.DaylightTransitionEnd
, date
.Year
);
1228 TryAddTicks (tStart
, -BaseUtcOffset
.Ticks
, out tStart
, DateTimeKind
.Utc
);
1229 TryAddTicks (tEnd
, -BaseUtcOffset
.Ticks
, out tEnd
, DateTimeKind
.Utc
);
1230 if ((date
>= tStart
) && (date
<= tEnd
)) {
1233 offset
= baseUtcOffset
;
1234 if (date
>= new DateTime (tStart
.Ticks
+ current
.DaylightDelta
.Ticks
, DateTimeKind
.Utc
))
1236 offset
+= current
.DaylightDelta
;
1240 if (date
>= new DateTime (tEnd
.Ticks
- current
.DaylightDelta
.Ticks
, DateTimeKind
.Utc
))
1242 offset
= baseUtcOffset
;
1252 private static DateTime
TransitionPoint (TransitionTime transition
, int year
)
1254 if (transition
.IsFixedDateRule
)
1255 return new DateTime (year
, transition
.Month
, transition
.Day
) + transition
.TimeOfDay
.TimeOfDay
;
1257 DayOfWeek first
= (new DateTime (year
, transition
.Month
, 1)).DayOfWeek
;
1258 int day
= 1 + (transition
.Week
- 1) * 7 + (transition
.DayOfWeek
- first
+ 7) % 7;
1259 if (day
> DateTime
.DaysInMonth (year
, transition
.Month
))
1263 return new DateTime (year
, transition
.Month
, day
) + transition
.TimeOfDay
.TimeOfDay
;
1266 static AdjustmentRule
[] ValidateRules (List
<AdjustmentRule
> adjustmentRules
)
1268 if (adjustmentRules
== null || adjustmentRules
.Count
== 0)
1271 AdjustmentRule prev
= null;
1272 foreach (AdjustmentRule current
in adjustmentRules
.ToArray ()) {
1273 if (prev
!= null && prev
.DateEnd
> current
.DateStart
) {
1274 adjustmentRules
.Remove (current
);
1278 return adjustmentRules
.ToArray ();
1281 #if LIBC || MONOTOUCH
1282 const int BUFFER_SIZE
= 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
1284 private static TimeZoneInfo
BuildFromStream (string id
, Stream stream
)
1286 byte [] buffer
= new byte [BUFFER_SIZE
];
1287 int length
= stream
.Read (buffer
, 0, BUFFER_SIZE
);
1289 if (!ValidTZFile (buffer
, length
))
1290 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
1293 return ParseTZBuffer (id
, buffer
, length
);
1294 } catch (InvalidTimeZoneException
) {
1296 } catch (Exception e
) {
1297 throw new InvalidTimeZoneException ("Time zone information file contains invalid data", e
);
1301 private static bool ValidTZFile (byte [] buffer
, int length
)
1303 StringBuilder magic
= new StringBuilder ();
1305 for (int i
= 0; i
< 4; i
++)
1306 magic
.Append ((char)buffer
[i
]);
1308 if (magic
.ToString () != "TZif")
1311 if (length
>= BUFFER_SIZE
)
1317 static int SwapInt32 (int i
)
1319 return (((i
>> 24) & 0xff)
1320 | ((i
>> 8) & 0xff00)
1321 | ((i
<< 8) & 0xff0000)
1322 | (((i
& 0xff) << 24)));
1325 static int ReadBigEndianInt32 (byte [] buffer
, int start
)
1327 int i
= BitConverter
.ToInt32 (buffer
, start
);
1328 if (!BitConverter
.IsLittleEndian
)
1331 return SwapInt32 (i
);
1334 private static TimeZoneInfo
ParseTZBuffer (string id
, byte [] buffer
, int length
)
1336 //Reading the header. 4 bytes for magic, 16 are reserved
1337 int ttisgmtcnt
= ReadBigEndianInt32 (buffer
, 20);
1338 int ttisstdcnt
= ReadBigEndianInt32 (buffer
, 24);
1339 int leapcnt
= ReadBigEndianInt32 (buffer
, 28);
1340 int timecnt
= ReadBigEndianInt32 (buffer
, 32);
1341 int typecnt
= ReadBigEndianInt32 (buffer
, 36);
1342 int charcnt
= ReadBigEndianInt32 (buffer
, 40);
1344 if (length
< 44 + timecnt
* 5 + typecnt
* 6 + charcnt
+ leapcnt
* 8 + ttisstdcnt
+ ttisgmtcnt
)
1345 throw new InvalidTimeZoneException ();
1347 Dictionary
<int, string> abbreviations
= ParseAbbreviations (buffer
, 44 + 4 * timecnt
+ timecnt
+ 6 * typecnt
, charcnt
);
1348 Dictionary
<int, TimeType
> time_types
= ParseTimesTypes (buffer
, 44 + 4 * timecnt
+ timecnt
, typecnt
, abbreviations
);
1349 List
<KeyValuePair
<DateTime
, TimeType
>> transitions
= ParseTransitions (buffer
, 44, timecnt
, time_types
);
1351 if (time_types
.Count
== 0)
1352 throw new InvalidTimeZoneException ();
1354 if (time_types
.Count
== 1 && time_types
[0].IsDst
)
1355 throw new InvalidTimeZoneException ();
1357 TimeSpan baseUtcOffset
= new TimeSpan (0);
1358 TimeSpan dstDelta
= new TimeSpan (0);
1359 string standardDisplayName
= null;
1360 string daylightDisplayName
= null;
1361 bool dst_observed
= false;
1362 DateTime dst_start
= DateTime
.MinValue
;
1363 List
<AdjustmentRule
> adjustmentRules
= new List
<AdjustmentRule
> ();
1364 bool storeTransition
= false;
1366 for (int i
= 0; i
< transitions
.Count
; i
++) {
1367 var pair
= transitions
[i
];
1368 DateTime ttime
= pair
.Key
;
1369 TimeType ttype
= pair
.Value
;
1371 if (standardDisplayName
!= ttype
.Name
)
1372 standardDisplayName
= ttype
.Name
;
1373 if (baseUtcOffset
.TotalSeconds
!= ttype
.Offset
) {
1374 baseUtcOffset
= new TimeSpan (0, 0, ttype
.Offset
);
1375 if (adjustmentRules
.Count
> 0) // We ignore AdjustmentRules but store transitions.
1376 storeTransition
= true;
1377 adjustmentRules
= new List
<AdjustmentRule
> ();
1378 dst_observed
= false;
1381 //FIXME: check additional fields for this:
1382 //most of the transitions are expressed in GMT
1383 dst_start
+= baseUtcOffset
;
1384 DateTime dst_end
= ttime
+ baseUtcOffset
+ dstDelta
;
1386 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1387 if (dst_end
.Date
== new DateTime (dst_end
.Year
, 1, 1) && dst_end
.Year
> dst_start
.Year
)
1388 dst_end
-= new TimeSpan (24, 0, 0);
1391 * AdjustmentRule specifies a DST period that starts and ends within a year.
1392 * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
1393 * Thus we fallback to the transitions.
1395 if (dst_start
.AddYears (1) < dst_end
)
1396 storeTransition
= true;
1398 DateTime dateStart
, dateEnd
;
1399 if (dst_start
.Month
< 7)
1400 dateStart
= new DateTime (dst_start
.Year
, 1, 1);
1402 dateStart
= new DateTime (dst_start
.Year
, 7, 1);
1404 if (dst_end
.Month
>= 7)
1405 dateEnd
= new DateTime (dst_end
.Year
, 12, 31);
1407 dateEnd
= new DateTime (dst_end
.Year
, 6, 30);
1410 TransitionTime transition_start
= TransitionTime
.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start
.TimeOfDay
, dst_start
.Month
, dst_start
.Day
);
1411 TransitionTime transition_end
= TransitionTime
.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end
.TimeOfDay
, dst_end
.Month
, dst_end
.Day
);
1412 if (transition_start
!= transition_end
) //y, that happened in Argentina in 1943-1946
1413 adjustmentRules
.Add (AdjustmentRule
.CreateAdjustmentRule (dateStart
, dateEnd
, dstDelta
, transition_start
, transition_end
));
1415 dst_observed
= false;
1417 if (daylightDisplayName
!= ttype
.Name
)
1418 daylightDisplayName
= ttype
.Name
;
1419 if (dstDelta
.TotalSeconds
!= ttype
.Offset
- baseUtcOffset
.TotalSeconds
) {
1420 // Round to nearest minute, since it's not possible to create an adjustment rule
1421 // with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
1422 // This happens for instance with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
1423 dstDelta
= new TimeSpan (0, 0, ttype
.Offset
) - baseUtcOffset
;
1424 if (dstDelta
.Ticks
% TimeSpan
.TicksPerMinute
!= 0)
1425 dstDelta
= TimeSpan
.FromMinutes ((long) (dstDelta
.TotalMinutes
+ 0.5f
));
1429 dst_observed
= true;
1434 if (adjustmentRules
.Count
== 0 && !storeTransition
) {
1435 if (standardDisplayName
== null) {
1436 var t
= time_types
[0];
1437 standardDisplayName
= t
.Name
;
1438 baseUtcOffset
= new TimeSpan (0, 0, t
.Offset
);
1440 tz
= CreateCustomTimeZone (id
, baseUtcOffset
, id
, standardDisplayName
);
1442 tz
= CreateCustomTimeZone (id
, baseUtcOffset
, id
, standardDisplayName
, daylightDisplayName
, ValidateRules (adjustmentRules
));
1445 if (storeTransition
&& transitions
.Count
> 0) {
1446 tz
.transitions
= transitions
;
1448 tz
.supportsDaylightSavingTime
= adjustmentRules
.Count
> 0;
1453 static Dictionary
<int, string> ParseAbbreviations (byte [] buffer
, int index
, int count
)
1455 var abbrevs
= new Dictionary
<int, string> ();
1456 int abbrev_index
= 0;
1457 var sb
= new StringBuilder ();
1458 for (int i
= 0; i
< count
; i
++) {
1459 char c
= (char) buffer
[index
+ i
];
1463 abbrevs
.Add (abbrev_index
, sb
.ToString ());
1464 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1465 //j == sb.Length empty substring also needs to be added #31432
1466 for (int j
= 1; j
<= sb
.Length
; j
++)
1467 abbrevs
.Add (abbrev_index
+ j
, sb
.ToString (j
, sb
.Length
- j
));
1468 abbrev_index
= i
+ 1;
1469 sb
= new StringBuilder ();
1475 static Dictionary
<int, TimeType
> ParseTimesTypes (byte [] buffer
, int index
, int count
, Dictionary
<int, string> abbreviations
)
1477 var types
= new Dictionary
<int, TimeType
> (count
);
1478 for (int i
= 0; i
< count
; i
++) {
1479 int offset
= ReadBigEndianInt32 (buffer
, index
+ 6 * i
);
1482 // The official tz database contains timezone with GMT offsets
1483 // not only in whole hours/minutes but in seconds. This happens for years
1484 // before 1901. For example
1486 // NAME GMTOFF RULES FORMAT UNTIL
1487 // Europe/Madrid -0:14:44 - LMT 1901 Jan 1 0:00s
1489 // .NET as of 4.6.2 cannot handle that and uses hours/minutes only, so
1490 // we remove seconds to not crash later
1492 offset
= (offset
/ 60) * 60;
1494 byte is_dst
= buffer
[index
+ 6 * i
+ 4];
1495 byte abbrev
= buffer
[index
+ 6 * i
+ 5];
1496 types
.Add (i
, new TimeType (offset
, (is_dst
!= 0), abbreviations
[(int)abbrev
]));
1501 static List
<KeyValuePair
<DateTime
, TimeType
>> ParseTransitions (byte [] buffer
, int index
, int count
, Dictionary
<int, TimeType
> time_types
)
1503 var list
= new List
<KeyValuePair
<DateTime
, TimeType
>> (count
);
1504 for (int i
= 0; i
< count
; i
++) {
1505 int unixtime
= ReadBigEndianInt32 (buffer
, index
+ 4 * i
);
1506 DateTime ttime
= DateTimeFromUnixTime (unixtime
);
1507 byte ttype
= buffer
[index
+ 4 * count
+ i
];
1508 list
.Add (new KeyValuePair
<DateTime
, TimeType
> (ttime
, time_types
[(int)ttype
]));
1513 static DateTime
DateTimeFromUnixTime (long unix_time
)
1515 DateTime date_time
= new DateTime (1970, 1, 1);
1516 return date_time
.AddSeconds (unix_time
);
1519 #region reference sources
1520 // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1521 internal static TimeSpan
GetLocalUtcOffset(DateTime dateTime
, TimeZoneInfoOptions flags
)
1524 return Local
.GetUtcOffset (dateTime
, out dst
);
1527 internal TimeSpan
GetUtcOffset(DateTime dateTime
, TimeZoneInfoOptions flags
)
1530 return GetUtcOffset (dateTime
, out dst
);
1533 static internal TimeSpan
GetUtcOffsetFromUtc (DateTime time
, TimeZoneInfo zone
, out Boolean isDaylightSavings
, out Boolean isAmbiguousLocalDst
)
1535 isDaylightSavings
= false;
1536 isAmbiguousLocalDst
= false;
1537 TimeSpan baseOffset
= zone
.BaseUtcOffset
;
1539 if (zone
.IsAmbiguousTime (time
)) {
1540 isAmbiguousLocalDst
= true;
1541 // return baseOffset;
1544 return zone
.GetUtcOffset (time
, out isDaylightSavings
);
1550 public readonly int Offset
;
1551 public readonly bool IsDst
;
1554 public TimeType (int offset
, bool is_dst
, string abbrev
)
1556 this.Offset
= offset
;
1557 this.IsDst
= is_dst
;
1561 public override string ToString ()
1563 return "offset: " + Offset
+ "s, is_dst: " + IsDst
+ ", zone name: " + Name
;