5 * Stephane Delcroix <stephane@delcroix.org>
7 * Permission is hereby granted, free of charge, to any person obtaining
8 * a copy of this software and associated documentation files (the
9 * "Software"), to deal in the Software without restriction, including
10 * without limitation the rights to use, copy, modify, merge, publish,
11 * distribute, sublicense, and/or sell copies of the Software, and to
12 * permit persons to whom the Software is furnished to do so, subject to
13 * the following conditions:
15 * The above copyright notice and this permission notice shall be
16 * included in all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 using System.Runtime.CompilerServices;
30 #if !INSIDE_CORLIB && (NET_4_0 || MOONLIGHT || MOBILE)
32 [assembly:TypeForwardedTo (typeof(TimeZoneInfo))]
34 #elif (INSIDE_CORLIB && (NET_4_0 || MOONLIGHT || MOBILE)) || (!INSIDE_CORLIB && (NET_3_5 && !NET_4_0 && !MOBILE))
36 using System.Collections.Generic;
37 using System.Collections.ObjectModel;
38 using System.Runtime.Serialization;
46 using Microsoft.Win32;
51 [TypeForwardedFrom (Consts.AssemblySystemCore_3_5)]
52 #elif MOONLIGHT || MOBILE
53 [TypeForwardedFrom (Consts.AssemblySystem_Core)]
55 [SerializableAttribute]
56 public sealed partial class TimeZoneInfo : IEquatable<TimeZoneInfo>, ISerializable, IDeserializationCallback
58 TimeSpan baseUtcOffset;
59 public TimeSpan BaseUtcOffset {
60 get { return baseUtcOffset; }
63 string daylightDisplayName;
64 public string DaylightName {
66 if (disableDaylightSavingTime)
68 return daylightDisplayName;
73 public string DisplayName {
74 get { return displayName; }
82 static TimeZoneInfo local;
83 public static TimeZoneInfo Local {
87 local = ZoneInfoDB.Default;
90 local = FindSystemTimeZoneByFileName ("Local", "/etc/localtime");
93 local = FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));
95 throw new TimeZoneNotFoundException ();
99 throw new TimeZoneNotFoundException ();
106 string standardDisplayName;
107 public string StandardName {
108 get { return standardDisplayName; }
111 bool disableDaylightSavingTime;
112 public bool SupportsDaylightSavingTime {
113 get { return !disableDaylightSavingTime; }
116 static TimeZoneInfo utc;
117 public static TimeZoneInfo Utc {
120 utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
125 static string timeZoneDirectory = null;
126 static string TimeZoneDirectory {
128 if (timeZoneDirectory == null)
129 timeZoneDirectory = "/usr/share/zoneinfo";
130 return timeZoneDirectory;
134 timeZoneDirectory = value;
138 private AdjustmentRule [] adjustmentRules;
141 static RegistryKey timeZoneKey = null;
142 static bool timeZoneKeySet = false;
143 static RegistryKey TimeZoneKey {
145 if (!timeZoneKeySet) {
146 int p = (int) Environment.OSVersion.Platform;
147 /* Only use the registry on non-Unix platforms. */
148 if ((p != 4) && (p != 6) && (p != 128))
149 timeZoneKey = Registry.LocalMachine.OpenSubKey (
150 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
152 timeZoneKeySet = true;
159 public static void ClearCachedData ()
163 systemTimeZones = null;
166 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
168 return ConvertTime (dateTime, TimeZoneInfo.Local, destinationTimeZone);
171 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
173 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
174 throw new ArgumentException ("Kind propery of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
176 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
177 throw new ArgumentException ("Kind propery of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
179 if (sourceTimeZone.IsInvalidTime (dateTime))
180 throw new ArgumentException ("dateTime parameter is an invalid time");
182 if (sourceTimeZone == null)
183 throw new ArgumentNullException ("sourceTimeZone");
185 if (destinationTimeZone == null)
186 throw new ArgumentNullException ("destinationTimeZone");
188 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
191 DateTime utc = ConvertTimeToUtc (dateTime);
193 if (destinationTimeZone == TimeZoneInfo.Utc)
196 return ConvertTimeFromUtc (utc, destinationTimeZone);
200 public static DateTimeOffset ConvertTime (DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
202 throw new NotImplementedException ();
205 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
207 return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
210 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
212 return ConvertTime (dateTime, FindSystemTimeZoneById (sourceTimeZoneId), FindSystemTimeZoneById (destinationTimeZoneId));
215 public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
217 return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
220 private DateTime ConvertTimeFromUtc (DateTime dateTime)
222 if (dateTime.Kind == DateTimeKind.Local)
223 throw new ArgumentException ("Kind property of dateTime is Local");
225 if (this == TimeZoneInfo.Utc)
226 return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
228 //FIXME: do not rely on DateTime implementation !
229 if (this == TimeZoneInfo.Local)
230 return DateTime.SpecifyKind (dateTime.ToLocalTime (), DateTimeKind.Unspecified);
232 AdjustmentRule rule = GetApplicableRule (dateTime);
234 if (rule != null && IsDaylightSavingTime (DateTime.SpecifyKind (dateTime, DateTimeKind.Utc)))
235 return DateTime.SpecifyKind (dateTime + BaseUtcOffset + rule.DaylightDelta , DateTimeKind.Unspecified);
237 return DateTime.SpecifyKind (dateTime + BaseUtcOffset, DateTimeKind.Unspecified);
240 public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
242 if (destinationTimeZone == null)
243 throw new ArgumentNullException ("destinationTimeZone");
245 return destinationTimeZone.ConvertTimeFromUtc (dateTime);
248 public static DateTime ConvertTimeToUtc (DateTime dateTime)
250 if (dateTime.Kind == DateTimeKind.Utc)
253 //FIXME: do not rely on DateTime implementation !
254 return DateTime.SpecifyKind (dateTime.ToUniversalTime (), DateTimeKind.Utc);
257 public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
259 if (sourceTimeZone == null)
260 throw new ArgumentNullException ("sourceTimeZone");
262 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
263 throw new ArgumentException ("Kind propery of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
265 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
266 throw new ArgumentException ("Kind propery of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
268 if (sourceTimeZone.IsInvalidTime (dateTime))
269 throw new ArgumentException ("dateTime parameter is an invalid time");
271 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone == TimeZoneInfo.Utc)
274 if (dateTime.Kind == DateTimeKind.Utc)
277 if (dateTime.Kind == DateTimeKind.Local)
278 return ConvertTimeToUtc (dateTime);
280 if (sourceTimeZone.IsAmbiguousTime (dateTime) || !sourceTimeZone.IsDaylightSavingTime (dateTime))
281 return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset, DateTimeKind.Utc);
283 AdjustmentRule rule = sourceTimeZone.GetApplicableRule (dateTime);
285 return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset - rule.DaylightDelta, DateTimeKind.Utc);
287 return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset, DateTimeKind.Utc);
291 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName)
293 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
296 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
298 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
301 public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
303 return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
306 public bool Equals (TimeZoneInfo other)
311 return other.Id == this.Id && HasSameRules (other);
314 public static TimeZoneInfo FindSystemTimeZoneById (string id)
316 //FIXME: this method should check for cached values in systemTimeZones
318 throw new ArgumentNullException ("id");
320 if (TimeZoneKey != null)
322 RegistryKey key = TimeZoneKey.OpenSubKey (id, false);
324 throw new TimeZoneNotFoundException ();
325 return FromRegistryKey(id, key);
329 return ZoneInfoDB.GetTimeZone (id);
331 string filepath = Path.Combine (TimeZoneDirectory, id);
332 return FindSystemTimeZoneByFileName (id, filepath);
334 throw new NotImplementedException ();
339 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
340 private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
342 if (!File.Exists (filepath))
343 throw new TimeZoneNotFoundException ();
345 byte [] buffer = new byte [BUFFER_SIZE];
347 using (FileStream stream = File.OpenRead (filepath)) {
348 length = stream.Read (buffer, 0, BUFFER_SIZE);
351 if (!ValidTZFile (buffer, length))
352 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
355 return ParseTZBuffer (id, buffer, length);
356 } catch (Exception e) {
357 throw new InvalidTimeZoneException (e.Message);
363 private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
365 byte [] reg_tzi = (byte []) key.GetValue ("TZI");
368 throw new InvalidTimeZoneException ();
370 int bias = BitConverter.ToInt32 (reg_tzi, 0);
371 TimeSpan baseUtcOffset = new TimeSpan (0, -bias, 0);
373 string display_name = (string) key.GetValue ("Display");
374 string standard_name = (string) key.GetValue ("Std");
375 string daylight_name = (string) key.GetValue ("Dlt");
377 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
379 RegistryKey dst_key = key.OpenSubKey ("Dynamic DST", false);
380 if (dst_key != null) {
381 int first_year = (int) dst_key.GetValue ("FirstEntry");
382 int last_year = (int) dst_key.GetValue ("LastEntry");
385 for (year=first_year; year<=last_year; year++) {
386 byte [] dst_tzi = (byte []) dst_key.GetValue (year.ToString ());
387 if (dst_tzi != null) {
388 int start_year = year == first_year ? 1 : year;
389 int end_year = year == last_year ? 9999 : year;
390 ParseRegTzi(adjustmentRules, start_year, end_year, dst_tzi);
395 ParseRegTzi(adjustmentRules, 1, 9999, reg_tzi);
397 return CreateCustomTimeZone (id, baseUtcOffset, display_name, standard_name, daylight_name, ValidateRules (adjustmentRules).ToArray ());
400 private static void ParseRegTzi (List<AdjustmentRule> adjustmentRules, int start_year, int end_year, byte [] buffer)
402 //int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
403 int daylight_bias = BitConverter.ToInt32 (buffer, 8);
405 int standard_year = BitConverter.ToInt16 (buffer, 12);
406 int standard_month = BitConverter.ToInt16 (buffer, 14);
407 int standard_dayofweek = BitConverter.ToInt16 (buffer, 16);
408 int standard_day = BitConverter.ToInt16 (buffer, 18);
409 int standard_hour = BitConverter.ToInt16 (buffer, 20);
410 int standard_minute = BitConverter.ToInt16 (buffer, 22);
411 int standard_second = BitConverter.ToInt16 (buffer, 24);
412 int standard_millisecond = BitConverter.ToInt16 (buffer, 26);
414 int daylight_year = BitConverter.ToInt16 (buffer, 28);
415 int daylight_month = BitConverter.ToInt16 (buffer, 30);
416 int daylight_dayofweek = BitConverter.ToInt16 (buffer, 32);
417 int daylight_day = BitConverter.ToInt16 (buffer, 34);
418 int daylight_hour = BitConverter.ToInt16 (buffer, 36);
419 int daylight_minute = BitConverter.ToInt16 (buffer, 38);
420 int daylight_second = BitConverter.ToInt16 (buffer, 40);
421 int daylight_millisecond = BitConverter.ToInt16 (buffer, 42);
423 if (standard_month == 0 || daylight_month == 0)
427 DateTime start_timeofday = new DateTime (1, 1, 1, daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
428 TransitionTime start_transition_time;
430 if (daylight_year == 0) {
431 start_date = new DateTime (start_year, 1, 1);
432 start_transition_time = TransitionTime.CreateFloatingDateRule (
433 start_timeofday, daylight_month, daylight_day,
434 (DayOfWeek) daylight_dayofweek);
437 start_date = new DateTime (daylight_year, daylight_month, daylight_day,
438 daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
439 start_transition_time = TransitionTime.CreateFixedDateRule (
440 start_timeofday, daylight_month, daylight_day);
444 DateTime end_timeofday = new DateTime (1, 1, 1, standard_hour, standard_minute, standard_second, standard_millisecond);
445 TransitionTime end_transition_time;
447 if (standard_year == 0) {
448 end_date = new DateTime (end_year, 12, 31);
449 end_transition_time = TransitionTime.CreateFloatingDateRule (
450 end_timeofday, standard_month, standard_day,
451 (DayOfWeek) standard_dayofweek);
454 end_date = new DateTime (standard_year, standard_month, standard_day,
455 standard_hour, standard_minute, standard_second, standard_millisecond);
456 end_transition_time = TransitionTime.CreateFixedDateRule (
457 end_timeofday, standard_month, standard_day);
460 TimeSpan daylight_delta = new TimeSpan(0, -daylight_bias, 0);
462 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (
463 start_date, end_date, daylight_delta,
464 start_transition_time, end_transition_time));
468 public static TimeZoneInfo FromSerializedString (string source)
470 throw new NotImplementedException ();
473 public AdjustmentRule [] GetAdjustmentRules ()
475 if (disableDaylightSavingTime)
476 return new AdjustmentRule [0];
478 return (AdjustmentRule []) adjustmentRules.Clone ();
481 public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
483 if (!IsAmbiguousTime (dateTime))
484 throw new ArgumentException ("dateTime is not an ambiguous time");
486 AdjustmentRule rule = GetApplicableRule (dateTime);
488 return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
490 return new TimeSpan[] {baseUtcOffset, baseUtcOffset};
493 public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
495 if (!IsAmbiguousTime (dateTimeOffset))
496 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
498 throw new NotImplementedException ();
501 public override int GetHashCode ()
503 int hash_code = Id.GetHashCode ();
504 foreach (AdjustmentRule rule in GetAdjustmentRules ())
505 hash_code ^= rule.GetHashCode ();
510 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
512 public void GetObjectData (SerializationInfo info, StreamingContext context)
515 throw new NotImplementedException ();
518 //FIXME: change this to a generic Dictionary and allow caching for FindSystemTimeZoneById
519 private static List<TimeZoneInfo> systemTimeZones = null;
520 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
522 if (systemTimeZones == null) {
523 systemTimeZones = new List<TimeZoneInfo> ();
525 if (TimeZoneKey != null) {
526 foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
528 systemTimeZones.Add (FindSystemTimeZoneById (id));
532 return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
536 foreach (string id in ZoneInfoDB.GetAvailableIds ()) {
537 systemTimeZones.Add (ZoneInfoDB.GetTimeZone (id));
540 string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
541 foreach (string continent in continents) {
543 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
545 string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
546 systemTimeZones.Add (FindSystemTimeZoneById (id));
547 } catch (ArgumentNullException) {
548 } catch (TimeZoneNotFoundException) {
549 } catch (InvalidTimeZoneException) {
550 } catch (Exception) {
557 throw new NotImplementedException ("This method is not implemented for this platform");
560 return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
563 public TimeSpan GetUtcOffset (DateTime dateTime)
565 if (IsDaylightSavingTime (dateTime)) {
566 AdjustmentRule rule = GetApplicableRule (dateTime);
568 return BaseUtcOffset + rule.DaylightDelta;
571 return BaseUtcOffset;
574 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
576 throw new NotImplementedException ();
579 public bool HasSameRules (TimeZoneInfo other)
582 throw new ArgumentNullException ("other");
584 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
587 if (this.adjustmentRules == null)
590 if (this.BaseUtcOffset != other.BaseUtcOffset)
593 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
596 for (int i = 0; i < adjustmentRules.Length; i++) {
597 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
604 public bool IsAmbiguousTime (DateTime dateTime)
606 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
607 throw new ArgumentException ("Kind is Local and time is Invalid");
609 if (this == TimeZoneInfo.Utc)
612 if (dateTime.Kind == DateTimeKind.Utc)
613 dateTime = ConvertTimeFromUtc (dateTime);
615 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
616 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
618 AdjustmentRule rule = GetApplicableRule (dateTime);
620 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
621 if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
628 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
630 throw new NotImplementedException ();
633 public bool IsDaylightSavingTime (DateTime dateTime)
635 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
636 throw new ArgumentException ("dateTime is invalid and Kind is Local");
638 if (this == TimeZoneInfo.Utc)
641 if (!SupportsDaylightSavingTime)
643 //FIXME: do not rely on DateTime implementation !
644 if ((dateTime.Kind == DateTimeKind.Local || dateTime.Kind == DateTimeKind.Unspecified) && this == TimeZoneInfo.Local)
645 return dateTime.IsDaylightSavingTime ();
647 //FIXME: do not rely on DateTime implementation !
648 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Utc)
649 return IsDaylightSavingTime (DateTime.SpecifyKind (dateTime.ToUniversalTime (), DateTimeKind.Utc));
651 AdjustmentRule rule = GetApplicableRule (dateTime.Date);
655 DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
656 DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
657 if (dateTime.Kind == DateTimeKind.Utc) {
658 DST_start -= BaseUtcOffset;
659 DST_end -= (BaseUtcOffset + rule.DaylightDelta);
662 return (dateTime >= DST_start && dateTime < DST_end);
665 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
667 throw new NotImplementedException ();
670 public bool IsInvalidTime (DateTime dateTime)
672 if (dateTime.Kind == DateTimeKind.Utc)
674 if (dateTime.Kind == DateTimeKind.Local && this != Local)
677 AdjustmentRule rule = GetApplicableRule (dateTime);
679 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
680 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
688 void IDeserializationCallback.OnDeserialization (object sender)
690 public void OnDeserialization (object sender)
693 throw new NotImplementedException ();
696 public string ToSerializedString ()
698 throw new NotImplementedException ();
701 public override string ToString ()
706 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
709 throw new ArgumentNullException ("id");
711 if (id == String.Empty)
712 throw new ArgumentException ("id parameter is an empty string");
714 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
715 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
717 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
718 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
722 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
725 if (adjustmentRules != null && adjustmentRules.Length != 0) {
726 AdjustmentRule prev = null;
727 foreach (AdjustmentRule current in adjustmentRules) {
729 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
731 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
732 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
733 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;");
735 if (prev != null && prev.DateStart > current.DateStart)
736 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
738 if (prev != null && prev.DateEnd > current.DateStart)
739 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
741 if (prev != null && prev.DateEnd == current.DateStart)
742 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
749 this.baseUtcOffset = baseUtcOffset;
750 this.displayName = displayName ?? id;
751 this.standardDisplayName = standardDisplayName ?? id;
752 this.daylightDisplayName = daylightDisplayName;
753 this.disableDaylightSavingTime = disableDaylightSavingTime;
754 this.adjustmentRules = adjustmentRules;
757 private AdjustmentRule GetApplicableRule (DateTime dateTime)
759 //Transitions are always in standard time
760 DateTime date = dateTime;
762 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
763 date = date.ToUniversalTime () + BaseUtcOffset;
765 if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc)
766 date = date + BaseUtcOffset;
768 if (adjustmentRules != null) {
769 foreach (AdjustmentRule rule in adjustmentRules) {
770 if (rule.DateStart > date.Date)
772 if (rule.DateEnd < date.Date)
780 private static DateTime TransitionPoint (TransitionTime transition, int year)
782 if (transition.IsFixedDateRule)
783 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
785 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
786 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first) % 7;
787 if (day > DateTime.DaysInMonth (year, transition.Month))
789 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
792 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
794 AdjustmentRule prev = null;
795 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
796 if (prev != null && prev.DateEnd > current.DateStart) {
797 adjustmentRules.Remove (current);
801 return adjustmentRules;
804 #if LIBC || MONODROID
805 private static bool ValidTZFile (byte [] buffer, int length)
807 StringBuilder magic = new StringBuilder ();
809 for (int i = 0; i < 4; i++)
810 magic.Append ((char)buffer [i]);
812 if (magic.ToString () != "TZif")
815 if (length >= BUFFER_SIZE)
821 static int SwapInt32 (int i)
823 return (((i >> 24) & 0xff)
824 | ((i >> 8) & 0xff00)
825 | ((i << 8) & 0xff0000)
829 static int ReadBigEndianInt32 (byte [] buffer, int start)
831 int i = BitConverter.ToInt32 (buffer, start);
832 if (!BitConverter.IsLittleEndian)
835 return SwapInt32 (i);
838 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
840 //Reading the header. 4 bytes for magic, 16 are reserved
841 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
842 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
843 int leapcnt = ReadBigEndianInt32 (buffer, 28);
844 int timecnt = ReadBigEndianInt32 (buffer, 32);
845 int typecnt = ReadBigEndianInt32 (buffer, 36);
846 int charcnt = ReadBigEndianInt32 (buffer, 40);
848 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
849 throw new InvalidTimeZoneException ();
851 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
852 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
853 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
855 if (time_types.Count == 0)
856 throw new InvalidTimeZoneException ();
858 if (time_types.Count == 1 && ((TimeType)time_types[0]).IsDst)
859 throw new InvalidTimeZoneException ();
861 TimeSpan baseUtcOffset = new TimeSpan (0);
862 TimeSpan dstDelta = new TimeSpan (0);
863 string standardDisplayName = null;
864 string daylightDisplayName = null;
865 bool dst_observed = false;
866 DateTime dst_start = DateTime.MinValue;
867 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
869 for (int i = 0; i < transitions.Count; i++) {
870 var pair = transitions [i];
871 DateTime ttime = pair.Key;
872 TimeType ttype = pair.Value;
874 if (standardDisplayName != ttype.Name || baseUtcOffset.TotalSeconds != ttype.Offset) {
875 standardDisplayName = ttype.Name;
876 daylightDisplayName = null;
877 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
878 adjustmentRules = new List<AdjustmentRule> ();
879 dst_observed = false;
882 //FIXME: check additional fields for this:
883 //most of the transitions are expressed in GMT
884 dst_start += baseUtcOffset;
885 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
887 //some weird timezone (America/Phoenix) have end dates on Jan 1st
888 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
889 dst_end -= new TimeSpan (24, 0, 0);
891 DateTime dateStart, dateEnd;
892 if (dst_start.Month < 7)
893 dateStart = new DateTime (dst_start.Year, 1, 1);
895 dateStart = new DateTime (dst_start.Year, 7, 1);
897 if (dst_end.Month >= 7)
898 dateEnd = new DateTime (dst_end.Year, 12, 31);
900 dateEnd = new DateTime (dst_end.Year, 6, 30);
903 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
904 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
905 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
906 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
908 dst_observed = false;
910 if (daylightDisplayName != ttype.Name || dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
911 daylightDisplayName = ttype.Name;
912 dstDelta = new TimeSpan(0, 0, ttype.Offset) - baseUtcOffset;
919 if (adjustmentRules.Count == 0) {
920 TimeType t = (TimeType)time_types [0];
921 if (standardDisplayName == null) {
922 standardDisplayName = t.Name;
923 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
925 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
927 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
931 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
933 var abbrevs = new Dictionary<int, string> ();
934 int abbrev_index = 0;
935 var sb = new StringBuilder ();
936 for (int i = 0; i < count; i++) {
937 char c = (char) buffer [index + i];
941 abbrevs.Add (abbrev_index, sb.ToString ());
942 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
943 for (int j = 1; j < sb.Length; j++)
944 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
945 abbrev_index = i + 1;
946 sb = new StringBuilder ();
952 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
954 var types = new Dictionary<int, TimeType> (count);
955 for (int i = 0; i < count; i++) {
956 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
957 byte is_dst = buffer [index + 6 * i + 4];
958 byte abbrev = buffer [index + 6 * i + 5];
959 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
964 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
966 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
967 for (int i = 0; i < count; i++) {
968 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
969 DateTime ttime = DateTimeFromUnixTime (unixtime);
970 byte ttype = buffer [index + 4 * count + i];
971 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
976 static DateTime DateTimeFromUnixTime (long unix_time)
978 DateTime date_time = new DateTime (1970, 1, 1);
979 return date_time.AddSeconds (unix_time);
984 public readonly int Offset;
985 public readonly bool IsDst;
988 public TimeType (int offset, bool is_dst, string abbrev)
990 this.Offset = offset;
995 public override string ToString ()
997 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;