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