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) || MOBILE_DESKTOP_HOST
154 [MethodImplAttribute(MethodImplOptions
.InternalCall
)]
155 extern static void mono_timezone_get_local_name (ref string name
);
157 static TimeZoneInfo
CreateLocal ()
160 if (IsWindows
&& LocalZoneKey
!= null) {
161 string name
= (string)LocalZoneKey
.GetValue ("TimeZoneKeyName");
163 name
= (string)LocalZoneKey
.GetValue ("StandardName"); // windows xp
164 name
= TrimSpecial (name
);
166 return TimeZoneInfo
.FindSystemTimeZoneById (name
);
167 } else if (IsWindows
) {
168 return GetLocalTimeZoneInfoWinRTFallback ();
172 string localName
= null;
173 mono_timezone_get_local_name (ref localName
);
175 return FindSystemTimeZoneByFileName (localName
, Path
.Combine (TimeZoneDirectory
, localName
));
180 var tz
= Environment
.GetEnvironmentVariable ("TZ");
182 if (tz
== String
.Empty
)
185 return FindSystemTimeZoneByFileName (tz
, Path
.Combine (TimeZoneDirectory
, tz
));
191 var tzFilePaths
= new string [] {
193 Path
.Combine (TimeZoneDirectory
, "localtime")};
195 foreach (var tzFilePath
in tzFilePaths
) {
197 string tzName
= null;
198 if (!TryGetNameFromPath (tzFilePath
, out tzName
))
200 return FindSystemTimeZoneByFileName (tzName
, tzFilePath
);
201 } catch (TimeZoneNotFoundException
) {
210 static TimeZoneInfo
FindSystemTimeZoneByIdCore (string id
)
213 string filepath
= Path
.Combine (TimeZoneDirectory
, id
);
214 return FindSystemTimeZoneByFileName (id
, filepath
);
216 throw new NotImplementedException ();
220 static void GetSystemTimeZonesCore (List
<TimeZoneInfo
> systemTimeZones
)
223 if (TimeZoneKey
!= null) {
224 foreach (string id
in TimeZoneKey
.GetSubKeyNames ()) {
225 using (RegistryKey subkey
= TimeZoneKey
.OpenSubKey (id
))
227 if (subkey
== null || subkey
.GetValue ("TZI") == null)
230 systemTimeZones
.Add (FindSystemTimeZoneById (id
));
234 } else if (IsWindows
) {
235 systemTimeZones
.AddRange (GetSystemTimeZonesWinRTFallback ());
241 string[] continents
= new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Australia", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"}
;
242 foreach (string continent
in continents
) {
244 foreach (string zonepath
in Directory
.GetFiles (Path
.Combine (TimeZoneDirectory
, continent
))) {
246 string id
= String
.Format ("{0}/{1}", continent
, Path
.GetFileName (zonepath
));
247 systemTimeZones
.Add (FindSystemTimeZoneById (id
));
248 } catch (ArgumentNullException
) {
249 } catch (TimeZoneNotFoundException
) {
250 } catch (InvalidTimeZoneException
) {
251 } catch (Exception
) {
258 throw new NotImplementedException ("This method is not implemented for this platform");
261 #endif // !MONODROID && !MONOTOUCH && !XAMMAC && !WASM
263 string standardDisplayName
;
264 public string StandardName
{
265 get { return standardDisplayName; }
268 bool supportsDaylightSavingTime
;
269 public bool SupportsDaylightSavingTime
{
270 get { return supportsDaylightSavingTime; }
273 static TimeZoneInfo utc
;
274 public static TimeZoneInfo Utc
{
277 utc
= CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
283 const string DefaultTimeZoneDirectory
= "/zoneinfo";
285 const string DefaultTimeZoneDirectory
= "/usr/share/zoneinfo";
287 static string timeZoneDirectory
;
288 static string TimeZoneDirectory
{
290 if (timeZoneDirectory
== null)
291 timeZoneDirectory
= readlink (DefaultTimeZoneDirectory
) ?? DefaultTimeZoneDirectory
;
292 return timeZoneDirectory
;
296 timeZoneDirectory
= value;
300 private AdjustmentRule
[] adjustmentRules
;
302 #if (!MOBILE || !FULL_AOT_DESKTOP || WIN_PLATFORM) && !XAMMAC_4_5
304 /// Determine whether windows of not (taken Stephane Delcroix's code)
306 private static bool IsWindows
309 int platform
= (int) Environment
.OSVersion
.Platform
;
310 return ((platform
!= 4) && (platform
!= 6) && (platform
!= 128));
315 /// Needed to trim misc garbage in MS registry keys
317 private static string TrimSpecial (string str
)
322 while (Istart
< str
.Length
&& !char.IsLetterOrDigit(str
[Istart
])) Istart
++;
323 var Iend
= str
.Length
- 1;
324 while (Iend
> Istart
&& !char.IsLetterOrDigit(str
[Iend
]) && str
[Iend
] != ')') // zone name can include parentheses like "Central Standard Time (Mexico)"
327 return str
.Substring (Istart
, Iend
-Istart
+1);
330 #if !FULL_AOT_DESKTOP || WIN_PLATFORM
331 static RegistryKey timeZoneKey
;
332 static RegistryKey TimeZoneKey
{
334 if (timeZoneKey
!= null)
340 return timeZoneKey
= Registry
.LocalMachine
.OpenSubKey (
341 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
349 static RegistryKey localZoneKey
;
350 static RegistryKey LocalZoneKey
{
352 if (localZoneKey
!= null)
359 return localZoneKey
= Registry
.LocalMachine
.OpenSubKey (
360 "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false);
367 #endif // !MOBILE || !FULL_AOT_DESKTOP || WIN_PLATFORM
369 private static bool TryAddTicks (DateTime date
, long ticks
, out DateTime result
, DateTimeKind kind
= DateTimeKind
.Unspecified
)
371 var resultTicks
= date
.Ticks
+ ticks
;
372 if (resultTicks
< DateTime
.MinValue
.Ticks
) {
373 result
= DateTime
.SpecifyKind (DateTime
.MinValue
, kind
);
377 if (resultTicks
> DateTime
.MaxValue
.Ticks
) {
378 result
= DateTime
.SpecifyKind (DateTime
.MaxValue
, kind
);
382 result
= new DateTime (resultTicks
, kind
);
386 public static void ClearCachedData ()
390 systemTimeZones
= null;
393 public static DateTime
ConvertTime (DateTime dateTime
, TimeZoneInfo destinationTimeZone
)
395 return ConvertTime (dateTime
, dateTime
.Kind
== DateTimeKind
.Utc
? TimeZoneInfo
.Utc
: TimeZoneInfo
.Local
, destinationTimeZone
);
398 public static DateTime
ConvertTime (DateTime dateTime
, TimeZoneInfo sourceTimeZone
, TimeZoneInfo destinationTimeZone
)
400 if (sourceTimeZone
== null)
401 throw new ArgumentNullException ("sourceTimeZone");
403 if (destinationTimeZone
== null)
404 throw new ArgumentNullException ("destinationTimeZone");
406 if (dateTime
.Kind
== DateTimeKind
.Local
&& sourceTimeZone
!= TimeZoneInfo
.Local
)
407 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
409 if (dateTime
.Kind
== DateTimeKind
.Utc
&& sourceTimeZone
!= TimeZoneInfo
.Utc
)
410 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
412 if (sourceTimeZone
.IsInvalidTime (dateTime
))
413 throw new ArgumentException ("dateTime parameter is an invalid time");
415 if (dateTime
.Kind
== DateTimeKind
.Local
&& sourceTimeZone
== TimeZoneInfo
.Local
&& destinationTimeZone
== TimeZoneInfo
.Local
)
418 DateTime utc
= ConvertTimeToUtc (dateTime
, sourceTimeZone
);
420 if (destinationTimeZone
!= TimeZoneInfo
.Utc
) {
421 utc
= ConvertTimeFromUtc (utc
, destinationTimeZone
);
422 if (dateTime
.Kind
== DateTimeKind
.Unspecified
)
423 return DateTime
.SpecifyKind (utc
, DateTimeKind
.Unspecified
);
429 public static DateTimeOffset
ConvertTime(DateTimeOffset dateTimeOffset
, TimeZoneInfo destinationTimeZone
)
431 if (destinationTimeZone
== null)
432 throw new ArgumentNullException("destinationTimeZone");
434 var utcDateTime
= dateTimeOffset
.UtcDateTime
;
437 var utcOffset
= destinationTimeZone
.GetUtcOffset(utcDateTime
, out isDst
);
439 return new DateTimeOffset(DateTime
.SpecifyKind(utcDateTime
, DateTimeKind
.Unspecified
) + utcOffset
, utcOffset
);
442 public static DateTime
ConvertTimeBySystemTimeZoneId (DateTime dateTime
, string destinationTimeZoneId
)
444 return ConvertTime (dateTime
, FindSystemTimeZoneById (destinationTimeZoneId
));
447 public static DateTime
ConvertTimeBySystemTimeZoneId (DateTime dateTime
, string sourceTimeZoneId
, string destinationTimeZoneId
)
449 TimeZoneInfo source_tz
;
450 if (dateTime
.Kind
== DateTimeKind
.Utc
&& sourceTimeZoneId
== TimeZoneInfo
.Utc
.Id
) {
453 source_tz
= FindSystemTimeZoneById (sourceTimeZoneId
);
456 return ConvertTime (dateTime
, source_tz
, FindSystemTimeZoneById (destinationTimeZoneId
));
459 public static DateTimeOffset
ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset
, string destinationTimeZoneId
)
461 return ConvertTime (dateTimeOffset
, FindSystemTimeZoneById (destinationTimeZoneId
));
464 private DateTime
ConvertTimeFromUtc (DateTime dateTime
)
466 if (dateTime
.Kind
== DateTimeKind
.Local
)
467 throw new ArgumentException ("Kind property of dateTime is Local");
469 if (this == TimeZoneInfo
.Utc
)
470 return DateTime
.SpecifyKind (dateTime
, DateTimeKind
.Utc
);
472 var utcOffset
= GetUtcOffset (dateTime
);
474 var kind
= (this == TimeZoneInfo
.Local
)? DateTimeKind
.Local
: DateTimeKind
.Unspecified
;
477 if (!TryAddTicks (dateTime
, utcOffset
.Ticks
, out result
, kind
))
478 return DateTime
.SpecifyKind (DateTime
.MaxValue
, kind
);
483 public static DateTime
ConvertTimeFromUtc (DateTime dateTime
, TimeZoneInfo destinationTimeZone
)
485 if (destinationTimeZone
== null)
486 throw new ArgumentNullException ("destinationTimeZone");
488 return destinationTimeZone
.ConvertTimeFromUtc (dateTime
);
491 public static DateTime
ConvertTimeToUtc (DateTime dateTime
)
493 if (dateTime
.Kind
== DateTimeKind
.Utc
)
496 return ConvertTimeToUtc (dateTime
, TimeZoneInfo
.Local
);
499 static internal DateTime
ConvertTimeToUtc(DateTime dateTime
, TimeZoneInfoOptions flags
)
501 return ConvertTimeToUtc (dateTime
, TimeZoneInfo
.Local
, flags
);
504 public static DateTime
ConvertTimeToUtc (DateTime dateTime
, TimeZoneInfo sourceTimeZone
)
506 return ConvertTimeToUtc (dateTime
, sourceTimeZone
, TimeZoneInfoOptions
.None
);
509 static DateTime
ConvertTimeToUtc (DateTime dateTime
, TimeZoneInfo sourceTimeZone
, TimeZoneInfoOptions flags
)
511 if ((flags
& TimeZoneInfoOptions
.NoThrowOnInvalidTime
) == 0) {
512 if (sourceTimeZone
== null)
513 throw new ArgumentNullException ("sourceTimeZone");
515 if (dateTime
.Kind
== DateTimeKind
.Utc
&& sourceTimeZone
!= TimeZoneInfo
.Utc
)
516 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
518 if (dateTime
.Kind
== DateTimeKind
.Local
&& sourceTimeZone
!= TimeZoneInfo
.Local
)
519 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
521 if (sourceTimeZone
.IsInvalidTime (dateTime
))
522 throw new ArgumentException ("dateTime parameter is an invalid time");
525 if (dateTime
.Kind
== DateTimeKind
.Utc
)
529 var utcOffset
= sourceTimeZone
.GetUtcOffset (dateTime
, out isDst
);
531 DateTime utcDateTime
;
532 TryAddTicks (dateTime
, -utcOffset
.Ticks
, out utcDateTime
, DateTimeKind
.Utc
);
536 static internal TimeSpan
GetDateTimeNowUtcOffsetFromUtc(DateTime time
, out Boolean isAmbiguousLocalDst
)
538 bool isDaylightSavings
;
539 return GetUtcOffsetFromUtc(time
, TimeZoneInfo
.Local
, out isDaylightSavings
, out isAmbiguousLocalDst
);
542 public static TimeZoneInfo
CreateCustomTimeZone (string id
, TimeSpan baseUtcOffset
, string displayName
, string standardDisplayName
)
544 return CreateCustomTimeZone (id
, baseUtcOffset
, displayName
, standardDisplayName
, null, null, true);
547 public static TimeZoneInfo
CreateCustomTimeZone (string id
, TimeSpan baseUtcOffset
, string displayName
, string standardDisplayName
, string daylightDisplayName
, TimeZoneInfo
.AdjustmentRule
[] adjustmentRules
)
549 return CreateCustomTimeZone (id
, baseUtcOffset
, displayName
, standardDisplayName
, daylightDisplayName
, adjustmentRules
, false);
552 public static TimeZoneInfo
CreateCustomTimeZone ( string id
, TimeSpan baseUtcOffset
, string displayName
, string standardDisplayName
, string daylightDisplayName
, TimeZoneInfo
.AdjustmentRule
[] adjustmentRules
, bool disableDaylightSavingTime
)
554 return new TimeZoneInfo (id
, baseUtcOffset
, displayName
, standardDisplayName
, daylightDisplayName
, adjustmentRules
, disableDaylightSavingTime
);
557 public override bool Equals (object obj
)
559 return Equals (obj
as TimeZoneInfo
);
562 public bool Equals (TimeZoneInfo other
)
567 return other
.Id
== this.Id
&& HasSameRules (other
);
570 public static TimeZoneInfo
FindSystemTimeZoneById (string id
)
572 //FIXME: this method should check for cached values in systemTimeZones
574 throw new ArgumentNullException ("id");
576 if (TimeZoneKey
!= null)
578 if (id
== "Coordinated Universal Time")
579 id
= "UTC"; //windows xp exception for "StandardName" property
580 RegistryKey key
= TimeZoneKey
.OpenSubKey (id
, false);
582 throw new TimeZoneNotFoundException ();
583 return FromRegistryKey(id
, key
);
584 } else if (IsWindows
) {
585 return FindSystemTimeZoneByIdWinRTFallback (id
);
588 // Local requires special logic that already exists in the Local property (bug #326)
592 return FindSystemTimeZoneByIdCore (id
);
596 private static TimeZoneInfo
FindSystemTimeZoneByFileName (string id
, string filepath
)
598 FileStream stream
= null;
600 stream
= File
.OpenRead (filepath
);
601 } catch (Exception ex
) {
602 throw new TimeZoneNotFoundException ("Couldn't read time zone file " + filepath
, ex
);
605 return BuildFromStream (id
, stream
);
614 private static TimeZoneInfo
FromRegistryKey (string id
, RegistryKey key
)
616 byte [] reg_tzi
= (byte []) key
.GetValue ("TZI");
619 throw new InvalidTimeZoneException ();
621 int bias
= BitConverter
.ToInt32 (reg_tzi
, 0);
622 TimeSpan baseUtcOffset
= new TimeSpan (0, -bias
, 0);
624 string display_name
= (string) key
.GetValue ("Display");
625 string standard_name
= (string) key
.GetValue ("Std");
626 string daylight_name
= (string) key
.GetValue ("Dlt");
628 List
<AdjustmentRule
> adjustmentRules
= new List
<AdjustmentRule
> ();
630 RegistryKey dst_key
= key
.OpenSubKey ("Dynamic DST", false);
631 if (dst_key
!= null) {
632 int first_year
= (int) dst_key
.GetValue ("FirstEntry");
633 int last_year
= (int) dst_key
.GetValue ("LastEntry");
636 for (year
=first_year
; year
<=last_year
; year
++) {
637 byte [] dst_tzi
= (byte []) dst_key
.GetValue (year
.ToString ());
638 if (dst_tzi
!= null) {
639 int start_year
= year
== first_year
? 1 : year
;
640 int end_year
= year
== last_year
? 9999 : year
;
641 ParseRegTzi(adjustmentRules
, start_year
, end_year
, dst_tzi
);
646 ParseRegTzi(adjustmentRules
, 1, 9999, reg_tzi
);
648 return CreateCustomTimeZone (id
, baseUtcOffset
, display_name
, standard_name
, daylight_name
, ValidateRules (adjustmentRules
));
651 private static void ParseRegTzi (List
<AdjustmentRule
> adjustmentRules
, int start_year
, int end_year
, byte [] buffer
)
653 //int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
654 int daylight_bias
= BitConverter
.ToInt32 (buffer
, 8);
656 int standard_year
= BitConverter
.ToInt16 (buffer
, 12);
657 int standard_month
= BitConverter
.ToInt16 (buffer
, 14);
658 int standard_dayofweek
= BitConverter
.ToInt16 (buffer
, 16);
659 int standard_day
= BitConverter
.ToInt16 (buffer
, 18);
660 int standard_hour
= BitConverter
.ToInt16 (buffer
, 20);
661 int standard_minute
= BitConverter
.ToInt16 (buffer
, 22);
662 int standard_second
= BitConverter
.ToInt16 (buffer
, 24);
663 int standard_millisecond
= BitConverter
.ToInt16 (buffer
, 26);
665 int daylight_year
= BitConverter
.ToInt16 (buffer
, 28);
666 int daylight_month
= BitConverter
.ToInt16 (buffer
, 30);
667 int daylight_dayofweek
= BitConverter
.ToInt16 (buffer
, 32);
668 int daylight_day
= BitConverter
.ToInt16 (buffer
, 34);
669 int daylight_hour
= BitConverter
.ToInt16 (buffer
, 36);
670 int daylight_minute
= BitConverter
.ToInt16 (buffer
, 38);
671 int daylight_second
= BitConverter
.ToInt16 (buffer
, 40);
672 int daylight_millisecond
= BitConverter
.ToInt16 (buffer
, 42);
674 if (standard_month
== 0 || daylight_month
== 0)
678 DateTime start_timeofday
= new DateTime (1, 1, 1, daylight_hour
, daylight_minute
, daylight_second
, daylight_millisecond
);
679 TransitionTime start_transition_time
;
681 start_date
= new DateTime (start_year
, 1, 1);
682 if (daylight_year
== 0) {
683 start_transition_time
= TransitionTime
.CreateFloatingDateRule (
684 start_timeofday
, daylight_month
, daylight_day
,
685 (DayOfWeek
) daylight_dayofweek
);
688 start_transition_time
= TransitionTime
.CreateFixedDateRule (
689 start_timeofday
, daylight_month
, daylight_day
);
693 DateTime end_timeofday
= new DateTime (1, 1, 1, standard_hour
, standard_minute
, standard_second
, standard_millisecond
);
694 TransitionTime end_transition_time
;
696 end_date
= new DateTime (end_year
, 12, 31);
697 if (standard_year
== 0) {
698 end_transition_time
= TransitionTime
.CreateFloatingDateRule (
699 end_timeofday
, standard_month
, standard_day
,
700 (DayOfWeek
) standard_dayofweek
);
703 end_transition_time
= TransitionTime
.CreateFixedDateRule (
704 end_timeofday
, standard_month
, standard_day
);
707 TimeSpan daylight_delta
= new TimeSpan(0, -daylight_bias
, 0);
709 adjustmentRules
.Add (AdjustmentRule
.CreateAdjustmentRule (
710 start_date
, end_date
, daylight_delta
,
711 start_transition_time
, end_transition_time
));
715 public AdjustmentRule
[] GetAdjustmentRules ()
717 if (!supportsDaylightSavingTime
|| adjustmentRules
== null)
718 return new AdjustmentRule
[0];
720 return (AdjustmentRule
[]) adjustmentRules
.Clone ();
723 public TimeSpan
[] GetAmbiguousTimeOffsets (DateTime dateTime
)
725 if (!IsAmbiguousTime (dateTime
))
726 throw new ArgumentException ("dateTime is not an ambiguous time");
728 AdjustmentRule rule
= GetApplicableRule (dateTime
);
730 return new TimeSpan
[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta}
;
732 return new TimeSpan
[] {baseUtcOffset, baseUtcOffset}
;
735 public TimeSpan
[] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset
)
737 if (!IsAmbiguousTime (dateTimeOffset
))
738 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
740 throw new NotImplementedException ();
743 public override int GetHashCode ()
745 int hash_code
= Id
.GetHashCode ();
746 foreach (AdjustmentRule rule
in GetAdjustmentRules ())
747 hash_code ^
= rule
.GetHashCode ();
751 void ISerializable
.GetObjectData (SerializationInfo info
, StreamingContext context
)
754 throw new ArgumentNullException ("info");
755 info
.AddValue ("Id", id
);
756 info
.AddValue ("DisplayName", displayName
);
757 info
.AddValue ("StandardName", standardDisplayName
);
758 info
.AddValue ("DaylightName", daylightDisplayName
);
759 info
.AddValue ("BaseUtcOffset", baseUtcOffset
);
760 info
.AddValue ("AdjustmentRules", adjustmentRules
);
761 info
.AddValue ("SupportsDaylightSavingTime", SupportsDaylightSavingTime
);
764 static ReadOnlyCollection
<TimeZoneInfo
> systemTimeZones
;
766 public static ReadOnlyCollection
<TimeZoneInfo
> GetSystemTimeZones ()
768 if (systemTimeZones
== null) {
769 var tz
= new List
<TimeZoneInfo
> ();
770 GetSystemTimeZonesCore (tz
);
771 Interlocked
.CompareExchange (ref systemTimeZones
, new ReadOnlyCollection
<TimeZoneInfo
> (tz
), null);
774 return systemTimeZones
;
777 public TimeSpan
GetUtcOffset (DateTime dateTime
)
780 return GetUtcOffset (dateTime
, out isDST
);
783 public TimeSpan
GetUtcOffset (DateTimeOffset dateTimeOffset
)
786 return GetUtcOffset (dateTimeOffset
.UtcDateTime
, out isDST
);
789 private TimeSpan
GetUtcOffset (DateTime dateTime
, out bool isDST
, bool forOffset
= false)
793 TimeZoneInfo tz
= this;
794 if (dateTime
.Kind
== DateTimeKind
.Utc
)
795 tz
= TimeZoneInfo
.Utc
;
797 if (dateTime
.Kind
== DateTimeKind
.Local
)
798 tz
= TimeZoneInfo
.Local
;
801 var tzOffset
= GetUtcOffsetHelper (dateTime
, tz
, out isTzDst
, forOffset
);
808 DateTime utcDateTime
;
809 if (!TryAddTicks (dateTime
, -tzOffset
.Ticks
, out utcDateTime
, DateTimeKind
.Utc
))
810 return BaseUtcOffset
;
812 return GetUtcOffsetHelper (utcDateTime
, this, out isDST
, forOffset
);
815 // This is an helper method used by the method above, do not use this on its own.
816 private static TimeSpan
GetUtcOffsetHelper (DateTime dateTime
, TimeZoneInfo tz
, out bool isDST
, bool forOffset
= false)
818 if (dateTime
.Kind
== DateTimeKind
.Local
&& tz
!= TimeZoneInfo
.Local
)
819 throw new Exception ();
823 if (tz
== TimeZoneInfo
.Utc
)
824 return TimeSpan
.Zero
;
827 if (tz
.TryGetTransitionOffset(dateTime
, out offset
, out isDST
, forOffset
))
830 if (dateTime
.Kind
== DateTimeKind
.Utc
) {
831 var utcRule
= tz
.GetApplicableRule (dateTime
);
832 if (utcRule
!= null && tz
.IsInDST (utcRule
, dateTime
)) {
834 return tz
.BaseUtcOffset
+ utcRule
.DaylightDelta
;
837 return tz
.BaseUtcOffset
;
840 DateTime stdUtcDateTime
;
841 if (!TryAddTicks (dateTime
, -tz
.BaseUtcOffset
.Ticks
, out stdUtcDateTime
, DateTimeKind
.Utc
))
842 return tz
.BaseUtcOffset
;
844 var tzRule
= tz
.GetApplicableRule (stdUtcDateTime
);
846 DateTime dstUtcDateTime
= DateTime
.MinValue
;
847 if (tzRule
!= null) {
848 if (!TryAddTicks (stdUtcDateTime
, -tzRule
.DaylightDelta
.Ticks
, out dstUtcDateTime
, DateTimeKind
.Utc
))
849 return tz
.BaseUtcOffset
;
852 if (tzRule
!= null && tz
.IsInDST (tzRule
, dateTime
)) {
853 // Replicate what .NET does when given a time which falls into the hour which is lost when
854 // DST starts. isDST should be false and the offset should be BaseUtcOffset without the
855 // DST delta while in that hour.
858 if (tz
.IsInDST (tzRule
, dstUtcDateTime
)) {
860 return tz
.BaseUtcOffset
+ tzRule
.DaylightDelta
;
862 return tz
.BaseUtcOffset
;
866 return tz
.BaseUtcOffset
;
869 public bool HasSameRules (TimeZoneInfo other
)
872 throw new ArgumentNullException ("other");
874 if ((this.adjustmentRules
== null) != (other
.adjustmentRules
== null))
877 if (this.adjustmentRules
== null)
880 if (this.BaseUtcOffset
!= other
.BaseUtcOffset
)
883 if (this.adjustmentRules
.Length
!= other
.adjustmentRules
.Length
)
886 for (int i
= 0; i
< adjustmentRules
.Length
; i
++) {
887 if (! (this.adjustmentRules
[i
]).Equals (other
.adjustmentRules
[i
]))
894 public bool IsAmbiguousTime (DateTime dateTime
)
896 if (dateTime
.Kind
== DateTimeKind
.Local
&& IsInvalidTime (dateTime
))
897 throw new ArgumentException ("Kind is Local and time is Invalid");
899 if (this == TimeZoneInfo
.Utc
)
902 if (dateTime
.Kind
== DateTimeKind
.Utc
)
903 dateTime
= ConvertTimeFromUtc (dateTime
);
905 if (dateTime
.Kind
== DateTimeKind
.Local
&& this != TimeZoneInfo
.Local
)
906 dateTime
= ConvertTime (dateTime
, TimeZoneInfo
.Local
, this);
908 AdjustmentRule rule
= GetApplicableRule (dateTime
);
910 DateTime tpoint
= TransitionPoint (rule
.DaylightTransitionEnd
, dateTime
.Year
);
911 if (dateTime
> tpoint
- rule
.DaylightDelta
&& dateTime
<= tpoint
)
918 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset
)
920 throw new NotImplementedException ();
923 private bool IsInDST (AdjustmentRule rule
, DateTime dateTime
)
925 // Check whether we're in the dateTime year's DST period
926 if (IsInDSTForYear (rule
, dateTime
, dateTime
.Year
))
929 // We might be in the dateTime previous year's DST period
930 return dateTime
.Year
> 1 && IsInDSTForYear (rule
, dateTime
, dateTime
.Year
- 1);
933 bool IsInDSTForYear (AdjustmentRule rule
, DateTime dateTime
, int year
)
935 DateTime DST_start
= TransitionPoint (rule
.DaylightTransitionStart
, year
);
936 DateTime DST_end
= TransitionPoint (rule
.DaylightTransitionEnd
, year
+ ((rule
.DaylightTransitionStart
.Month
< rule
.DaylightTransitionEnd
.Month
) ? 0 : 1));
937 if (dateTime
.Kind
== DateTimeKind
.Utc
) {
938 DST_start
-= BaseUtcOffset
;
939 DST_end
-= BaseUtcOffset
;
941 DST_end
-= rule
.DaylightDelta
;
942 return (dateTime
>= DST_start
&& dateTime
< DST_end
);
945 public bool IsDaylightSavingTime (DateTime dateTime
)
947 if (dateTime
.Kind
== DateTimeKind
.Local
&& IsInvalidTime (dateTime
))
948 throw new ArgumentException ("dateTime is invalid and Kind is Local");
950 if (this == TimeZoneInfo
.Utc
)
953 if (!SupportsDaylightSavingTime
)
957 GetUtcOffset (dateTime
, out isDst
);
962 internal bool IsDaylightSavingTime (DateTime dateTime
, TimeZoneInfoOptions flags
)
964 return IsDaylightSavingTime (dateTime
);
967 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset
)
969 var dateTime
= dateTimeOffset
.DateTime
;
971 if (dateTime
.Kind
== DateTimeKind
.Local
&& IsInvalidTime (dateTime
))
972 throw new ArgumentException ("dateTime is invalid and Kind is Local");
974 if (this == TimeZoneInfo
.Utc
)
977 if (!SupportsDaylightSavingTime
)
981 GetUtcOffset (dateTime
, out isDst
, true);
986 internal DaylightTime
GetDaylightChanges (int year
)
988 DateTime start
= DateTime
.MinValue
, end
= DateTime
.MinValue
;
989 TimeSpan delta
= new TimeSpan ();
991 if (transitions
!= null) {
992 end
= DateTime
.MaxValue
;
993 for (var i
= transitions
.Count
- 1; i
>= 0; i
--) {
994 var pair
= transitions
[i
];
995 DateTime ttime
= pair
.Key
;
996 TimeType ttype
= pair
.Value
;
998 if (ttime
.Year
> year
)
1000 if (ttime
.Year
< year
)
1004 // DaylightTime.Delta is relative to the current BaseUtcOffset.
1005 delta
= new TimeSpan (0, 0, ttype
.Offset
) - BaseUtcOffset
;
1012 // DaylightTime.Start is relative to the Standard time.
1013 if (!TryAddTicks (start
, BaseUtcOffset
.Ticks
, out start
))
1014 start
= DateTime
.MinValue
;
1016 // DaylightTime.End is relative to the DST time.
1017 if (!TryAddTicks (end
, BaseUtcOffset
.Ticks
+ delta
.Ticks
, out end
))
1018 end
= DateTime
.MinValue
;
1020 AdjustmentRule first
= null, last
= null;
1022 // Rule start/end dates are either very specific or very broad depending on the platform
1023 // 2015-10-04..2016-04-03 - Rule for a time zone in southern hemisphere on non-Windows platforms
1024 // 2016-03-27..2016-10-03 - Rule for a time zone in northern hemisphere on non-Windows platforms
1025 // 0001-01-01..9999-12-31 - Rule for a time zone on Windows
1027 foreach (var rule
in GetAdjustmentRules ()) {
1028 if (rule
.DateStart
.Year
> year
|| rule
.DateEnd
.Year
< year
)
1030 if (rule
.DateStart
.Year
<= year
&& (first
== null || rule
.DateStart
.Year
> first
.DateStart
.Year
))
1032 if (rule
.DateEnd
.Year
>= year
&& (last
== null || rule
.DateEnd
.Year
< last
.DateEnd
.Year
))
1036 if (first
== null || last
== null)
1037 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
1039 start
= TransitionPoint (first
.DaylightTransitionStart
, year
);
1040 end
= TransitionPoint (last
.DaylightTransitionEnd
, year
);
1041 delta
= first
.DaylightDelta
;
1044 if (start
== DateTime
.MinValue
|| end
== DateTime
.MinValue
)
1045 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
1047 return new DaylightTime (start
, end
, delta
);
1050 public bool IsInvalidTime (DateTime dateTime
)
1052 if (dateTime
.Kind
== DateTimeKind
.Utc
)
1054 if (dateTime
.Kind
== DateTimeKind
.Local
&& this != Local
)
1057 AdjustmentRule rule
= GetApplicableRule (dateTime
);
1059 DateTime tpoint
= TransitionPoint (rule
.DaylightTransitionStart
, dateTime
.Year
);
1060 if (dateTime
>= tpoint
&& dateTime
< tpoint
+ rule
.DaylightDelta
)
1067 void IDeserializationCallback
.OnDeserialization (object sender
)
1070 TimeZoneInfo
.Validate (id
, baseUtcOffset
, adjustmentRules
);
1071 } catch (ArgumentException ex
) {
1072 throw new SerializationException ("invalid serialization data", ex
);
1076 private static void Validate (string id
, TimeSpan baseUtcOffset
, AdjustmentRule
[] adjustmentRules
)
1079 throw new ArgumentNullException ("id");
1081 if (id
== String
.Empty
)
1082 throw new ArgumentException ("id parameter is an empty string");
1084 if (baseUtcOffset
.Ticks
% TimeSpan
.TicksPerMinute
!= 0)
1085 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1087 if (baseUtcOffset
> new TimeSpan (14, 0, 0) || baseUtcOffset
< new TimeSpan (-14, 0, 0))
1088 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1092 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1095 if (adjustmentRules
!= null && adjustmentRules
.Length
!= 0) {
1096 AdjustmentRule prev
= null;
1097 foreach (AdjustmentRule current
in adjustmentRules
) {
1098 if (current
== null)
1099 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1101 if ((baseUtcOffset
+ current
.DaylightDelta
< new TimeSpan (-14, 0, 0)) ||
1102 (baseUtcOffset
+ current
.DaylightDelta
> new TimeSpan (14, 0, 0)))
1103 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;");
1105 if (prev
!= null && prev
.DateStart
> current
.DateStart
)
1106 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1108 if (prev
!= null && prev
.DateEnd
> current
.DateStart
)
1109 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1111 if (prev
!= null && prev
.DateEnd
== current
.DateStart
)
1112 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1119 public override string ToString ()
1124 private TimeZoneInfo (SerializationInfo info
, StreamingContext context
)
1127 throw new ArgumentNullException ("info");
1128 id
= (string) info
.GetValue ("Id", typeof (string));
1129 displayName
= (string) info
.GetValue ("DisplayName", typeof (string));
1130 standardDisplayName
= (string) info
.GetValue ("StandardName", typeof (string));
1131 daylightDisplayName
= (string) info
.GetValue ("DaylightName", typeof (string));
1132 baseUtcOffset
= (TimeSpan
) info
.GetValue ("BaseUtcOffset", typeof (TimeSpan
));
1133 adjustmentRules
= (TimeZoneInfo
.AdjustmentRule
[]) info
.GetValue ("AdjustmentRules", typeof (TimeZoneInfo
.AdjustmentRule
[]));
1134 supportsDaylightSavingTime
= (bool) info
.GetValue ("SupportsDaylightSavingTime", typeof (bool));
1137 private TimeZoneInfo (string id
, TimeSpan baseUtcOffset
, string displayName
, string standardDisplayName
, string daylightDisplayName
, TimeZoneInfo
.AdjustmentRule
[] adjustmentRules
, bool disableDaylightSavingTime
)
1140 throw new ArgumentNullException ("id");
1142 if (id
== String
.Empty
)
1143 throw new ArgumentException ("id parameter is an empty string");
1145 if (baseUtcOffset
.Ticks
% TimeSpan
.TicksPerMinute
!= 0)
1146 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1148 if (baseUtcOffset
> new TimeSpan (14, 0, 0) || baseUtcOffset
< new TimeSpan (-14, 0, 0))
1149 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1153 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1156 bool supportsDaylightSavingTime
= !disableDaylightSavingTime
;
1158 if (adjustmentRules
!= null && adjustmentRules
.Length
!= 0) {
1159 AdjustmentRule prev
= null;
1160 foreach (AdjustmentRule current
in adjustmentRules
) {
1161 if (current
== null)
1162 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1164 if ((baseUtcOffset
+ current
.DaylightDelta
< new TimeSpan (-14, 0, 0)) ||
1165 (baseUtcOffset
+ current
.DaylightDelta
> new TimeSpan (14, 0, 0)))
1166 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;");
1168 if (prev
!= null && prev
.DateStart
> current
.DateStart
)
1169 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1171 if (prev
!= null && prev
.DateEnd
> current
.DateStart
)
1172 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1174 if (prev
!= null && prev
.DateEnd
== current
.DateStart
)
1175 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1180 supportsDaylightSavingTime
= false;
1184 this.baseUtcOffset
= baseUtcOffset
;
1185 this.displayName
= displayName
?? id
;
1186 this.standardDisplayName
= standardDisplayName
?? id
;
1187 this.daylightDisplayName
= daylightDisplayName
;
1188 this.supportsDaylightSavingTime
= supportsDaylightSavingTime
;
1189 this.adjustmentRules
= adjustmentRules
;
1192 private AdjustmentRule
GetApplicableRule (DateTime dateTime
)
1194 //Applicable rules are in standard time
1195 DateTime date
= dateTime
;
1197 if (dateTime
.Kind
== DateTimeKind
.Local
&& this != TimeZoneInfo
.Local
) {
1198 if (!TryAddTicks (date
.ToUniversalTime (), BaseUtcOffset
.Ticks
, out date
))
1200 } else if (dateTime
.Kind
== DateTimeKind
.Utc
&& this != TimeZoneInfo
.Utc
) {
1201 if (!TryAddTicks (date
, BaseUtcOffset
.Ticks
, out date
))
1205 // get the date component of the datetime
1208 if (adjustmentRules
!= null) {
1209 foreach (AdjustmentRule rule
in adjustmentRules
) {
1210 if (rule
.DateStart
> date
)
1212 if (rule
.DateEnd
< date
)
1220 private bool TryGetTransitionOffset (DateTime dateTime
, out TimeSpan offset
, out bool isDst
, bool forOffset
= false)
1222 offset
= BaseUtcOffset
;
1225 if (transitions
== null)
1228 //Transitions are in UTC
1229 DateTime date
= dateTime
;
1231 if (dateTime
.Kind
== DateTimeKind
.Local
&& this != TimeZoneInfo
.Local
) {
1232 if (!TryAddTicks (date
.ToUniversalTime (), BaseUtcOffset
.Ticks
, out date
, DateTimeKind
.Utc
))
1237 if (dateTime
.Kind
!= DateTimeKind
.Utc
) {
1238 if (!TryAddTicks (date
, -BaseUtcOffset
.Ticks
, out date
, DateTimeKind
.Utc
))
1244 AdjustmentRule current
= GetApplicableRule (date
);
1245 if (current
!= null) {
1246 DateTime tStart
= TransitionPoint (current
.DaylightTransitionStart
, date
.Year
);
1247 DateTime tEnd
= TransitionPoint (current
.DaylightTransitionEnd
, date
.Year
);
1248 TryAddTicks (tStart
, -BaseUtcOffset
.Ticks
, out tStart
, DateTimeKind
.Utc
);
1249 TryAddTicks (tEnd
, -BaseUtcOffset
.Ticks
, out tEnd
, DateTimeKind
.Utc
);
1250 if ((date
>= tStart
) && (date
<= tEnd
)) {
1253 offset
= baseUtcOffset
;
1254 if (isUtc
|| (date
>= new DateTime (tStart
.Ticks
+ current
.DaylightDelta
.Ticks
, DateTimeKind
.Utc
)))
1256 offset
+= current
.DaylightDelta
;
1260 if (date
>= new DateTime (tEnd
.Ticks
- current
.DaylightDelta
.Ticks
, DateTimeKind
.Utc
))
1262 offset
= baseUtcOffset
;
1272 private static DateTime
TransitionPoint (TransitionTime transition
, int year
)
1274 if (transition
.IsFixedDateRule
) {
1275 var daysInMonth
= DateTime
.DaysInMonth (year
, transition
.Month
);
1276 var transitionDay
= transition
.Day
<= daysInMonth
? transition
.Day
: daysInMonth
;
1277 return new DateTime (year
, transition
.Month
, transitionDay
) + transition
.TimeOfDay
.TimeOfDay
;
1280 DayOfWeek first
= (new DateTime (year
, transition
.Month
, 1)).DayOfWeek
;
1281 int day
= 1 + (transition
.Week
- 1) * 7 + (transition
.DayOfWeek
- first
+ 7) % 7;
1282 if (day
> DateTime
.DaysInMonth (year
, transition
.Month
))
1286 return new DateTime (year
, transition
.Month
, day
) + transition
.TimeOfDay
.TimeOfDay
;
1289 static AdjustmentRule
[] ValidateRules (List
<AdjustmentRule
> adjustmentRules
)
1291 if (adjustmentRules
== null || adjustmentRules
.Count
== 0)
1294 AdjustmentRule prev
= null;
1295 foreach (AdjustmentRule current
in adjustmentRules
.ToArray ()) {
1296 if (prev
!= null && prev
.DateEnd
> current
.DateStart
) {
1297 adjustmentRules
.Remove (current
);
1301 return adjustmentRules
.ToArray ();
1304 #if LIBC || MONOTOUCH
1305 const int BUFFER_SIZE
= 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
1307 private static TimeZoneInfo
BuildFromStream (string id
, Stream stream
)
1309 byte [] buffer
= new byte [BUFFER_SIZE
];
1310 int length
= stream
.Read (buffer
, 0, BUFFER_SIZE
);
1312 if (!ValidTZFile (buffer
, length
))
1313 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
1316 return ParseTZBuffer (id
, buffer
, length
);
1317 } catch (InvalidTimeZoneException
) {
1319 } catch (Exception e
) {
1320 throw new InvalidTimeZoneException ("Time zone information file contains invalid data", e
);
1324 private static bool ValidTZFile (byte [] buffer
, int length
)
1326 StringBuilder magic
= new StringBuilder ();
1328 for (int i
= 0; i
< 4; i
++)
1329 magic
.Append ((char)buffer
[i
]);
1331 if (magic
.ToString () != "TZif")
1334 if (length
>= BUFFER_SIZE
)
1340 static int SwapInt32 (int i
)
1342 return (((i
>> 24) & 0xff)
1343 | ((i
>> 8) & 0xff00)
1344 | ((i
<< 8) & 0xff0000)
1345 | (((i
& 0xff) << 24)));
1348 static int ReadBigEndianInt32 (byte [] buffer
, int start
)
1350 int i
= BitConverter
.ToInt32 (buffer
, start
);
1351 if (!BitConverter
.IsLittleEndian
)
1354 return SwapInt32 (i
);
1357 static long ReadBigEndianInt64(byte[] buffer
, int start
)
1359 byte[] longBytes
= new byte[8];
1360 for (int i
= 0; i
< 8; i
++) {
1361 longBytes
[i
] = buffer
[start
+ i
];
1364 if (BitConverter
.IsLittleEndian
) {
1365 Array
.Reverse(longBytes
);
1368 return BitConverter
.ToInt64(longBytes
, 0);
1371 private static TimeZoneInfo
ParseTZBuffer (string id
, byte [] buffer
, int length
)
1373 //Reading the header. 4 bytes for magic, 16 are reserved
1374 int ttisgmtcnt
= ReadBigEndianInt32 (buffer
, 20);
1375 int ttisstdcnt
= ReadBigEndianInt32 (buffer
, 24);
1376 int leapcnt
= ReadBigEndianInt32 (buffer
, 28);
1377 int timecnt
= ReadBigEndianInt32 (buffer
, 32);
1378 int typecnt
= ReadBigEndianInt32 (buffer
, 36);
1379 int charcnt
= ReadBigEndianInt32 (buffer
, 40);
1381 byte version
= buffer
[4];
1383 int timeValuesLength
= 4;
1384 if (version
== '2' || version
== '3') {
1385 index
+= 44 + (int)((timeValuesLength
* timecnt
) + timecnt
+ (6 * typecnt
) + ((timeValuesLength
+ 4) * leapcnt
) + ttisstdcnt
+ ttisgmtcnt
+ charcnt
);
1386 // move index past the V1 information to read the V2 information
1388 ttisgmtcnt
= ReadBigEndianInt32 (buffer
, 20 + index
);
1389 ttisstdcnt
= ReadBigEndianInt32 (buffer
, 24 + index
);
1390 leapcnt
= ReadBigEndianInt32 (buffer
, 28 + index
);
1391 timecnt
= ReadBigEndianInt32 (buffer
, 32 + index
);
1392 typecnt
= ReadBigEndianInt32 (buffer
, 36 + index
);
1393 charcnt
= ReadBigEndianInt32 (buffer
, 40 + index
);
1395 timeValuesLength
= 8;
1398 if (length
< 44 + timecnt
* 5 + typecnt
* 6 + charcnt
+ leapcnt
* 8 + ttisstdcnt
+ ttisgmtcnt
)
1399 throw new InvalidTimeZoneException ();
1401 Dictionary
<int, string> abbreviations
= ParseAbbreviations (buffer
, index
+ 44 + timeValuesLength
* timecnt
+ timecnt
+ 6 * typecnt
, charcnt
);
1402 Dictionary
<int, TimeType
> time_types
= ParseTimesTypes (buffer
, index
+ 44 + timeValuesLength
* timecnt
+ timecnt
, typecnt
, abbreviations
);
1403 List
<KeyValuePair
<DateTime
, TimeType
>> transitions
= ParseTransitions (buffer
, index
+ 44, timecnt
, timeValuesLength
, time_types
);
1405 if (time_types
.Count
== 0)
1406 throw new InvalidTimeZoneException ();
1408 if (time_types
.Count
== 1 && time_types
[0].IsDst
)
1409 throw new InvalidTimeZoneException ();
1411 TimeSpan baseUtcOffset
= new TimeSpan (0);
1412 TimeSpan dstDelta
= new TimeSpan (0);
1413 string standardDisplayName
= null;
1414 string daylightDisplayName
= null;
1415 bool dst_observed
= false;
1416 DateTime dst_start
= DateTime
.MinValue
;
1417 List
<AdjustmentRule
> adjustmentRules
= new List
<AdjustmentRule
> ();
1418 bool storeTransition
= false;
1420 for (int i
= 0; i
< transitions
.Count
; i
++) {
1421 var pair
= transitions
[i
];
1422 DateTime ttime
= pair
.Key
;
1423 TimeType ttype
= pair
.Value
;
1425 if (standardDisplayName
!= ttype
.Name
)
1426 standardDisplayName
= ttype
.Name
;
1427 if (baseUtcOffset
.TotalSeconds
!= ttype
.Offset
) {
1428 baseUtcOffset
= new TimeSpan (0, 0, ttype
.Offset
);
1429 if (adjustmentRules
.Count
> 0) // We ignore AdjustmentRules but store transitions.
1430 storeTransition
= true;
1431 adjustmentRules
= new List
<AdjustmentRule
> ();
1432 dst_observed
= false;
1435 //FIXME: check additional fields for this:
1436 //most of the transitions are expressed in GMT
1437 dst_start
+= baseUtcOffset
;
1438 DateTime dst_end
= ttime
+ baseUtcOffset
+ dstDelta
;
1440 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1441 if (dst_end
.Date
== new DateTime (dst_end
.Year
, 1, 1) && dst_end
.Year
> dst_start
.Year
)
1442 dst_end
-= new TimeSpan (24, 0, 0);
1445 * AdjustmentRule specifies a DST period that starts and ends within a year.
1446 * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
1447 * Thus we fallback to the transitions.
1449 if (dst_start
.AddYears (1) < dst_end
)
1450 storeTransition
= true;
1452 DateTime dateStart
, dateEnd
;
1453 if (dst_start
.Month
< 7)
1454 dateStart
= new DateTime (dst_start
.Year
, 1, 1);
1456 dateStart
= new DateTime (dst_start
.Year
, 7, 1);
1458 if (dst_end
.Month
>= 7)
1459 dateEnd
= new DateTime (dst_end
.Year
, 12, 31);
1461 dateEnd
= new DateTime (dst_end
.Year
, 6, 30);
1464 TransitionTime transition_start
= TransitionTime
.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start
.TimeOfDay
, dst_start
.Month
, dst_start
.Day
);
1465 TransitionTime transition_end
= TransitionTime
.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end
.TimeOfDay
, dst_end
.Month
, dst_end
.Day
);
1466 if (transition_start
!= transition_end
) //y, that happened in Argentina in 1943-1946
1467 adjustmentRules
.Add (AdjustmentRule
.CreateAdjustmentRule (dateStart
, dateEnd
, dstDelta
, transition_start
, transition_end
));
1469 dst_observed
= false;
1471 if (daylightDisplayName
!= ttype
.Name
)
1472 daylightDisplayName
= ttype
.Name
;
1473 if (dstDelta
.TotalSeconds
!= ttype
.Offset
- baseUtcOffset
.TotalSeconds
) {
1474 // Round to nearest minute, since it's not possible to create an adjustment rule
1475 // with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
1476 // This happens for instance with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
1477 dstDelta
= new TimeSpan (0, 0, ttype
.Offset
) - baseUtcOffset
;
1478 if (dstDelta
.Ticks
% TimeSpan
.TicksPerMinute
!= 0)
1479 dstDelta
= TimeSpan
.FromMinutes ((long) (dstDelta
.TotalMinutes
+ 0.5f
));
1483 dst_observed
= true;
1488 if (adjustmentRules
.Count
== 0 && !storeTransition
) {
1489 if (standardDisplayName
== null) {
1490 var t
= time_types
[0];
1491 standardDisplayName
= t
.Name
;
1492 baseUtcOffset
= new TimeSpan (0, 0, t
.Offset
);
1494 tz
= CreateCustomTimeZone (id
, baseUtcOffset
, id
, standardDisplayName
);
1496 tz
= CreateCustomTimeZone (id
, baseUtcOffset
, id
, standardDisplayName
, daylightDisplayName
, ValidateRules (adjustmentRules
));
1499 if (storeTransition
&& transitions
.Count
> 0) {
1500 tz
.transitions
= transitions
;
1502 tz
.supportsDaylightSavingTime
= adjustmentRules
.Count
> 0;
1507 static Dictionary
<int, string> ParseAbbreviations (byte [] buffer
, int index
, int count
)
1509 var abbrevs
= new Dictionary
<int, string> ();
1510 int abbrev_index
= 0;
1511 var sb
= new StringBuilder ();
1512 for (int i
= 0; i
< count
; i
++) {
1513 char c
= (char) buffer
[index
+ i
];
1517 abbrevs
.Add (abbrev_index
, sb
.ToString ());
1518 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1519 //j == sb.Length empty substring also needs to be added #31432
1520 for (int j
= 1; j
<= sb
.Length
; j
++)
1521 abbrevs
.Add (abbrev_index
+ j
, sb
.ToString (j
, sb
.Length
- j
));
1522 abbrev_index
= i
+ 1;
1523 sb
= new StringBuilder ();
1529 static Dictionary
<int, TimeType
> ParseTimesTypes (byte [] buffer
, int index
, int count
, Dictionary
<int, string> abbreviations
)
1531 var types
= new Dictionary
<int, TimeType
> (count
);
1532 for (int i
= 0; i
< count
; i
++) {
1533 int offset
= ReadBigEndianInt32 (buffer
, index
+ 6 * i
);
1536 // The official tz database contains timezone with GMT offsets
1537 // not only in whole hours/minutes but in seconds. This happens for years
1538 // before 1901. For example
1540 // NAME GMTOFF RULES FORMAT UNTIL
1541 // Europe/Madrid -0:14:44 - LMT 1901 Jan 1 0:00s
1543 // .NET as of 4.6.2 cannot handle that and uses hours/minutes only, so
1544 // we remove seconds to not crash later
1546 offset
= (offset
/ 60) * 60;
1548 byte is_dst
= buffer
[index
+ 6 * i
+ 4];
1549 byte abbrev
= buffer
[index
+ 6 * i
+ 5];
1550 types
.Add (i
, new TimeType (offset
, (is_dst
!= 0), abbreviations
[(int)abbrev
]));
1555 static List
<KeyValuePair
<DateTime
, TimeType
>> ParseTransitions (byte [] buffer
, int index
, int count
, int timeValuesLength
, Dictionary
<int, TimeType
> time_types
)
1557 var list
= new List
<KeyValuePair
<DateTime
, TimeType
>> (count
);
1558 for (int i
= 0; i
< count
; i
++) {
1560 if (timeValuesLength
== 8) {
1561 unixtime
= ReadBigEndianInt64 (buffer
, index
+ timeValuesLength
* i
);
1563 unixtime
= ReadBigEndianInt32 (buffer
, index
+ timeValuesLength
* i
);
1566 DateTime ttime
= DateTimeFromUnixTime (unixtime
);
1567 byte ttype
= buffer
[index
+ timeValuesLength
* count
+ i
];
1568 list
.Add (new KeyValuePair
<DateTime
, TimeType
> (ttime
, time_types
[(int)ttype
]));
1573 static DateTime
DateTimeFromUnixTime (long unix_time
) =>
1574 unix_time
< DateTimeOffset
.UnixMinSeconds
? DateTime
.MinValue
:
1575 unix_time
> DateTimeOffset
.UnixMaxSeconds
? DateTime
.MaxValue
:
1576 DateTimeOffset
.FromUnixTimeSeconds(unix_time
).UtcDateTime
;
1578 #region reference sources
1579 // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1580 internal static TimeSpan
GetLocalUtcOffset(DateTime dateTime
, TimeZoneInfoOptions flags
)
1583 return Local
.GetUtcOffset (dateTime
, out dst
);
1586 internal TimeSpan
GetUtcOffset(DateTime dateTime
, TimeZoneInfoOptions flags
)
1589 return GetUtcOffset (dateTime
, out dst
);
1592 static internal TimeSpan
GetUtcOffsetFromUtc (DateTime time
, TimeZoneInfo zone
, out Boolean isDaylightSavings
, out Boolean isAmbiguousLocalDst
)
1594 isDaylightSavings
= false;
1595 isAmbiguousLocalDst
= false;
1596 TimeSpan baseOffset
= zone
.BaseUtcOffset
;
1598 if (zone
.IsAmbiguousTime (time
)) {
1599 isAmbiguousLocalDst
= true;
1600 // return baseOffset;
1603 return zone
.GetUtcOffset (time
, out isDaylightSavings
);
1609 public readonly int Offset
;
1610 public readonly bool IsDst
;
1613 public TimeType (int offset
, bool is_dst
, string abbrev
)
1615 this.Offset
= offset
;
1616 this.IsDst
= is_dst
;
1620 public override string ToString ()
1622 return "offset: " + Offset
+ "s, is_dst: " + IsDst
+ ", zone name: " + Name
;