5 * Stephane Delcroix <stephane@delcroix.org>
7 * Copyright 2011 Xamarin Inc.
9 * Permission is hereby granted, free of charge, to any person obtaining
10 * a copy of this software and associated documentation files (the
11 * "Software"), to deal in the Software without restriction, including
12 * without limitation the rights to use, copy, modify, merge, publish,
13 * distribute, sublicense, and/or sell copies of the Software, and to
14 * permit persons to whom the Software is furnished to do so, subject to
15 * the following conditions:
17 * The above copyright notice and this permission notice shall be
18 * included in all copies or substantial portions of the Software.
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 using System.Runtime.CompilerServices;
32 #if !INSIDE_CORLIB && (NET_4_0 || MOONLIGHT || MOBILE)
34 [assembly:TypeForwardedTo (typeof(TimeZoneInfo))]
36 #elif (INSIDE_CORLIB && (NET_4_0 || MOONLIGHT || MOBILE)) || (!INSIDE_CORLIB && (NET_3_5 && !NET_4_0 && !MOBILE))
38 using System.Collections.Generic;
39 using System.Collections.ObjectModel;
40 using System.Runtime.Serialization;
48 using Microsoft.Win32;
53 [TypeForwardedFrom (Consts.AssemblySystemCore_3_5)]
54 #elif MOONLIGHT || MOBILE
55 [TypeForwardedFrom (Consts.AssemblySystem_Core)]
57 [SerializableAttribute]
58 public sealed partial class TimeZoneInfo : IEquatable<TimeZoneInfo>, ISerializable, IDeserializationCallback
60 TimeSpan baseUtcOffset;
61 public TimeSpan BaseUtcOffset {
62 get { return baseUtcOffset; }
65 string daylightDisplayName;
66 public string DaylightName {
68 if (disableDaylightSavingTime)
70 return daylightDisplayName;
75 public string DisplayName {
76 get { return displayName; }
84 static TimeZoneInfo local;
85 public static TimeZoneInfo Local {
89 local = ZoneInfoDB.Default;
92 local = FindSystemTimeZoneByFileName ("Local", "/etc/localtime");
95 local = FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));
97 throw new TimeZoneNotFoundException ();
101 throw new TimeZoneNotFoundException ();
108 string standardDisplayName;
109 public string StandardName {
110 get { return standardDisplayName; }
113 bool disableDaylightSavingTime;
114 public bool SupportsDaylightSavingTime {
115 get { return !disableDaylightSavingTime; }
118 static TimeZoneInfo utc;
119 public static TimeZoneInfo Utc {
122 utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
127 static string timeZoneDirectory = null;
128 static string TimeZoneDirectory {
130 if (timeZoneDirectory == null)
131 timeZoneDirectory = "/usr/share/zoneinfo";
132 return timeZoneDirectory;
136 timeZoneDirectory = value;
140 private AdjustmentRule [] adjustmentRules;
143 static RegistryKey timeZoneKey = null;
144 static bool timeZoneKeySet = false;
145 static RegistryKey TimeZoneKey {
147 if (!timeZoneKeySet) {
148 int p = (int) Environment.OSVersion.Platform;
149 /* Only use the registry on non-Unix platforms. */
150 if ((p != 4) && (p != 6) && (p != 128))
151 timeZoneKey = Registry.LocalMachine.OpenSubKey (
152 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
154 timeZoneKeySet = true;
161 public static void ClearCachedData ()
165 systemTimeZones = null;
168 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
170 return ConvertTime (dateTime, TimeZoneInfo.Local, destinationTimeZone);
173 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
175 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
176 throw new ArgumentException ("Kind propery of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
178 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
179 throw new ArgumentException ("Kind propery of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
181 if (sourceTimeZone.IsInvalidTime (dateTime))
182 throw new ArgumentException ("dateTime parameter is an invalid time");
184 if (sourceTimeZone == null)
185 throw new ArgumentNullException ("sourceTimeZone");
187 if (destinationTimeZone == null)
188 throw new ArgumentNullException ("destinationTimeZone");
190 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
193 DateTime utc = ConvertTimeToUtc (dateTime);
195 if (destinationTimeZone == TimeZoneInfo.Utc)
198 return ConvertTimeFromUtc (utc, destinationTimeZone);
202 public static DateTimeOffset ConvertTime (DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
204 throw new NotImplementedException ();
207 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
209 return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
212 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
214 return ConvertTime (dateTime, FindSystemTimeZoneById (sourceTimeZoneId), FindSystemTimeZoneById (destinationTimeZoneId));
217 public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
219 return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
222 private DateTime ConvertTimeFromUtc (DateTime dateTime)
224 if (dateTime.Kind == DateTimeKind.Local)
225 throw new ArgumentException ("Kind property of dateTime is Local");
227 if (this == TimeZoneInfo.Utc)
228 return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
230 //FIXME: do not rely on DateTime implementation !
231 if (this == TimeZoneInfo.Local)
232 return DateTime.SpecifyKind (dateTime.ToLocalTime (), DateTimeKind.Unspecified);
234 AdjustmentRule rule = GetApplicableRule (dateTime);
236 if (rule != null && IsDaylightSavingTime (DateTime.SpecifyKind (dateTime, DateTimeKind.Utc)))
237 return DateTime.SpecifyKind (dateTime + BaseUtcOffset + rule.DaylightDelta , DateTimeKind.Unspecified);
239 return DateTime.SpecifyKind (dateTime + BaseUtcOffset, DateTimeKind.Unspecified);
242 public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
244 if (destinationTimeZone == null)
245 throw new ArgumentNullException ("destinationTimeZone");
247 return destinationTimeZone.ConvertTimeFromUtc (dateTime);
250 public static DateTime ConvertTimeToUtc (DateTime dateTime)
252 if (dateTime.Kind == DateTimeKind.Utc)
255 //FIXME: do not rely on DateTime implementation !
256 return DateTime.SpecifyKind (dateTime.ToUniversalTime (), DateTimeKind.Utc);
259 public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
261 if (sourceTimeZone == null)
262 throw new ArgumentNullException ("sourceTimeZone");
264 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
265 throw new ArgumentException ("Kind propery of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
267 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
268 throw new ArgumentException ("Kind propery of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
270 if (sourceTimeZone.IsInvalidTime (dateTime))
271 throw new ArgumentException ("dateTime parameter is an invalid time");
273 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone == TimeZoneInfo.Utc)
276 if (dateTime.Kind == DateTimeKind.Utc)
279 if (dateTime.Kind == DateTimeKind.Local)
280 return ConvertTimeToUtc (dateTime);
282 if (sourceTimeZone.IsAmbiguousTime (dateTime) || !sourceTimeZone.IsDaylightSavingTime (dateTime))
283 return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset, DateTimeKind.Utc);
285 AdjustmentRule rule = sourceTimeZone.GetApplicableRule (dateTime);
287 return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset - rule.DaylightDelta, DateTimeKind.Utc);
289 return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset, DateTimeKind.Utc);
293 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName)
295 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
298 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
300 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
303 public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
305 return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
308 public bool Equals (TimeZoneInfo other)
313 return other.Id == this.Id && HasSameRules (other);
316 public static TimeZoneInfo FindSystemTimeZoneById (string id)
318 //FIXME: this method should check for cached values in systemTimeZones
320 throw new ArgumentNullException ("id");
322 if (TimeZoneKey != null)
324 RegistryKey key = TimeZoneKey.OpenSubKey (id, false);
326 throw new TimeZoneNotFoundException ();
327 return FromRegistryKey(id, key);
331 return ZoneInfoDB.GetTimeZone (id);
333 // Local requires special logic that already exists in the Local property (bug #326)
336 string filepath = Path.Combine (TimeZoneDirectory, id);
337 return FindSystemTimeZoneByFileName (id, filepath);
339 throw new NotImplementedException ();
344 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
345 private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
347 if (!File.Exists (filepath))
348 throw new TimeZoneNotFoundException ();
350 byte [] buffer = new byte [BUFFER_SIZE];
352 using (FileStream stream = File.OpenRead (filepath)) {
353 length = stream.Read (buffer, 0, BUFFER_SIZE);
356 if (!ValidTZFile (buffer, length))
357 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
360 return ParseTZBuffer (id, buffer, length);
361 } catch (Exception e) {
362 throw new InvalidTimeZoneException (e.Message);
368 private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
370 byte [] reg_tzi = (byte []) key.GetValue ("TZI");
373 throw new InvalidTimeZoneException ();
375 int bias = BitConverter.ToInt32 (reg_tzi, 0);
376 TimeSpan baseUtcOffset = new TimeSpan (0, -bias, 0);
378 string display_name = (string) key.GetValue ("Display");
379 string standard_name = (string) key.GetValue ("Std");
380 string daylight_name = (string) key.GetValue ("Dlt");
382 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
384 RegistryKey dst_key = key.OpenSubKey ("Dynamic DST", false);
385 if (dst_key != null) {
386 int first_year = (int) dst_key.GetValue ("FirstEntry");
387 int last_year = (int) dst_key.GetValue ("LastEntry");
390 for (year=first_year; year<=last_year; year++) {
391 byte [] dst_tzi = (byte []) dst_key.GetValue (year.ToString ());
392 if (dst_tzi != null) {
393 int start_year = year == first_year ? 1 : year;
394 int end_year = year == last_year ? 9999 : year;
395 ParseRegTzi(adjustmentRules, start_year, end_year, dst_tzi);
400 ParseRegTzi(adjustmentRules, 1, 9999, reg_tzi);
402 return CreateCustomTimeZone (id, baseUtcOffset, display_name, standard_name, daylight_name, ValidateRules (adjustmentRules).ToArray ());
405 private static void ParseRegTzi (List<AdjustmentRule> adjustmentRules, int start_year, int end_year, byte [] buffer)
407 //int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
408 int daylight_bias = BitConverter.ToInt32 (buffer, 8);
410 int standard_year = BitConverter.ToInt16 (buffer, 12);
411 int standard_month = BitConverter.ToInt16 (buffer, 14);
412 int standard_dayofweek = BitConverter.ToInt16 (buffer, 16);
413 int standard_day = BitConverter.ToInt16 (buffer, 18);
414 int standard_hour = BitConverter.ToInt16 (buffer, 20);
415 int standard_minute = BitConverter.ToInt16 (buffer, 22);
416 int standard_second = BitConverter.ToInt16 (buffer, 24);
417 int standard_millisecond = BitConverter.ToInt16 (buffer, 26);
419 int daylight_year = BitConverter.ToInt16 (buffer, 28);
420 int daylight_month = BitConverter.ToInt16 (buffer, 30);
421 int daylight_dayofweek = BitConverter.ToInt16 (buffer, 32);
422 int daylight_day = BitConverter.ToInt16 (buffer, 34);
423 int daylight_hour = BitConverter.ToInt16 (buffer, 36);
424 int daylight_minute = BitConverter.ToInt16 (buffer, 38);
425 int daylight_second = BitConverter.ToInt16 (buffer, 40);
426 int daylight_millisecond = BitConverter.ToInt16 (buffer, 42);
428 if (standard_month == 0 || daylight_month == 0)
432 DateTime start_timeofday = new DateTime (1, 1, 1, daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
433 TransitionTime start_transition_time;
435 if (daylight_year == 0) {
436 start_date = new DateTime (start_year, 1, 1);
437 start_transition_time = TransitionTime.CreateFloatingDateRule (
438 start_timeofday, daylight_month, daylight_day,
439 (DayOfWeek) daylight_dayofweek);
442 start_date = new DateTime (daylight_year, daylight_month, daylight_day,
443 daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
444 start_transition_time = TransitionTime.CreateFixedDateRule (
445 start_timeofday, daylight_month, daylight_day);
449 DateTime end_timeofday = new DateTime (1, 1, 1, standard_hour, standard_minute, standard_second, standard_millisecond);
450 TransitionTime end_transition_time;
452 if (standard_year == 0) {
453 end_date = new DateTime (end_year, 12, 31);
454 end_transition_time = TransitionTime.CreateFloatingDateRule (
455 end_timeofday, standard_month, standard_day,
456 (DayOfWeek) standard_dayofweek);
459 end_date = new DateTime (standard_year, standard_month, standard_day,
460 standard_hour, standard_minute, standard_second, standard_millisecond);
461 end_transition_time = TransitionTime.CreateFixedDateRule (
462 end_timeofday, standard_month, standard_day);
465 TimeSpan daylight_delta = new TimeSpan(0, -daylight_bias, 0);
467 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (
468 start_date, end_date, daylight_delta,
469 start_transition_time, end_transition_time));
473 public static TimeZoneInfo FromSerializedString (string source)
475 throw new NotImplementedException ();
478 public AdjustmentRule [] GetAdjustmentRules ()
480 if (disableDaylightSavingTime)
481 return new AdjustmentRule [0];
483 return (AdjustmentRule []) adjustmentRules.Clone ();
486 public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
488 if (!IsAmbiguousTime (dateTime))
489 throw new ArgumentException ("dateTime is not an ambiguous time");
491 AdjustmentRule rule = GetApplicableRule (dateTime);
493 return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
495 return new TimeSpan[] {baseUtcOffset, baseUtcOffset};
498 public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
500 if (!IsAmbiguousTime (dateTimeOffset))
501 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
503 throw new NotImplementedException ();
506 public override int GetHashCode ()
508 int hash_code = Id.GetHashCode ();
509 foreach (AdjustmentRule rule in GetAdjustmentRules ())
510 hash_code ^= rule.GetHashCode ();
515 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
517 public void GetObjectData (SerializationInfo info, StreamingContext context)
520 throw new NotImplementedException ();
523 //FIXME: change this to a generic Dictionary and allow caching for FindSystemTimeZoneById
524 private static List<TimeZoneInfo> systemTimeZones = null;
525 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
527 if (systemTimeZones == null) {
528 systemTimeZones = new List<TimeZoneInfo> ();
530 if (TimeZoneKey != null) {
531 foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
533 systemTimeZones.Add (FindSystemTimeZoneById (id));
537 return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
541 foreach (string id in ZoneInfoDB.GetAvailableIds ()) {
542 systemTimeZones.Add (ZoneInfoDB.GetTimeZone (id));
545 string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
546 foreach (string continent in continents) {
548 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
550 string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
551 systemTimeZones.Add (FindSystemTimeZoneById (id));
552 } catch (ArgumentNullException) {
553 } catch (TimeZoneNotFoundException) {
554 } catch (InvalidTimeZoneException) {
555 } catch (Exception) {
562 throw new NotImplementedException ("This method is not implemented for this platform");
565 return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
568 public TimeSpan GetUtcOffset (DateTime dateTime)
570 if (IsDaylightSavingTime (dateTime)) {
571 AdjustmentRule rule = GetApplicableRule (dateTime);
573 return BaseUtcOffset + rule.DaylightDelta;
576 return BaseUtcOffset;
579 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
581 throw new NotImplementedException ();
584 public bool HasSameRules (TimeZoneInfo other)
587 throw new ArgumentNullException ("other");
589 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
592 if (this.adjustmentRules == null)
595 if (this.BaseUtcOffset != other.BaseUtcOffset)
598 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
601 for (int i = 0; i < adjustmentRules.Length; i++) {
602 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
609 public bool IsAmbiguousTime (DateTime dateTime)
611 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
612 throw new ArgumentException ("Kind is Local and time is Invalid");
614 if (this == TimeZoneInfo.Utc)
617 if (dateTime.Kind == DateTimeKind.Utc)
618 dateTime = ConvertTimeFromUtc (dateTime);
620 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
621 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
623 AdjustmentRule rule = GetApplicableRule (dateTime);
625 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
626 if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
633 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
635 throw new NotImplementedException ();
638 public bool IsDaylightSavingTime (DateTime dateTime)
640 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
641 throw new ArgumentException ("dateTime is invalid and Kind is Local");
643 if (this == TimeZoneInfo.Utc)
646 if (!SupportsDaylightSavingTime)
648 //FIXME: do not rely on DateTime implementation !
649 if ((dateTime.Kind == DateTimeKind.Local || dateTime.Kind == DateTimeKind.Unspecified) && this == TimeZoneInfo.Local)
650 return dateTime.IsDaylightSavingTime ();
652 //FIXME: do not rely on DateTime implementation !
653 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Utc)
654 return IsDaylightSavingTime (DateTime.SpecifyKind (dateTime.ToUniversalTime (), DateTimeKind.Utc));
656 AdjustmentRule rule = GetApplicableRule (dateTime.Date);
660 DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
661 DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
662 if (dateTime.Kind == DateTimeKind.Utc) {
663 DST_start -= BaseUtcOffset;
664 DST_end -= (BaseUtcOffset + rule.DaylightDelta);
667 return (dateTime >= DST_start && dateTime < DST_end);
670 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
672 throw new NotImplementedException ();
675 public bool IsInvalidTime (DateTime dateTime)
677 if (dateTime.Kind == DateTimeKind.Utc)
679 if (dateTime.Kind == DateTimeKind.Local && this != Local)
682 AdjustmentRule rule = GetApplicableRule (dateTime);
684 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
685 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
693 void IDeserializationCallback.OnDeserialization (object sender)
695 public void OnDeserialization (object sender)
698 throw new NotImplementedException ();
701 public string ToSerializedString ()
703 throw new NotImplementedException ();
706 public override string ToString ()
711 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
714 throw new ArgumentNullException ("id");
716 if (id == String.Empty)
717 throw new ArgumentException ("id parameter is an empty string");
719 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
720 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
722 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
723 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
727 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
730 if (adjustmentRules != null && adjustmentRules.Length != 0) {
731 AdjustmentRule prev = null;
732 foreach (AdjustmentRule current in adjustmentRules) {
734 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
736 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
737 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
738 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;");
740 if (prev != null && prev.DateStart > current.DateStart)
741 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
743 if (prev != null && prev.DateEnd > current.DateStart)
744 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
746 if (prev != null && prev.DateEnd == current.DateStart)
747 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
754 this.baseUtcOffset = baseUtcOffset;
755 this.displayName = displayName ?? id;
756 this.standardDisplayName = standardDisplayName ?? id;
757 this.daylightDisplayName = daylightDisplayName;
758 this.disableDaylightSavingTime = disableDaylightSavingTime;
759 this.adjustmentRules = adjustmentRules;
762 private AdjustmentRule GetApplicableRule (DateTime dateTime)
764 //Transitions are always in standard time
765 DateTime date = dateTime;
767 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
768 date = date.ToUniversalTime () + BaseUtcOffset;
770 if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc)
771 date = date + BaseUtcOffset;
773 if (adjustmentRules != null) {
774 foreach (AdjustmentRule rule in adjustmentRules) {
775 if (rule.DateStart > date.Date)
777 if (rule.DateEnd < date.Date)
785 private static DateTime TransitionPoint (TransitionTime transition, int year)
787 if (transition.IsFixedDateRule)
788 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
790 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
791 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first) % 7;
792 if (day > DateTime.DaysInMonth (year, transition.Month))
794 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
797 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
799 AdjustmentRule prev = null;
800 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
801 if (prev != null && prev.DateEnd > current.DateStart) {
802 adjustmentRules.Remove (current);
806 return adjustmentRules;
809 #if LIBC || MONODROID
810 private static bool ValidTZFile (byte [] buffer, int length)
812 StringBuilder magic = new StringBuilder ();
814 for (int i = 0; i < 4; i++)
815 magic.Append ((char)buffer [i]);
817 if (magic.ToString () != "TZif")
820 if (length >= BUFFER_SIZE)
826 static int SwapInt32 (int i)
828 return (((i >> 24) & 0xff)
829 | ((i >> 8) & 0xff00)
830 | ((i << 8) & 0xff0000)
834 static int ReadBigEndianInt32 (byte [] buffer, int start)
836 int i = BitConverter.ToInt32 (buffer, start);
837 if (!BitConverter.IsLittleEndian)
840 return SwapInt32 (i);
843 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
845 //Reading the header. 4 bytes for magic, 16 are reserved
846 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
847 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
848 int leapcnt = ReadBigEndianInt32 (buffer, 28);
849 int timecnt = ReadBigEndianInt32 (buffer, 32);
850 int typecnt = ReadBigEndianInt32 (buffer, 36);
851 int charcnt = ReadBigEndianInt32 (buffer, 40);
853 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
854 throw new InvalidTimeZoneException ();
856 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
857 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
858 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
860 if (time_types.Count == 0)
861 throw new InvalidTimeZoneException ();
863 if (time_types.Count == 1 && ((TimeType)time_types[0]).IsDst)
864 throw new InvalidTimeZoneException ();
866 TimeSpan baseUtcOffset = new TimeSpan (0);
867 TimeSpan dstDelta = new TimeSpan (0);
868 string standardDisplayName = null;
869 string daylightDisplayName = null;
870 bool dst_observed = false;
871 DateTime dst_start = DateTime.MinValue;
872 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
874 for (int i = 0; i < transitions.Count; i++) {
875 var pair = transitions [i];
876 DateTime ttime = pair.Key;
877 TimeType ttype = pair.Value;
879 if (standardDisplayName != ttype.Name || baseUtcOffset.TotalSeconds != ttype.Offset) {
880 standardDisplayName = ttype.Name;
881 daylightDisplayName = null;
882 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
883 adjustmentRules = new List<AdjustmentRule> ();
884 dst_observed = false;
887 //FIXME: check additional fields for this:
888 //most of the transitions are expressed in GMT
889 dst_start += baseUtcOffset;
890 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
892 //some weird timezone (America/Phoenix) have end dates on Jan 1st
893 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
894 dst_end -= new TimeSpan (24, 0, 0);
896 DateTime dateStart, dateEnd;
897 if (dst_start.Month < 7)
898 dateStart = new DateTime (dst_start.Year, 1, 1);
900 dateStart = new DateTime (dst_start.Year, 7, 1);
902 if (dst_end.Month >= 7)
903 dateEnd = new DateTime (dst_end.Year, 12, 31);
905 dateEnd = new DateTime (dst_end.Year, 6, 30);
908 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
909 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
910 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
911 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
913 dst_observed = false;
915 if (daylightDisplayName != ttype.Name || dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
916 daylightDisplayName = ttype.Name;
917 dstDelta = new TimeSpan(0, 0, ttype.Offset) - baseUtcOffset;
924 if (adjustmentRules.Count == 0) {
925 TimeType t = (TimeType)time_types [0];
926 if (standardDisplayName == null) {
927 standardDisplayName = t.Name;
928 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
930 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
932 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
936 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
938 var abbrevs = new Dictionary<int, string> ();
939 int abbrev_index = 0;
940 var sb = new StringBuilder ();
941 for (int i = 0; i < count; i++) {
942 char c = (char) buffer [index + i];
946 abbrevs.Add (abbrev_index, sb.ToString ());
947 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
948 for (int j = 1; j < sb.Length; j++)
949 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
950 abbrev_index = i + 1;
951 sb = new StringBuilder ();
957 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
959 var types = new Dictionary<int, TimeType> (count);
960 for (int i = 0; i < count; i++) {
961 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
962 byte is_dst = buffer [index + 6 * i + 4];
963 byte abbrev = buffer [index + 6 * i + 5];
964 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
969 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
971 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
972 for (int i = 0; i < count; i++) {
973 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
974 DateTime ttime = DateTimeFromUnixTime (unixtime);
975 byte ttype = buffer [index + 4 * count + i];
976 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
981 static DateTime DateTimeFromUnixTime (long unix_time)
983 DateTime date_time = new DateTime (1970, 1, 1);
984 return date_time.AddSeconds (unix_time);
989 public readonly int Offset;
990 public readonly bool IsDst;
993 public TimeType (int offset, bool is_dst, string abbrev)
995 this.Offset = offset;
1000 public override string ToString ()
1002 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;